FifeGUI 0.2.0
A C++ GUI library designed for games.
tabbedarea.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause
2// SPDX-FileCopyrightText: 2004 - 2008 Olof Naessén and Per Larsson
3// SPDX-FileCopyrightText: 2013 - 2026 Fifengine contributors
4
5#include "fifechan/widgets/tabbedarea.hpp"
6
7#include <algorithm>
8#include <utility>
9
10#include "fifechan/exception.hpp"
11#include "fifechan/focushandler.hpp"
12#include "fifechan/font.hpp"
13#include "fifechan/graphics.hpp"
14#include "fifechan/widgets/tab.hpp"
15
16namespace fcn
17{
18 TabbedArea::TabbedArea() : mTabContainer(new Container()), mWidgetContainer(new Container())
19 {
20 setFocusable(true);
21 addKeyListener(this);
22 addMouseListener(this);
23
24 mTabContainer->setOpaque(false);
25 mTabContainer->setLayout(Container::LayoutPolicy::Horizontal);
26
27 mWidgetContainer->setLayout(Container::LayoutPolicy::Vertical);
28 mWidgetContainer->setPadding(6);
29
30 add(mTabContainer);
31 add(mWidgetContainer);
32 }
33
34 TabbedArea::~TabbedArea()
35 {
36 remove(mTabContainer);
37 remove(mWidgetContainer);
38
39 delete mTabContainer;
40 delete mWidgetContainer;
41 }
42
43 void TabbedArea::addTab(Tab* tab, Widget* widget)
44 {
45 tab->setTabbedArea(this);
46 tab->addActionListener(this);
48 if (tab->getLayout() == Container::LayoutPolicy::Absolute) {
49 tab->setLayout(getLayout());
50 }
51 mTabContainer->add(tab);
52 mTabs.emplace_back(tab, widget);
53
54 if (mSelectedTab == nullptr) {
55 setSelectedTab(tab);
56 } else {
58 }
59 }
60
61 void TabbedArea::removeTabWithIndex(unsigned int index)
62 {
63 if (index >= mTabs.size()) {
64 throwException("No such tab index.");
65 }
66
67 removeTab(mTabs[index].first);
68 }
69
70 void TabbedArea::removeTab(Tab* tab)
71 {
72 int tabIndexToBeSelected = -1;
73
74 if (tab == mSelectedTab) {
75 int const index = getSelectedTabIndex();
76 int const mTabsSize = static_cast<int>(mTabs.size());
77 if (index == static_cast<int>((mTabsSize - 1) != 0 && mTabsSize >= 2)) {
78 tabIndexToBeSelected = index - 1;
79 } else if (index == mTabsSize - 1 && mTabsSize == 1) {
80 tabIndexToBeSelected = -1;
81 } else {
82 tabIndexToBeSelected = index;
83 }
84 }
85
86 auto iter = std::ranges::find_if(mTabs, [tab](std::pair<Tab*, Widget*> const & p) {
87 return p.first == tab;
88 });
89 if (iter != mTabs.end()) {
90 mTabContainer->remove(tab);
91 mTabs.erase(iter);
92 }
93
94 auto iter2 = std::ranges::find_if(mTabsToDelete, [tab](std::unique_ptr<Tab> const & t) {
95 return t.get() == tab;
96 });
97 if (iter2 != mTabsToDelete.end()) {
98 mTabsToDelete.erase(iter2);
99 }
100
101 if (tabIndexToBeSelected == -1) {
102 mSelectedTab = nullptr;
103 mWidgetContainer->removeAllChildren();
104 adaptLayout();
105 } else {
106 mWidgetContainer->removeAllChildren();
107 setSelectedTab(tabIndexToBeSelected);
108 }
109 }
110
112 {
113 return mTabs.size();
114 }
115
116 bool TabbedArea::isTabSelected(unsigned int index) const
117 {
118 if (index >= mTabs.size()) {
119 throwException("No such tab index.");
120 }
121
122 return mSelectedTab == mTabs[index].first;
123 }
124
125 bool TabbedArea::isTabSelected(Tab* tab) const
126 {
127 return mSelectedTab == tab;
128 }
129
130 void TabbedArea::setSelectedTab(unsigned int index)
131 {
132 if (index >= mTabs.size()) {
133 throwException("No such tab index.");
134 }
135
136 setSelectedTab(mTabs[index].first);
137 }
138
140 {
141 if (tab == mSelectedTab) {
142 return;
143 }
144 unsigned int i = 0;
145 for (i = 0; i < mTabs.size(); i++) {
146 if (mTabs[i].first == mSelectedTab) {
147 mWidgetContainer->remove(mTabs[i].second);
148 }
149 }
150
151 for (i = 0; i < mTabs.size(); i++) {
152 if (mTabs[i].first == tab) {
153 mSelectedTab = tab;
154 mWidgetContainer->add(mTabs[i].second);
155 }
156 }
157 adaptLayout();
158 }
159
161 {
162 unsigned int i = 0;
163 for (i = 0; i < mTabs.size(); i++) {
164 if (mTabs[i].first == mSelectedTab) {
165 return i;
166 }
167 }
168
169 return -1;
170 }
171
173 {
174 return mSelectedTab;
175 }
176
177 void TabbedArea::setOpaque(bool opaque)
178 {
179 mOpaque = opaque;
180 }
181
183 {
184 return mOpaque;
185 }
186
188 {
189 mTabContainer->setBackgroundWidget(widget);
190 mWidgetContainer->setBackgroundWidget(widget);
191 }
192
194 {
195 return mTabContainer->getBackgroundWidget();
196 }
197
199 {
200 Color const & faceColor = getBaseColor();
201 int const alpha = getBaseColor().a;
202 Color highlightColor = faceColor + 0x303030;
203 highlightColor.a = alpha;
204 Color shadowColor = faceColor - 0x303030;
205 shadowColor.a = alpha;
206
207 // Draw a border.
208 graphics->setColor(highlightColor);
209 graphics->drawLine(0, mTabContainer->getHeight(), 0, getHeight() - 2);
210 graphics->setColor(shadowColor);
211 graphics->drawLine(getWidth() - 1, mTabContainer->getHeight() + 1, getWidth() - 1, getHeight() - 1);
212 graphics->drawLine(1, getHeight() - 1, getWidth() - 1, getHeight() - 1);
213
214 if (isOpaque()) {
215 graphics->setColor(getBaseColor());
216 graphics->fillRectangle(1, 1, getWidth() - 2, getHeight() - 2);
217 }
218
219 // Draw a line underneath the tabs.
220 graphics->setColor(highlightColor);
221 graphics->drawLine(1, mTabContainer->getHeight(), getWidth() - 1, mTabContainer->getHeight());
222
223 // If a tab is selected, remove the line right underneath
224 // the selected tab.
225 if (mSelectedTab != nullptr) {
226 graphics->setColor(getBaseColor());
227 graphics->drawLine(
228 mSelectedTab->getX() + 1,
229 mTabContainer->getHeight(),
230 mSelectedTab->getX() + mSelectedTab->getWidth() - 2,
231 mTabContainer->getHeight());
232 }
233
234 // drawChildren(graphics);
235 }
236
238 {
239 Rectangle rec;
240 rec.x = getBorderSize() + getPaddingLeft();
241 rec.y = getBorderSize() + getPaddingTop();
244 return rec;
245 }
246
247 void TabbedArea::resizeToContent(bool recursion)
248 {
249 if (recursion) {
250 mTabContainer->resizeToContent(recursion);
251 mWidgetContainer->resizeToContent(recursion);
252 }
254 adjustSize();
256 }
257
258 void TabbedArea::expandContent(bool recursion)
259 {
260 if (recursion) {
261 mTabContainer->expandContent(recursion);
262 mWidgetContainer->expandContent(recursion);
263 }
264 adjustSize();
266 }
267
269 {
270 // int totalTabWidth = 0; // UNUSED - possibly for future scrollable tabs feature
271 // int totalTabHeight = 0; // UNUSED - possibly for future scrollable tabs feature
272 int maxTabWidth = 0;
273 int maxTabHeight = 0;
274
275 // Rectangle const area = getChildrenArea(); // UNUSED - possibly for future scrollable tabs feature
276
277 for (auto& mTab : mTabs) {
278 // totalTabWidth += mTabs[i].first->getWidth(); // UNUSED
279 // totalTabHeight += mTabs[i].first->getHeight(); // UNUSED
280 maxTabWidth = std::max(mTab.first->getWidth(), maxTabWidth);
281 maxTabHeight = std::max(mTab.first->getHeight(), maxTabHeight);
282 }
283
284 if (getLayout() == Container::LayoutPolicy::Vertical) {
285 mTabContainer->setSize(maxTabWidth, getHeight() - 2);
286 mWidgetContainer->setSize(getWidth() - maxTabWidth - 2, getHeight() - 2);
287 mWidgetContainer->setPosition(maxTabWidth + 1, 1);
288 } else if (getLayout() == Container::LayoutPolicy::Horizontal) {
289 mTabContainer->setSize(getWidth() - 2, maxTabHeight);
290 mWidgetContainer->setSize(getWidth() - 2, getHeight() - maxTabHeight - 2);
291 mWidgetContainer->setPosition(1, maxTabHeight + 1);
292 }
293 }
294
296 {
297 int maxTabWidth = 0;
298 int maxTabHeight = 0;
299 unsigned int i = 0;
300 for (i = 0; i < mTabs.size(); i++) {
301 maxTabWidth = std::max(mTabs[i].first->getWidth(), maxTabWidth);
302 maxTabHeight = std::max(mTabs[i].first->getHeight(), maxTabHeight);
303 }
304
305 if (getLayout() == Container::LayoutPolicy::Vertical) {
306 int y = 0;
307 for (i = 0; i < mTabs.size(); i++) {
308 Tab* tab = mTabs[i].first;
309 tab->setPosition(maxTabWidth - tab->getWidth(), y);
310 y += tab->getHeight();
311 }
312 } else if (getLayout() == Container::LayoutPolicy::Horizontal) {
313 int x = 0;
314 for (i = 0; i < mTabs.size(); i++) {
315 Tab* tab = mTabs[i].first;
316 tab->setPosition(x, maxTabHeight - tab->getHeight());
317 x += tab->getWidth();
318 }
319 }
320 }
321
322 void TabbedArea::setWidth(int width)
323 {
324 // This may seem odd, but we want the TabbedArea to adjust
325 // it's size properly before we call Widget::setWidth as
326 // Widget::setWidth might distribute a resize event.
327 fcn::Rectangle const dim = mDimension;
328 mDimension.width = width;
329 adjustSize();
330 mDimension = dim;
331 Widget::setWidth(width);
332 }
333
334 void TabbedArea::setHeight(int height)
335 {
336 // This may seem odd, but we want the TabbedArea to adjust
337 // it's size properly before we call Widget::setHeight as
338 // Widget::setHeight might distribute a resize event.
339 fcn::Rectangle const dim = mDimension;
340 mDimension.height = height;
341 adjustSize();
342 mDimension = dim;
343 Widget::setHeight(height);
344 }
345
346 void TabbedArea::setSize(int width, int height)
347 {
348 // This may seem odd, but we want the TabbedArea to adjust
349 // it's size properly before we call Widget::setSize as
350 // Widget::setSize might distribute a resize event.
351 fcn::Rectangle const dim = mDimension;
352 mDimension.width = width;
353 mDimension.height = height;
354 adjustSize();
355 mDimension = dim;
356 Widget::setSize(width, height);
357 }
358
359 void TabbedArea::setDimension(Rectangle const & dimension)
360 {
361 // This may seem odd, but we want the TabbedArea to adjust
362 // it's size properly before we call Widget::setDimension as
363 // Widget::setDimension might distribute a resize event.
364 fcn::Rectangle const dim = mDimension;
365 mDimension = dimension;
366 adjustSize();
367 mDimension = dim;
368 Widget::setDimension(dimension);
369 }
370
372 {
373 if (keyEvent.isConsumed() || !isFocused()) {
374 return;
375 }
376
377 if (keyEvent.getKey().getValue() == Key::Left) {
378 int index = getSelectedTabIndex();
379 index--;
380
381 if (index < 0) {
382 return;
383 }
384
385 setSelectedTab(mTabs[index].first);
386
387 keyEvent.consume();
388 } else if (keyEvent.getKey().getValue() == Key::Right) {
389 int index = getSelectedTabIndex();
390 index++;
391
392 if (std::cmp_greater_equal(index, mTabs.size())) {
393 return;
394 }
395
396 setSelectedTab(mTabs[index].first);
397
398 keyEvent.consume();
399 }
400 }
401
403 {
404 // we ignore that, otherwise the tab can not be pressed
405 // because the content consumed the event
406 // if (mouseEvent.isConsumed())
407 //{
408 // return;
409 //}
410 if (mouseEvent.getButton() == MouseEvent::Button::Left) {
411 Widget* widget = mTabContainer->getWidgetAt(mouseEvent.getX(), mouseEvent.getY());
412 Tab* tab = dynamic_cast<Tab*>(widget);
413
414 if (tab != nullptr) {
415 setSelectedTab(tab);
416 }
417 }
418
419 // Request focus only if the source of the event
420 // is not focusable. If the source of the event
421 // is focused we don't want to steal the focus.
422 if (!mouseEvent.getSource()->isFocusable()) {
423 requestFocus();
424 }
425 }
426
427 void TabbedArea::death(Event const & event)
428 {
429 Tab* tab = dynamic_cast<Tab*>(event.getSource());
430
431 if (tab != nullptr) {
432 removeTab(tab);
433 } else {
434 // Container::death(event);
435 }
436 }
437
438 void TabbedArea::action(ActionEvent const & actionEvent)
439 {
440 Widget* source = actionEvent.getSource();
441 Tab* tab = dynamic_cast<Tab*>(source);
442
443 if (tab == nullptr) {
444 throwException("Received an action from a widget that's not a tab!");
445 }
446
447 setSelectedTab(tab);
448 }
449
450 void TabbedArea::setBaseColor(Color const & color)
451 {
453 mWidgetContainer->setBaseColor(color);
454 mTabContainer->setBaseColor(color);
455 for (auto const & tabEntry : mTabs) {
456 tabEntry.first->setBaseColor(color);
457 }
458 }
459
461 {
462 mTabContainer->setLayout(policy);
463 }
464
466 {
467 return mTabContainer->getLayout();
468 }
469
470 void TabbedArea::setUniformSize(bool uniform)
471 {
472 mTabContainer->setUniformSize(uniform);
473 }
474
476 {
477 return mTabContainer->isUniformSize();
478 }
479
480 void TabbedArea::setVerticalSpacing(unsigned int spacing)
481 {
482 mTabContainer->setVerticalSpacing(spacing);
483 }
484
486 {
487 return mTabContainer->getVerticalSpacing();
488 }
489
490 void TabbedArea::setHorizontalSpacing(unsigned int spacing)
491 {
492 mTabContainer->setHorizontalSpacing(spacing);
493 }
494
496 {
497 return mTabContainer->getHorizontalSpacing();
498 }
499} // namespace fcn
Represents an action trigger (e.g., button click).
Color.
Definition color.hpp:56
uint8_t a
Alpha color component (0-255).
Definition color.hpp:322
A composite widget capable of holding and managing child widgets.
Definition container.hpp:32
virtual void setLayout(LayoutPolicy policy)
Sets the layout of the container.
LayoutPolicy
The layout policy of the container.
Definition container.hpp:38
virtual LayoutPolicy getLayout() const
Gets the layout of the container.
Base class for all GUI event objects.
Definition event.hpp:24
Widget * getSource() const
Gets the source widget of the event.
Definition event.cpp:11
Abstract interface providing primitive drawing functions (lines, rectangles, etc.).
Definition graphics.hpp:57
virtual void drawLine(int x1, int y1, int x2, int y2)=0
Draws a line.
virtual void setColor(Color const &color)=0
Sets the color to use when drawing.
virtual void fillRectangle(Rectangle const &rectangle)=0
Draws a filled rectangle.
bool isConsumed() const
Checks if the input event is consumed.
void consume()
Marks the event as consumed.
Represents a key event.
Definition keyevent.hpp:22
Key const & getKey() const
Gets the key of the event.
Definition keyevent.cpp:39
int getValue() const
Gets the value of the key.
Definition key.cpp:28
Represents a mouse event.
int getX() const
Gets the x coordinate of the mouse event.
int getY() const
Gets the y coordinate of the mouse event.
MouseEvent::Button getButton() const
Gets the button of the mouse event.
Represents a rectangular area (X, Y, Width, Height).
Definition rectangle.hpp:20
int width
Holds the width of the rectangle.
int y
Holds the x coordinate of the rectangle.
int x
Holds the x coordinate of the rectangle.
int height
Holds the height of the rectangle.
void setTabbedArea(TabbedArea *tabbedArea)
Sets the tabbed area the tab should be a part of.
Definition tab.cpp:30
Container * mTabContainer
Holds the container for the tabs.
bool mOpaque
True if the tabbed area is opaque, false otherwise.
virtual bool isUniformSize() const
True if the tab container tries to expand the childs to a uniform size.
void resizeToContent(bool recursion) override
Resize this widget to fit its content.
virtual unsigned int getHorizontalSpacing() const
Get the horizontal spacing between rows.
virtual unsigned int getVerticalSpacing() const
Get the vertical spacing between rows.
virtual int getSelectedTabIndex() const
Gets the index of the selected tab.
Rectangle getChildrenArea() override
Gets the area of the widget occupied by the widget's children.
void setHeight(int height)
Set the height of the tabbed area in pixels.
void mousePressed(MouseEvent &mouseEvent) override
Called when a mouse button has been pressed on the widget area.
virtual void setUniformSize(bool uniform)
If enabled, the free space is distributed in a way that the size of the childrens will be equal (if p...
void setDimension(Rectangle const &dimension)
Set the area dimension for the tabbed area.
virtual void death(Event const &event)
DeathListener callback invoked when a child widget dies.
virtual bool isTabSelected(unsigned int index) const
Checks if a tab given an index is selected or not.
virtual void setHorizontalSpacing(unsigned int spacing)
Set the horizontal spacing between columns.
virtual void setSelectedTab(unsigned int index)
Sets a tab given an index to be selected.
void setLayout(Container::LayoutPolicy policy)
Sets the layout of the tabbedarea.
void setBaseColor(Color const &color) override
Set the base/background color used for the tabbed area.
virtual void addTab(Tab *tab, Widget *widget)
Adds a tab to the tabbed area.
void action(ActionEvent const &actionEvent) override
Called when an action is received from a widget.
void adjustTabPositions()
Adjusts the positions of the tabs.
void setSize(int width, int height)
Set the size (width and height) of the tabbed area in pixels.
void keyPressed(KeyEvent &keyEvent) override
Called if a key is pressed when the widget has keyboard focus.
void setWidth(int width)
Set the width of the tabbed area in pixels.
Container * mWidgetContainer
Holds the container for the widgets.
Widget * getBackgroundWidget()
Get the background widget, or nullptr if none is set.
void adjustSize() override
Adjusts the size of the tab container and the widget container.
std::vector< std::unique_ptr< Tab > > mTabsToDelete
Holds a vector of tabs to delete in the destructor.
Container::LayoutPolicy getLayout() const
Gets the layout of the tabbedarea.
std::vector< std::pair< Tab *, Widget * > > mTabs
A map between a tab and a widget to display when the tab is selected.
void setBackgroundWidget(Widget *widget)
Set the background widget which is drawn behind tabs.
int getNumberOfTabs() const
Returns the number of tabs in this tabbed area.
virtual void removeTabWithIndex(unsigned int index)
Removes a tab from the tabbed area.
virtual void setVerticalSpacing(unsigned int spacing)
Set the vertical spacing between rows.
void setOpaque(bool opaque)
Sets the tabbed area to be opaque or not.
Tab * getSelectedTab() const
Gets the selected tab.
virtual void removeTab(Tab *tab)
Removes a tab from the tabbed area.
bool isOpaque() const
Checks if the tabbed area is opaque or not.
Tab * mSelectedTab
Holds the selected tab.
void draw(Graphics *graphics) override
Draws the widget.
void expandContent(bool recursion) override
Expands child widgets to fit this widget's size.
Color const & getBaseColor() const
Gets the base color.
Definition widget.cpp:596
int getWidth() const
Gets the width of the widget.
Definition widget.cpp:170
void resizeToChildren()
Resizes the widget to fit it's children exactly.
Definition widget.cpp:1170
virtual bool isFocused() const
Checks if the widget is focused.
Definition widget.cpp:497
void setDimension(Rectangle const &dimension)
Sets the dimension of the widget.
Definition widget.cpp:223
Rectangle mDimension
Holds the dimension of the widget.
Definition widget.hpp:1675
unsigned int getPaddingLeft() const
Gets the left padding.
Definition widget.cpp:477
Widget()
Constructor.
Definition widget.cpp:36
void setSize(int width, int height)
Sets the size of the widget.
Definition widget.cpp:855
virtual void requestFocus()
Requests focus for the widget.
Definition widget.cpp:520
void setPosition(int x, int y)
Sets position of the widget.
Definition widget.cpp:214
void addActionListener(ActionListener *actionListener)
Adds an action listener to the widget.
Definition widget.cpp:714
void adaptLayout()
Execute the layouting.
Definition widget.hpp:1399
void setWidth(int width)
Sets the width of the widget.
Definition widget.cpp:162
unsigned int getPaddingTop() const
Gets the top padding.
Definition widget.cpp:447
unsigned int getBorderSize() const
Gets the size of the widget's border.
Definition widget.cpp:381
unsigned int getPaddingBottom() const
Gets the bottom padding.
Definition widget.cpp:467
virtual void setBaseColor(Color const &color)
Sets the base color of the widget.
Definition widget.cpp:591
bool isFocusable() const
Checks if a widget is focusable.
Definition widget.cpp:515
int getHeight() const
Gets the height of the widget.
Definition widget.cpp:183
void setHeight(int height)
Sets the height of the widget.
Definition widget.cpp:175
unsigned int getPaddingRight() const
Gets the right padding.
Definition widget.cpp:457