FifeGUI 0.2.0
A C++ GUI library designed for games.
dropdown.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/dropdown.hpp"
6
7#include <memory>
8
9#include "fifechan/exception.hpp"
10#include "fifechan/font.hpp"
11#include "fifechan/graphics.hpp"
12#include "fifechan/key.hpp"
13#include "fifechan/listmodel.hpp"
14#include "fifechan/mouseinput.hpp"
15#include "fifechan/widgets/listbox.hpp"
16#include "fifechan/widgets/scrollarea.hpp"
17
18namespace fcn
19{
20 DropDown::DropDown(ListModel* listModel, ScrollArea* scrollArea, ListBox* listBox) :
21 mInternalScrollArea(scrollArea == nullptr), mInternalListBox(listBox == nullptr)
22 {
23 setWidth(100);
24 setFocusable(true);
25
27
29 mOwnedScrollArea = std::make_unique<ScrollArea>();
31 } else {
32 mScrollArea = scrollArea;
33 }
34
35 if (mInternalListBox) {
36 mOwnedListBox = std::make_unique<ListBox>();
37 mListBox = mOwnedListBox.get();
38 } else {
39 mListBox = listBox;
40 }
41
42 mScrollArea->setContent(mListBox);
44
45 mListBox->addActionListener(this);
46 mListBox->addSelectionListener(this);
47
48 setListModel(listModel);
49
50 addMouseListener(this);
51 addKeyListener(this);
52 addFocusListener(this);
53
55 }
56
57 DropDown::~DropDown()
58 {
62 }
63
65 }
66
67 void DropDown::draw(Graphics* graphics)
68 {
69 int h = 0;
70
71 if (mDroppedDown) {
73 } else {
74 h = getHeight();
75 }
76
77 Color const faceColor = getBaseColor();
78 Color highlightColor;
79 Color shadowColor;
80 int const alpha = getBaseColor().a;
81 highlightColor = faceColor + 0x303030;
82 highlightColor.a = alpha;
83 shadowColor = faceColor - 0x303030;
84 shadowColor.a = alpha;
85
86 // Draw a border.
87 graphics->setColor(shadowColor);
88 graphics->drawLine(0, 0, getWidth() - 1, 0);
89 graphics->drawLine(0, 1, 0, h - 2);
90 graphics->setColor(highlightColor);
91 graphics->drawLine(getWidth() - 1, 1, getWidth() - 1, h - 1);
92 graphics->drawLine(0, h - 1, getWidth() - 1, h - 1);
93
94 // Push a clip area so the other drawings don't need to worry
95 // about the border.
96 graphics->pushClipArea(Rectangle(1, 1, getWidth() - 2, h - 2));
97 ClipRectangle const & currentClipArea = graphics->getCurrentClipArea();
98
99 graphics->setColor(getBackgroundColor());
100 graphics->fillRectangle(0, 0, currentClipArea.width, currentClipArea.height);
101
102 if (isFocused()) {
103 graphics->setColor(getSelectionColor());
104 graphics->fillRectangle(0, 0, currentClipArea.width - currentClipArea.height, currentClipArea.height);
105 graphics->setColor(getForegroundColor());
106 }
107
108 if ((mListBox->getListModel() != nullptr) && mListBox->getSelected() >= 0) {
109 graphics->setColor(getForegroundColor());
110 graphics->setFont(getFont());
111
112 graphics->drawText(mListBox->getListModel()->getElementAt(mListBox->getSelected()), 1, 0);
113 } else if ((mListBox->getListModel() != nullptr) && mListBox->getSelected() < 0) {
114 graphics->setColor(getForegroundColor());
115 graphics->setFont(getFont());
116
117 graphics->drawText("", 1, 0);
118 }
119
120 // Push a clip area before drawing the button.
121 graphics->pushClipArea(Rectangle(
122 currentClipArea.width - currentClipArea.height, 0, currentClipArea.height, currentClipArea.height));
123 drawButton(graphics);
124 graphics->popClipArea();
125 graphics->popClipArea();
126
127 if (mDroppedDown) {
128 // Draw a border around the children.
129 graphics->setColor(shadowColor);
131 // drawChildren(graphics);
132 }
133 }
134
136 {
137 Color faceColor;
138 Color highlightColor;
139 Color shadowColor;
140 int offset = 0;
141 int const alpha = getBaseColor().a;
142
143 if (mPushed) {
144 faceColor = getBaseColor() - 0x303030;
145 faceColor.a = alpha;
146 highlightColor = faceColor - 0x303030;
147 highlightColor.a = alpha;
148 shadowColor = faceColor + 0x303030;
149 shadowColor.a = alpha;
150 offset = 1;
151 } else {
152 faceColor = getBaseColor();
153 faceColor.a = alpha;
154 highlightColor = faceColor + 0x303030;
155 highlightColor.a = alpha;
156 shadowColor = faceColor - 0x303030;
157 shadowColor.a = alpha;
158 offset = 0;
159 }
160
161 ClipRectangle const & currentClipArea = graphics->getCurrentClipArea();
162 graphics->setColor(highlightColor);
163 graphics->drawLine(0, 0, currentClipArea.width - 1, 0);
164 graphics->drawLine(0, 1, 0, currentClipArea.height - 1);
165 graphics->setColor(shadowColor);
166 graphics->drawLine(currentClipArea.width - 1, 1, currentClipArea.width - 1, currentClipArea.height - 1);
167 graphics->drawLine(1, currentClipArea.height - 1, currentClipArea.width - 2, currentClipArea.height - 1);
168
169 graphics->setColor(faceColor);
170 graphics->fillRectangle(1, 1, currentClipArea.width - 2, currentClipArea.height - 2);
171
172 graphics->setColor(getForegroundColor());
173
174 int i = 0;
175 int const n = currentClipArea.height / 3;
176 int const dx = currentClipArea.height / 2;
177 int const dy = (currentClipArea.height * 2) / 3;
178 for (i = 0; i < n; i++) {
179 graphics->drawLine(dx - i + offset, dy - i + offset, dx + i + offset, dy - i + offset);
180 }
181 }
182
184 {
185 return mListBox->getSelected();
186 }
187
188 void DropDown::setSelected(int selected)
189 {
190 mListBox->setSelected(selected);
191 }
192
194 {
195 if (keyEvent.isConsumed()) {
196 return;
197 }
198
199 Key const key = keyEvent.getKey();
200
201 if ((key.getValue() == Key::Enter || key.getValue() == Key::Space) && !mDroppedDown) {
202 dropDown();
203 keyEvent.consume();
204 } else if (key.getValue() == Key::Up) {
206 keyEvent.consume();
207 } else if (key.getValue() == Key::Down) {
209 keyEvent.consume();
210 }
211 }
212
214 {
215 // If we have a mouse press on the widget.
216 if (0 <= mouseEvent.getY() && mouseEvent.getY() < getHeight() && mouseEvent.getX() >= 0 &&
217 mouseEvent.getX() < getWidth() && mouseEvent.getButton() == MouseEvent::Button::Left && !mDroppedDown &&
218 mouseEvent.getSource() == this) {
219 mPushed = true;
220 dropDown();
222 } else if (
223 // Fold up the listbox if the upper part is clicked after fold down
224 0 <= mouseEvent.getY() && mouseEvent.getY() < mFoldedUpHeight && mouseEvent.getX() >= 0 &&
225 mouseEvent.getX() < getWidth() && mouseEvent.getButton() == MouseEvent::Button::Left && mDroppedDown &&
226 mouseEvent.getSource() == this) {
227 mPushed = false;
228 foldUp();
230 } else if (
231 // If we have a mouse press outside the widget
232 0 > mouseEvent.getY() || mouseEvent.getY() >= getHeight() || mouseEvent.getX() < 0 ||
233 mouseEvent.getX() >= getWidth()) {
234 mPushed = false;
235 foldUp();
236 }
237 }
238
240 {
241 if (mIsDragged) {
242 mPushed = false;
243 }
244
245 // Released outside of widget. Can happen when we have modal input focus.
246 if ((0 > mouseEvent.getY() || mouseEvent.getY() >= getHeight() || mouseEvent.getX() < 0 ||
247 mouseEvent.getX() >= getWidth()) &&
248 mouseEvent.getButton() == MouseEvent::Button::Left && isModalMouseInputFocused()) {
250
251 if (mIsDragged) {
252 foldUp();
253 }
254 } else if (mouseEvent.getButton() == MouseEvent::Button::Left) {
255 mPushed = false;
256 }
257
258 mIsDragged = false;
259 }
260
262 {
263 mIsDragged = true;
264
265 mouseEvent.consume();
266 }
267
269 {
270 mListBox->setListModel(listModel);
271
272 adjustHeight();
273 }
274
276 {
277 return mListBox->getListModel();
278 }
279
281 {
282 if (mScrollArea == nullptr) {
283 throwException("Scroll area has been deleted.");
284 }
285
286 if (mListBox == nullptr) {
287 throwException("List box has been deleted.");
288 }
289
290 int const listBoxHeight = mListBox->getHeight();
291
292 // We add 2 for the border
293 int const h2 = getFont()->getHeight() + 2;
294
295 setHeight(h2);
296
297 // The addition/subtraction of 2 compensates for the separation lines,
298 // which are separating the selected element view and the scroll area.
299
300 if (mDroppedDown && (getParent() != nullptr)) {
301 int const h = getParent()->getChildrenArea().height - getY();
302
303 if (listBoxHeight > h - h2 - 2) {
304 mScrollArea->setHeight(h - h2 - 2);
305 setHeight(h);
306 } else {
307 setHeight(listBoxHeight + h2 + 2);
308 mScrollArea->setHeight(listBoxHeight);
309 }
310 }
311
312 mScrollArea->setWidth(getWidth());
313 // Resize the ListBox to exactly fit the ScrollArea.
314 mListBox->setWidth(mScrollArea->getChildrenArea().width);
315 mScrollArea->setPosition(0, 0);
316 }
317
318 void DropDown::resizeToContent(bool recursion)
319 {
320 (void)recursion; // unused parameter
321
322 if (mScrollArea != nullptr) {
323 mScrollArea->resizeToContent();
324 }
325
326 if (mListBox != nullptr) {
327 mScrollArea->resizeToContent();
328 }
329 adjustHeight();
330 }
331
333 {
334 adjustHeight();
335 }
336
338 {
339 if (!mDroppedDown) {
340 mDroppedDown = true;
342 adjustHeight();
343
344 if (getParent() != nullptr) {
345 getParent()->moveToTop(this);
346 }
347 }
348
349 mListBox->requestFocus();
350 }
351
353 {
354 if (mDroppedDown) {
355 mDroppedDown = false;
356 adjustHeight();
357 mInternalFocusHandler.focusNone();
358 }
359 }
360
361 void DropDown::focusLost([[maybe_unused]] Event const & event)
362 {
363 foldUp();
364 mInternalFocusHandler.focusNone();
365 }
366
367 void DropDown::death(Event const & event)
368 {
369 if (event.getSource() == mScrollArea) {
370 if (mOwnedScrollArea.get() == mScrollArea) {
371 (void)mOwnedScrollArea.release();
372 }
373 mScrollArea = nullptr;
374 }
375 }
376
377 void DropDown::action([[maybe_unused]] ActionEvent const & actionEvent)
378 {
379 foldUp();
382 }
383
385 {
386 if (mDroppedDown) {
387 // Calculate the children area (with the one pixel border in mind)
388 return {1, mFoldedUpHeight + 1, getWidth() - 2, getHeight() - mFoldedUpHeight - 2};
389 }
390
391 return {};
392 }
393
394 void DropDown::setBaseColor(Color const & color)
395 {
397 mScrollArea->setBaseColor(color);
398 }
399
400 if (mInternalListBox) {
401 mListBox->setBaseColor(color);
402 }
403
405 }
406
408 {
410 mScrollArea->setBackgroundColor(color);
411 }
412
413 if (mInternalListBox) {
414 mListBox->setBackgroundColor(color);
415 }
416
418 }
419
421 {
423 mScrollArea->setForegroundColor(color);
424 }
425
426 if (mInternalListBox) {
427 mListBox->setForegroundColor(color);
428 }
429
431 }
432
434 {
436 mScrollArea->setFont(font);
437 }
438
439 if (mInternalListBox) {
440 mListBox->setFont(font);
441 }
442
443 Widget::setFont(font);
444 }
445
447 {
448 if (isFocused() && mouseEvent.getSource() == this) {
449 mouseEvent.consume();
450
451 if (mListBox->getSelected() > 0) {
452 mListBox->setSelected(mListBox->getSelected() - 1);
453 }
454 }
455 }
456
458 {
459 if (isFocused() && mouseEvent.getSource() == this) {
460 mouseEvent.consume();
461
462 mListBox->setSelected(mListBox->getSelected() + 1);
463 }
464 }
465
467 {
469
470 if (mInternalListBox) {
471 mListBox->setSelectionColor(color);
472 }
473 }
474
475 void DropDown::valueChanged([[maybe_unused]] SelectionEvent const & event)
476 {
478 }
479
480 void DropDown::addSelectionListener(SelectionListener* selectionListener)
481 {
482 mSelectionListeners.push_back(selectionListener);
483 }
484
485 void DropDown::removeSelectionListener(SelectionListener* selectionListener)
486 {
487 mSelectionListeners.remove(selectionListener);
488 }
489
491 {
493
494 for (iter = mSelectionListeners.begin(); iter != mSelectionListeners.end(); ++iter) {
495 SelectionEvent const event(this);
496 (*iter)->valueChanged(event);
497 }
498 }
499} // namespace fcn
Represents an action trigger (e.g., button click).
A rectangle specifically used for clipping rendering regions.
Color.
Definition color.hpp:56
uint8_t a
Alpha color component (0-255).
Definition color.hpp:322
void draw(Graphics *graphics) override
Draws the widget.
Definition dropdown.cpp:67
virtual void death(Event const &event)
DeathListener callback invoked when a observed widget is destroyed.
Definition dropdown.cpp:367
void setFont(Font *font) override
Set the font used to render items in the dropdown.
Definition dropdown.cpp:433
void action(ActionEvent const &actionEvent) override
Called when an action is received from a widget.
Definition dropdown.cpp:377
DropDown(ListModel *listModel=nullptr, ScrollArea *scrollArea=nullptr, ListBox *listBox=nullptr)
Constructor.
Definition dropdown.cpp:20
bool mInternalListBox
True if an internal list box is used, false if a list box has been passed to the drop down which the ...
Definition dropdown.hpp:271
bool mIsDragged
True if the drop down is dragged.
Definition dropdown.hpp:276
bool mInternalScrollArea
True if an internal scroll area is used, false if a scroll area has been passed to the drop down whic...
Definition dropdown.hpp:264
void focusLost(Event const &event) override
Called when a widget loses focus.
Definition dropdown.cpp:361
void adjustSize() override
Resizes the widget's size to fit the content exactly.
Definition dropdown.cpp:332
ScrollArea * mScrollArea
The scroll area used.
Definition dropdown.hpp:246
virtual void dropDown()
Sets the drop down to be dropped down.
Definition dropdown.cpp:337
void mouseWheelMovedDown(MouseEvent &mouseEvent) override
Called when the mouse wheel has moved down on the widget area.
Definition dropdown.cpp:457
std::unique_ptr< ScrollArea > mOwnedScrollArea
Owned internal scroll area when not supplied externally.
Definition dropdown.hpp:236
void mouseDragged(MouseEvent &mouseEvent) override
Called when the mouse has moved and the mouse has previously been pressed on the widget.
Definition dropdown.cpp:261
Rectangle getChildrenArea() override
Gets the area of the widget occupied by the widget's children.
Definition dropdown.cpp:384
void valueChanged(SelectionEvent const &event) override
Called when the value of a selection has been changed in a Widget.
Definition dropdown.cpp:475
void removeSelectionListener(SelectionListener *selectionListener)
Removes a selection listener from the drop down.
Definition dropdown.cpp:485
virtual void drawButton(Graphics *graphics)
Draws the button of the drop down.
Definition dropdown.cpp:135
SelectionListenerList mSelectionListeners
The selection listener's of the drop down.
Definition dropdown.hpp:286
int getSelected() const
Gets the selected item as an index in the list model.
Definition dropdown.cpp:183
ListBox * mListBox
The list box used.
Definition dropdown.hpp:251
void setListModel(ListModel *listModel)
Sets the list model to use when displaying the list.
Definition dropdown.cpp:268
void keyPressed(KeyEvent &keyEvent) override
Called if a key is pressed when the widget has keyboard focus.
Definition dropdown.cpp:193
void mouseReleased(MouseEvent &mouseEvent) override
Called when a mouse button has been released on the widget area.
Definition dropdown.cpp:239
void setForegroundColor(Color const &color) override
Set the foreground/text color used in the dropdown.
Definition dropdown.cpp:420
void adjustHeight()
Adjusts the height of the drop down to fit the height of the drop down's parent's height.
Definition dropdown.cpp:280
void setBackgroundColor(Color const &color) override
Set the explicit background color for the dropdown.
Definition dropdown.cpp:407
void resizeToContent()
Resizes the widget's size to fit the content exactly, calls recursively all childs.
Definition widget.hpp:1417
bool mDroppedDown
True if the drop down is dropped down, false otherwise.
Definition dropdown.hpp:218
bool mPushed
True if the drop down has been pushed with the mouse, false otherwise.
Definition dropdown.hpp:224
void setSelected(int selected)
Sets the selected item.
Definition dropdown.cpp:188
void setBaseColor(Color const &color) override
Set the base color used for the dropdown background/controls.
Definition dropdown.cpp:394
virtual void foldUp()
Sets the drop down to be folded up.
Definition dropdown.cpp:352
void setSelectionColor(Color const &color) override
Set the color used for the selected item highlight.
Definition dropdown.cpp:466
void addSelectionListener(SelectionListener *selectionListener)
Adds a selection listener to the drop down.
Definition dropdown.cpp:480
void distributeValueChangedEvent()
Distributes a value changed event to all selection listeners of the drop down.
Definition dropdown.cpp:490
ListModel * getListModel() const
Gets the list model used.
Definition dropdown.cpp:275
std::unique_ptr< ListBox > mOwnedListBox
Owned internal list box when not supplied externally.
Definition dropdown.hpp:241
FocusHandler mInternalFocusHandler
The internal focus handler used to keep track of focus for the internal list box.
Definition dropdown.hpp:257
void mousePressed(MouseEvent &mouseEvent) override
Called when a mouse button has been pressed on the widget area.
Definition dropdown.cpp:213
SelectionListenerList::iterator SelectionListenerIterator
Typedef.
Definition dropdown.hpp:291
int mFoldedUpHeight
Holds what the height is if the drop down is folded up.
Definition dropdown.hpp:231
void mouseWheelMovedUp(MouseEvent &mouseEvent) override
Called when the mouse wheel has moved up on the widget area.
Definition dropdown.cpp:446
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 for font rendering.
Definition font.hpp:24
virtual int getHeight() const =0
Gets the height of the glyphs in the font.
Abstract interface providing primitive drawing functions (lines, rectangles, etc.).
Definition graphics.hpp:57
virtual void popClipArea()
Removes the top most clip area from the stack.
Definition graphics.cpp:55
void drawText(std::string const &text, int x, int y)
Draws text with a default left alignment.
Definition graphics.hpp:362
virtual ClipRectangle const & getCurrentClipArea()
Gets the current clip area.
Definition graphics.cpp:65
virtual bool pushClipArea(Rectangle area)
Pushes a clip area onto the stack.
Definition graphics.cpp:18
virtual void setFont(Font *font)
Sets the font to use when drawing text.
Definition graphics.cpp:79
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 drawRectangle(Rectangle const &rectangle)=0
Draws a simple, non-filled rectangle with a one pixel width.
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
Represents a keyboard key or character code.
Definition key.hpp:20
int getValue() const
Gets the value of the key.
Definition key.cpp:28
A scrollable list box allowing item selection.
Definition listbox.hpp:34
void removeSelectionListener(SelectionListener *selectionListener)
Removes a selection listener from the list box.
Definition listbox.cpp:256
Interface for a data model representing a list (used by ListBox/DropDown).
Definition listmodel.hpp:24
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 height
Holds the height of the rectangle.
A scrollable viewport for viewing widgets larger than the visible area.
Represents a change in selection state (e.g., list item selected).
void addFocusListener(FocusListener *focusListener)
Adds a focus listener to the widget.
Definition widget.cpp:744
Color const & getBaseColor() const
Gets the base color.
Definition widget.cpp:596
virtual bool isModalMouseInputFocused() const
Checks if the widget or it's parent has modal mouse input focus.
Definition widget.cpp:941
int getY() const
Gets the y coordinate of the widget.
Definition widget.cpp:209
void setFocusable(bool focusable)
Sets the widget to be focusable, or not.
Definition widget.cpp:506
int getWidth() const
Gets the width of the widget.
Definition widget.cpp:170
virtual Widget * getParent() const
Gets the widget's parent container.
Definition widget.cpp:157
void add(Widget *widget)
Adds a child to the widget.
Definition widget.cpp:1272
virtual void releaseModalMouseInputFocus()
Releases modal mouse input focus.
Definition widget.cpp:919
virtual Rectangle getChildrenArea()
Gets the area of the widget occupied by the widget's children.
Definition widget.cpp:992
virtual void moveToTop(Widget *widget)
Moves a widget to the top of this widget.
Definition widget.cpp:1290
virtual bool isFocused() const
Checks if the widget is focused.
Definition widget.cpp:497
void removeActionListener(ActionListener *actionListener)
Removes an added action listener from the widget.
Definition widget.cpp:719
Color const & getBackgroundColor() const
Gets the background color.
Definition widget.cpp:616
virtual void setForegroundColor(Color const &color)
Sets the foreground color.
Definition widget.cpp:601
void addMouseListener(MouseListener *mouseListener)
Adds a mouse listener to the widget.
Definition widget.cpp:754
static bool widgetExists(Widget const *widget)
Checks if a widget exists or not, that is if it still exists an instance of the object.
Definition widget.cpp:827
void setWidth(int width)
Sets the width of the widget.
Definition widget.cpp:162
virtual void requestModalMouseInputFocus()
Requests modal mouse input focus.
Definition widget.cpp:901
void addKeyListener(KeyListener *keyListener)
Adds a key listener to the widget.
Definition widget.cpp:734
void setInternalFocusHandler(FocusHandler *internalFocusHandler)
Sets the internal focus handler.
Definition widget.cpp:1002
virtual void setBaseColor(Color const &color)
Sets the base color of the widget.
Definition widget.cpp:591
Color const & getForegroundColor() const
Gets the foreground color.
Definition widget.cpp:606
virtual void setFont(Font *font)
Sets the font for the widget.
Definition widget.cpp:821
Font * getFont() const
Gets the font set for the widget.
Definition widget.cpp:796
void distributeActionEvent()
Distributes an action event to all action listeners of the widget.
Definition widget.cpp:1113
int getHeight() const
Gets the height of the widget.
Definition widget.cpp:183
virtual void setSelectionColor(Color const &color)
Sets the selection color.
Definition widget.cpp:621
void setHeight(int height)
Sets the height of the widget.
Definition widget.cpp:175
Color const & getSelectionColor() const
Gets the selection color.
Definition widget.cpp:626
virtual void setBackgroundColor(Color const &color)
Sets the background color.
Definition widget.cpp:611