FifeGUI 0.3.0
A C++ GUI library designed for games.
gui.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/gui.hpp"
7
8// Standard library includes
9#include <algorithm>
10#include <cassert>
11#include <cstdint>
12#include <list>
13#include <memory>
14#include <queue>
15#include <string>
16#include <utility>
17#include <vector>
18
19// Platform config include
20#include "fifechan/platform.hpp"
21
22// Project headers (subdirs before local)
23#include "fifechan/events/keyevent.hpp"
24#include "fifechan/events/mouseevent.hpp"
25#include "fifechan/events/textinputevent.hpp"
26#include "fifechan/exception.hpp"
27#include "fifechan/focushandler.hpp"
28#include "fifechan/graphics.hpp"
29#include "fifechan/input.hpp"
30#include "fifechan/keyinput.hpp"
31#include "fifechan/listeners/deathlistener.hpp"
32#include "fifechan/listeners/keylistener.hpp"
33#include "fifechan/listeners/mouselistener.hpp"
34#include "fifechan/listeners/visibilityeventhandler.hpp"
35#include "fifechan/mouseinput.hpp"
36#include "fifechan/rectangle.hpp"
37#include "fifechan/widget.hpp"
38
39namespace fcn
40{
45 class GuiDeathListener : public DeathListener
46 {
47 public:
49 explicit GuiDeathListener(Gui* gui) : mGui(gui)
50 {
51 }
52 ~GuiDeathListener() override = default;
53
54 void death(Event const & event) override
55 {
56 mGui->widgetDied(event.getSource());
57 }
58
59 GuiDeathListener(GuiDeathListener const &) = delete;
60 GuiDeathListener& operator=(GuiDeathListener const &) = delete;
62 GuiDeathListener& operator=(GuiDeathListener&&) = delete;
63
64 private:
65 Gui* mGui;
66 };
67
68 Gui::Gui() :
69 mTop(nullptr),
70 mGraphics(nullptr),
71 mInput(nullptr),
72 mTabbing(true),
73 mFocusHandler(new FocusHandler()),
74 mVisibilityEventHandler(new VisibilityEventHandler(this)),
75 mShiftPressed(false),
76 mMetaPressed(false),
77 mControlPressed(false),
78 mAltPressed(false),
79 mLastMousePressButton(0),
80 mLastMousePressTimeStamp(0),
81 mLastMouseX(0),
82 mLastMouseY(0),
83 mClickCount(1),
84 mLastMouseDragButton(0),
85 mDeathListener(new GuiDeathListener(this))
86 {
87 assert("focus handler must be allocated" && mFocusHandler != nullptr);
88 assert("visibility handler must be allocated" && mVisibilityEventHandler != nullptr);
89 assert("death listener must be allocated" && mDeathListener != nullptr);
90
91 Widget::_setVisibilityEventHandler(mVisibilityEventHandler);
92
93 Widget::_setGuiDeathListener(mDeathListener);
94 }
95
96 Gui::~Gui()
97 {
99 if (mTop != nullptr) {
100 mTop->_setFocusHandler(nullptr);
101 }
102 mTop = nullptr;
103 }
106
107 delete mFocusHandler;
109 delete mDeathListener;
110 }
111
113 {
114 // `top` may be nullptr to clear the current top widget; no assertion needed.
115
116 if (top != mOwnedTop.get()) {
117 mOwnedTop.reset();
118 }
119
120 if (mTop != nullptr) {
121 mTop->_setFocusHandler(nullptr);
122 }
123 if (top != nullptr) {
125 }
126
127 mTop = top;
128 }
129
130 void Gui::setTop(std::unique_ptr<Widget> top)
131 {
132 mOwnedTop = std::move(top);
133 setTop(mOwnedTop.get());
134 }
135
137 {
138 setTop(top);
139 }
140
141 void Gui::setRoot(std::unique_ptr<Widget> top)
142 {
143 setTop(std::move(top));
144 }
145
147 {
148 return mTop;
149 }
150
152 {
153 // `graphics` may be nullptr to clear the current graphics; no assertion needed.
154
155 if (graphics != mOwnedGraphics.get()) {
156 mOwnedGraphics.reset();
157 }
158 mGraphics = graphics;
159 }
160
161 void Gui::setGraphics(std::unique_ptr<Graphics> graphics)
162 {
163 mOwnedGraphics = std::move(graphics);
165 }
166
168 {
169 return mGraphics;
170 }
171
172 void Gui::setInput(Input* input)
173 {
174 // `input` may be nullptr to clear the current input; no assertion needed.
175
176 if (input != mOwnedInput.get()) {
177 mOwnedInput.reset();
178 }
179 mInput = input;
180 }
181
182 void Gui::setInput(std::unique_ptr<Input> input)
183 {
184 mOwnedInput = std::move(input);
185 setInput(mOwnedInput.get());
186 }
187
189 {
190 return mInput;
191 }
192
193 void Gui::initialize(std::unique_ptr<Graphics> graphics, std::unique_ptr<Input> input, int width, int height)
194 {
195 (void)width;
196 (void)height;
197 setGraphics(std::move(graphics));
198 setInput(std::move(input));
199 }
200
202 {
203 return mFocusHandler;
204 }
205
206 void Gui::setGlobalFont(std::string const & filename, int size)
207 {
208 if (mGraphics == nullptr) {
209 throwException("No graphics set");
210 }
211
212 auto createdFont = mGraphics->createFont(filename, size);
213 if (!createdFont) {
214 throwException("Failed to create font using active graphics backend");
215 }
216
217 mGlobalFont = std::move(createdFont);
219 }
220
222 {
223 assert("Top widget must be set" && mTop != nullptr);
224
225 if (mTop == nullptr) {
226 throwException("No top widget set");
227 }
228
231
232 if (mInput != nullptr) {
233 mInput->_pollInput();
234
238 }
239
240 mTop->_logic();
241
244 }
245
247 {
248 assert("Top widget must be set" && mTop != nullptr);
249 assert("Graphics must be set" && mGraphics != nullptr);
250
251 if (mTop == nullptr) {
252 throwException("No top widget set");
253 }
254
255 if (mGraphics == nullptr) {
256 throwException("No graphics set");
257 }
258
259 if (!mTop->isVisible()) {
260 return;
261 }
262
263 mGraphics->_beginDraw();
264 mTop->_draw(mGraphics);
265 mGraphics->_endDraw();
266 }
267
269 {
270 mFocusHandler->focusNone();
271 }
272
273 void Gui::setTabbingEnabled(bool tabbing)
274 {
275 mTabbing = tabbing;
276 }
277
279 {
280 return mTabbing;
281 }
282
284 {
285 mKeyListeners.push_back(keyListener);
286 }
287
289 {
290 mKeyListeners.remove(keyListener);
291 }
292
294 {
295 mHiddenWidgets.push_back(widget);
296 }
297
299 {
300 mShownWidgets.push_back(widget);
301 }
302
303 void Gui::widgetDied(Widget const * widget)
304 {
305 std::erase(mShownWidgets, widget);
306 std::erase(mHiddenWidgets, widget);
307 }
308
310 {
311 while (!mInput->isMouseQueueEmpty()) {
312 MouseInput const mouseInput = mInput->dequeueMouseInput();
313
314 switch (mouseInput.getType()) {
315 case MouseInput::Type::Pressed:
316 handleMousePressed(mouseInput);
317 break;
318 case MouseInput::Type::Released:
319 handleMouseReleased(mouseInput);
320 break;
321 case MouseInput::Type::Moved:
322 handleMouseMoved(mouseInput);
323 break;
324 case MouseInput::Type::WheelMovedDown:
325 handleMouseWheelMovedDown(mouseInput);
326 break;
327 case MouseInput::Type::WheelMovedUp:
328 handleMouseWheelMovedUp(mouseInput);
329 break;
330 case MouseInput::Type::WheelMovedRight:
331 handleMouseWheelMovedRight(mouseInput);
332 break;
333 case MouseInput::Type::WheelMovedLeft:
334 handleMouseWheelMovedLeft(mouseInput);
335 break;
336 default:
337 throwException("Unknown mouse input type.");
338 break;
339 }
340
341 // Save the current mouse state. It's needed to send
342 // mouse exited events and mouse entered events when
343 // the mouse exits a widget and when a widget releases
344 // modal mouse input focus.
345 mLastMouseX = mouseInput.getX();
346 mLastMouseY = mouseInput.getY();
347 }
348 }
349
351 {
352 while (!mInput->isTextQueueEmpty()) {
353 std::string const text = mInput->dequeueTextInput();
354
355 Widget* focused = mFocusHandler->getFocused();
356 if (focused == nullptr || !focused->isEnabled()) {
357 continue;
358 }
359
360 TextInputEvent textEvent(
361 focused,
362 focused,
367 text,
369
370 focused->textInput(textEvent);
371 }
372 }
373
375 {
376 while (!mInput->isKeyQueueEmpty()) {
377 KeyInput const keyInput = mInput->dequeueKeyInput();
378
379 // Save modifiers state
380 mShiftPressed = keyInput.isShiftPressed();
381 mMetaPressed = keyInput.isMetaPressed();
383 mAltPressed = keyInput.isAltPressed();
384
385 KeyEvent keyEventToGlobalKeyListeners(
386 nullptr,
387 nullptr,
392 static_cast<KeyEvent::Type>(static_cast<std::uint8_t>(keyInput.getType())),
393 keyInput.isNumericPad(),
394 keyInput.getKey());
395
396 distributeKeyEventToGlobalKeyListeners(keyEventToGlobalKeyListeners);
397
398 // If a global key listener consumes the event it will not be
399 // sent further to the source of the event.
400 if (keyEventToGlobalKeyListeners.isConsumed()) {
401 continue;
402 }
403
404 bool keyEventConsumed = false;
405
406 // Send key inputs to the focused widgets
407 if (mFocusHandler->getFocused() != nullptr) {
408 Widget* source = getKeyEventSource();
409 KeyEvent keyEvent(
410 source,
411 source,
416 static_cast<KeyEvent::Type>(static_cast<std::uint8_t>(keyInput.getType())),
417 keyInput.isNumericPad(),
418 keyInput.getKey());
419
420 if (!mFocusHandler->getFocused()->isFocusable()) {
421 mFocusHandler->focusNone();
422 } else {
423 distributeKeyEvent(keyEvent);
424 }
425
426 keyEventConsumed = keyEvent.isConsumed();
427 }
428
429 // If the key event hasn't been consumed and
430 // tabbing is enable check for tab press and
431 // change focus.
432 if (!keyEventConsumed && mTabbing && keyInput.getKey().getValue() == fcn::Key::TAB &&
433 keyInput.getType() == KeyInput::Type::Pressed) {
434 if (keyInput.isShiftPressed()) {
435 mFocusHandler->tabPrevious();
436 } else {
437 mFocusHandler->tabNext();
438 }
439 }
440 }
441 }
442
443 void Gui::handleMouseMoved(MouseInput const & mouseInput)
444 {
445 // Get the last widgets with the mouse using the last known mouse position.
446 std::vector<Widget*> const mLastWidgetsWithMouse = getWidgetsAt(mLastMouseX, mLastMouseY);
447
448 // Check if the mouse has left the application window.
449 if (mouseInput.getX() < 0 || mouseInput.getY() < 0 ||
450 !mTop->getDimension().isContaining(mouseInput.getX(), mouseInput.getY())) {
451 for (auto const & w : mLastWidgetsWithMouse) {
453 w,
454 MouseEvent::Type::Exited,
455 static_cast<MouseEvent::Button>(mouseInput.getButton()),
456 mouseInput.getX(),
457 mouseInput.getY(),
458 true,
459 true);
460 }
461 } else {
462 // The mouse is in the application window.
463
464 // Calculate which widgets should receive a mouse exited event
465 // and which should receive a mouse entered event by using the
466 // last known mouse position and the latest mouse position.
467 std::vector<Widget*> const mWidgetsWithMouse = getWidgetsAt(mouseInput.getX(), mouseInput.getY());
468 std::vector<Widget*> mWidgetsWithMouseExited;
469 std::vector<Widget*> mWidgetsWithMouseEntered;
470
471 // compute difference: last - current => exited
472 std::ranges::copy_if(
473 mLastWidgetsWithMouse,
474
475 std::back_inserter(mWidgetsWithMouseExited),
476 [&](Widget* w) {
477 return std::ranges::find(mWidgetsWithMouse, w) == mWidgetsWithMouse.end();
478 });
479 // compute difference: current - last => entered
480 std::ranges::copy_if(
481 mWidgetsWithMouse,
482
483 std::back_inserter(mWidgetsWithMouseEntered),
484 [&](Widget* w) {
485 return std::ranges::find(mLastWidgetsWithMouse, w) == mLastWidgetsWithMouse.end();
486 });
487
488 for (auto const & w : mWidgetsWithMouseExited) {
490 w,
491 MouseEvent::Type::Exited,
492 static_cast<MouseEvent::Button>(mouseInput.getButton()),
493 mouseInput.getX(),
494 mouseInput.getY(),
495 true,
496 true);
497 // As the mouse has exited a widget we need
498 // to reset the click count and the last mouse
499 // press time stamp.
500 mClickCount = 1;
502 }
503 for (auto const & widget : mWidgetsWithMouseEntered) {
504 // If a widget has modal mouse input focus we
505 // only want to send entered events to that widget
506 // and the widget's parents.
507 if (!mFocusHandler->hasModalFocus() || widget->isUnderMouseModal()) {
509 widget,
510 MouseEvent::Type::Entered,
511 static_cast<MouseEvent::Button>(mouseInput.getButton()),
512 mouseInput.getX(),
513 mouseInput.getY(),
514 true,
515 true);
516 }
517 }
518 }
519
520 if (mFocusHandler->getDraggedWidget() != nullptr) {
522 mFocusHandler->getDraggedWidget(),
523 MouseEvent::Type::Dragged,
525 mouseInput.getX(),
526 mouseInput.getY());
527 } else {
528 Widget* sourceWidget = getMouseEventSource(mouseInput.getX(), mouseInput.getY());
530 sourceWidget,
531 MouseEvent::Type::Moved,
532 static_cast<MouseEvent::Button>(mouseInput.getButton()),
533 mouseInput.getX(),
534 mouseInput.getY());
535 }
536 }
537
538 void Gui::handleMousePressed(MouseInput const & mouseInput)
539 {
540 Widget* sourceWidget = getMouseEventSource(mouseInput.getX(), mouseInput.getY());
541
542 if (mFocusHandler->getDraggedWidget() != nullptr) {
543 sourceWidget = mFocusHandler->getDraggedWidget();
544 }
545
546 int sourceWidgetX = 0;
547 int sourceWidgetY = 0;
548 sourceWidget->getAbsolutePosition(sourceWidgetX, sourceWidgetY);
549
550 if (mouseInput.getTimeStamp() - mLastMousePressTimeStamp < 250 &&
551 mLastMousePressButton == static_cast<unsigned int>(mouseInput.getButton())) {
552 mClickCount++;
553 } else {
554 mClickCount = 1;
555 }
556
558 sourceWidget,
559 MouseEvent::Type::Pressed,
560 static_cast<MouseEvent::Button>(mouseInput.getButton()),
561 mouseInput.getX(),
562 mouseInput.getY());
563
564 if (!mFocusHandler->hasModalFocus() || sourceWidget->isModalFocused()) {
565 sourceWidget->requestFocus();
566 }
567
568 mFocusHandler->setLastWidgetPressed(sourceWidget);
569
570 mFocusHandler->setDraggedWidget(sourceWidget);
571 mLastMouseDragButton = static_cast<unsigned int>(mouseInput.getButton());
572
573 mLastMousePressButton = static_cast<unsigned int>(mouseInput.getButton());
575 }
576
578 {
579 Widget* sourceWidget = getMouseEventSource(mouseInput.getX(), mouseInput.getY());
580
581 if (mFocusHandler->getDraggedWidget() != nullptr) {
582 sourceWidget = mFocusHandler->getDraggedWidget();
583 }
584
585 int sourceWidgetX = 0;
586 int sourceWidgetY = 0;
587 sourceWidget->getAbsolutePosition(sourceWidgetX, sourceWidgetY);
588
590 sourceWidget,
591 MouseEvent::Type::WheelMovedDown,
592 static_cast<MouseEvent::Button>(mouseInput.getButton()),
593 mouseInput.getX(),
594 mouseInput.getY());
595 }
596
598 {
599 Widget* sourceWidget = getMouseEventSource(mouseInput.getX(), mouseInput.getY());
600
601 if (mFocusHandler->getDraggedWidget() != nullptr) {
602 sourceWidget = mFocusHandler->getDraggedWidget();
603 }
604
605 int sourceWidgetX = 0;
606 int sourceWidgetY = 0;
607 sourceWidget->getAbsolutePosition(sourceWidgetX, sourceWidgetY);
608
610 sourceWidget,
611 MouseEvent::Type::WheelMovedUp,
612 static_cast<MouseEvent::Button>(mouseInput.getButton()),
613 mouseInput.getX(),
614 mouseInput.getY());
615 }
616
618 {
619 Widget* sourceWidget = getMouseEventSource(mouseInput.getX(), mouseInput.getY());
620
621 if (mFocusHandler->getDraggedWidget() != nullptr) {
622 sourceWidget = mFocusHandler->getDraggedWidget();
623 }
624
625 int sourceWidgetX = 0;
626 int sourceWidgetY = 0;
627 sourceWidget->getAbsolutePosition(sourceWidgetX, sourceWidgetY);
628
630 sourceWidget,
631 MouseEvent::Type::WheelMovedRight,
632 static_cast<MouseEvent::Button>(mouseInput.getButton()),
633 mouseInput.getX(),
634 mouseInput.getY());
635 }
636
638 {
639 Widget* sourceWidget = getMouseEventSource(mouseInput.getX(), mouseInput.getY());
640
641 if (mFocusHandler->getDraggedWidget() != nullptr) {
642 sourceWidget = mFocusHandler->getDraggedWidget();
643 }
644
645 int sourceWidgetX = 0;
646 int sourceWidgetY = 0;
647 sourceWidget->getAbsolutePosition(sourceWidgetX, sourceWidgetY);
648
650 sourceWidget,
651 MouseEvent::Type::WheelMovedLeft,
652 static_cast<MouseEvent::Button>(mouseInput.getButton()),
653 mouseInput.getX(),
654 mouseInput.getY());
655 }
656
657 void Gui::handleMouseReleased(MouseInput const & mouseInput)
658 {
659 Widget* sourceWidget = getMouseEventSource(mouseInput.getX(), mouseInput.getY());
660
661 if (mFocusHandler->getDraggedWidget() != nullptr) {
662 if (sourceWidget != mFocusHandler->getLastWidgetPressed()) {
663 mFocusHandler->setLastWidgetPressed(nullptr);
664 }
665
666 sourceWidget = mFocusHandler->getDraggedWidget();
667 }
668
669 int sourceWidgetX = 0;
670 int sourceWidgetY = 0;
671 sourceWidget->getAbsolutePosition(sourceWidgetX, sourceWidgetY);
672
674 sourceWidget,
675 MouseEvent::Type::Released,
676 static_cast<MouseEvent::Button>(mouseInput.getButton()),
677 mouseInput.getX(),
678 mouseInput.getY());
679
680 if (static_cast<unsigned int>(mouseInput.getButton()) == mLastMousePressButton &&
681 mFocusHandler->getLastWidgetPressed() == sourceWidget) {
683 sourceWidget,
684 MouseEvent::Type::Clicked,
685 static_cast<MouseEvent::Button>(mouseInput.getButton()),
686 mouseInput.getX(),
687 mouseInput.getY());
688
689 mFocusHandler->setLastWidgetPressed(nullptr);
690 } else {
692 mClickCount = 0;
693 }
694
695 if (mFocusHandler->getDraggedWidget() != nullptr) {
696 mFocusHandler->setDraggedWidget(nullptr);
697 }
698 }
699
700 Widget* Gui::getWidgetAt(int x, int y, Widget* exclude)
701 {
702 assert("Top widget must be set" && mTop != nullptr);
703
704 // If the widget's parent has no child then we have found the widget..
705 Widget* parent = mTop;
706 Widget* child = mTop;
707
708 while (child != nullptr) {
709 Widget* swap = child;
710 int parentX = 0;
711 int parentY = 0;
712 parent->getAbsolutePosition(parentX, parentY);
713 child = parent->getWidgetAt(x - parentX, y - parentY, exclude);
714 parent = swap;
715 }
716
717 return parent;
718 }
719
720 std::vector<Widget*> Gui::getWidgetsAt(int x, int y)
721 {
722 assert("Top widget must be set" && mTop != nullptr);
723
724 std::vector<Widget*> result;
725
726 Widget* widget = mTop;
727
728 while (widget != nullptr) {
729 result.push_back(widget);
730 int absoluteX = 0;
731 int absoluteY = 0;
732 widget->getAbsolutePosition(absoluteX, absoluteY);
733 widget = widget->getWidgetAt(x - absoluteX, y - absoluteY);
734 }
735
736 return result;
737 }
738
739 // =========================================================================
740 // Helper functions for mouse event source routing
741 // =========================================================================
742
743 namespace
744 {
749 bool isDescendantOf(fcn::Widget const * child, fcn::Widget const * ancestor) noexcept
750 {
751 if (child == nullptr || ancestor == nullptr) {
752 return false;
753 }
754
755 fcn::Widget const * current = child;
756 while (current != nullptr) {
757 if (current == ancestor) {
758 return true;
759 }
760 current = current->getParent();
761 }
762 return false;
763 }
764
770 fcn::Widget* getMouseCapture()
771 {
773 }
774 } // anonymous namespace
775
776 // =========================================================================
777 // Public API for explicit mouse capture (can be called from application code)
778 // =========================================================================
779
781 {
782 if (widget != nullptr) {
783 widget->captureMouse();
784 }
785 }
786
788 {
789 if (widget != nullptr) {
790 widget->releaseMouse();
791 }
792 }
793
794 // =========================================================================
795 // Gui::getMouseEventSource - Main implementation
796 // =========================================================================
797
799 {
800 Widget* leaf = getWidgetAt(x, y);
801 if (leaf == nullptr) {
802 return nullptr;
803 }
804
805 // 1. Check explicit capture first (platform-level) - HIGHEST PRECEDENCE
806 if (Widget* explicitCapture = getMouseCapture()) {
807 if (isDescendantOf(leaf, explicitCapture)) {
808 return leaf;
809 }
810 return explicitCapture;
811 }
812
813 // 2. Fallback to modal stack's mouse owner
814 Widget* modalRoot = mFocusHandler->getMouseCaptureOwner();
815 if (modalRoot == nullptr) {
816 return leaf;
817 }
818
819 // 3. If leaf is inside modal tree, let it receive events
820 if (isDescendantOf(leaf, modalRoot)) {
821 return leaf;
822 }
823
824 // 4. Outside modal tree -> block interaction by returning modal root
825 return modalRoot;
826 }
827
829 {
830 Widget* widget = mFocusHandler->getFocused();
831
832 while (widget->_getInternalFocusHandler() != nullptr &&
833 widget->_getInternalFocusHandler()->getFocused() != nullptr) {
834 widget = widget->_getInternalFocusHandler()->getFocused();
835 }
836
837 return widget;
838 }
839
841 Widget* source,
842 MouseEvent::Type type,
843 MouseEvent::Button button,
844 int x,
845 int y,
846 bool force,
847 bool /*toSourceOnly*/)
848 {
849 Widget* parent = source;
850 Widget* widget = source;
851
852 Widget const * modalFocus = mFocusHandler->getFocusOwner();
853 if (modalFocus != nullptr && !widget->isModalFocused() && !force) {
854 return;
855 }
856
857 Widget const * modalMouse = mFocusHandler->getMouseCaptureOwner();
858 if (modalMouse != nullptr && !widget->isUnderMouseModal() && !force) {
859 return;
860 }
861
862 MouseEvent mouseEvent(
863 source, source, mShiftPressed, mControlPressed, mAltPressed, mMetaPressed, type, button, x, y, mClickCount);
864
865 while (parent != nullptr) {
866 // If the widget has been removed due to input
867 // cancel the distribution.
868 if (!Widget::widgetExists(widget)) {
869 break;
870 }
871
872 parent = widget->getParent();
873
874 if (widget->isEnabled() || force) {
875 int widgetX = 0;
876 int widgetY = 0;
877 widget->getAbsolutePosition(widgetX, widgetY);
878
879 mouseEvent.mX = x - widgetX;
880 mouseEvent.mY = y - widgetY;
881 mouseEvent.mDistributor = widget;
882 std::list<MouseListener*> mouseListeners = widget->_getMouseListeners();
883
884 // Send the event to all mouse listeners of the widget.
885 for (auto& mouseListener : mouseListeners) {
886 switch (mouseEvent.getType()) {
887 case MouseEvent::Type::Entered:
888 mouseListener->mouseEntered(mouseEvent);
889 break;
890 case MouseEvent::Type::Exited:
891 mouseListener->mouseExited(mouseEvent);
892 break;
893 case MouseEvent::Type::Moved:
894 mouseListener->mouseMoved(mouseEvent);
895 break;
896 case MouseEvent::Type::Pressed:
897 mouseListener->mousePressed(mouseEvent);
898 break;
899 case MouseEvent::Type::Released:
900 mouseListener->mouseReleased(mouseEvent);
901 break;
902 case MouseEvent::Type::WheelMovedUp:
903 mouseListener->mouseWheelMovedUp(mouseEvent);
904 break;
905 case MouseEvent::Type::WheelMovedDown:
906 mouseListener->mouseWheelMovedDown(mouseEvent);
907 break;
908 case MouseEvent::Type::WheelMovedRight:
909 mouseListener->mouseWheelMovedRight(mouseEvent);
910 break;
911 case MouseEvent::Type::WheelMovedLeft:
912 mouseListener->mouseWheelMovedLeft(mouseEvent);
913 break;
914 case MouseEvent::Type::Dragged:
915 mouseListener->mouseDragged(mouseEvent);
916 break;
917 case MouseEvent::Type::Clicked:
918 mouseListener->mouseClicked(mouseEvent);
919 break;
920 }
921 }
922 }
923
924 Widget const * swap = widget;
925 widget = parent;
926 parent = swap->getParent();
927
928 // If a non-modal focused widget has been reached
929 // and we have modal focus cancel the distribution.
930 Widget const * modalFocusLoop = mFocusHandler->getFocusOwner();
931 if (modalFocusLoop != nullptr && widget != nullptr && !widget->isModalFocused()) {
932 break;
933 }
934
935 // If a non-modal mouse input focused widget has been reached
936 // and we have modal mouse input focus cancel the distribution.
937 Widget const * modalMouseLoop = mFocusHandler->getMouseCaptureOwner();
938 if (modalMouseLoop != nullptr && widget != nullptr && !widget->isUnderMouseModal()) {
939 break;
940 }
941 }
942 }
943
945 {
946 Widget* parent = keyEvent.getSource();
947 Widget* widget = keyEvent.getSource();
948
949 Widget const * modalFocus = mFocusHandler->getFocusOwner();
950 if (modalFocus != nullptr && !widget->isModalFocused()) {
951 return;
952 }
953
954 while (parent != nullptr) {
955 // If the widget has been removed due to input
956 // cancel the distribution.
957 if (!Widget::widgetExists(widget)) {
958 break;
959 }
960
961 parent = widget->getParent();
962
963 if (widget->isEnabled()) {
964 keyEvent.mDistributor = widget;
965 std::list<KeyListener*> keyListeners = widget->_getKeyListeners();
966
967 // Send the event to all key listeners of the source widget.
968 for (auto& keyListener : keyListeners) {
969 switch (keyEvent.getType()) {
970 case KeyEvent::Type::Pressed:
971 keyListener->keyPressed(keyEvent);
972 break;
973 case KeyEvent::Type::Released:
974 keyListener->keyReleased(keyEvent);
975 break;
976 default:
977 throwException("Unknown key event type.");
978 }
979 }
980 }
981
982 Widget const * swap = widget;
983 widget = parent;
984 parent = swap->getParent();
985
986 // If a non-modal focused widget has been reached
987 // and we have modal focus cancel the distribution.
988 Widget const * modalFocusLoop = mFocusHandler->getFocusOwner();
989 if (modalFocusLoop != nullptr && !widget->isModalFocused()) {
990 break;
991 }
992 }
993 }
994
996 {
998
999 for (it = mKeyListeners.begin(); it != mKeyListeners.end(); ++it) {
1000 switch (keyEvent.getType()) {
1001 case KeyEvent::Type::Pressed:
1002 (*it)->keyPressed(keyEvent);
1003 break;
1004 case KeyEvent::Type::Released:
1005 (*it)->keyReleased(keyEvent);
1006 break;
1007 default:
1008 throwException("Unknown key event type.");
1009 }
1010
1011 if (keyEvent.isConsumed()) {
1012 break;
1013 }
1014 }
1015 }
1016
1018 {
1019 // Check if modal mouse input focus has been gained by a widget.
1020 if ((mFocusHandler->getLastWidgetWithModalMouseInputFocus() != mFocusHandler->getMouseCaptureOwner()) &&
1021 (mFocusHandler->getLastWidgetWithModalMouseInputFocus() == nullptr)) {
1023 } else if (
1024 // Check if modal mouse input focus has been released.
1025 (mFocusHandler->getLastWidgetWithModalMouseInputFocus() != mFocusHandler->getMouseCaptureOwner()) &&
1026 (mFocusHandler->getLastWidgetWithModalMouseInputFocus() != nullptr)) {
1028 }
1029 }
1030
1032 {
1033 // Check if modal focus has been gained by a widget.
1034 if ((mFocusHandler->getLastWidgetWithModalFocus() != mFocusHandler->getFocusOwner()) &&
1035 (mFocusHandler->getLastWidgetWithModalFocus() == nullptr)) {
1037 } else if (
1038 // Check if modal focus has been released.
1039 (mFocusHandler->getLastWidgetWithModalFocus() != mFocusHandler->getFocusOwner()) &&
1040 (mFocusHandler->getLastWidgetWithModalFocus() != nullptr)) {
1042 }
1043 }
1044
1046 {
1047 // Get all widgets at the last known mouse position
1048 // and send them a mouse exited event.
1049 std::vector<Widget*> const mWidgetsWithMouse = getWidgetsAt(mLastMouseX, mLastMouseY);
1050
1051 for (auto const & w : mWidgetsWithMouse) {
1052 if (w->isModalFocused() || w->isUnderMouseModal()) {
1053 continue;
1054 }
1056 w,
1057 MouseEvent::Type::Exited,
1061 true,
1062 true);
1063 }
1064 mFocusHandler->setLastWidgetWithModalFocus(mFocusHandler->getFocusOwner());
1065 }
1066
1068 {
1069 // Get all widgets at the last known mouse position
1070 // and send them a mouse entered event.
1071 std::vector<Widget*> const mWidgetsWithMouse = getWidgetsAt(mLastMouseX, mLastMouseY);
1072
1073 for (auto const & w : mWidgetsWithMouse) {
1075 w,
1076 MouseEvent::Type::Entered,
1080 false,
1081 true);
1082 }
1083 mFocusHandler->setLastWidgetWithModalFocus(nullptr);
1084 }
1085
1087 {
1088 // Get all widgets at the last known mouse position
1089 // and send them a mouse exited event.
1090 std::vector<Widget*> const mWidgetsWithMouse = getWidgetsAt(mLastMouseX, mLastMouseY);
1091
1092 for (auto const & w : mWidgetsWithMouse) {
1093 if (w->isUnderMouseModal()) {
1094 continue;
1095 }
1097 w,
1098 MouseEvent::Type::Exited,
1102 true,
1103 true);
1104 }
1105 mFocusHandler->setLastWidgetWithModalMouseInputFocus(mFocusHandler->getMouseCaptureOwner());
1106 }
1107
1109 {
1110 // Get all widgets at the last known mouse position
1111 // and send them a mouse entered event.
1112 std::vector<Widget*> const mWidgetsWithMouse = getWidgetsAt(mLastMouseX, mLastMouseY);
1113
1114 for (auto const & w : mWidgetsWithMouse) {
1116 w,
1117 MouseEvent::Type::Entered,
1121 false,
1122 true);
1123 }
1124 mFocusHandler->setLastWidgetWithModalMouseInputFocus(nullptr);
1125 }
1126
1128 {
1129 // process snapshot of hidden widgets and clear the vector
1130 std::vector<Widget*> const pending = std::move(mHiddenWidgets);
1131 mHiddenWidgets.clear();
1132
1133 for (auto* const hiddenWidget : pending) {
1134 // make sure that the widget wasn't freed after hiding
1135 if (Widget::widgetExists(hiddenWidget) && hiddenWidget->isEnabled()) {
1136 int hiddenWidgetX = 0;
1137 int hiddenWidgetY = 0;
1138 hiddenWidget->getAbsolutePosition(hiddenWidgetX, hiddenWidgetY);
1139
1140 Rectangle const r(hiddenWidgetX, hiddenWidgetY, hiddenWidget->getWidth(), hiddenWidget->getHeight());
1141
1143 // get the widget that has the cursor now and distribute that the mouse entered it
1144 Widget* underMouseCursor = getWidgetAt(mLastMouseX, mLastMouseY);
1145
1147 underMouseCursor,
1148 MouseEvent::Type::Entered,
1149 MouseEvent::Button::Empty,
1152 true,
1153 true);
1154 }
1155 }
1156 }
1157 }
1158
1160 {
1161 // process snapshot of shown widgets and clear the vector
1162 std::vector<Widget*> const pending = std::move(mShownWidgets);
1163 mShownWidgets.clear();
1164
1165 for (auto* shownWidget : pending) {
1166 // if the shown widget has the mouse cursor inside it
1167 int shownWidgetX = 0;
1168 int shownWidgetY = 0;
1169 shownWidget->getAbsolutePosition(shownWidgetX, shownWidgetY);
1170
1171 Rectangle const r(shownWidgetX, shownWidgetY, shownWidget->getWidth(), shownWidget->getHeight());
1172
1173 if (r.isContaining(mLastMouseX, mLastMouseY) && shownWidget->isEnabled()) {
1174 // find which widget had the mouse before and distribute that the mouse exited it
1175 Widget* underMouseCursorBefore = getWidgetAt(mLastMouseX, mLastMouseY, shownWidget);
1176
1178 underMouseCursorBefore,
1179 MouseEvent::Type::Exited,
1180 MouseEvent::Button::Empty,
1183 true,
1184 true);
1185
1186 // find which specific widget in the shown widget had the mouse before
1187 // and distribute that the mouse exited it
1188 Widget* underMouseCursorNow = getWidgetAt(mLastMouseX, mLastMouseY);
1189
1191 underMouseCursorNow,
1192 MouseEvent::Type::Entered,
1193 MouseEvent::Button::Empty,
1196 true,
1197 true);
1198 }
1199 }
1200 }
1201} // namespace fcn
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
Manages focus navigation and assignment among widgets within a Gui instance.
virtual Widget * getFocused() const
Gets the widget with focus.
Abstract interface providing primitive drawing functions (lines, rectangles, etc.).
Definition graphics.hpp:58
Internal listener that forwards death notifications from widgets to the owning Gui so it can clean up...
Definition gui.cpp:46
GuiDeathListener(Gui *gui)
Construct a GuiDeathListener bound to a Gui instance.
Definition gui.cpp:49
void death(Event const &event) override
Called when a widget dies.
Definition gui.cpp:54
The central GUI manager.
Definition gui.hpp:108
Graphics * mGraphics
Holds the graphics implementation used.
Definition gui.hpp:578
virtual void distributeKeyEvent(KeyEvent &keyEvent)
Distributes a key event.
Definition gui.cpp:944
Widget * mTop
Holds the top widget.
Definition gui.hpp:563
Input * mInput
Holds the input implementation used.
Definition gui.hpp:583
virtual void distributeKeyEventToGlobalKeyListeners(KeyEvent &keyEvent)
Distributes a key event to the global key listeners.
Definition gui.cpp:995
virtual Graphics * getGraphics() const
Gets the graphics object used for drawing.
Definition gui.cpp:167
virtual Input * getInput() const
Gets the input object being used for input handling.
Definition gui.cpp:188
virtual void handleModalMouseInputFocus()
Handles modal mouse input focus.
Definition gui.cpp:1017
virtual void focusNone()
Focuses none of the widgets in the GUI.
Definition gui.cpp:268
Widget * getWidgetAt(int x, int y)
Gets the widget at a certain position.
Definition gui.hpp:506
FocusHandler * mFocusHandler
Holds the focus handler for the GUI.
Definition gui.hpp:588
void addHiddenWidget(Widget *widget)
Inform gui that a widget was hidden.
Definition gui.cpp:293
virtual void setRoot(Widget *top)
Alias for setTop.
Definition gui.cpp:136
KeyListenerList mKeyListeners
Holds the global key listeners of the GUI.
Definition gui.hpp:609
bool mTabbing
True if tabbing is enabled, false otherwise.
Definition gui.hpp:598
virtual void draw()
Draws the GUI.
Definition gui.cpp:246
int mLastMouseDragButton
Holds the last button used when a drag of a widget was initiated.
Definition gui.hpp:662
virtual void handleModalFocus()
Handles modal focus.
Definition gui.cpp:1031
virtual FocusHandler * getFocusHandler() const
Returns the focus handler used by this GUI.
Definition gui.cpp:201
void distributeMouseEvent(Widget *source, MouseEvent::Type type, MouseEvent::Button button, int x, int y)
Convenience overload: distribute a mouse event forwarding to the full overload with force and toSourc...
Definition gui.hpp:449
int mLastMousePressTimeStamp
Holds the last mouse press time stamp.
Definition gui.hpp:639
virtual Widget * getMouseEventSource(int x, int y)
Gets the source of the mouse event.
Definition gui.cpp:798
unsigned int mLastMousePressButton
Holds the last mouse button pressed.
Definition gui.hpp:634
void releaseMouse(Widget *widget)
Releases explicit mouse capture from a widget.
Definition gui.cpp:787
virtual void handleMouseInput()
Handles all mouse input.
Definition gui.cpp:309
virtual void setGraphics(Graphics *graphics)
Sets the graphics object to use for drawing.
Definition gui.cpp:151
virtual void handleModalFocusReleased()
Handles modal focus released.
Definition gui.cpp:1067
bool mControlPressed
True if control is pressed, false otherwise.
Definition gui.hpp:624
virtual void handleTextInput()
Handles text input from the backend.
Definition gui.cpp:350
bool mMetaPressed
True if meta is pressed, false otherwise.
Definition gui.hpp:619
virtual void initialize(std::unique_ptr< Graphics > graphics, std::unique_ptr< Input > input, int width, int height)
Initializes GUI backends in one call.
Definition gui.cpp:193
std::unique_ptr< Widget > mOwnedTop
Optional owned top widget (when Gui takes ownership).
Definition gui.hpp:668
std::unique_ptr< Graphics > mOwnedGraphics
Optional owned graphics backend instance.
Definition gui.hpp:671
virtual Widget * getKeyEventSource()
Gets the source of the key event.
Definition gui.cpp:828
int mLastMouseX
Holds the last mouse x coordinate.
Definition gui.hpp:644
virtual void handleMouseWheelMovedRight(MouseInput const &mouseInput)
Handles mouse wheel moved right input.
Definition gui.cpp:617
virtual void setInput(Input *input)
Sets the input object to use for input handling.
Definition gui.cpp:172
virtual void setTabbingEnabled(bool tabbing)
Sets tabbing enabled, or not.
Definition gui.cpp:273
virtual void handleModalMouseInputFocusReleased()
Handles modal mouse input focus released.
Definition gui.cpp:1108
virtual Widget * getTop() const
Gets the top widget.
Definition gui.cpp:146
virtual void handleKeyInput()
Handles key input.
Definition gui.cpp:374
virtual void handleHiddenWidgets()
Handles hidden widgets.
Definition gui.cpp:1127
std::vector< Widget * > mShownWidgets
Holds shown widgets.
Definition gui.hpp:573
virtual std::vector< Widget * > getWidgetsAt(int x, int y)
Gets all widgets a certain coordinate in the GUI.
Definition gui.cpp:720
bool mAltPressed
True if alt is pressed, false otherwise.
Definition gui.hpp:629
std::vector< Widget * > mHiddenWidgets
Holds hidden widgets.
Definition gui.hpp:568
int mLastMouseY
Holds the last mouse y coordinate.
Definition gui.hpp:649
KeyListenerList::iterator KeyListenerListIterator
Iterator for KeyListenerList.
Definition gui.hpp:604
virtual void handleShownWidgets()
Handles shown widgets.
Definition gui.cpp:1159
GuiDeathListener * mDeathListener
Listener notified when the GUI or top widget is destroyed.
Definition gui.hpp:665
virtual void handleMouseWheelMovedDown(MouseInput const &mouseInput)
Handles mouse wheel moved down input.
Definition gui.cpp:577
virtual bool isTabbingEnabled()
Checks if tabbing is enabled.
Definition gui.cpp:278
std::unique_ptr< Input > mOwnedInput
Optional owned input backend instance.
Definition gui.hpp:674
virtual void removeGlobalKeyListener(KeyListener *keyListener)
Removes global key listener from the GUI.
Definition gui.cpp:288
std::shared_ptr< Font > mGlobalFont
Shared global font used by widgets when not overridden.
Definition gui.hpp:677
void addShownWidget(Widget *widget)
Inform gui that a widget was shown.
Definition gui.cpp:298
virtual void handleModalMouseInputFocusGained()
Handles modal mouse input focus gained.
Definition gui.cpp:1086
virtual void handleMouseMoved(MouseInput const &mouseInput)
Handles mouse moved input.
Definition gui.cpp:443
virtual void handleMouseWheelMovedUp(MouseInput const &mouseInput)
Handles mouse wheel moved up input.
Definition gui.cpp:597
virtual void addGlobalKeyListener(KeyListener *keyListener)
Adds a global key listener to the GUI.
Definition gui.cpp:283
virtual void setTop(Widget *top)
Sets the top widget.
Definition gui.cpp:112
bool mShiftPressed
True if shift is pressed, false otherwise.
Definition gui.hpp:614
virtual void setGlobalFont(std::string const &filename, int size)
Loads a font using the active graphics backend and sets it as global widget font.
Definition gui.cpp:206
virtual void handleModalFocusGained()
Handles modal focus gained.
Definition gui.cpp:1045
virtual void handleMouseWheelMovedLeft(MouseInput const &mouseInput)
Handles mouse wheel moved left input.
Definition gui.cpp:637
virtual void handleMousePressed(MouseInput const &mouseInput)
Handles mouse pressed input.
Definition gui.cpp:538
void widgetDied(Widget const *widget)
Inform gui that a widget was deleted.
Definition gui.cpp:303
void captureMouse(Widget *widget)
Explicitly captures mouse input to a widget.
Definition gui.cpp:780
virtual void logic()
Performs logic of the GUI.
Definition gui.cpp:221
int mClickCount
Holds the current click count.
Definition gui.hpp:655
VisibilityEventHandler * mVisibilityEventHandler
Holds the visibility event handler for the GUI.
Definition gui.hpp:593
virtual void handleMouseReleased(MouseInput const &mouseInput)
Handles mouse released input.
Definition gui.cpp:657
bool isConsumed() const
Checks if the input event is consumed.
Widget * mDistributor
Holds the distributor of the event.
Abstract interface for polling user input devices.
Definition input.hpp:37
Represents a key event.
Definition keyevent.hpp:26
Type getType() const
Gets the type of the event.
Definition keyevent.cpp:38
Type
Key event types.
Definition keyevent.hpp:32
Internal class representing raw keyboard input data.
Definition keyinput.hpp:32
bool isAltPressed() const
Checks if alt is pressed.
Definition keyinput.cpp:66
bool isNumericPad() const
Checks if the key was pressed at the numeric pad.
Definition keyinput.cpp:86
Type getType() const
Gets the type of the key input.
Definition keyinput.cpp:29
bool isControlPressed() const
Checks if control is pressed.
Definition keyinput.cpp:56
bool isMetaPressed() const
Checks if meta is pressed.
Definition keyinput.cpp:76
Key const & getKey() const
Gets the key of the key input.
Definition keyinput.cpp:41
bool isShiftPressed() const
Checks if shift is pressed.
Definition keyinput.cpp:46
Interface for listening to keyboard events.
Represents a mouse event.
MouseEvent::Type getType() const
Gets the type of the event.
Button
Mouse button types.
int mY
Holds the y-coordinate of the mouse event.
Type
Mouse event types.
int mX
Holds the x-coordinate of the mouse event.
Internal class representing raw mouse input data.
Type getType() const
Gets the type of the mouse input.
Button getButton() const
Gets the button pressed.
int getX() const
Gets the x coordinate of the mouse input.
int getY() const
Gets the y coordinate of the mouse input.
int getTimeStamp() const
Gets the time stamp of the input.
Represents a rectangular area (X, Y, Width, Height).
Definition rectangle.hpp:22
bool isContaining(int x, int y) const
Checks the rectangle contains a point.
Definition rectangle.cpp:63
Text input event for IME (input method editor) composition, dead keys, and pasted text.
@ Input
Text was input (SDL_EVENT_TEXT_INPUT).
Abstract base class defining the common behavior, properties, and lifecycle of all GUI elements.
Definition widget.hpp:56
static Widget * getMouseCapture()
Gets the widget that currently has mouse capture.
Definition widget.cpp:1629
virtual bool isModalFocused() const
Checks if the widget or it's parent has modal focus.
Definition widget.cpp:1102
virtual bool isUnderMouseModal() const
Checks if the widget or its parent is under a mouse modal.
Definition widget.cpp:1115
virtual Widget * getParent() const
Gets the widget's parent container.
Definition widget.cpp:239
virtual std::list< KeyListener * > const & _getKeyListeners()
Gets the key listeners of the widget.
Definition widget.cpp:1156
bool isEnabled() const
Checks if the widget is enabled.
Definition widget.cpp:1079
virtual void requestFocus()
Requests focus for the widget.
Definition widget.cpp:660
virtual FocusHandler * _getInternalFocusHandler()
Gets the internal focus handler used.
Definition widget.cpp:1171
virtual void textInput(TextInputEvent &event)
Called when text input (IME, dead-key, paste) is received.
Definition widget.hpp:479
Widget * getWidgetAt(int x, int y)
Gets a widget at a certain position in the widget.
Definition widget.hpp:1287
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:1037
static void _setGuiDeathListener(DeathListener *deathListener)
Set the global GUI death listener used to observe widget deletions.
Definition widget.cpp:853
virtual std::list< MouseListener * > const & _getMouseListeners()
Gets the mouse listeners of the widget.
Definition widget.cpp:1151
bool captureMouse()
Attempts to capture mouse input to this widget.
Definition widget.cpp:1611
static void _setVisibilityEventHandler(VisibilityEventHandler *visibilityEventHandler)
Sets the visibility event handler to be used.
Definition widget.cpp:843
static void setGlobalFont(Font *font)
Sets the global font to be used by default for all widgets.
Definition widget.cpp:1013
void releaseMouse()
Releases mouse capture from this widget, if it has capture.
Definition widget.cpp:1621
virtual void getAbsolutePosition(int &x, int &y) const
Gets the absolute position on the screen for the widget.
Definition widget.cpp:978
virtual void _setFocusHandler(FocusHandler *focusHandler)
Sets the focus handler to be used.
Definition widget.cpp:807
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.