FifeGUI 0.3.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// Corresponding header include
6#include "fifechan/widgets/tabbedarea.hpp"
7
8// Standard library includes
9#include <algorithm>
10#include <iterator>
11#include <utility>
12
13// Project headers (subdirs before local)
14#include "fifechan/exception.hpp"
15#include "fifechan/focushandler.hpp"
16#include "fifechan/font.hpp"
17#include "fifechan/graphics.hpp"
18#include "fifechan/widgets/container.hpp"
19#include "fifechan/widgets/tab.hpp"
20
21namespace fcn
22{
23 TabbedArea::TabbedArea() : mTabContainer(new Container()), mWidgetContainer(new Container())
24 {
25 setFocusable(true);
26 addKeyListener(this);
27 addMouseListener(this);
28
29 mTabContainer->setOpaque(false);
30 mTabContainer->setLayout(Container::LayoutPolicy::Horizontal);
31
32 mWidgetContainer->setLayout(Container::LayoutPolicy::Vertical);
33 mWidgetContainer->setPadding(6);
34
35 add(mTabContainer);
36 add(mWidgetContainer);
37 }
38
39 TabbedArea::~TabbedArea()
40 {
41 remove(mTabContainer);
42 remove(mWidgetContainer);
43
44 delete mTabContainer;
45 delete mWidgetContainer;
46 }
47
48 void TabbedArea::addTab(Tab* tab, Widget* widget)
49 {
50 tab->setTabbedArea(this);
51 tab->addActionListener(this);
53 if (tab->getLayout() == Container::LayoutPolicy::Absolute) {
54 tab->setLayout(getLayout());
55 }
56 mTabContainer->add(tab);
57 mTabs.emplace_back(tab, widget);
58
59 if (mSelectedTab == nullptr) {
60 setSelectedTab(tab);
61 } else {
63 }
64 }
65
66 void TabbedArea::removeTabWithIndex(unsigned int index)
67 {
68 if (index >= mTabs.size()) {
69 throwException("No such tab index.");
70 }
71
72 removeTab(mTabs.at(index).first);
73 }
74
75 void TabbedArea::removeTab(Tab* tab)
76 {
77 int tabIndexToBeSelected = -1;
78
79 if (tab == mSelectedTab) {
80 int const index = getSelectedTabIndex();
81 int const mTabsSize = static_cast<int>(mTabs.size());
82 if (index == mTabsSize - 1 && mTabsSize > 1) {
83 tabIndexToBeSelected = index - 1;
84 } else if (mTabsSize == 1) {
85 tabIndexToBeSelected = -1;
86 } else {
87 tabIndexToBeSelected = index;
88 }
89 }
90
91 auto iter = std::ranges::find_if(mTabs, [tab](std::pair<Tab*, Widget*> const & p) {
92 return p.first == tab;
93 });
94 if (iter != mTabs.end()) {
95 mTabContainer->remove(tab);
96 mTabs.erase(iter);
97 }
98
99 auto iter2 = std::ranges::find_if(mTabsToDelete, [tab](std::unique_ptr<Tab> const & t) {
100 return t.get() == tab;
101 });
102 if (iter2 != mTabsToDelete.end()) {
103 mTabsToDelete.erase(iter2);
104 }
105
106 if (tab == mSelectedTab) {
107 mWidgetContainer->removeAllChildren();
108 if (tabIndexToBeSelected == -1) {
109 mSelectedTab = nullptr;
110 } else {
111 setSelectedTab(tabIndexToBeSelected);
112 }
113 }
114 adaptLayout();
115 }
116
118 {
119 return mTabs.size();
120 }
121
122 bool TabbedArea::isTabSelected(unsigned int index) const
123 {
124 if (index >= mTabs.size()) {
125 throwException("No such tab index.");
126 }
127
128 return mSelectedTab == mTabs.at(index).first;
129 }
130
131 bool TabbedArea::isTabSelected(Tab* tab) const
132 {
133 return mSelectedTab == tab;
134 }
135
136 void TabbedArea::setSelectedTab(unsigned int index)
137 {
138 if (index >= mTabs.size()) {
139 throwException("No such tab index.");
140 }
141
142 setSelectedTab(mTabs.at(index).first);
143 }
144
146 {
147 if (tab == mSelectedTab) {
148 return;
149 }
150 unsigned int i = 0;
151 for (i = 0; i < mTabs.size(); i++) {
152 if (mTabs.at(i).first == mSelectedTab) {
153 mWidgetContainer->remove(mTabs.at(i).second);
154 }
155 }
156
157 for (i = 0; i < mTabs.size(); i++) {
158 if (mTabs.at(i).first == tab) {
159 mSelectedTab = tab;
160 // TODO: check if this is still needed
161 // If the widget is already parented elsewhere, remove it first
162 if (mTabs.at(i).second != nullptr) {
163 if (mTabs.at(i).second->getParent() != nullptr) {
164 if (auto* parentContainer = dynamic_cast<Container*>(mTabs.at(i).second->getParent())) {
165 parentContainer->remove(mTabs.at(i).second);
166 }
167 }
168 }
169 mWidgetContainer->add(mTabs.at(i).second);
170 }
171 }
172 adaptLayout();
173 }
174
176 {
177 auto it = std::ranges::find_if(mTabs, [this](auto const & tab) {
178 return tab.first == mSelectedTab;
179 });
180
181 if (it != mTabs.end()) {
182 return static_cast<int>(std::distance(mTabs.begin(), it));
183 }
184
185 return -1;
186 }
187
189 {
190 return mSelectedTab;
191 }
192
193 void TabbedArea::setOpaque(bool opaque)
194 {
195 mOpaque = opaque;
196 }
197
199 {
200 return mOpaque;
201 }
202
204 {
205 mTabContainer->setBackgroundWidget(widget);
206 mWidgetContainer->setBackgroundWidget(widget);
207 }
208
210 {
211 return mTabContainer->getBackgroundWidget();
212 }
213
215 {
216 Color const & faceColor = getBaseColor();
217 int const alpha = getBaseColor().a;
218 Color highlightColor = faceColor + 0x303030;
219 highlightColor.a = alpha;
220 Color shadowColor = faceColor - 0x303030;
221 shadowColor.a = alpha;
222
223 // Draw a border.
224 graphics->setColor(highlightColor);
225 graphics->drawLine(0, mTabContainer->getHeight(), 0, getHeight() - 2);
226 graphics->setColor(shadowColor);
227 graphics->drawLine(getWidth() - 1, mTabContainer->getHeight() + 1, getWidth() - 1, getHeight() - 1);
228 graphics->drawLine(1, getHeight() - 1, getWidth() - 1, getHeight() - 1);
229
230 if (isOpaque()) {
231 graphics->setColor(getBaseColor());
232 graphics->fillRectangle(1, 1, getWidth() - 2, getHeight() - 2);
233 }
234
235 // Draw a line underneath the tabs.
236 graphics->setColor(highlightColor);
237 graphics->drawLine(1, mTabContainer->getHeight(), getWidth() - 1, mTabContainer->getHeight());
238
239 // If a tab is selected, remove the line right underneath
240 // the selected tab.
241 if (mSelectedTab != nullptr) {
242 graphics->setColor(getBaseColor());
243 graphics->drawLine(
244 mSelectedTab->getX() + 1,
245 mTabContainer->getHeight(),
246 mSelectedTab->getX() + mSelectedTab->getWidth() - 2,
247 mTabContainer->getHeight());
248 }
249
250 // drawChildren(graphics);
251 }
252
254 {
255 Rectangle rec;
256 rec.x = getBorderSize() + getPaddingLeft();
257 rec.y = getBorderSize() + getPaddingTop();
260 return rec;
261 }
262
263 void TabbedArea::resizeToContent(bool recursion)
264 {
265 if (recursion) {
266 mTabContainer->resizeToContent(recursion);
267 mWidgetContainer->resizeToContent(recursion);
268 }
270 adjustSize();
272 }
273
274 void TabbedArea::expandContent(bool recursion)
275 {
276 if (recursion) {
277 mTabContainer->expandContent(recursion);
278 mWidgetContainer->expandContent(recursion);
279 }
280 adjustSize();
282 }
283
285 {
286 // int totalTabWidth = 0; // UNUSED - possibly for future scrollable tabs feature
287 // int totalTabHeight = 0; // UNUSED - possibly for future scrollable tabs feature
288 int maxTabWidth = 0; // NOLINT(misc-const-correctness)
289 int maxTabHeight = 0; // NOLINT(misc-const-correctness)
290
291 // Rectangle const area = getChildrenArea(); // UNUSED - possibly for future scrollable tabs feature
292
293 for (auto& mTab : mTabs) {
294 // totalTabWidth += mTabs[i].first->getWidth(); // UNUSED
295 // totalTabHeight += mTabs[i].first->getHeight(); // UNUSED
296 maxTabWidth = std::max(mTab.first->getWidth(), maxTabWidth);
297 maxTabHeight = std::max(mTab.first->getHeight(), maxTabHeight);
298 }
299
300 if (getLayout() == Container::LayoutPolicy::Vertical) {
301 mTabContainer->setSize(maxTabWidth, getHeight() - 2);
302 mWidgetContainer->setSize(getWidth() - maxTabWidth - 2, getHeight() - 2);
303 mWidgetContainer->setPosition(maxTabWidth + 1, 1);
304 } else if (getLayout() == Container::LayoutPolicy::Horizontal) {
305 mTabContainer->setSize(getWidth() - 2, maxTabHeight);
306 mWidgetContainer->setSize(getWidth() - 2, getHeight() - maxTabHeight - 2);
307 mWidgetContainer->setPosition(1, maxTabHeight + 1);
308 }
309 }
310
312 {
313 int maxTabWidth = 0;
314 int maxTabHeight = 0;
315 unsigned int i = 0;
316 for (i = 0; i < mTabs.size(); i++) {
317 maxTabWidth = std::max(mTabs.at(i).first->getWidth(), maxTabWidth);
318 maxTabHeight = std::max(mTabs.at(i).first->getHeight(), maxTabHeight);
319 }
320
321 if (getLayout() == Container::LayoutPolicy::Vertical) {
322 int y = 0;
323 for (i = 0; i < mTabs.size(); i++) {
324 Tab* tab = mTabs.at(i).first;
325 tab->setPosition(maxTabWidth - tab->getWidth(), y);
326 y += tab->getHeight();
327 }
328 } else if (getLayout() == Container::LayoutPolicy::Horizontal) {
329 int x = 0;
330 for (i = 0; i < mTabs.size(); i++) {
331 Tab* tab = mTabs.at(i).first;
332 tab->setPosition(x, maxTabHeight - tab->getHeight());
333 x += tab->getWidth();
334 }
335 }
336 }
337
338 void TabbedArea::setWidth(int width)
339 {
340 // This may seem odd, but we want the TabbedArea to adjust
341 // it's size properly before we call Widget::setWidth as
342 // Widget::setWidth might distribute a resize event.
343 fcn::Rectangle const dim = mDimension;
344 mDimension.width = width;
345 adjustSize();
346 mDimension = dim;
347 Widget::setWidth(width);
348 }
349
350 void TabbedArea::setHeight(int height)
351 {
352 // This may seem odd, but we want the TabbedArea to adjust
353 // it's size properly before we call Widget::setHeight as
354 // Widget::setHeight might distribute a resize event.
355 fcn::Rectangle const dim = mDimension;
356 mDimension.height = height;
357 adjustSize();
358 mDimension = dim;
359 Widget::setHeight(height);
360 }
361
362 void TabbedArea::setSize(int width, int height)
363 {
364 // This may seem odd, but we want the TabbedArea to adjust
365 // it's size properly before we call Widget::setSize as
366 // Widget::setSize might distribute a resize event.
367 fcn::Rectangle const dim = mDimension;
368 mDimension.width = width;
369 mDimension.height = height;
370 adjustSize();
371 mDimension = dim;
372 Widget::setSize(width, height);
373 }
374
375 void TabbedArea::setDimension(Rectangle const & dimension)
376 {
377 // This may seem odd, but we want the TabbedArea to adjust
378 // it's size properly before we call Widget::setDimension as
379 // Widget::setDimension might distribute a resize event.
380 fcn::Rectangle const dim = mDimension;
381 mDimension = dimension;
382 adjustSize();
383 mDimension = dim;
384 Widget::setDimension(dimension);
385 }
386
388 {
389 if (keyEvent.isConsumed() || !isFocused()) {
390 return;
391 }
392
393 if (keyEvent.getKey().getValue() == fcn::Key::LEFT) {
394 int index = getSelectedTabIndex();
395 index--;
396
397 if (index < 0) {
398 return;
399 }
400
401 setSelectedTab(mTabs.at(index).first);
402
403 keyEvent.consume();
404 } else if (keyEvent.getKey().getValue() == fcn::Key::RIGHT) {
405 int index = getSelectedTabIndex();
406 index++;
407
408 if (std::cmp_greater_equal(index, mTabs.size())) {
409 return;
410 }
411
412 setSelectedTab(mTabs.at(index).first);
413
414 keyEvent.consume();
415 }
416 }
417
419 {
420 // we ignore that, otherwise the tab can not be pressed
421 // because the content consumed the event
422 // if (mouseEvent.isConsumed())
423 //{
424 // return;
425 //}
426 if (mouseEvent.getButton() == MouseEvent::Button::Left) {
427 Widget* widget = mTabContainer->getWidgetAt(mouseEvent.getX(), mouseEvent.getY());
428 Tab* tab = dynamic_cast<Tab*>(widget);
429
430 if (tab != nullptr) {
431 setSelectedTab(tab);
432 }
433 }
434
435 // Request focus only if the source of the event
436 // is not focusable. If the source of the event
437 // is focused we don't want to steal the focus.
438 if (!mouseEvent.getSource()->isFocusable()) {
439 requestFocus();
440 }
441 }
442
443 void TabbedArea::death(Event const & event)
444 {
445 Tab* tab = dynamic_cast<Tab*>(event.getSource());
446
447 if (tab != nullptr) {
448 removeTab(tab);
449 } else {
450 // Container::death(event);
451 }
452 }
453
454 void TabbedArea::action(ActionEvent const & actionEvent)
455 {
456 Widget* source = actionEvent.getSource();
457 Tab* tab = dynamic_cast<Tab*>(source);
458
459 if (tab == nullptr) {
460 throwException("Received an action from a widget that's not a tab!");
461 }
462
463 setSelectedTab(tab);
464 }
465
466 void TabbedArea::setBaseColor(Color const & color)
467 {
469 mWidgetContainer->setBaseColor(color);
470 mTabContainer->setBaseColor(color);
471 for (auto const & tabEntry : mTabs) {
472 tabEntry.first->setBaseColor(color);
473 }
474 }
475
477 {
478 mTabContainer->setLayout(policy);
479 }
480
482 {
483 return mTabContainer->getLayout();
484 }
485
486 void TabbedArea::setUniformSize(bool uniform)
487 {
488 mTabContainer->setUniformSize(uniform);
489 }
490
492 {
493 return mTabContainer->isUniformSize();
494 }
495
496 void TabbedArea::setVerticalSpacing(unsigned int spacing)
497 {
498 mTabContainer->setVerticalSpacing(spacing);
499 }
500
502 {
503 return mTabContainer->getVerticalSpacing();
504 }
505
506 void TabbedArea::setHorizontalSpacing(unsigned int spacing)
507 {
508 mTabContainer->setHorizontalSpacing(spacing);
509 }
510
512 {
513 return mTabContainer->getHorizontalSpacing();
514 }
515} // namespace fcn
Represents an action trigger (e.g., button click).
Color.
Definition color.hpp:58
uint8_t a
Alpha color component (0-255).
Definition color.hpp:350
A composite widget capable of holding and managing child widgets.
Definition container.hpp:36
virtual void setLayout(LayoutPolicy policy)
Sets the layout of the container.
LayoutPolicy
The layout policy of the container.
Definition container.hpp:50
virtual LayoutPolicy getLayout() const
Gets the layout of the container.
Base class for all GUI event objects.
Definition event.hpp:25
Widget * getSource() const
Gets the source widget of the event.
Definition event.cpp:20
Abstract interface providing primitive drawing functions (lines, rectangles, etc.).
Definition graphics.hpp:58
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 this event as consumed.
Represents a key event.
Definition keyevent.hpp:26
Key const & getKey() const
Gets the key of the event.
Definition keyevent.cpp:48
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:22
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:32
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.
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 mousePressed(MouseEvent &mouseEvent) override
Called when a mouse button has been pressed down on the widget area.
virtual void setUniformSize(bool uniform)
Enables or disables uniform sizing of child elements.
void setSize(int width, int height) override
Set the size (width and height) of the tabbed area in pixels.
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.
void setDimension(Rectangle const &dimension) override
Set the area dimension for the tabbed area.
void resizeToContent(bool recursion=true) override
Resizes the widget's size to fit the content exactly, calls recursively all childs.
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
Handles an action event emitted by a widget.
void adjustTabPositions()
Adjusts the positions of the tabs.
void keyPressed(KeyEvent &keyEvent) override
Called if a key is pressed when the widget has keyboard focus.
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
Stores tabs owned by this instance for automatic destruction.
void setWidth(int width) override
Set the width of the tabbed area in pixels.
Container::LayoutPolicy getLayout() const
Gets the layout of the tabbedarea.
std::vector< std::pair< Tab *, Widget * > > mTabs
Associates each tab with the widget displayed when it 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.
void setHeight(int height) override
Set the height of the tabbed area in pixels.
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:742
int getWidth() const
Gets the width of the widget.
Definition widget.cpp:252
void resizeToChildren()
Resizes the widget to fit it's children exactly.
Definition widget.cpp:1344
virtual bool isFocused() const
Checks if the widget is focused.
Definition widget.cpp:624
virtual void setDimension(Rectangle const &dimension)
Sets the dimension of the widget.
Definition widget.cpp:315
Rectangle mDimension
Holds the dimension of the widget.
Definition widget.hpp:1908
unsigned int getPaddingLeft() const
Gets the left padding.
Definition widget.cpp:604
Widget()
Constructor.
Definition widget.cpp:52
virtual void setSize(int width, int height)
Sets the size of the widget.
Definition widget.cpp:1065
virtual void requestFocus()
Requests focus for the widget.
Definition widget.cpp:660
void setPosition(int x, int y)
Sets position of the widget.
Definition widget.cpp:306
void addActionListener(ActionListener *actionListener)
Adds an action listener to the widget.
Definition widget.cpp:863
void adaptLayout()
Execute the layouting.
Definition widget.hpp:1560
virtual void setWidth(int width)
Sets the width of the widget.
Definition widget.cpp:244
unsigned int getPaddingTop() const
Gets the top padding.
Definition widget.cpp:574
unsigned int getBorderSize() const
Gets the size of the widget's border.
Definition widget.cpp:474
unsigned int getPaddingBottom() const
Gets the bottom padding.
Definition widget.cpp:594
virtual void setBaseColor(Color const &color)
Sets the base color of the widget.
Definition widget.cpp:737
bool isFocusable() const
Checks if a widget is focusable.
Definition widget.cpp:655
int getHeight() const
Gets the height of the widget.
Definition widget.cpp:265
virtual void setHeight(int height)
Sets the height of the widget.
Definition widget.cpp:257
unsigned int getPaddingRight() const
Gets the right padding.
Definition widget.cpp:584
Used replacement tokens by configure_file():
void throwException(std::string const &message, std::source_location location=std::source_location::current())
Throw an Exception capturing the current source location.