Serenity Operating System
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}