UI/Qt: Draw custom browser window chrome
Add shared chrome styling helpers and use them to paint the Linux browser tab strip and surrounding toolbar widgets. Make the browser window frameless on Qt so the tabs, toolbar, and window controls can share one integrated chrome surface.
This commit is contained in:
parent
201e380854
commit
328d89f3c1
16 changed files with 836 additions and 45 deletions
|
|
@ -9,6 +9,7 @@
|
|||
#include <AK/Base64.h>
|
||||
#include <LibWebView/Autocomplete.h>
|
||||
#include <UI/Qt/Autocomplete.h>
|
||||
#include <UI/Qt/ChromeStyle.h>
|
||||
#include <UI/Qt/Icon.h>
|
||||
#include <UI/Qt/StringUtils.h>
|
||||
|
||||
|
|
@ -356,13 +357,16 @@ Autocomplete::Autocomplete(QLineEdit* anchor)
|
|||
// position_popup() rather than made its own window, so that showing
|
||||
// it never causes the address bar to lose keyboard focus.
|
||||
m_popup = new QFrame();
|
||||
m_popup->setObjectName("LadybirdAutocompletePopup");
|
||||
m_popup->setFocusPolicy(Qt::NoFocus);
|
||||
m_popup->setFrameShape(QFrame::StyledPanel);
|
||||
m_popup->setFrameShadow(QFrame::Raised);
|
||||
m_popup->setAutoFillBackground(true);
|
||||
m_popup->setStyleSheet(ChromeStyle::autocomplete_popup_style_sheet(m_anchor->palette()));
|
||||
m_popup->hide();
|
||||
|
||||
m_list_view = new QListView(m_popup);
|
||||
m_list_view->setObjectName("LadybirdAutocompleteList");
|
||||
m_list_view->setFocusPolicy(Qt::NoFocus);
|
||||
m_list_view->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_list_view->setMouseTracking(true);
|
||||
|
|
@ -426,6 +430,7 @@ void Autocomplete::show_with_suggestions(Vector<WebView::AutocompleteSuggestion>
|
|||
return;
|
||||
}
|
||||
|
||||
m_popup->setStyleSheet(ChromeStyle::autocomplete_popup_style_sheet(m_anchor->palette()));
|
||||
position_popup();
|
||||
if (!m_popup->isVisible())
|
||||
m_popup->show();
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@
|
|||
#include <LibWebView/Application.h>
|
||||
#include <LibWebView/BookmarkStore.h>
|
||||
#include <UI/Qt/BookmarksBar.h>
|
||||
#include <UI/Qt/ChromeStyle.h>
|
||||
#include <UI/Qt/Icon.h>
|
||||
#include <UI/Qt/Menu.h>
|
||||
#include <UI/Qt/StringUtils.h>
|
||||
|
||||
#include <QAction>
|
||||
#include <QEvent>
|
||||
#include <QMenu>
|
||||
#include <QMouseEvent>
|
||||
#include <QToolButton>
|
||||
|
|
@ -35,15 +37,36 @@ static void install_menu_event_filter(QObject* filter, QMenu* menu)
|
|||
BookmarksBar::BookmarksBar(QWidget* parent)
|
||||
: QToolBar(parent)
|
||||
{
|
||||
setObjectName("LadybirdBookmarksBar");
|
||||
setIconSize({ BOOKMARK_BUTTON_ICON_SIZE, BOOKMARK_BUTTON_ICON_SIZE });
|
||||
setVisible(WebView::Application::settings().show_bookmarks_bar());
|
||||
setMovable(false);
|
||||
setFloatable(false);
|
||||
update_chrome_style();
|
||||
|
||||
installEventFilter(this);
|
||||
|
||||
rebuild();
|
||||
}
|
||||
|
||||
bool BookmarksBar::event(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::PaletteChange)
|
||||
update_chrome_style();
|
||||
|
||||
return QToolBar::event(event);
|
||||
}
|
||||
|
||||
void BookmarksBar::update_chrome_style()
|
||||
{
|
||||
if (m_is_updating_chrome_style)
|
||||
return;
|
||||
|
||||
m_is_updating_chrome_style = true;
|
||||
setStyleSheet(ChromeStyle::bookmarks_bar_style_sheet(palette()));
|
||||
m_is_updating_chrome_style = false;
|
||||
}
|
||||
|
||||
void BookmarksBar::rebuild()
|
||||
{
|
||||
for (auto* action : actions()) {
|
||||
|
|
|
|||
|
|
@ -28,12 +28,14 @@ public:
|
|||
void show_context_menu(QPoint, Optional<WebView::BookmarkItem const&>, Optional<String const&> target_folder_id);
|
||||
|
||||
private:
|
||||
virtual bool event(QEvent*) override;
|
||||
virtual bool eventFilter(QObject* object, QEvent* event) override;
|
||||
|
||||
bool handle_left_mouse_click(QMouseEvent*, QObject*);
|
||||
bool handle_middle_mouse_click(QMouseEvent*, QObject*);
|
||||
bool handle_right_mouse_click(QMouseEvent*, QObject*);
|
||||
void extract_item_properties(QObject*);
|
||||
void update_chrome_style();
|
||||
|
||||
QMenu& bookmarks_bar_context_menu();
|
||||
QMenu& bookmark_context_menu();
|
||||
|
|
@ -46,6 +48,7 @@ private:
|
|||
String m_selected_bookmark_menu_item_id;
|
||||
QString m_selected_bookmark_menu_item_type;
|
||||
Optional<String> m_selected_bookmark_menu_target_folder_id;
|
||||
bool m_is_updating_chrome_style { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,10 @@
|
|||
#include <UI/Qt/TabBar.h>
|
||||
#include <UI/Qt/WebContentView.h>
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QAction>
|
||||
#include <QActionGroup>
|
||||
#include <QApplication>
|
||||
#include <QGuiApplication>
|
||||
#include <QInputDialog>
|
||||
#include <QMenuBar>
|
||||
|
|
@ -187,7 +189,9 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, IsPopupWindow
|
|||
{
|
||||
auto const& browser_options = WebView::Application::browser_options();
|
||||
|
||||
setWindowFlag(Qt::FramelessWindowHint);
|
||||
setWindowIcon(app_icon());
|
||||
qApp->installEventFilter(this);
|
||||
|
||||
// Listen for DPI changes
|
||||
m_device_pixel_ratio = devicePixelRatio();
|
||||
|
|
@ -429,6 +433,11 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, IsPopupWindow
|
|||
on_devtools_enabled();
|
||||
}
|
||||
|
||||
BrowserWindow::~BrowserWindow()
|
||||
{
|
||||
qApp->removeEventFilter(this);
|
||||
}
|
||||
|
||||
void BrowserWindow::rebuild_bookmarks_menu()
|
||||
{
|
||||
repopulate_application_menu(*m_bookmarks_menu, *this, Application::the().bookmarks_menu());
|
||||
|
|
@ -819,6 +828,53 @@ bool BrowserWindow::event(QEvent* event)
|
|||
return QMainWindow::event(event);
|
||||
}
|
||||
|
||||
bool BrowserWindow::eventFilter(QObject* object, QEvent* event)
|
||||
{
|
||||
if (event->type() != QEvent::MouseButtonPress)
|
||||
return QMainWindow::eventFilter(object, event);
|
||||
|
||||
if (isMaximized() || isFullScreen())
|
||||
return QMainWindow::eventFilter(object, event);
|
||||
|
||||
auto* widget = as_if<QWidget>(object);
|
||||
if (!widget || widget->window() != this)
|
||||
return QMainWindow::eventFilter(object, event);
|
||||
if (qobject_cast<QAbstractButton*>(object))
|
||||
return QMainWindow::eventFilter(object, event);
|
||||
|
||||
auto* mouse_event = static_cast<QMouseEvent*>(event);
|
||||
if (mouse_event->button() != Qt::LeftButton)
|
||||
return QMainWindow::eventFilter(object, event);
|
||||
|
||||
auto edges = resize_edges_for_position(widget->mapTo(this, mouse_event->position().toPoint()));
|
||||
if (edges == Qt::Edges {})
|
||||
return QMainWindow::eventFilter(object, event);
|
||||
|
||||
auto* handle = windowHandle();
|
||||
if (!handle || !handle->startSystemResize(edges))
|
||||
return QMainWindow::eventFilter(object, event);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Qt::Edges BrowserWindow::resize_edges_for_position(QPoint const& position) const
|
||||
{
|
||||
static constexpr int resize_border_width = 6;
|
||||
|
||||
Qt::Edges edges;
|
||||
if (position.x() <= resize_border_width)
|
||||
edges |= Qt::LeftEdge;
|
||||
if (position.x() >= width() - resize_border_width)
|
||||
edges |= Qt::RightEdge;
|
||||
if (position.y() <= resize_border_width
|
||||
&& (position.x() <= resize_border_width * 2 || position.x() >= width() - resize_border_width * 2))
|
||||
edges |= Qt::TopEdge;
|
||||
if (position.y() >= height() - resize_border_width)
|
||||
edges |= Qt::BottomEdge;
|
||||
|
||||
return edges;
|
||||
}
|
||||
|
||||
void BrowserWindow::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ public:
|
|||
};
|
||||
|
||||
BrowserWindow(Vector<URL::URL> const& initial_urls, IsPopupWindow is_popup_window = IsPopupWindow::No, Tab* parent_tab = nullptr, Optional<u64> page_index = {});
|
||||
virtual ~BrowserWindow() override;
|
||||
|
||||
WebContentView& view() const { return m_current_tab->view(); }
|
||||
|
||||
|
|
@ -135,6 +136,7 @@ public slots:
|
|||
|
||||
private:
|
||||
virtual bool event(QEvent*) override;
|
||||
virtual bool eventFilter(QObject*, QEvent*) override;
|
||||
virtual void resizeEvent(QResizeEvent*) override;
|
||||
virtual void changeEvent(QEvent* event) override;
|
||||
virtual void moveEvent(QMoveEvent*) override;
|
||||
|
|
@ -145,6 +147,7 @@ private:
|
|||
void initialize_tab(Tab*);
|
||||
|
||||
void set_current_tab(Tab* tab);
|
||||
Qt::Edges resize_edges_for_position(QPoint const&) const;
|
||||
|
||||
template<typename Callback>
|
||||
void for_each_tab(Callback&& callback)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ target_sources(ladybird PRIVATE
|
|||
Autocomplete.cpp
|
||||
BookmarksBar.cpp
|
||||
BrowserWindow.cpp
|
||||
ChromeStyle.cpp
|
||||
EventLoopImplementationQt.cpp
|
||||
EventLoopImplementationQtEventTarget.cpp
|
||||
FindInPageWidget.cpp
|
||||
|
|
|
|||
367
UI/Qt/ChromeStyle.cpp
Normal file
367
UI/Qt/ChromeStyle.cpp
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <UI/Qt/ChromeStyle.h>
|
||||
|
||||
namespace Ladybird::ChromeStyle {
|
||||
|
||||
static bool is_dark(QPalette const& palette)
|
||||
{
|
||||
return palette.color(QPalette::Window).lightness() < 128;
|
||||
}
|
||||
|
||||
static QColor mix(QColor const& from, QColor const& to, double amount)
|
||||
{
|
||||
auto channel = [&](int from_channel, int to_channel) {
|
||||
return static_cast<int>(from_channel + (to_channel - from_channel) * amount);
|
||||
};
|
||||
|
||||
return QColor {
|
||||
channel(from.red(), to.red()),
|
||||
channel(from.green(), to.green()),
|
||||
channel(from.blue(), to.blue()),
|
||||
};
|
||||
}
|
||||
|
||||
QColor chrome_background(QPalette const& palette)
|
||||
{
|
||||
auto window = palette.color(QPalette::Window);
|
||||
return is_dark(palette)
|
||||
? mix(window, QColor(18, 22, 28), 0.42)
|
||||
: mix(window, QColor(240, 244, 248), 0.58);
|
||||
}
|
||||
|
||||
QColor chrome_surface(QPalette const& palette)
|
||||
{
|
||||
auto base = palette.color(QPalette::Base);
|
||||
return is_dark(palette)
|
||||
? mix(base, QColor(40, 45, 54), 0.46)
|
||||
: mix(base, QColor(255, 255, 255), 0.72);
|
||||
}
|
||||
|
||||
QColor chrome_surface_hover(QPalette const& palette)
|
||||
{
|
||||
auto accent = palette.color(QPalette::Highlight);
|
||||
return is_dark(palette)
|
||||
? mix(chrome_surface(palette), accent.lighter(135), 0.18)
|
||||
: mix(chrome_surface(palette), accent.lighter(165), 0.22);
|
||||
}
|
||||
|
||||
QColor chrome_surface_pressed(QPalette const& palette)
|
||||
{
|
||||
auto accent = palette.color(QPalette::Highlight);
|
||||
return is_dark(palette)
|
||||
? mix(chrome_surface(palette), accent.lighter(125), 0.28)
|
||||
: mix(chrome_surface(palette), accent.lighter(145), 0.32);
|
||||
}
|
||||
|
||||
QColor chrome_border(QPalette const& palette)
|
||||
{
|
||||
return is_dark(palette)
|
||||
? mix(chrome_surface(palette), QColor(255, 255, 255), 0.16)
|
||||
: mix(chrome_background(palette), QColor(92, 105, 120), 0.22);
|
||||
}
|
||||
|
||||
QColor chrome_accent(QPalette const& palette)
|
||||
{
|
||||
return palette.color(QPalette::Highlight);
|
||||
}
|
||||
|
||||
QColor chrome_muted_text(QPalette const& palette)
|
||||
{
|
||||
return palette.color(QPalette::PlaceholderText);
|
||||
}
|
||||
|
||||
QString style_sheet_color(QColor const& color)
|
||||
{
|
||||
return QStringLiteral("rgb(%1, %2, %3)").arg(color.red()).arg(color.green()).arg(color.blue());
|
||||
}
|
||||
|
||||
QString navigation_toolbar_style_sheet(QPalette const& palette)
|
||||
{
|
||||
auto background = style_sheet_color(chrome_background(palette));
|
||||
auto surface_hover = style_sheet_color(chrome_surface_hover(palette));
|
||||
auto surface_pressed = style_sheet_color(chrome_surface_pressed(palette));
|
||||
auto border = style_sheet_color(chrome_border(palette));
|
||||
auto text = style_sheet_color(palette.color(QPalette::ButtonText));
|
||||
auto disabled_text = style_sheet_color(chrome_muted_text(palette));
|
||||
|
||||
return QStringLiteral(R"(
|
||||
QToolBar#LadybirdNavigationToolbar {
|
||||
background: %1;
|
||||
border: 0;
|
||||
border-bottom: 1px solid %4;
|
||||
padding: 5px 8px 6px 8px;
|
||||
spacing: 6px;
|
||||
}
|
||||
|
||||
QToolBar#LadybirdNavigationToolbar QToolButton {
|
||||
color: %5;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
min-width: 30px;
|
||||
min-height: 30px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
QToolBar#LadybirdNavigationToolbar QToolButton:hover {
|
||||
background: %2;
|
||||
border-color: %4;
|
||||
}
|
||||
|
||||
QToolBar#LadybirdNavigationToolbar QToolButton:pressed,
|
||||
QToolBar#LadybirdNavigationToolbar QToolButton:checked {
|
||||
background: %3;
|
||||
border-color: %4;
|
||||
}
|
||||
|
||||
QToolBar#LadybirdNavigationToolbar QToolButton:disabled {
|
||||
color: %6;
|
||||
}
|
||||
|
||||
QToolBar#LadybirdNavigationToolbar QToolButton::menu-indicator {
|
||||
image: none;
|
||||
}
|
||||
)")
|
||||
.arg(background, surface_hover, surface_pressed, border, text, disabled_text);
|
||||
}
|
||||
|
||||
QString location_edit_style_sheet(QPalette const& palette)
|
||||
{
|
||||
auto surface = style_sheet_color(chrome_surface(palette));
|
||||
auto hover = style_sheet_color(chrome_surface_hover(palette));
|
||||
auto border = style_sheet_color(chrome_border(palette));
|
||||
auto accent = style_sheet_color(chrome_accent(palette));
|
||||
auto text = style_sheet_color(palette.color(QPalette::Text));
|
||||
auto placeholder = style_sheet_color(chrome_muted_text(palette));
|
||||
auto selection = style_sheet_color(chrome_accent(palette));
|
||||
auto selection_text = style_sheet_color(palette.color(QPalette::HighlightedText));
|
||||
|
||||
return QStringLiteral(R"(
|
||||
QLineEdit#LadybirdLocationEdit {
|
||||
color: %5;
|
||||
background: %1;
|
||||
border: 1px solid %3;
|
||||
border-radius: 8px;
|
||||
min-height: 30px;
|
||||
padding: 3px 12px;
|
||||
selection-background-color: %7;
|
||||
selection-color: %8;
|
||||
}
|
||||
|
||||
QLineEdit#LadybirdLocationEdit:hover {
|
||||
background: %2;
|
||||
}
|
||||
|
||||
QLineEdit#LadybirdLocationEdit:focus {
|
||||
background: %1;
|
||||
border-color: %4;
|
||||
}
|
||||
|
||||
QLineEdit#LadybirdLocationEdit:disabled {
|
||||
color: %6;
|
||||
}
|
||||
)")
|
||||
.arg(surface, hover, border, accent, text, placeholder, selection, selection_text);
|
||||
}
|
||||
|
||||
QString bookmarks_bar_style_sheet(QPalette const& palette)
|
||||
{
|
||||
auto background = style_sheet_color(chrome_background(palette));
|
||||
auto hover = style_sheet_color(chrome_surface_hover(palette));
|
||||
auto pressed = style_sheet_color(chrome_surface_pressed(palette));
|
||||
auto border = style_sheet_color(chrome_border(palette));
|
||||
auto text = style_sheet_color(palette.color(QPalette::ButtonText));
|
||||
|
||||
return QStringLiteral(R"(
|
||||
QToolBar#LadybirdBookmarksBar {
|
||||
color: %5;
|
||||
background: %1;
|
||||
border: 0;
|
||||
border-bottom: 1px solid %4;
|
||||
padding: 4px 8px;
|
||||
spacing: 3px;
|
||||
}
|
||||
|
||||
QToolBar#LadybirdBookmarksBar QToolButton {
|
||||
color: %5;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 7px;
|
||||
padding: 4px 7px;
|
||||
}
|
||||
|
||||
QToolBar#LadybirdBookmarksBar QToolButton:hover {
|
||||
background: %2;
|
||||
border-color: %4;
|
||||
}
|
||||
|
||||
QToolBar#LadybirdBookmarksBar QToolButton:pressed,
|
||||
QToolBar#LadybirdBookmarksBar QToolButton:checked {
|
||||
background: %3;
|
||||
border-color: %4;
|
||||
}
|
||||
)")
|
||||
.arg(background, hover, pressed, border, text);
|
||||
}
|
||||
|
||||
QString find_in_page_style_sheet(QPalette const& palette)
|
||||
{
|
||||
auto background = style_sheet_color(chrome_background(palette));
|
||||
auto surface = style_sheet_color(chrome_surface(palette));
|
||||
auto hover = style_sheet_color(chrome_surface_hover(palette));
|
||||
auto pressed = style_sheet_color(chrome_surface_pressed(palette));
|
||||
auto border = style_sheet_color(chrome_border(palette));
|
||||
auto accent = style_sheet_color(chrome_accent(palette));
|
||||
auto text = style_sheet_color(palette.color(QPalette::Text));
|
||||
auto muted = style_sheet_color(chrome_muted_text(palette));
|
||||
|
||||
return QStringLiteral(R"(
|
||||
QWidget#LadybirdFindInPageBar {
|
||||
background: %1;
|
||||
border-top: 1px solid %5;
|
||||
}
|
||||
|
||||
QWidget#LadybirdFindInPageBar QLineEdit {
|
||||
color: %7;
|
||||
background: %2;
|
||||
border: 1px solid %5;
|
||||
border-radius: 8px;
|
||||
min-height: 26px;
|
||||
padding: 2px 9px;
|
||||
selection-background-color: %6;
|
||||
}
|
||||
|
||||
QWidget#LadybirdFindInPageBar QLineEdit:focus {
|
||||
border-color: %6;
|
||||
}
|
||||
|
||||
QWidget#LadybirdFindInPageBar QPushButton {
|
||||
color: %7;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 7px;
|
||||
min-height: 26px;
|
||||
}
|
||||
|
||||
QWidget#LadybirdFindInPageBar QPushButton:hover {
|
||||
background: %3;
|
||||
border-color: %5;
|
||||
}
|
||||
|
||||
QWidget#LadybirdFindInPageBar QPushButton:pressed {
|
||||
background: %4;
|
||||
border-color: %5;
|
||||
}
|
||||
|
||||
QWidget#LadybirdFindInPageBar QCheckBox,
|
||||
QWidget#LadybirdFindInPageBar QLabel {
|
||||
color: %8;
|
||||
}
|
||||
)")
|
||||
.arg(background, surface, hover, pressed, border, accent, text, muted);
|
||||
}
|
||||
|
||||
QString tab_widget_style_sheet(QPalette const& palette)
|
||||
{
|
||||
auto background = style_sheet_color(chrome_background(palette));
|
||||
auto hover = style_sheet_color(chrome_surface_hover(palette));
|
||||
auto pressed = style_sheet_color(chrome_surface_pressed(palette));
|
||||
auto border = style_sheet_color(chrome_border(palette));
|
||||
auto text = style_sheet_color(palette.color(QPalette::ButtonText));
|
||||
auto close_hover = style_sheet_color(QColor(196, 43, 28));
|
||||
auto close_text = style_sheet_color(QColor(255, 255, 255));
|
||||
|
||||
return QStringLiteral(R"(
|
||||
QWidget#LadybirdTabStrip {
|
||||
color: %5;
|
||||
background: %1;
|
||||
border: 0;
|
||||
border-bottom: 1px solid %4;
|
||||
}
|
||||
|
||||
QToolButton#LadybirdNewTabButton,
|
||||
QPushButton#LadybirdTabButton {
|
||||
color: %5;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
min-width: 24px;
|
||||
min-height: 24px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
QToolButton#LadybirdNewTabButton:hover,
|
||||
QPushButton#LadybirdTabButton:hover {
|
||||
background: %2;
|
||||
border-color: %4;
|
||||
}
|
||||
|
||||
QToolButton#LadybirdNewTabButton:pressed,
|
||||
QPushButton#LadybirdTabButton:pressed,
|
||||
QPushButton#LadybirdTabButton:checked {
|
||||
background: %3;
|
||||
border-color: %4;
|
||||
}
|
||||
|
||||
QToolButton#LadybirdWindowButton,
|
||||
QToolButton#LadybirdCloseWindowButton {
|
||||
color: %5;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
min-width: 40px;
|
||||
min-height: 32px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
QToolButton#LadybirdWindowButton:hover {
|
||||
background: %2;
|
||||
}
|
||||
|
||||
QToolButton#LadybirdWindowButton:pressed {
|
||||
background: %3;
|
||||
}
|
||||
|
||||
QToolButton#LadybirdCloseWindowButton:hover {
|
||||
color: %7;
|
||||
background: %6;
|
||||
}
|
||||
|
||||
QToolButton#LadybirdCloseWindowButton:pressed {
|
||||
color: %7;
|
||||
background: %6;
|
||||
}
|
||||
)")
|
||||
.arg(background, hover, pressed, border, text, close_hover, close_text);
|
||||
}
|
||||
|
||||
QString autocomplete_popup_style_sheet(QPalette const& palette)
|
||||
{
|
||||
auto surface = style_sheet_color(chrome_surface(palette));
|
||||
auto border = style_sheet_color(chrome_border(palette));
|
||||
auto text = style_sheet_color(palette.color(QPalette::Text));
|
||||
|
||||
return QStringLiteral(R"(
|
||||
QFrame#LadybirdAutocompletePopup {
|
||||
color: %3;
|
||||
background: %1;
|
||||
border: 1px solid %2;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
QListView#LadybirdAutocompleteList {
|
||||
color: %3;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
}
|
||||
)")
|
||||
.arg(surface, border, text);
|
||||
}
|
||||
|
||||
}
|
||||
31
UI/Qt/ChromeStyle.h
Normal file
31
UI/Qt/ChromeStyle.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QColor>
|
||||
#include <QPalette>
|
||||
#include <QString>
|
||||
|
||||
namespace Ladybird::ChromeStyle {
|
||||
|
||||
QColor chrome_background(QPalette const&);
|
||||
QColor chrome_surface(QPalette const&);
|
||||
QColor chrome_surface_hover(QPalette const&);
|
||||
QColor chrome_surface_pressed(QPalette const&);
|
||||
QColor chrome_border(QPalette const&);
|
||||
QColor chrome_accent(QPalette const&);
|
||||
QColor chrome_muted_text(QPalette const&);
|
||||
|
||||
QString style_sheet_color(QColor const&);
|
||||
QString navigation_toolbar_style_sheet(QPalette const&);
|
||||
QString location_edit_style_sheet(QPalette const&);
|
||||
QString bookmarks_bar_style_sheet(QPalette const&);
|
||||
QString find_in_page_style_sheet(QPalette const&);
|
||||
QString tab_widget_style_sheet(QPalette const&);
|
||||
QString autocomplete_popup_style_sheet(QPalette const&);
|
||||
|
||||
}
|
||||
|
|
@ -4,11 +4,13 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <UI/Qt/ChromeStyle.h>
|
||||
#include <UI/Qt/FindInPageWidget.h>
|
||||
#include <UI/Qt/Icon.h>
|
||||
#include <UI/Qt/StringUtils.h>
|
||||
#include <UI/Qt/Tab.h>
|
||||
|
||||
#include <QEvent>
|
||||
#include <QKeyEvent>
|
||||
|
||||
namespace Ladybird {
|
||||
|
|
@ -18,8 +20,10 @@ FindInPageWidget::FindInPageWidget(Tab* tab, WebContentView* content_view)
|
|||
, m_tab(tab)
|
||||
, m_content_view(content_view)
|
||||
{
|
||||
setObjectName("LadybirdFindInPageBar");
|
||||
setFocusPolicy(Qt::FocusPolicy::StrongFocus);
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
update_chrome_style();
|
||||
|
||||
auto* layout = new QHBoxLayout(this);
|
||||
setLayout(layout);
|
||||
|
|
@ -88,6 +92,28 @@ FindInPageWidget::FindInPageWidget(Tab* tab, WebContentView* content_view)
|
|||
|
||||
FindInPageWidget::~FindInPageWidget() = default;
|
||||
|
||||
bool FindInPageWidget::event(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::PaletteChange) {
|
||||
update_chrome_style();
|
||||
m_previous_button->setIcon(create_tvg_icon_with_theme_colors("up", palette()));
|
||||
m_next_button->setIcon(create_tvg_icon_with_theme_colors("down", palette()));
|
||||
m_exit_button->setIcon(create_tvg_icon_with_theme_colors("close", palette()));
|
||||
}
|
||||
|
||||
return QWidget::event(event);
|
||||
}
|
||||
|
||||
void FindInPageWidget::update_chrome_style()
|
||||
{
|
||||
if (m_is_updating_chrome_style)
|
||||
return;
|
||||
|
||||
m_is_updating_chrome_style = true;
|
||||
setStyleSheet(ChromeStyle::find_in_page_style_sheet(palette()));
|
||||
m_is_updating_chrome_style = false;
|
||||
}
|
||||
|
||||
void FindInPageWidget::find_text_changed()
|
||||
{
|
||||
auto query = ak_string_from_qstring(m_find_text->text());
|
||||
|
|
|
|||
|
|
@ -36,12 +36,14 @@ public:
|
|||
public slots:
|
||||
|
||||
private:
|
||||
virtual bool event(QEvent*) override;
|
||||
virtual void keyPressEvent(QKeyEvent*) override;
|
||||
virtual void focusInEvent(QFocusEvent*) override;
|
||||
virtual void showEvent(QShowEvent*) override;
|
||||
virtual void hideEvent(QHideEvent*) override;
|
||||
|
||||
void find_text_changed();
|
||||
void update_chrome_style();
|
||||
|
||||
Tab* m_tab { nullptr };
|
||||
WebContentView* m_content_view { nullptr };
|
||||
|
|
@ -52,6 +54,7 @@ private:
|
|||
QPushButton* m_exit_button { nullptr };
|
||||
QCheckBox* m_match_case { nullptr };
|
||||
QLabel* m_result_label { nullptr };
|
||||
bool m_is_updating_chrome_style { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <LibWebView/Autocomplete.h>
|
||||
#include <LibWebView/URL.h>
|
||||
#include <UI/Qt/Autocomplete.h>
|
||||
#include <UI/Qt/ChromeStyle.h>
|
||||
#include <UI/Qt/Icon.h>
|
||||
#include <UI/Qt/LocationEdit.h>
|
||||
#include <UI/Qt/StringUtils.h>
|
||||
|
|
@ -133,6 +134,10 @@ LocationEdit::LocationEdit(QWidget* parent)
|
|||
: QLineEdit(parent)
|
||||
, m_autocomplete(new Autocomplete(this))
|
||||
{
|
||||
setObjectName("LadybirdLocationEdit");
|
||||
setMinimumHeight(34);
|
||||
update_chrome_style();
|
||||
|
||||
m_leading_icon_action = addAction(create_tvg_icon_with_theme_colors("search", palette()), QLineEdit::LeadingPosition);
|
||||
m_leading_icon_action->setToolTip("Search");
|
||||
|
||||
|
|
@ -229,8 +234,10 @@ LocationEdit::LocationEdit(QWidget* parent)
|
|||
void LocationEdit::changeEvent(QEvent* event)
|
||||
{
|
||||
QLineEdit::changeEvent(event);
|
||||
if (event->type() == QEvent::PaletteChange)
|
||||
if (event->type() == QEvent::PaletteChange) {
|
||||
update_chrome_style();
|
||||
update_location_icon();
|
||||
}
|
||||
}
|
||||
|
||||
void LocationEdit::focusInEvent(QFocusEvent* event)
|
||||
|
|
@ -304,6 +311,16 @@ void LocationEdit::search_engine_changed()
|
|||
update_placeholder();
|
||||
}
|
||||
|
||||
void LocationEdit::update_chrome_style()
|
||||
{
|
||||
if (m_is_updating_chrome_style)
|
||||
return;
|
||||
|
||||
m_is_updating_chrome_style = true;
|
||||
setStyleSheet(ChromeStyle::location_edit_style_sheet(palette()));
|
||||
m_is_updating_chrome_style = false;
|
||||
}
|
||||
|
||||
void LocationEdit::update_placeholder()
|
||||
{
|
||||
if (auto const& search_engine = WebView::Application::settings().search_engine(); search_engine.has_value()) {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ private:
|
|||
virtual void search_engine_changed() override;
|
||||
|
||||
void update_placeholder();
|
||||
void update_chrome_style();
|
||||
void update_location_icon();
|
||||
void update_loading_icon();
|
||||
void highlight_location();
|
||||
|
|
@ -69,6 +70,7 @@ private:
|
|||
QIcon m_favicon;
|
||||
bool m_url_is_hidden { false };
|
||||
bool m_is_loading { false };
|
||||
bool m_is_updating_chrome_style { false };
|
||||
int m_loading_animation_frame { 0 };
|
||||
|
||||
bool m_is_applying_inline_autocomplete { false };
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <LibWebView/Utilities.h>
|
||||
#include <LibWebView/WebContentClient.h>
|
||||
#include <UI/Qt/BrowserWindow.h>
|
||||
#include <UI/Qt/ChromeStyle.h>
|
||||
#include <UI/Qt/Icon.h>
|
||||
#include <UI/Qt/Menu.h>
|
||||
#include <UI/Qt/Settings.h>
|
||||
|
|
@ -88,6 +89,7 @@ Tab::Tab(BrowserWindow* window, RefPtr<WebView::WebContentClient> parent_client,
|
|||
m_find_in_page = new FindInPageWidget(this, m_view);
|
||||
m_find_in_page->setVisible(false);
|
||||
m_toolbar = new QToolBar(this);
|
||||
m_toolbar->setObjectName("LadybirdNavigationToolbar");
|
||||
m_location_edit = new LocationEdit(this);
|
||||
m_bookmarks_bar = new BookmarksBar(this);
|
||||
m_loading_animation_timer = new QTimer(this);
|
||||
|
|
@ -121,7 +123,6 @@ Tab::Tab(BrowserWindow* window, RefPtr<WebView::WebContentClient> parent_client,
|
|||
m_hamburger_button->setIcon(create_tvg_icon_with_theme_colors("hamburger", palette()));
|
||||
m_hamburger_button->setPopupMode(QToolButton::InstantPopup);
|
||||
m_hamburger_button->setMenu(&m_window->hamburger_menu());
|
||||
m_hamburger_button->setStyleSheet(":menu-indicator {image: none}");
|
||||
|
||||
QObject::connect(&m_window->hamburger_menu(), &QMenu::aboutToShow, m_hamburger_button, [this]() {
|
||||
m_hamburger_button->setDown(true);
|
||||
|
|
@ -153,9 +154,9 @@ Tab::Tab(BrowserWindow* window, RefPtr<WebView::WebContentClient> parent_client,
|
|||
|
||||
m_toolbar->setIconSize({ 16, 16 });
|
||||
m_toolbar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
|
||||
// This is a little awkward, but without this Qt shrinks the button to the size of the icon.
|
||||
// Note: toolButtonStyle="0" -> ToolButtonIconOnly.
|
||||
m_toolbar->setStyleSheet("QToolButton[toolButtonStyle=\"0\"]{padding:0px 4px 0px 4px;height:24px}");
|
||||
m_toolbar->setMovable(false);
|
||||
m_toolbar->setFloatable(false);
|
||||
update_chrome_style();
|
||||
|
||||
m_hamburger_button_action->setVisible(!Settings::the()->show_menubar());
|
||||
|
||||
|
|
@ -609,6 +610,7 @@ void Tab::update_hover_label()
|
|||
bool Tab::event(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::PaletteChange) {
|
||||
update_chrome_style();
|
||||
recreate_toolbar_icons();
|
||||
if (m_is_loading)
|
||||
update_tab_icon();
|
||||
|
|
@ -618,6 +620,17 @@ bool Tab::event(QEvent* event)
|
|||
return QWidget::event(event);
|
||||
}
|
||||
|
||||
void Tab::update_chrome_style()
|
||||
{
|
||||
if (m_is_updating_chrome_style)
|
||||
return;
|
||||
|
||||
m_is_updating_chrome_style = true;
|
||||
m_toolbar->setStyleSheet(ChromeStyle::navigation_toolbar_style_sheet(palette()));
|
||||
m_hover_label->setStyleSheet(QStringLiteral("padding: 2px 6px; border-radius: 6px;"));
|
||||
m_is_updating_chrome_style = false;
|
||||
}
|
||||
|
||||
void Tab::recreate_toolbar_icons()
|
||||
{
|
||||
m_navigate_back_action->setIcon(create_tvg_icon_with_theme_colors("back", palette()));
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ private:
|
|||
virtual void config_variable_changed(WebView::ConfigVariableID) override;
|
||||
|
||||
void recreate_toolbar_icons();
|
||||
void update_chrome_style();
|
||||
void update_tab_title();
|
||||
void set_loading(bool);
|
||||
void update_tab_icon();
|
||||
|
|
@ -119,6 +120,7 @@ private:
|
|||
QIcon m_favicon;
|
||||
QTimer* m_loading_animation_timer { nullptr };
|
||||
bool m_is_loading { false };
|
||||
bool m_is_updating_chrome_style { false };
|
||||
int m_loading_animation_frame { 0 };
|
||||
|
||||
QMenu* m_context_menu { nullptr };
|
||||
|
|
|
|||
301
UI/Qt/TabBar.cpp
301
UI/Qt/TabBar.cpp
|
|
@ -7,55 +7,45 @@
|
|||
*/
|
||||
|
||||
#include <AK/StdLibExtras.h>
|
||||
#include <UI/Qt/ChromeStyle.h>
|
||||
#include <UI/Qt/Icon.h>
|
||||
#include <UI/Qt/Tab.h>
|
||||
#include <UI/Qt/TabBar.h>
|
||||
|
||||
#include <QContextMenuEvent>
|
||||
#include <QEvent>
|
||||
#include <QFontMetrics>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMouseEvent>
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QProxyStyle>
|
||||
#include <QStyleOption>
|
||||
#include <QPainterPath>
|
||||
#include <QStyle>
|
||||
#include <QToolButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWindow>
|
||||
|
||||
namespace Ladybird {
|
||||
|
||||
class LeftAlignedTabStyle final : public QProxyStyle {
|
||||
public:
|
||||
virtual void drawControl(ControlElement element, QStyleOption const* option, QPainter* painter, QWidget const* widget = nullptr) const override
|
||||
{
|
||||
if (element != QStyle::CE_TabBarTabLabel) {
|
||||
QProxyStyle::drawControl(element, option, painter, widget);
|
||||
return;
|
||||
}
|
||||
|
||||
auto const* tab = qstyleoption_cast<QStyleOptionTab const*>(option);
|
||||
if (!tab) {
|
||||
QProxyStyle::drawControl(element, option, painter, widget);
|
||||
return;
|
||||
}
|
||||
|
||||
QStyleOptionTab tab_without_text(*tab);
|
||||
tab_without_text.text.clear();
|
||||
QProxyStyle::drawControl(element, &tab_without_text, painter, widget);
|
||||
|
||||
auto text_rect = subElementRect(QStyle::SE_TabBarTabText, tab, widget);
|
||||
int text_alignment = Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic;
|
||||
if (!styleHint(QStyle::SH_UnderlineShortcut, tab, widget))
|
||||
text_alignment |= Qt::TextHideMnemonic;
|
||||
|
||||
drawItemText(painter, text_rect, text_alignment, tab->palette, tab->state & QStyle::State_Enabled, tab->text,
|
||||
widget ? widget->foregroundRole() : QPalette::WindowText);
|
||||
}
|
||||
};
|
||||
static QPainterPath active_tab_path(QRectF const& rect, qreal radius)
|
||||
{
|
||||
QPainterPath path;
|
||||
path.moveTo(rect.left(), rect.bottom());
|
||||
path.lineTo(rect.left(), rect.top() + radius);
|
||||
path.quadTo(rect.left(), rect.top(), rect.left() + radius, rect.top());
|
||||
path.lineTo(rect.right() - radius, rect.top());
|
||||
path.quadTo(rect.right(), rect.top(), rect.right(), rect.top() + radius);
|
||||
path.lineTo(rect.right(), rect.bottom());
|
||||
path.closeSubpath();
|
||||
return path;
|
||||
}
|
||||
|
||||
TabBar::TabBar(TabWidget* tab_widget)
|
||||
: QTabBar(tab_widget)
|
||||
, m_tab_widget(tab_widget)
|
||||
{
|
||||
setMouseTracking(true);
|
||||
setMinimumHeight(38);
|
||||
}
|
||||
|
||||
void TabBar::set_available_width(int width)
|
||||
|
|
@ -72,21 +62,106 @@ QSize TabBar::tabSizeHint(int index) const
|
|||
|
||||
if (auto count = this->count(); count > 0) {
|
||||
auto width = (m_available_width > 0 ? m_available_width : this->width()) / count;
|
||||
width = min(225, width);
|
||||
width = min(240, width);
|
||||
width = max(128, width);
|
||||
|
||||
hint.setWidth(width);
|
||||
}
|
||||
|
||||
hint.setHeight(34);
|
||||
return hint;
|
||||
}
|
||||
|
||||
void TabBar::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
painter.fillRect(rect(), ChromeStyle::chrome_background(palette()));
|
||||
|
||||
auto border = ChromeStyle::chrome_border(palette());
|
||||
auto accent = ChromeStyle::chrome_accent(palette());
|
||||
auto text_color = palette().color(QPalette::Text);
|
||||
auto muted_text_color = ChromeStyle::chrome_muted_text(palette());
|
||||
|
||||
for (int index = 0; index < count(); ++index) {
|
||||
auto tab_rect = tabRect(index);
|
||||
if (!tab_rect.isValid() || tab_rect.right() < 0 || tab_rect.left() > width())
|
||||
continue;
|
||||
|
||||
bool is_selected = index == currentIndex();
|
||||
bool is_hovered = index == m_hovered_tab_index;
|
||||
|
||||
if (is_selected || is_hovered) {
|
||||
auto shape_rect = QRectF(tab_rect).adjusted(2.0, is_selected ? 4.0 : 7.0, -2.0, is_selected ? 0.0 : -6.0);
|
||||
auto surface = is_selected ? ChromeStyle::chrome_surface(palette()) : ChromeStyle::chrome_surface_hover(palette());
|
||||
|
||||
auto tab_path = is_selected
|
||||
? active_tab_path(shape_rect, 7.0)
|
||||
: [&] {
|
||||
QPainterPath path;
|
||||
path.addRoundedRect(shape_rect, 7.0, 7.0);
|
||||
return path;
|
||||
}();
|
||||
|
||||
painter.setBrush(surface);
|
||||
painter.setPen(is_selected ? QPen(border, 1) : Qt::NoPen);
|
||||
painter.drawPath(tab_path);
|
||||
|
||||
if (is_selected) {
|
||||
painter.setPen(QPen(accent, 2));
|
||||
painter.drawLine(QPointF(shape_rect.left() + 9.0, shape_rect.top() + 1.0),
|
||||
QPointF(shape_rect.right() - 9.0, shape_rect.top() + 1.0));
|
||||
}
|
||||
} else if (index > 0 && index != currentIndex() + 1) {
|
||||
auto separator = border;
|
||||
separator.setAlpha(110);
|
||||
painter.setPen(separator);
|
||||
painter.drawLine(tab_rect.topLeft() + QPoint(0, 11), tab_rect.bottomLeft() - QPoint(0, 10));
|
||||
}
|
||||
|
||||
auto contents_rect = tab_rect.adjusted(13, 1, -13, 0);
|
||||
if (auto* left_button = tabButton(index, QTabBar::LeftSide); left_button && left_button->isVisible())
|
||||
contents_rect.setLeft(max(contents_rect.left(), left_button->geometry().right() + 6));
|
||||
if (auto* right_button = tabButton(index, QTabBar::RightSide); right_button && right_button->isVisible())
|
||||
contents_rect.setRight(min(contents_rect.right(), right_button->geometry().left() - 6));
|
||||
|
||||
auto icon = tabIcon(index);
|
||||
if (!icon.isNull() && contents_rect.width() > 26) {
|
||||
constexpr int icon_size = 16;
|
||||
QRect icon_rect {
|
||||
contents_rect.left(),
|
||||
contents_rect.top() + (contents_rect.height() - icon_size) / 2,
|
||||
icon_size,
|
||||
icon_size,
|
||||
};
|
||||
icon.paint(&painter, icon_rect);
|
||||
contents_rect.setLeft(icon_rect.right() + 7);
|
||||
}
|
||||
|
||||
QFont tab_font = font();
|
||||
if (is_selected)
|
||||
tab_font.setWeight(QFont::DemiBold);
|
||||
painter.setFont(tab_font);
|
||||
|
||||
QFontMetrics font_metrics(tab_font);
|
||||
auto title = font_metrics.elidedText(tabText(index), Qt::ElideRight, max(0, contents_rect.width()));
|
||||
painter.setPen(is_selected || is_hovered ? text_color : muted_text_color);
|
||||
painter.drawText(contents_rect, Qt::AlignLeft | Qt::AlignVCenter, title);
|
||||
}
|
||||
}
|
||||
|
||||
void TabBar::contextMenuEvent(QContextMenuEvent* event)
|
||||
{
|
||||
if (!m_tab_widget)
|
||||
return;
|
||||
|
||||
if (auto* tab = m_tab_widget->tab(tabAt(event->pos())))
|
||||
auto tab_index = tabAt(event->pos());
|
||||
if (tab_index < 0)
|
||||
return;
|
||||
|
||||
if (auto* tab = m_tab_widget->tab(tab_index))
|
||||
tab->context_menu()->exec(event->globalPos());
|
||||
}
|
||||
|
||||
|
|
@ -94,8 +169,16 @@ void TabBar::mousePressEvent(QMouseEvent* event)
|
|||
{
|
||||
event->ignore();
|
||||
|
||||
auto rect_of_current_tab = tabRect(tabAt(event->pos()));
|
||||
m_x_position_in_selected_tab_while_dragging = event->pos().x() - rect_of_current_tab.x();
|
||||
auto pressed_tab = tabAt(event->pos());
|
||||
if (pressed_tab < 0 && event->button() == Qt::LeftButton) {
|
||||
if (start_window_move())
|
||||
return;
|
||||
}
|
||||
|
||||
if (pressed_tab >= 0) {
|
||||
auto rect_of_current_tab = tabRect(pressed_tab);
|
||||
m_x_position_in_selected_tab_while_dragging = event->pos().x() - rect_of_current_tab.x();
|
||||
}
|
||||
|
||||
QTabBar::mousePressEvent(event);
|
||||
}
|
||||
|
|
@ -104,6 +187,16 @@ void TabBar::mouseMoveEvent(QMouseEvent* event)
|
|||
{
|
||||
event->ignore();
|
||||
|
||||
if (auto hovered_tab = tabAt(event->pos()); hovered_tab != m_hovered_tab_index) {
|
||||
m_hovered_tab_index = hovered_tab;
|
||||
update();
|
||||
}
|
||||
|
||||
if (count() == 0) {
|
||||
QTabBar::mouseMoveEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
auto rect_of_first_tab = tabRect(0);
|
||||
auto rect_of_last_tab = tabRect(count() - 1);
|
||||
|
||||
|
|
@ -123,13 +216,45 @@ void TabBar::mouseMoveEvent(QMouseEvent* event)
|
|||
}
|
||||
}
|
||||
|
||||
void TabBar::leaveEvent(QEvent* event)
|
||||
{
|
||||
m_hovered_tab_index = -1;
|
||||
update();
|
||||
QTabBar::leaveEvent(event);
|
||||
}
|
||||
|
||||
void TabBar::mouseDoubleClickEvent(QMouseEvent* event)
|
||||
{
|
||||
if (tabAt(event->pos()) < 0 && event->button() == Qt::LeftButton) {
|
||||
toggle_window_maximized();
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
QTabBar::mouseDoubleClickEvent(event);
|
||||
}
|
||||
|
||||
bool TabBar::start_window_move()
|
||||
{
|
||||
auto* handle = window()->windowHandle();
|
||||
if (!handle)
|
||||
return false;
|
||||
return handle->startSystemMove();
|
||||
}
|
||||
|
||||
void TabBar::toggle_window_maximized()
|
||||
{
|
||||
auto* top_level_window = window();
|
||||
if (top_level_window->isMaximized())
|
||||
top_level_window->showNormal();
|
||||
else
|
||||
top_level_window->showMaximized();
|
||||
}
|
||||
|
||||
TabWidget::TabWidget(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
m_tab_bar = new TabBar(this);
|
||||
auto* tab_style = new LeftAlignedTabStyle;
|
||||
tab_style->setParent(m_tab_bar);
|
||||
m_tab_bar->setStyle(tab_style);
|
||||
|
||||
m_tab_bar->setDocumentMode(true);
|
||||
m_tab_bar->setElideMode(Qt::TextElideMode::ElideRight);
|
||||
|
|
@ -142,27 +267,48 @@ TabWidget::TabWidget(QWidget* parent)
|
|||
m_stacked_widget = new QStackedWidget(this);
|
||||
|
||||
m_new_tab_button = new QToolButton(this);
|
||||
m_new_tab_button->setObjectName("LadybirdNewTabButton");
|
||||
m_new_tab_button->setIconSize(QSize(16, 16));
|
||||
m_new_tab_button->setAutoRaise(true);
|
||||
m_new_tab_button->setToolTip("New Tab");
|
||||
|
||||
m_minimize_window_button = new QToolButton(this);
|
||||
m_minimize_window_button->setObjectName("LadybirdWindowButton");
|
||||
m_minimize_window_button->setToolTip("Minimize");
|
||||
m_minimize_window_button->setIconSize(QSize(14, 14));
|
||||
|
||||
m_maximize_window_button = new QToolButton(this);
|
||||
m_maximize_window_button->setObjectName("LadybirdWindowButton");
|
||||
m_maximize_window_button->setIconSize(QSize(14, 14));
|
||||
|
||||
m_close_window_button = new QToolButton(this);
|
||||
m_close_window_button->setObjectName("LadybirdCloseWindowButton");
|
||||
m_close_window_button->setToolTip("Close");
|
||||
m_close_window_button->setIconSize(QSize(14, 14));
|
||||
|
||||
recreate_icons();
|
||||
|
||||
auto* tab_bar_row_layout = new QHBoxLayout();
|
||||
tab_bar_row_layout->setSpacing(0);
|
||||
tab_bar_row_layout->setContentsMargins(0, 0, 0, 0);
|
||||
tab_bar_row_layout->setContentsMargins(8, 0, 8, 0);
|
||||
tab_bar_row_layout->addWidget(m_tab_bar);
|
||||
tab_bar_row_layout->addWidget(m_new_tab_button);
|
||||
tab_bar_row_layout->addStretch(1);
|
||||
tab_bar_row_layout->addWidget(m_minimize_window_button);
|
||||
tab_bar_row_layout->addWidget(m_maximize_window_button);
|
||||
tab_bar_row_layout->addWidget(m_close_window_button);
|
||||
|
||||
m_tab_bar_row = new QWidget(this);
|
||||
m_tab_bar_row->setObjectName("LadybirdTabStrip");
|
||||
m_tab_bar_row->setMinimumHeight(40);
|
||||
m_tab_bar_row->setLayout(tab_bar_row_layout);
|
||||
m_tab_bar_row->installEventFilter(this);
|
||||
|
||||
auto* main_layout = new QVBoxLayout(this);
|
||||
main_layout->setSpacing(0);
|
||||
main_layout->setContentsMargins(0, 0, 0, 0);
|
||||
main_layout->addWidget(m_tab_bar_row);
|
||||
main_layout->addWidget(m_stacked_widget, 1);
|
||||
update_chrome_style();
|
||||
|
||||
connect(m_tab_bar, &QTabBar::currentChanged, this, [this](int index) {
|
||||
if (index >= 0 && index < m_stacked_widget->count())
|
||||
|
|
@ -182,6 +328,16 @@ TabWidget::TabWidget(QWidget* parent)
|
|||
m_stacked_widget->insertWidget(to, widget);
|
||||
m_stacked_widget->setCurrentIndex(m_tab_bar->currentIndex());
|
||||
});
|
||||
|
||||
connect(m_minimize_window_button, &QToolButton::clicked, this, [this] {
|
||||
window()->showMinimized();
|
||||
});
|
||||
connect(m_maximize_window_button, &QToolButton::clicked, this, [this] {
|
||||
toggle_window_maximized();
|
||||
});
|
||||
connect(m_close_window_button, &QToolButton::clicked, this, [this] {
|
||||
window()->close();
|
||||
});
|
||||
}
|
||||
|
||||
void TabWidget::add_tab(Tab* widget, QString const& label)
|
||||
|
|
@ -239,11 +395,35 @@ bool TabWidget::event(QEvent* event)
|
|||
}
|
||||
} else if (type == QEvent::PaletteChange) {
|
||||
recreate_icons();
|
||||
update_chrome_style();
|
||||
} else if (type == QEvent::WindowStateChange) {
|
||||
update_window_button_icons();
|
||||
}
|
||||
|
||||
return QWidget::event(event);
|
||||
}
|
||||
|
||||
bool TabWidget::eventFilter(QObject* watched, QEvent* event)
|
||||
{
|
||||
if (watched == m_tab_bar_row) {
|
||||
if (event->type() == QEvent::MouseButtonDblClick) {
|
||||
auto* mouse_event = static_cast<QMouseEvent*>(event);
|
||||
if (mouse_event->button() == Qt::LeftButton) {
|
||||
toggle_window_maximized();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
auto* mouse_event = static_cast<QMouseEvent*>(event);
|
||||
if (mouse_event->button() == Qt::LeftButton && start_window_move())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return QWidget::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
void TabWidget::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
|
|
@ -261,12 +441,53 @@ void TabWidget::update_tab_layout()
|
|||
void TabWidget::recreate_icons()
|
||||
{
|
||||
m_new_tab_button->setIcon(create_tvg_icon_with_theme_colors("new_tab", palette()));
|
||||
update_window_button_icons();
|
||||
}
|
||||
|
||||
void TabWidget::update_chrome_style()
|
||||
{
|
||||
if (m_is_updating_chrome_style)
|
||||
return;
|
||||
|
||||
m_is_updating_chrome_style = true;
|
||||
m_tab_bar_row->setStyleSheet(ChromeStyle::tab_widget_style_sheet(palette()));
|
||||
m_is_updating_chrome_style = false;
|
||||
}
|
||||
|
||||
void TabWidget::update_window_button_icons()
|
||||
{
|
||||
auto* top_level_window = window();
|
||||
auto* button_style = top_level_window->style();
|
||||
m_minimize_window_button->setIcon(button_style->standardIcon(QStyle::SP_TitleBarMinButton));
|
||||
m_maximize_window_button->setIcon(button_style->standardIcon(top_level_window->isMaximized() ? QStyle::SP_TitleBarNormalButton : QStyle::SP_TitleBarMaxButton));
|
||||
m_maximize_window_button->setToolTip(top_level_window->isMaximized() ? "Restore" : "Maximize");
|
||||
m_close_window_button->setIcon(button_style->standardIcon(QStyle::SP_TitleBarCloseButton));
|
||||
}
|
||||
|
||||
void TabWidget::toggle_window_maximized()
|
||||
{
|
||||
auto* top_level_window = window();
|
||||
if (top_level_window->isMaximized())
|
||||
top_level_window->showNormal();
|
||||
else
|
||||
top_level_window->showMaximized();
|
||||
update_window_button_icons();
|
||||
}
|
||||
|
||||
bool TabWidget::start_window_move()
|
||||
{
|
||||
auto* handle = window()->windowHandle();
|
||||
if (!handle)
|
||||
return false;
|
||||
return handle->startSystemMove();
|
||||
}
|
||||
|
||||
TabBarButton::TabBarButton(QIcon const& icon, QWidget* parent)
|
||||
: QPushButton(icon, {}, parent)
|
||||
{
|
||||
resize({ 20, 20 });
|
||||
setObjectName("LadybirdTabButton");
|
||||
setFixedSize({ 24, 24 });
|
||||
setIconSize({ 14, 14 });
|
||||
setFlat(true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ class QAction;
|
|||
class QContextMenuEvent;
|
||||
class QEvent;
|
||||
class QIcon;
|
||||
class QMouseEvent;
|
||||
class QPaintEvent;
|
||||
class QToolButton;
|
||||
|
||||
namespace Ladybird {
|
||||
|
|
@ -39,12 +41,19 @@ public:
|
|||
virtual void contextMenuEvent(QContextMenuEvent* event) override;
|
||||
|
||||
private:
|
||||
virtual void paintEvent(QPaintEvent*) override;
|
||||
virtual void leaveEvent(QEvent*) override;
|
||||
virtual void mouseDoubleClickEvent(QMouseEvent*) override;
|
||||
void mousePressEvent(QMouseEvent*) override;
|
||||
void mouseMoveEvent(QMouseEvent*) override;
|
||||
|
||||
bool start_window_move();
|
||||
void toggle_window_maximized();
|
||||
|
||||
QPointer<TabWidget> m_tab_widget;
|
||||
|
||||
int m_available_width { 0 };
|
||||
int m_hovered_tab_index { -1 };
|
||||
int m_x_position_in_selected_tab_while_dragging { 0 };
|
||||
};
|
||||
|
||||
|
|
@ -82,16 +91,25 @@ signals:
|
|||
|
||||
protected:
|
||||
virtual bool event(QEvent* event) override;
|
||||
virtual bool eventFilter(QObject*, QEvent*) override;
|
||||
virtual void resizeEvent(QResizeEvent*) override;
|
||||
|
||||
private:
|
||||
void update_tab_layout();
|
||||
void recreate_icons();
|
||||
void update_chrome_style();
|
||||
void update_window_button_icons();
|
||||
void toggle_window_maximized();
|
||||
bool start_window_move();
|
||||
|
||||
TabBar* m_tab_bar { nullptr };
|
||||
QStackedWidget* m_stacked_widget { nullptr };
|
||||
QToolButton* m_new_tab_button { nullptr };
|
||||
QToolButton* m_minimize_window_button { nullptr };
|
||||
QToolButton* m_maximize_window_button { nullptr };
|
||||
QToolButton* m_close_window_button { nullptr };
|
||||
QWidget* m_tab_bar_row { nullptr };
|
||||
bool m_is_updating_chrome_style { false };
|
||||
};
|
||||
|
||||
class TabBarButton final : public QPushButton {
|
||||
|
|
|
|||
Loading…
Reference in a new issue