LibGUI/TabWidget: Add close button to tabs

This adds an optional close button to tabs, useful in
for example browser and pixelpaint.
This commit is contained in:
Marcus Nilsson 2021-06-19 12:10:43 +02:00 committed by Andreas Kling
parent fbaf763e2a
commit 49d40a908c
Notes: sideshowbarker 2024-07-18 11:58:33 +09:00
2 changed files with 89 additions and 8 deletions

View File

@ -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();
}
}

View File

@ -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 };
};
}