diff --git a/Userland/Libraries/LibGUI/TabWidget.cpp b/Userland/Libraries/LibGUI/TabWidget.cpp index 57b5b4a089..caddf24254 100644 --- a/Userland/Libraries/LibGUI/TabWidget.cpp +++ b/Userland/Libraries/LibGUI/TabWidget.cpp @@ -205,15 +205,17 @@ void TabWidget::paint_event(PaintEvent& event) bool hovered = static_cast(i) == m_hovered_tab_index; auto button_rect = this->button_rect(i); Gfx::StylePainter::paint_tab_button(painter, button_rect, palette(), false, hovered, m_tabs[i].widget->is_enabled(), m_tab_position == TabPosition::Top); - auto tab_button_content_rect = button_rect.translated(0, m_tab_position == TabPosition::Top ? 1 : 0); + auto tab_button_content_rect = button_rect.translated(4, m_tab_position == TabPosition::Top ? 1 : 0); + paint_tab_icon_if_needed(m_tabs[i].icon, button_rect, tab_button_content_rect); + tab_button_content_rect.set_width(tab_button_content_rect.width() - (m_close_button_enabled ? 16 : 2)); Gfx::IntRect text_rect { 0, 0, min(tab_button_content_rect.width(), font().width(m_tabs[i].title)), font().glyph_height() }; text_rect.inflate(6, 4); text_rect.align_within(tab_button_content_rect, m_text_alignment); text_rect.intersect(tab_button_content_rect); - painter.draw_text(text_rect, m_tabs[i].title, Gfx::TextAlignment::Center, palette().button_text(), Gfx::TextElision::Right); + painter.draw_text(text_rect, m_tabs[i].title, Gfx::TextAlignment::CenterLeft, palette().button_text(), Gfx::TextElision::Right); } for (size_t i = 0; i < m_tabs.size(); ++i) { @@ -222,15 +224,16 @@ void TabWidget::paint_event(PaintEvent& event) bool hovered = static_cast(i) == m_hovered_tab_index; auto button_rect = this->button_rect(i); Gfx::StylePainter::paint_tab_button(painter, button_rect, palette(), true, hovered, m_tabs[i].widget->is_enabled(), m_tab_position == TabPosition::Top); - auto tab_button_content_rect = button_rect.translated(0, m_tab_position == TabPosition::Top ? 1 : 0); + auto tab_button_content_rect = button_rect.translated(4, m_tab_position == TabPosition::Top ? 1 : 0); paint_tab_icon_if_needed(m_tabs[i].icon, button_rect, tab_button_content_rect); + tab_button_content_rect.set_width(tab_button_content_rect.width() - (m_close_button_enabled ? 16 : 2)); Gfx::IntRect text_rect { 0, 0, min(tab_button_content_rect.width(), font().width(m_tabs[i].title)), font().glyph_height() }; text_rect.inflate(6, 4); text_rect.align_within(tab_button_content_rect, m_text_alignment); text_rect.intersect(tab_button_content_rect); - painter.draw_text(text_rect, m_tabs[i].title, Gfx::TextAlignment::Center, palette().button_text(), Gfx::TextElision::Right); + painter.draw_text(text_rect, m_tabs[i].title, Gfx::TextAlignment::CenterLeft, palette().button_text(), Gfx::TextElision::Right); if (is_focused()) { painter.draw_focus_rect(text_rect, palette().focus_outline()); @@ -246,6 +249,22 @@ void TabWidget::paint_event(PaintEvent& event) } break; } + + if (!m_close_button_enabled) + return; + + for (size_t i = 0; i < m_tabs.size(); ++i) { + bool hovered_close_button = static_cast(i) == m_hovered_close_button_index; + bool pressed_close_button = static_cast(i) == m_pressed_close_button_index; + auto close_button_rect = this->close_button_rect(i); + + if (hovered_close_button) + Gfx::StylePainter::paint_frame(painter, close_button_rect, palette(), Gfx::FrameShape::Box, pressed_close_button ? Gfx::FrameShadow::Sunken : Gfx::FrameShadow::Raised, 1); + + Gfx::IntRect icon_rect { close_button_rect.x() + 3, close_button_rect.y() + 3, 6, 6 }; + painter.draw_line(icon_rect.top_left(), icon_rect.bottom_right(), palette().button_text()); + painter.draw_line(icon_rect.top_right(), icon_rect.bottom_left(), palette().button_text()); + } } int TabWidget::uniform_tab_width() const @@ -271,11 +290,13 @@ void TabWidget::set_bar_visible(bool bar_visible) Gfx::IntRect TabWidget::button_rect(int index) const { int x_offset = bar_margin(); + int close_button_offset = m_close_button_enabled ? 16 : 0; + for (int i = 0; i < index; ++i) { - auto tab_width = m_uniform_tabs ? uniform_tab_width() : m_tabs[i].width(font()); + auto tab_width = m_uniform_tabs ? uniform_tab_width() : m_tabs[i].width(font()) + close_button_offset; x_offset += tab_width; } - Gfx::IntRect rect { x_offset, 0, m_uniform_tabs ? uniform_tab_width() : m_tabs[index].width(font()), bar_height() }; + Gfx::IntRect rect { x_offset, 0, m_uniform_tabs ? uniform_tab_width() : m_tabs[index].width(font()) + close_button_offset, bar_height() }; if (m_tabs[index].widget != m_active_widget) { rect.translate_by(0, m_tab_position == TabPosition::Top ? 2 : 0); rect.set_height(rect.height() - 2); @@ -287,6 +308,19 @@ Gfx::IntRect TabWidget::button_rect(int index) const return rect; } +Gfx::IntRect TabWidget::close_button_rect(int index) const +{ + auto rect = button_rect(index); + Gfx::IntRect close_button_rect { 0, 0, 12, 12 }; + + if (m_tabs[index].widget == m_active_widget) + close_button_rect.translate_by(rect.right() - 16, rect.top() + (m_tab_position == TabPosition::Top ? 5 : 4)); + else + close_button_rect.translate_by(rect.right() - 15, rect.top() + (m_tab_position == TabPosition::Top ? 4 : 3)); + + return close_button_rect; +} + int TabWidget::TabData::width(const Gfx::Font& font) const { return 16 + font.width(title) + (icon ? (16 + 4) : 0); @@ -296,9 +330,17 @@ void TabWidget::mousedown_event(MouseEvent& event) { for (size_t i = 0; i < m_tabs.size(); ++i) { auto button_rect = this->button_rect(i); + auto close_button_rect = this->close_button_rect(i); + if (!button_rect.contains(event.position())) continue; + if (event.button() == MouseButton::Left) { + if (m_close_button_enabled && close_button_rect.contains(event.position())) { + m_pressed_close_button_index = i; + update_bar(); + return; + } set_active_widget(m_tabs[i].widget); } else if (event.button() == MouseButton::Middle) { auto* widget = m_tabs[i].widget; @@ -311,27 +353,58 @@ void TabWidget::mousedown_event(MouseEvent& event) } } +void TabWidget::mouseup_event(MouseEvent& event) +{ + if (event.button() != MouseButton::Left) + return; + + if (!m_close_button_enabled) + return; + + for (size_t i = 0; i < m_tabs.size(); ++i) { + auto close_button_rect = this->close_button_rect(i); + if (close_button_rect.contains(event.position())) { + auto* widget = m_tabs[i].widget; + deferred_invoke([this, widget](auto&) { + if (on_tab_close_click && widget) + on_tab_close_click(*widget); + }); + m_pressed_close_button_index = -1; + return; + } + } +} + void TabWidget::mousemove_event(MouseEvent& event) { int hovered_tab = -1; + int hovered_close_button = -1; + for (size_t i = 0; i < m_tabs.size(); ++i) { auto button_rect = this->button_rect(i); + auto close_button_rect = this->close_button_rect(i); + + if (close_button_rect.contains(event.position())) + hovered_close_button = i; + if (!button_rect.contains(event.position())) continue; hovered_tab = i; if (m_tabs[i].widget == m_active_widget) break; } - if (hovered_tab == m_hovered_tab_index) + if (hovered_tab == m_hovered_tab_index && hovered_close_button == m_hovered_close_button_index) return; m_hovered_tab_index = hovered_tab; + m_hovered_close_button_index = hovered_close_button; update_bar(); } void TabWidget::leave_event(Core::Event&) { - if (m_hovered_tab_index != -1) { + if (m_hovered_tab_index != -1 || m_hovered_close_button_index != -1) { m_hovered_tab_index = -1; + m_hovered_close_button_index = -1; update_bar(); } } diff --git a/Userland/Libraries/LibGUI/TabWidget.h b/Userland/Libraries/LibGUI/TabWidget.h index 16c9e88f4a..68713a6ec7 100644 --- a/Userland/Libraries/LibGUI/TabWidget.h +++ b/Userland/Libraries/LibGUI/TabWidget.h @@ -65,9 +65,12 @@ public: void set_bar_visible(bool bar_visible); bool is_bar_visible() const { return m_bar_visible; }; + void set_close_button_enabled(bool close_button_enabled) { m_close_button_enabled = close_button_enabled; }; + Function on_tab_count_change; Function on_change; Function on_middle_click; + Function on_tab_close_click; Function on_context_menu_request; protected: @@ -77,6 +80,7 @@ protected: virtual void child_event(Core::ChildEvent&) override; virtual void resize_event(ResizeEvent&) override; virtual void mousedown_event(MouseEvent&) override; + virtual void mouseup_event(MouseEvent&) override; virtual void mousemove_event(MouseEvent&) override; virtual void leave_event(Core::Event&) override; virtual void keydown_event(KeyEvent&) override; @@ -85,6 +89,7 @@ protected: private: Gfx::IntRect child_rect_for_size(const Gfx::IntSize&) const; Gfx::IntRect button_rect(int index) const; + Gfx::IntRect close_button_rect(int index) const; Gfx::IntRect bar_rect() const; Gfx::IntRect container_rect() const; void update_bar(); @@ -102,10 +107,13 @@ private: Vector m_tabs; TabPosition m_tab_position { TabPosition::Top }; int m_hovered_tab_index { -1 }; + int m_hovered_close_button_index { -1 }; + int m_pressed_close_button_index { -1 }; GUI::Margins m_container_margins { 2, 2, 2, 2 }; Gfx::TextAlignment m_text_alignment { Gfx::TextAlignment::Center }; bool m_uniform_tabs { false }; bool m_bar_visible { true }; + bool m_close_button_enabled { false }; }; }