Serenity Operating System
at hosted 262 lines 8.4 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <LibGUI/BoxLayout.h> 28#include <LibGUI/Painter.h> 29#include <LibGUI/TabWidget.h> 30#include <LibGfx/Font.h> 31#include <LibGfx/Palette.h> 32#include <LibGfx/StylePainter.h> 33 34namespace GUI { 35 36TabWidget::TabWidget() 37{ 38} 39 40TabWidget::~TabWidget() 41{ 42} 43 44void TabWidget::add_widget(const StringView& title, Widget& widget) 45{ 46 m_tabs.append({ title, &widget }); 47 add_child(widget); 48} 49 50void TabWidget::remove_widget(Widget& widget) 51{ 52 m_tabs.remove_first_matching([&widget](auto& entry) { return &widget == entry.widget; }); 53 remove_child(widget); 54} 55 56void TabWidget::set_active_widget(Widget* widget) 57{ 58 if (widget == m_active_widget) 59 return; 60 61 if (m_active_widget) 62 m_active_widget->set_visible(false); 63 m_active_widget = widget; 64 if (m_active_widget) { 65 m_active_widget->set_relative_rect(child_rect_for_size(size())); 66 m_active_widget->set_visible(true); 67 deferred_invoke([this](auto&) { 68 if (on_change) 69 on_change(*m_active_widget); 70 }); 71 } 72 73 update_bar(); 74} 75 76void TabWidget::resize_event(ResizeEvent& event) 77{ 78 if (!m_active_widget) 79 return; 80 m_active_widget->set_relative_rect(child_rect_for_size(event.size())); 81} 82 83Gfx::Rect TabWidget::child_rect_for_size(const Gfx::Size& size) const 84{ 85 Gfx::Rect rect; 86 switch (m_tab_position) { 87 case TabPosition::Top: 88 rect = { { container_padding(), bar_height() + container_padding() }, { size.width() - container_padding() * 2, size.height() - bar_height() - container_padding() * 2 } }; 89 break; 90 case TabPosition::Bottom: 91 rect = { { container_padding(), container_padding() }, { size.width() - container_padding() * 2, size.height() - bar_height() - container_padding() * 2 } }; 92 break; 93 } 94 if (rect.is_empty()) 95 return {}; 96 return rect; 97} 98 99void TabWidget::child_event(Core::ChildEvent& event) 100{ 101 if (!event.child() || !Core::is<Widget>(*event.child())) 102 return Widget::child_event(event); 103 auto& child = Core::to<Widget>(*event.child()); 104 if (event.type() == Event::ChildAdded) { 105 if (!m_active_widget) 106 set_active_widget(&child); 107 else if (m_active_widget != &child) 108 child.set_visible(false); 109 } else if (event.type() == Event::ChildRemoved) { 110 if (m_active_widget == &child) { 111 Widget* new_active_widget = nullptr; 112 for_each_child_widget([&](auto& new_child) { 113 new_active_widget = &new_child; 114 return IterationDecision::Break; 115 }); 116 set_active_widget(new_active_widget); 117 } 118 } 119 Widget::child_event(event); 120} 121 122Gfx::Rect TabWidget::bar_rect() const 123{ 124 switch (m_tab_position) { 125 case TabPosition::Top: 126 return { 0, 0, width(), bar_height() }; 127 case TabPosition::Bottom: 128 return { 0, height() - bar_height(), width(), bar_height() }; 129 } 130 ASSERT_NOT_REACHED(); 131} 132 133Gfx::Rect TabWidget::container_rect() const 134{ 135 switch (m_tab_position) { 136 case TabPosition::Top: 137 return { 0, bar_height(), width(), height() - bar_height() }; 138 case TabPosition::Bottom: 139 return { 0, 0, width(), height() - bar_height() }; 140 } 141 ASSERT_NOT_REACHED(); 142} 143 144void TabWidget::paint_event(PaintEvent& event) 145{ 146 Painter painter(*this); 147 painter.add_clip_rect(event.rect()); 148 149 auto container_rect = this->container_rect(); 150 auto padding_rect = container_rect; 151 for (int i = 0; i < container_padding(); ++i) { 152 painter.draw_rect(padding_rect, palette().button()); 153 padding_rect.shrink(2, 2); 154 } 155 156 Gfx::StylePainter::paint_frame(painter, container_rect, palette(), Gfx::FrameShape::Container, Gfx::FrameShadow::Raised, 2); 157 158 for (size_t i = 0; i < m_tabs.size(); ++i) { 159 if (m_tabs[i].widget == m_active_widget) 160 continue; 161 bool hovered = static_cast<int>(i) == m_hovered_tab_index; 162 auto button_rect = this->button_rect(i); 163 Gfx::StylePainter::paint_tab_button(painter, button_rect, palette(), false, hovered, m_tabs[i].widget->is_enabled()); 164 painter.draw_text(button_rect.translated(0, 1), m_tabs[i].title, Gfx::TextAlignment::Center, palette().button_text()); 165 } 166 167 for (size_t i = 0; i < m_tabs.size(); ++i) { 168 if (m_tabs[i].widget != m_active_widget) 169 continue; 170 bool hovered = static_cast<int>(i) == m_hovered_tab_index; 171 auto button_rect = this->button_rect(i); 172 Gfx::StylePainter::paint_tab_button(painter, button_rect, palette(), true, hovered, m_tabs[i].widget->is_enabled()); 173 painter.draw_text(button_rect.translated(0, 1), m_tabs[i].title, Gfx::TextAlignment::Center, palette().button_text()); 174 painter.draw_line(button_rect.bottom_left().translated(1, 1), button_rect.bottom_right().translated(-1, 1), palette().button()); 175 break; 176 } 177} 178 179Gfx::Rect TabWidget::button_rect(int index) const 180{ 181 int x_offset = 2; 182 for (int i = 0; i < index; ++i) 183 x_offset += m_tabs[i].width(font()); 184 Gfx::Rect rect { x_offset, 0, m_tabs[index].width(font()), bar_height() }; 185 if (m_tabs[index].widget != m_active_widget) { 186 rect.move_by(0, 2); 187 rect.set_height(rect.height() - 2); 188 } else { 189 rect.move_by(-2, 0); 190 rect.set_width(rect.width() + 4); 191 } 192 rect.move_by(bar_rect().location()); 193 return rect; 194} 195 196int TabWidget::TabData::width(const Gfx::Font& font) const 197{ 198 return 16 + font.width(title); 199} 200 201void TabWidget::mousedown_event(MouseEvent& event) 202{ 203 for (size_t i = 0; i < m_tabs.size(); ++i) { 204 auto button_rect = this->button_rect(i); 205 if (!button_rect.contains(event.position())) 206 continue; 207 set_active_widget(m_tabs[i].widget); 208 return; 209 } 210} 211 212void TabWidget::mousemove_event(MouseEvent& event) 213{ 214 int hovered_tab = -1; 215 for (size_t i = 0; i < m_tabs.size(); ++i) { 216 auto button_rect = this->button_rect(i); 217 if (!button_rect.contains(event.position())) 218 continue; 219 hovered_tab = i; 220 if (m_tabs[i].widget == m_active_widget) 221 break; 222 } 223 if (hovered_tab == m_hovered_tab_index) 224 return; 225 m_hovered_tab_index = hovered_tab; 226 update_bar(); 227} 228 229void TabWidget::leave_event(Core::Event&) 230{ 231 if (m_hovered_tab_index != -1) { 232 m_hovered_tab_index = -1; 233 update_bar(); 234 } 235} 236 237void TabWidget::update_bar() 238{ 239 auto invalidation_rect = bar_rect(); 240 invalidation_rect.set_height(invalidation_rect.height() + 1); 241 update(invalidation_rect); 242} 243 244void TabWidget::set_tab_position(TabPosition tab_position) 245{ 246 if (m_tab_position == tab_position) 247 return; 248 m_tab_position = tab_position; 249 if (m_active_widget) 250 m_active_widget->set_relative_rect(child_rect_for_size(size())); 251 update(); 252} 253 254int TabWidget::active_tab_index() const 255{ 256 for (size_t i = 0; i < m_tabs.size(); i++) { 257 if (m_tabs.at(i).widget == m_active_widget) 258 return i; 259 } 260 return -1; 261} 262}