Explorar el Código

LibGUI/TabWidget: Add close button to tabs

This adds an optional close button to tabs, useful in
for example browser and pixelpaint.
Marcus Nilsson hace 4 años
padre
commit
49d40a908c
Se han modificado 2 ficheros con 89 adiciones y 8 borrados
  1. 81 8
      Userland/Libraries/LibGUI/TabWidget.cpp
  2. 8 0
      Userland/Libraries/LibGUI/TabWidget.h

+ 81 - 8
Userland/Libraries/LibGUI/TabWidget.cpp

@@ -205,15 +205,17 @@ void TabWidget::paint_event(PaintEvent& event)
         bool hovered = static_cast<int>(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<int>(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<int>(i) == m_hovered_close_button_index;
+        bool pressed_close_button = static_cast<int>(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();
     }
 }

+ 8 - 0
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<void(size_t)> on_tab_count_change;
     Function<void(Widget&)> on_change;
     Function<void(Widget&)> on_middle_click;
+    Function<void(Widget&)> on_tab_close_click;
     Function<void(Widget&, const ContextMenuEvent&)> 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<TabData> 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 };
 };
 
 }