FifeGUI 0.3.0
A C++ GUI library designed for games.
focushandler.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/focushandler.hpp"
7
8// Standard library includes
9#include <algorithm>
10#include <iostream>
11#include <list>
12#include <ranges>
13#include <utility>
14
15// Platform config include
16#include "fifechan/platform.hpp"
17
18// Project headers (subdirs before local)
19#include "fifechan/events/event.hpp"
20#include "fifechan/exception.hpp"
21#include "fifechan/listeners/focuslistener.hpp"
22#include "fifechan/widget.hpp"
23
24namespace fcn
25{
26 FocusHandler::FocusHandler() :
27 mFocusedWidget(nullptr),
28 mDraggedWidget(nullptr),
29 mLastWidgetWithMouse(nullptr),
30 mLastWidgetWithModalFocus(nullptr),
31 mLastWidgetWithModalMouseInputFocus(nullptr),
32 mLastWidgetPressed(nullptr)
33 {
34 }
35
37 {
38 if (widget == nullptr || widget == mFocusedWidget) {
39 return;
40 }
41
42 auto itFoundWidget = std::ranges::find(mWidgets, widget);
43
44 if (itFoundWidget == mWidgets.end()) {
45 throwException("Trying to focus a non-existing widget.");
46 }
47
48 Widget* oldFocused = mFocusedWidget;
49
50 if (oldFocused != widget) {
51 setFocusedWidget(*itFoundWidget);
52
53 if (oldFocused != nullptr) {
54 Event const focusEvent(oldFocused);
55 distributeFocusLostEvent(focusEvent);
56 }
57
58 Event const focusEvent(*itFoundWidget);
60 }
61 }
62
64 {
65 return !mModalStack.empty();
66 }
67
68 void FocusHandler::pushModal(Widget* focusOwner, Widget* mouseOwner)
69 {
70 if (focusOwner == nullptr && mouseOwner == nullptr) {
71 return;
72 }
73
74 // Capture the mouseOwner
75 // release existing mouse capture, if it conflicts
76 if (Widget* existingCapture = Widget::getMouseCapture()) {
77 if (mouseOwner != nullptr && !mouseOwner->isDescendantOf(existingCapture)) {
78 existingCapture->releaseMouse();
79 }
80 }
81
82 ModalState state{};
83 state.focusOwner = focusOwner;
84 state.mouseOwner = mouseOwner;
85 mModalStack.push_back(state);
86
87 if (focusOwner != nullptr && mFocusedWidget != nullptr && !mFocusedWidget->isModalFocused()) {
88 focusNone();
89 }
90 }
91
92 void FocusHandler::popModal() noexcept
93 {
94 if (mModalStack.empty()) {
95 return;
96 }
97
98 mModalStack.pop_back();
99
100 Widget* prevFocus = nullptr;
101 if (!mModalStack.empty() && (mModalStack.back().focusOwner != nullptr)) {
102 prevFocus = mModalStack.back().focusOwner;
103 }
104
105 if (prevFocus != nullptr) {
106 try {
107 requestFocus(prevFocus);
108 } catch (...) {
109 std::cerr << "[FocusHandler] Warning: focus request failed in noexcept context\n";
110 }
111 }
112 }
113
115 {
116 mModalStack.clear();
117 }
118
120 {
121 if (widget == nullptr || mFocusedWidget != widget) {
122 return;
123 }
124
125 Widget* focused = mFocusedWidget;
126 mFocusedWidget = nullptr;
127
128 Event const focusEvent(focused);
129 distributeFocusLostEvent(focusEvent);
130 }
131
133 {
134 if (mFocusedWidget == widget) {
135 return;
136 }
137
138 Widget* old = mFocusedWidget;
139
140 mFocusedWidget = widget;
141
142 if (old != nullptr) {
143 old->setFocused(false);
144 }
145
146 if (widget != nullptr) {
147 widget->setFocused(true);
148 }
149 }
150
152 {
153 return mFocusedWidget;
154 }
155
157 {
158 if (mModalStack.empty()) {
159 return nullptr;
160 }
161
162 return mModalStack.back().focusOwner;
163 }
164
166 {
167 if (mModalStack.empty()) {
168 return nullptr;
169 }
170
171 return mModalStack.back().mouseOwner;
172 }
173
175 {
176 int i = 0;
177 int focusedWidget = -1;
178
179 for (i = 0; std::cmp_less(i, mWidgets.size()); ++i) {
180 if (mWidgets.at(i) == mFocusedWidget) {
181 focusedWidget = i;
182 }
183 }
184
185 int const focused = focusedWidget;
186
187 // i is a counter that ensures that the following loop
188 // won't get stuck in an infinite loop
189 int num_widgets = static_cast<int>(mWidgets.size());
190
191 for (; num_widgets > 0; --num_widgets) {
192 ++focusedWidget;
193
194 if (std::cmp_greater_equal(focusedWidget, mWidgets.size())) {
195 focusedWidget = 0;
196 }
197
198 if (focusedWidget == focused) {
199 return;
200 }
201
202 if (mWidgets.at(focusedWidget)->isFocusable()) {
203 break;
204 }
205
206 if (num_widgets == 1) {
207 focusedWidget = -1;
208 break;
209 }
210 }
211
212 if (focusedWidget >= 0) {
213 mFocusedWidget = mWidgets.at(focusedWidget);
214 Event const focusEvent(mFocusedWidget);
215 distributeFocusGainedEvent(focusEvent);
216 }
217
218 if (focused >= 0) {
219 Event const focusEvent(mWidgets.at(focused));
220 distributeFocusLostEvent(focusEvent);
221 }
222 }
223
225 {
226 if (mWidgets.empty()) {
227 mFocusedWidget = nullptr;
228 return;
229 }
230
231 int i = 0;
232 int focusedWidget = -1;
233 for (i = 0; std::cmp_less(i, mWidgets.size()); ++i) {
234 if (mWidgets.at(i) == mFocusedWidget) {
235 focusedWidget = i;
236 }
237 }
238 int const focused = focusedWidget;
239
240 // i is a counter that ensures that the following loop
241 // won't get stuck in an infinite loop
242 i = static_cast<int>(mWidgets.size());
243 while (i > 0) {
244 --focusedWidget;
245
246 if (i == 1) {
247 focusedWidget = -1;
248 break;
249 }
250
251 --i;
252
253 if (focusedWidget < 0) {
254 focusedWidget = mWidgets.size() - 1;
255 }
256
257 if (focusedWidget == focused) {
258 return;
259 }
260
261 if (mWidgets.at(focusedWidget)->isFocusable()) {
262 break;
263 }
264 }
265
266 if (focusedWidget >= 0) {
267 mFocusedWidget = mWidgets.at(focusedWidget);
268 Event const focusEvent(mFocusedWidget);
269 distributeFocusGainedEvent(focusEvent);
270 }
271
272 if (focused >= 0) {
273 Event const focusEvent(mWidgets.at(focused));
274 distributeFocusLostEvent(focusEvent);
275 }
276 }
277
278 bool FocusHandler::isFocused(Widget const * widget) const
279 {
280 return mFocusedWidget == widget;
281 }
282
284 {
285 mWidgets.push_back(widget);
286 }
287
289 {
290 if (isFocused(widget)) {
291 mFocusedWidget = nullptr;
292 }
293
294 auto iter = std::ranges::find(mWidgets, widget);
295 if (iter != std::ranges::end(mWidgets)) {
296 mWidgets.erase(iter);
297 }
298
299 if (mDraggedWidget == widget) {
300 mDraggedWidget = nullptr;
301 return;
302 }
303
304 if (mLastWidgetWithMouse == widget) {
305 mLastWidgetWithMouse = nullptr;
306 return;
307 }
308
309 if (mLastWidgetWithModalFocus == widget) {
311 return;
312 }
313
316 return;
317 }
318
319 if (mLastWidgetPressed == widget) {
320 mLastWidgetPressed = nullptr;
321 return;
322 }
323 }
324
326 {
327 if (mFocusedWidget != nullptr) {
328 Widget* focused = mFocusedWidget;
329 mFocusedWidget = nullptr;
330
331 Event const focusEvent(focused);
332 distributeFocusLostEvent(focusEvent);
333 }
334 }
335
337 {
338 if (mFocusedWidget != nullptr) {
339 if (!mFocusedWidget->isTabOutEnabled()) {
340 return;
341 }
342 }
343
344 if (mWidgets.empty()) {
345 mFocusedWidget = nullptr;
346 return;
347 }
348
349 int i = 0;
350 int focusedWidget = -1;
351 for (i = 0; std::cmp_less(i, mWidgets.size()); ++i) {
352 if (mWidgets.at(i) == mFocusedWidget) {
353 focusedWidget = i;
354 }
355 }
356 int const focused = focusedWidget;
357
358 i = static_cast<int>(mWidgets.size());
359
360 while (i > 0) {
361 ++focusedWidget;
362
363 if (i == 1) {
364 focusedWidget = -1;
365 break;
366 }
367
368 --i;
369
370 if (std::cmp_greater_equal(focusedWidget, mWidgets.size())) {
371 focusedWidget = 0;
372 }
373
374 if (focusedWidget == focused) {
375 return;
376 }
377
378 if (mWidgets.at(focusedWidget)->isFocusable() && mWidgets.at(focusedWidget)->isTabInEnabled() &&
379 (getFocusOwner()) == nullptr ||
380 mWidgets.at(focusedWidget)->isModalFocused()) {
381 break;
382 }
383 }
384
385 if (focusedWidget >= 0) {
386 mFocusedWidget = mWidgets.at(focusedWidget);
387 Event const focusEvent(mFocusedWidget);
388 distributeFocusGainedEvent(focusEvent);
389 }
390
391 if (focused >= 0) {
392 Event const focusEvent(mWidgets.at(focused));
393 distributeFocusLostEvent(focusEvent);
394 }
395 }
396
398 {
399 if (mFocusedWidget != nullptr) {
400 if (!mFocusedWidget->isTabOutEnabled()) {
401 return;
402 }
403 }
404
405 if (mWidgets.empty()) {
406 mFocusedWidget = nullptr;
407 return;
408 }
409
410 int i = 0;
411 int focusedWidget = -1;
412 for (i = 0; std::cmp_less(i, mWidgets.size()); ++i) {
413 if (mWidgets.at(i) == mFocusedWidget) {
414 focusedWidget = i;
415 }
416 }
417 int const focused = focusedWidget;
418
419 i = static_cast<int>(mWidgets.size());
420
421 while (i > 0) {
422 --focusedWidget;
423
424 if (i == 1) {
425 focusedWidget = -1;
426 break;
427 }
428
429 --i;
430
431 if (focusedWidget <= 0) {
432 focusedWidget = mWidgets.size() - 1;
433 }
434
435 if (focusedWidget == focused) {
436 return;
437 }
438
439 if (mWidgets.at(focusedWidget)->isFocusable() && mWidgets.at(focusedWidget)->isTabInEnabled() &&
440 (getFocusOwner() == nullptr || mWidgets.at(focusedWidget)->isModalFocused())) {
441 break;
442 }
443 }
444
445 if (focusedWidget >= 0) {
446 mFocusedWidget = mWidgets.at(focusedWidget);
447 Event const focusEvent(mFocusedWidget);
448 distributeFocusGainedEvent(focusEvent);
449 }
450
451 if (focused >= 0) {
452 Event const focusEvent(mWidgets.at(focused));
453 distributeFocusLostEvent(focusEvent);
454 }
455 }
456
458 {
459 Widget* sourceWidget = focusEvent.getSource();
460
461 std::list<FocusListener*> focusListeners = sourceWidget->_getFocusListeners();
462
463 // Send the event to all focus listeners of the widget.
464 for (auto& focusListener : focusListeners) {
465 focusListener->focusLost(focusEvent);
466 }
467 }
468
470 {
471 Widget* sourceWidget = focusEvent.getSource();
472
473 std::list<FocusListener*> focusListeners = sourceWidget->_getFocusListeners();
474
475 // Send the event to all focus listeners of the widget.
476 for (auto& focusListener : focusListeners) {
477 focusListener->focusGained(focusEvent);
478 }
479 }
480
485
487 {
488 mDraggedWidget = draggedWidget;
489 }
490
495
497 {
498 mLastWidgetWithMouse = lastWidgetWithMouse;
499 }
500
505
506 void FocusHandler::setLastWidgetWithModalFocus(Widget* lastWidgetWithModalFocus)
507 {
508 mLastWidgetWithModalFocus = lastWidgetWithModalFocus;
509 }
510
515
516 void FocusHandler::setLastWidgetWithModalMouseInputFocus(Widget* lastWidgetWithModalMouseInputFocus)
517 {
518 mLastWidgetWithModalMouseInputFocus = lastWidgetWithModalMouseInputFocus;
519 }
520
525
527 {
528 mLastWidgetPressed = lastWidgetPressed;
529 }
530
532 {
533 }
534
535} // 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
std::vector< ModalState > mModalStack
Holds the modal stack for nested modal dialogs.
Widget * mFocusedWidget
Holds the focused widget.
virtual bool hasModalFocus() const
Checks if any modal state is active.
virtual Widget * getLastWidgetWithMouse()
Gets the last widget with the mouse.
WidgetVector mWidgets
Holds the widgets currently being handled by the focus handler.
Widget * mDraggedWidget
Holds the dragged widget.
virtual Widget * getFocusOwner() const
Gets the active input root widget for focus routing.
virtual void focusPrevious()
Focuses the previous widget added to a container.
virtual void widgetHidden(Widget *widget)
Informs the focus handler that a widget was hidden.
virtual void setLastWidgetWithMouse(Widget *lastWidgetWithMouse)
Sets the last widget with the mouse.
virtual void popModal() noexcept
Pops the current modal state from the stack.
virtual void focusNone()
Focuses nothing.
virtual Widget * getDraggedWidget()
Gets the widget being dragged.
Widget * mLastWidgetWithModalMouseInputFocus
Holds the last widget with modal mouse input focus.
virtual void setFocusedWidget(Widget *widget)
Sets the focused widget directly.
Widget * mLastWidgetPressed
Holds the last widget pressed.
virtual void add(Widget *widget)
Adds a widget to by handles by the focus handler.
virtual void setLastWidgetWithModalFocus(Widget *lastWidgetWithModalFocus)
Sets the last widget with modal focus.
virtual void pushModal(Widget *focusOwner, Widget *mouseOwner=nullptr)
Pushes a new modal state onto the stack.
virtual void setLastWidgetPressed(Widget *lastWidgetPressed)
Sets the last widget pressed.
virtual Widget * getLastWidgetWithModalMouseInputFocus()
Gets the last widget with modal mouse input focus.
virtual Widget * getLastWidgetWithModalFocus()
Gets the last widget with modal focus.
virtual void remove(Widget *widget)
Removes a widget from the focus handler.
virtual bool isFocused(Widget const *widget) const
Checks if a widget is focused.
virtual void distributeFocusGainedEvent(Event const &focusEvent)
Distributes a focus gained event.
virtual void tabPrevious()
Focuses the previous widget which allows tabbing in unless current focused Widget disallows tabbing o...
Widget * mLastWidgetWithModalFocus
Holds the last widget with modal focus.
virtual Widget * getMouseCaptureOwner() const
Gets the widget with modal mouse input focus.
virtual void clearModal()
Clears all modal states from the stack.
virtual void tabNext()
Focuses the next widget which allows tabbing in unless the current focused Widget disallows tabbing o...
virtual void distributeFocusLostEvent(Event const &focusEvent)
Distributes a focus lost event.
virtual void focusNext()
Focuses the next widget added to a container.
virtual void releaseFocus(Widget *widget)
Releases focus for the specified widget if it is currently focused.
virtual void setDraggedWidget(Widget *draggedWidget)
Sets the widget being dragged.
virtual void requestFocus(Widget *widget)
Requests focus for a widget.
virtual Widget * getLastWidgetPressed()
Gets the last widget pressed.
Widget * mLastWidgetWithMouse
Holds the last widget with the mouse.
virtual void setLastWidgetWithModalMouseInputFocus(Widget *lastWidgetWithModalMouseInputFocus)
Sets the last widget with modal mouse input focus.
virtual Widget * getFocused() const
Gets the widget with focus.
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 void setFocused(bool focused)
Sets the widget as focused, or not.
Definition widget.cpp:629
bool isDescendantOf(Widget const *ancestor) const noexcept
Checks if this widget is a descendant of (or equal to) the given ancestor.
Definition widget.cpp:1639
virtual std::list< FocusListener * > const & _getFocusListeners()
Gets the focus listeners of the widget.
Definition widget.cpp:1161
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.
Represents a single modal state level with focus and mouse owners.
Widget * focusOwner
Widget with modal focus at this level.
Widget * mouseOwner
Widget with modal mouse input at this level.