Serenity Operating System
at portability 252 lines 8.1 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::set_active_widget(Widget* widget) 51{ 52 if (widget == m_active_widget) 53 return; 54 55 if (m_active_widget) 56 m_active_widget->set_visible(false); 57 m_active_widget = widget; 58 if (m_active_widget) { 59 m_active_widget->set_relative_rect(child_rect_for_size(size())); 60 m_active_widget->set_visible(true); 61 } 62 63 update_bar(); 64} 65 66void TabWidget::resize_event(ResizeEvent& event) 67{ 68 if (!m_active_widget) 69 return; 70 m_active_widget->set_relative_rect(child_rect_for_size(event.size())); 71} 72 73Gfx::Rect TabWidget::child_rect_for_size(const Gfx::Size& size) const 74{ 75 Gfx::Rect rect; 76 switch (m_tab_position) { 77 case TabPosition::Top: 78 rect = { { container_padding(), bar_height() + container_padding() }, { size.width() - container_padding() * 2, size.height() - bar_height() - container_padding() * 2 } }; 79 break; 80 case TabPosition::Bottom: 81 rect = { { container_padding(), container_padding() }, { size.width() - container_padding() * 2, size.height() - bar_height() - container_padding() * 2 } }; 82 break; 83 } 84 if (rect.is_empty()) 85 return {}; 86 return rect; 87} 88 89void TabWidget::child_event(Core::ChildEvent& event) 90{ 91 if (!event.child() || !Core::is<Widget>(*event.child())) 92 return Widget::child_event(event); 93 auto& child = Core::to<Widget>(*event.child()); 94 if (event.type() == Event::ChildAdded) { 95 if (!m_active_widget) 96 set_active_widget(&child); 97 else if (m_active_widget != &child) 98 child.set_visible(false); 99 } else if (event.type() == Event::ChildRemoved) { 100 if (m_active_widget == &child) { 101 Widget* new_active_widget = nullptr; 102 for_each_child_widget([&](auto& new_child) { 103 new_active_widget = &new_child; 104 return IterationDecision::Break; 105 }); 106 set_active_widget(new_active_widget); 107 } 108 } 109 Widget::child_event(event); 110} 111 112Gfx::Rect TabWidget::bar_rect() const 113{ 114 switch (m_tab_position) { 115 case TabPosition::Top: 116 return { 0, 0, width(), bar_height() }; 117 case TabPosition::Bottom: 118 return { 0, height() - bar_height(), width(), bar_height() }; 119 } 120 ASSERT_NOT_REACHED(); 121} 122 123Gfx::Rect TabWidget::container_rect() const 124{ 125 switch (m_tab_position) { 126 case TabPosition::Top: 127 return { 0, bar_height(), width(), height() - bar_height() }; 128 case TabPosition::Bottom: 129 return { 0, 0, width(), height() - bar_height() }; 130 } 131 ASSERT_NOT_REACHED(); 132} 133 134void TabWidget::paint_event(PaintEvent& event) 135{ 136 Painter painter(*this); 137 painter.add_clip_rect(event.rect()); 138 139 auto container_rect = this->container_rect(); 140 auto padding_rect = container_rect; 141 for (int i = 0; i < container_padding(); ++i) { 142 painter.draw_rect(padding_rect, palette().button()); 143 padding_rect.shrink(2, 2); 144 } 145 146 Gfx::StylePainter::paint_frame(painter, container_rect, palette(), Gfx::FrameShape::Container, Gfx::FrameShadow::Raised, 2); 147 148 for (size_t i = 0; i < m_tabs.size(); ++i) { 149 if (m_tabs[i].widget == m_active_widget) 150 continue; 151 bool hovered = static_cast<int>(i) == m_hovered_tab_index; 152 auto button_rect = this->button_rect(i); 153 Gfx::StylePainter::paint_tab_button(painter, button_rect, palette(), false, hovered, m_tabs[i].widget->is_enabled()); 154 painter.draw_text(button_rect.translated(0, 1), m_tabs[i].title, Gfx::TextAlignment::Center, palette().button_text()); 155 } 156 157 for (size_t i = 0; i < m_tabs.size(); ++i) { 158 if (m_tabs[i].widget != m_active_widget) 159 continue; 160 bool hovered = static_cast<int>(i) == m_hovered_tab_index; 161 auto button_rect = this->button_rect(i); 162 Gfx::StylePainter::paint_tab_button(painter, button_rect, palette(), true, hovered, m_tabs[i].widget->is_enabled()); 163 painter.draw_text(button_rect.translated(0, 1), m_tabs[i].title, Gfx::TextAlignment::Center, palette().button_text()); 164 painter.draw_line(button_rect.bottom_left().translated(1, 1), button_rect.bottom_right().translated(-1, 1), palette().button()); 165 break; 166 } 167} 168 169Gfx::Rect TabWidget::button_rect(int index) const 170{ 171 int x_offset = 2; 172 for (int i = 0; i < index; ++i) 173 x_offset += m_tabs[i].width(font()); 174 Gfx::Rect rect { x_offset, 0, m_tabs[index].width(font()), bar_height() }; 175 if (m_tabs[index].widget != m_active_widget) { 176 rect.move_by(0, 2); 177 rect.set_height(rect.height() - 2); 178 } else { 179 rect.move_by(-2, 0); 180 rect.set_width(rect.width() + 4); 181 } 182 rect.move_by(bar_rect().location()); 183 return rect; 184} 185 186int TabWidget::TabData::width(const Gfx::Font& font) const 187{ 188 return 16 + font.width(title); 189} 190 191void TabWidget::mousedown_event(MouseEvent& event) 192{ 193 for (size_t i = 0; i < m_tabs.size(); ++i) { 194 auto button_rect = this->button_rect(i); 195 if (!button_rect.contains(event.position())) 196 continue; 197 set_active_widget(m_tabs[i].widget); 198 return; 199 } 200} 201 202void TabWidget::mousemove_event(MouseEvent& event) 203{ 204 int hovered_tab = -1; 205 for (size_t i = 0; i < m_tabs.size(); ++i) { 206 auto button_rect = this->button_rect(i); 207 if (!button_rect.contains(event.position())) 208 continue; 209 hovered_tab = i; 210 if (m_tabs[i].widget == m_active_widget) 211 break; 212 } 213 if (hovered_tab == m_hovered_tab_index) 214 return; 215 m_hovered_tab_index = hovered_tab; 216 update_bar(); 217} 218 219void TabWidget::leave_event(Core::Event&) 220{ 221 if (m_hovered_tab_index != -1) { 222 m_hovered_tab_index = -1; 223 update_bar(); 224 } 225} 226 227void TabWidget::update_bar() 228{ 229 auto invalidation_rect = bar_rect(); 230 invalidation_rect.set_height(invalidation_rect.height() + 1); 231 update(invalidation_rect); 232} 233 234void TabWidget::set_tab_position(TabPosition tab_position) 235{ 236 if (m_tab_position == tab_position) 237 return; 238 m_tab_position = tab_position; 239 if (m_active_widget) 240 m_active_widget->set_relative_rect(child_rect_for_size(size())); 241 update(); 242} 243 244int TabWidget::active_tab_index() const 245{ 246 for (size_t i = 0; i < m_tabs.size(); i++) { 247 if (m_tabs.at(i).widget == m_active_widget) 248 return i; 249 } 250 return -1; 251} 252}