The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
dispatcher.hpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2009 - 2025
3 by Mark de Wever <koraq@xs4all.nl>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12
13 See the COPYING file for more details.
14*/
15
16#pragma once
17
20#include "utils/general.hpp"
21
22#include <SDL3/SDL_events.h>
23
24#include <functional>
25#include <list>
26#include <map>
27
28struct point;
29
30namespace gui2
31{
32class widget;
33
34namespace event
35{
36struct message;
37
38/**
39 * Callback function signature alias template.
40 *
41 * All callbacks take these four arguments in addition to any arguments
42 * specified by the parameter pack.
43 *
44 * Parameters:
45 * 1. The widget handling this event.
46 * 2. The event type.
47 * 3. Reference to the flag controlling whether this event has been handled.
48 * 4. Reference to the flag controlling whether to halt execution of this event.
49 */
50template<typename... T>
51using dispatcher_callback = std::function<void(widget&, const ui_event, bool&, bool&, T...)>;
52
53/**
54 * Used for events in event_category::general.
55 */
57
58/**
59 * Used for events in event_category::mouse.
60 *
61 * Extra parameters:
62 * 5. The x,y coordinate of the mouse when this event is fired.
63 */
65
66/**
67 * Used for events in event_category::keyboard.
68 *
69 * Extra parameters:
70 * 5. The keycode of the key that triggered this event.
71 * 6. Any applicable active modifer key.
72 * 7. Any applicable text associated with the key.
73 */
75
76/**
77 * Used for events in event_category::touch_motion.
78 *
79 * Extra parameters:
80 * 5. Origin of the touch event, in x,y format.
81 * 6. Number of pixels dragged, in x,y format.
82 */
84
85/**
86 * Used for events in event_category::touch_gesture.
87 *
88 * Extra parameters: (TODO: document what these actually are)
89 * 5. center
90 * 6. dTheta
91 * 7. dDist
92 * 8. numFingers
93 */
95
96/**
97 * Used for events in event_category::notification.
98 *
99 * Extra parameters:
100 * 5. A dummy void* parameter which will always be nullptr, used to differentiate
101 * this function from signal.
102 */
104
105/**
106 * Used for events in event_category::message.
107 *
108 * Extra parameters:
109 * 5. The applicable data this event requires.
110 */
112
113/**
114 * Used for events in event_category::raw_event.
115 *
116 * Extra parameters:
117 * 5. The raw SDL_Event.
118 */
120
121/**
122 * Used for eventsin event_category::text_input.
123 *
124 * Extra parameters:
125 * 5. The text entered.
126 * 6. The current input position.
127 * 7. The current text selection length.
128 */
130
131/** Hotkey function handler signature. */
132using hotkey_function = std::function<bool(widget& dispatcher, hotkey::HOTKEY_COMMAND id)>;
133
134/**
135 * Base class for event handling.
136 *
137 * A dispatcher has slots for events, when an event arrives it looks for the
138 * functions that registered themselves for that event and calls their
139 * callbacks.
140 *
141 * This class is a base class for all widgets[1], what a widget does on a
142 * callback can differ greatly, an image might ignore all events a window can
143 * track the mouse location and fire MOUSE_ENTER and MOUSE_LEAVE events to the
144 * widgets involved.
145 *
146 * [1] Not really sure whether it will be a base class for a widget or
147 * styled_widget yet.
148 */
150{
152
153public:
154 dispatcher();
155 virtual ~dispatcher();
156
157 /**
158 * Connects the dispatcher to the event handler.
159 *
160 * When a dispatcher is connected to the event handler it will get the
161 * events directly from the event handler. This is wanted for top level
162 * items like windows but not for most other widgets.
163 *
164 * So a window can call connect to register itself, it will automatically
165 * disconnect upon destruction.
166 */
167 void connect();
168
169 /**
170 * Disconnects the dispatcher from the event handler.
171 */
172 void disconnect();
173
174 /** Return whether the dispatcher is currently connected. */
175 bool is_connected() const
176 {
177 return connected_;
178 }
179
180 /**
181 * Determines whether the location is inside an active widget.
182 *
183 * This is used to see whether a mouse event is inside the widget.
184 *
185 * @param coordinate The coordinate to test whether inside the
186 * widget.
187 *
188 * @result True if inside an active widget, false
189 * otherwise.
190 */
191 virtual bool is_at(const point& coordinate) const = 0;
192
194 pre = 1,
195 child = 2,
196 post = 4
197 };
198
199 bool has_event(const ui_event event, const event_queue_type event_type);
200
201 /** Fires an event which has no extra parameters. */
202 bool fire(const ui_event event, widget& target);
203
204 /**
205 * Fires an event which takes a coordinate parameter.
206 *
207 * @param event The event to fire.
208 * @param target The widget that should receive the event.
209 * @param coordinate The mouse position for the event.
210 */
211 bool fire(const ui_event event, widget& target, const point& coordinate);
212
213 /**
214 * Fires an event which takes keyboard parameters.
215 *
216 * @param event The event to fire.
217 * @param target The widget that should receive the event.
218 * @param key The SDL key code of the key pressed.
219 * @param modifier The SDL key modifiers used.
220 * @param unicode The unicode value for the key pressed.
221 */
222 bool fire(const ui_event event,
223 widget& target,
224 const SDL_Keycode key,
225 const SDL_Keymod modifier,
226 const std::string& unicode);
227
228 /**
229 * Fires an event which takes touch-motion parameters.
230 *
231 * @param event The event to fire.
232 * @param target The widget that should receive the event.
233 * @param pos The location touched.
234 * @param distance The distance moved.
235 */
236 bool fire(const ui_event event, widget& target, const point& pos, const point& distance);
237
238 /**
239 * Fires an event which takes touch-gesture parameters.
240 *
241 * @param event The event to fire.
242 * @param target The widget that should receive the event.
243 * @param center The location touched.
244 * @param dTheta Probably the direction moved.
245 * @param dDist The distance moved.
246 * @param numFingers Probably the number of fingers touching the screen.
247 */
248 bool fire(const ui_event event, widget& target, const point& center, float dTheta, float dDist, uint8_t numFingers);
249
250 /**
251 * Fires an event which takes notification parameters.
252 *
253 * @note the void* parameter is a dummy needed for SFINAE.
254 *
255 * @param event The event to fire.
256 * @param target The widget that should receive the event.
257 */
258 bool fire(const ui_event event, widget& target, void*);
259
260 /**
261 * Fires an event which takes message parameters.
262 *
263 * @param event The event to fire.
264 * @param target The widget that should receive the event.
265 * Normally this is the window holding the
266 * widget.
267 * @param msg The extra information needed for a window
268 * (or another widget in the chain) to handle
269 * the message.
270 */
271 bool fire(const ui_event event, widget& target, const message& msg);
272
273 /**
274 * Fires an event that's a raw SDL event
275 * @param event The event to fire.
276 * @param target The widget that should receive the event.
277 * Normally this is the window holding the
278 * widget.
279 * @param sdlevent The raw SDL event
280 */
281 bool fire(const ui_event event, widget& target, const SDL_Event& sdlevent);
282
283 /**
284 * Fires an event which takes text input parameters
285 * @param event The event to fire.
286 * @param target The widget that should receive the event.
287 * Normally this is the window holding the
288 * widget.
289 * @param text The text involved in the event
290 * @param start The start point for IME editing
291 * @param len The selection length for IME editing
292 */
293 bool fire(const ui_event event, widget& target, const std::string& text, int32_t start, int32_t len);
294
295 /**
296 * The position where to add a new callback in the signal handler.
297 *
298 * The signal handler has three callback queues:
299 * * pre_child These callbacks are called before a container widget sends it
300 * to the child items. Widgets without children should also use this
301 * queue.
302 * * child The callbacks for the proper child widget(s) are called.
303 * * post_child The callbacks for the parent container to be called after
304 * the child.
305 *
306 * For every queue it's possible to add a new event in the front or in the
307 * back.
308 *
309 * Whether all three queues are executed depend on the whether the
310 * callbacks modify the handled and halt flag.
311 * * When the halt flag is set execution of the current queue stops, when
312 * doing so the handled flag must be set as well.
313 * * When the handled flag is set the events in that queue are executed and
314 * no more queues afterwards.
315 *
316 * Here are some use case examples.
317 * A button that plays a sound and executes an optional user callback:
318 * * The buttons internal click handler is invoked and sets the handled
319 * flag
320 * * The callback installed by the user is in the same queue and gets
321 * executed afterwards.
322 *
323 * A toggle button may or may not be toggled:
324 * * The user inserts a callback, that validates whether the action is
325 * allowed, if not allowed it sets the halt flag (and handled), else
326 * leaves the flags untouched.
327 * * The normal buttons toggle function then might get invoked and if so
328 * sets the handled flag.
329 * * Optionally there is another user callback invoked at this point.
330 */
339
340 /**
341 * Adds a callback to the appropriate queue based on event type.
342 *
343 * @tparam E The event the callback needs to react to.
344 * @tparam F The event signature. This must match the
345 * appropriate queue's callback signature.
346 *
347 * @param func The callback function.
348 * @param position The position to place the callback.
349 */
350 template<ui_event E, typename F>
351 void connect_signal(const F& func, const queue_position position = back_child)
352 {
353 get_signal_queue<get_event_category(E)>().connect_signal(E, position, func);
354 }
355
356 /**
357 * Removes a callback from the appropriate queue based on event type.
358 *
359 * @tparam E The event the callback needs to react to.
360 * @tparam F The event signature. This must match the
361 * appropriate queue's callback signature.
362 *
363 * @param func The callback function.
364 * @param position The place where the function was added.
365 * Needed remove the event from the right
366 * place. (The function doesn't care whether
367 * was added in front or back.)
368 */
369 template<ui_event E, typename F>
370 void disconnect_signal(const F& func, const queue_position position = back_child)
371 {
372 get_signal_queue<get_event_category(E)>().disconnect_signal(E, position, func);
373 }
374
375 /**
376 * The behavior of the mouse events.
377 *
378 * Normally for mouse events there's first checked whether a dispatcher has
379 * captured the mouse if so it gets the event.
380 * If not the dispatcher is searched from the back to the front in the
381 * layers and its behavior is checked.
382 * * none The event is never send to the layer and goes on the the next
383 * layer. This is used for tooltips who might cover a button but a click
384 * on the tooltips should still click the button.
385 * * all The event is always send to this layer and stops the search for a
386 * next layer.
387 * * hit If the mouse is inside the dispatcher area the event is send and
388 * no longer searched further. If not inside tests the last layer.
389 *
390 * If after these tests no dispatcher is found the event is ignored.
391 */
392 enum class mouse_behavior {
393 all,
394 hit,
395 none
396 };
397
398 /** Captures the mouse. */
400 {
402 }
403
404 /** Releases the mouse capture. */
406 {
408 }
409
410 /***** ***** ***** setters/getters ***** ***** *****/
411
416
418 {
419 return mouse_behavior_;
420 }
421
422 void set_want_keyboard_input(const bool want_keyboard_input)
423 {
424 want_keyboard_input_ = want_keyboard_input;
425 }
426
428 {
430 }
431
432 /**
433 * Registers a hotkey.
434 *
435 * @todo add a static function register_global_hotkey.
436 *
437 * Once that's done execute_hotkey will first try to execute a global
438 * hotkey and if that fails tries the hotkeys in this dispatcher.
439 *
440 * @param id The hotkey to register.
441 * @param function The callback function to call.
442 */
443 template<typename Func>
444 void register_hotkey(const hotkey::HOTKEY_COMMAND id, Func&& function)
445 {
446 hotkeys_[id] = std::move(function);
447 }
448
449 /**
450 * Executes a hotkey.
451 *
452 * @param id The hotkey to execute.
453 *
454 * @returns true if the hotkey is handled, false
455 * otherwise.
456 */
458
459private:
460 /** Helper struct to generate the various signal types. */
461 template<class T>
463 {
464 signal_type() = default;
465
466 std::list<T> pre_child;
467 std::list<T> child;
468 std::list<T> post_child;
469
470 /**
471 * Checks whether the queue of a given type is empty.
472 *
473 * @param queue_type The queue to check. This may be one or more types
474 * OR'd together (event_queue_type is bit-unique).
475 *
476 * @returns True if ALL the matching queues are empty, or false
477 * if any of the matching queues is NOT empty.
478 */
480 {
481 if((queue_type & dispatcher::pre) && !pre_child.empty()) {
482 return false;
483 }
484
485 if((queue_type & dispatcher::child) && !child.empty()) {
486 return false;
487 }
488
489 if((queue_type & dispatcher::post) && !post_child.empty()) {
490 return false;
491 }
492
493 return true;
494 }
495 };
496
497 /** Helper struct to generate the various event queues. */
498 template<class T>
500 {
501 signal_queue() = default;
502
503 signal_queue(const signal_queue&) = delete;
505
506 using callback = T;
507 std::map<ui_event, signal_type<T>> queue;
508
509 void connect_signal(const ui_event event, const queue_position position, const T& signal)
510 {
511 switch(position) {
512 case front_pre_child:
513 queue[event].pre_child.push_front(signal);
514 break;
515 case back_pre_child:
516 queue[event].pre_child.push_back(signal);
517 break;
518
519 case front_child:
520 queue[event].child.push_front(signal);
521 break;
522 case back_child:
523 queue[event].child.push_back(signal);
524 break;
525
526 case front_post_child:
527 queue[event].post_child.push_front(signal);
528 break;
529 case back_post_child:
530 queue[event].post_child.push_back(signal);
531 break;
532 }
533 }
534
535 void disconnect_signal(const ui_event event, const queue_position position, const T& signal)
536 {
537 // This is std::function<T>::target_type()
538 const auto predicate = [&signal](const T& element) { return signal.target_type() == element.target_type(); };
539
540 /* The function doesn't differentiate between front and back position so fall
541 * down from front to back.
542 *
543 * NOTE: This used to only remove the first signal of matching target type.
544 * That behavior could be restored in the future if needed.
545 * - vultraz, 2017-05-02
546 */
547 switch(position) {
548 case front_pre_child:
549 [[fallthrough]];
550 case back_pre_child:
551 queue[event].pre_child.remove_if(predicate);
552 break;
553
554 case front_child:
555 [[fallthrough]];
556 case back_child:
557 queue[event].child.remove_if(predicate);
558 break;
559
560 case front_post_child:
561 [[fallthrough]];
562 case back_post_child:
563 queue[event].post_child.remove_if(predicate);
564 break;
565 }
566 }
567 };
568
569 /** The mouse behavior for the dispatcher. */
571
572 /**
573 * Does the dispatcher want to receive keyboard input.
574 *
575 * @todo The entire mouse and keyboard handling can use a code review to
576 * seen whether it might be combined in one flag field. At the moment the
577 * keyboard doesn't look whether a dialog has the mouse focus before
578 * sending the event, so maybe we should add an active dispatcher to keep
579 * track of it. But since at the moment there are only non-modal windows
580 * and tooltips it's not a problem.
581 */
583
584 /** Signal queue for callbacks in event_category::general. */
586
587 /** Signal queue for callbacks in event_category::mouse. */
589
590 /** Signal queue for callbacks in event_category::keyboard. */
592
593 /** Signal queue for callbacks in event_category::touch_motion. */
595
596 /** Signal queue for callbacks in event_category::touch_gesture. */
598
599 /** Signal queue for callbacks in event_category::notification. */
601
602 /** Signal queue for callbacks in event_category::message. */
604
605 /** Signal queue for callbacks in event_category::raw_event. */
607
608 /** Signal queue for callbacks in event_category::text_input. */
610
611 /** Are we connected to the event handler. */
613
614 /** The registered hotkeys for this dispatcher. */
615 std::map<hotkey::HOTKEY_COMMAND, hotkey_function> hotkeys_;
616
617 template<event_category cat>
619 {
620 if constexpr(cat == event_category::general) {
621 return signal_queue_;
622 } else if constexpr(cat == event_category::mouse) { // Tee hee
623 return signal_mouse_queue_;
624 } else if constexpr(cat == event_category::keyboard) {
626 } else if constexpr(cat == event_category::touch_motion) {
628 } else if constexpr(cat == event_category::touch_gesture) {
630 } else if constexpr(cat == event_category::notification) {
632 } else if constexpr(cat == event_category::message) {
634 } else if constexpr(cat == event_category::raw_event) {
636 } else if constexpr(cat == event_category::text_input) {
638 } else {
639 static_assert(utils::dependent_false_v<decltype(cat)>, "No matching signal queue for category");
640 }
641 }
642};
643
644/***** ***** ***** ***** ***** Common helpers ***** ***** ***** ***** *****/
645
646/*
647 * These helpers can be used to easily add callbacks to a dispatcher (widget).
648 * This is just a list of common ones all others can be used as well.
649 */
650
651/**
652 * Connects the signal for 'snooping' on the keypress.
653 *
654 * This callback is called before the widget itself allowing you to either
655 * snoop on the input or filter it.
656 */
657void connect_signal_pre_key_press(dispatcher& dispatcher, const signal_keyboard& signal);
658
659/** Connects a signal handler for a left mouse button click. */
660void connect_signal_mouse_left_click(dispatcher& dispatcher, const signal& signal);
661
662/** Disconnects a signal handler for a left mouse button click. */
663void disconnect_signal_mouse_left_click(dispatcher& dispatcher, const signal& signal);
664
665/** Connects a signal handler for a left mouse button release. */
666void connect_signal_mouse_left_release(dispatcher& dispatcher, const signal& signal);
667
668/** Disconnects a signal handler for a left mouse button release. */
669void disconnect_signal_mouse_left_release(dispatcher& dispatcher, const signal& signal);
670
671/**
672 * Connects a signal handler for a left mouse button double click.
673 *
674 * I'm not exactly sure why this works in this queue position with toggle
675 * panels, but it does. Will revisit if it becomes an issue later (ie, if
676 * this is used with other widgets and doesn't work).
677 *
678 * - vultraz, 2017-08-23
679 */
680void connect_signal_mouse_left_double_click(dispatcher& dispatcher, const signal& signal);
681
682/** Connects a signal handler for getting a notification upon modification. */
683void connect_signal_notify_modified(dispatcher& dispatcher, const signal_notification& signal);
684
685} // namespace event
686
687} // namespace gui2
Base class for event handling.
void connect_signal(const F &func, const queue_position position=back_child)
Adds a callback to the appropriate queue based on event type.
void connect()
Connects the dispatcher to the event handler.
signal_queue< signal_touch_gesture > signal_touch_gesture_queue_
Signal queue for callbacks in event_category::touch_gesture.
queue_position
The position where to add a new callback in the signal handler.
bool fire(const ui_event event, widget &target)
Fires an event which has no extra parameters.
bool has_event(const ui_event event, const event_queue_type event_type)
void set_mouse_behavior(const mouse_behavior mouse_behavior)
virtual bool is_at(const point &coordinate) const =0
Determines whether the location is inside an active widget.
signal_queue< signal_message > signal_message_queue_
Signal queue for callbacks in event_category::message.
signal_queue< signal_text_input > signal_text_input_queue_
Signal queue for callbacks in event_category::text_input.
void register_hotkey(const hotkey::HOTKEY_COMMAND id, Func &&function)
Registers a hotkey.
mouse_behavior
The behavior of the mouse events.
bool get_want_keyboard_input() const
signal_queue< signal_raw_event > signal_raw_event_queue_
Signal queue for callbacks in event_category::raw_event.
signal_queue< signal_notification > signal_notification_queue_
Signal queue for callbacks in event_category::notification.
void disconnect()
Disconnects the dispatcher from the event handler.
signal_queue< signal > signal_queue_
Signal queue for callbacks in event_category::general.
mouse_behavior mouse_behavior_
The mouse behavior for the dispatcher.
bool want_keyboard_input_
Does the dispatcher want to receive keyboard input.
void set_want_keyboard_input(const bool want_keyboard_input)
void capture_mouse()
Captures the mouse.
signal_queue< signal_touch_motion > signal_touch_motion_queue_
Signal queue for callbacks in event_category::touch_motion.
void disconnect_signal(const F &func, const queue_position position=back_child)
Removes a callback from the appropriate queue based on event type.
bool connected_
Are we connected to the event handler.
bool execute_hotkey(const hotkey::HOTKEY_COMMAND id)
Executes a hotkey.
mouse_behavior get_mouse_behavior() const
bool is_connected() const
Return whether the dispatcher is currently connected.
signal_queue< signal_keyboard > signal_keyboard_queue_
Signal queue for callbacks in event_category::keyboard.
void release_mouse()
Releases the mouse capture.
signal_queue< signal_mouse > signal_mouse_queue_
Signal queue for callbacks in event_category::mouse.
std::map< hotkey::HOTKEY_COMMAND, hotkey_function > hotkeys_
The registered hotkeys for this dispatcher.
Base class for all widgets.
Definition widget.hpp:55
std::string id
Text to match against addon_info.tags()
Definition manager.cpp:199
dispatcher_callback< const point &, float, float, uint8_t > signal_touch_gesture
Used for events in event_category::touch_gesture.
void connect_signal_pre_key_press(dispatcher &dispatcher, const signal_keyboard &signal)
Connects the signal for 'snooping' on the keypress.
ui_event
The event sent to the dispatcher.
Definition handler.hpp:115
void disconnect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Disconnects a signal handler for a left mouse button click.
dispatcher_callback< const std::string &, int32_t, int32_t > signal_text_input
Used for eventsin event_category::text_input.
std::function< bool(widget &dispatcher, hotkey::HOTKEY_COMMAND id)> hotkey_function
Hotkey function handler signature.
dispatcher_callback< void * > signal_notification
Used for events in event_category::notification.
void capture_mouse(dispatcher *dispatcher)
Captures the mouse.
Definition handler.cpp:904
void release_mouse(dispatcher *dispatcher)
Releases a captured mouse.
Definition handler.cpp:911
void disconnect_signal_mouse_left_release(dispatcher &dispatcher, const signal &signal)
Disconnects a signal handler for a left mouse button release.
dispatcher_callback<> signal
Used for events in event_category::general.
@ notification
Callbacks with a sender aka notification messages.
@ keyboard
Callbacks with the keyboard values (these haven't been determined yet).
@ mouse
Callbacks with a coordinate as extra parameter.
@ message
Callbacks with a sender aka notification messages.
@ general
Callbacks without extra parameters.
void connect_signal_mouse_left_release(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button release.
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification &signal)
Connects a signal handler for getting a notification upon modification.
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
dispatcher_callback< const message & > signal_message
Used for events in event_category::message.
std::function< void(widget &, const ui_event, bool &, bool &, T...)> dispatcher_callback
Callback function signature alias template.
dispatcher_callback< const SDL_Event & > signal_raw_event
Used for events in event_category::raw_event.
dispatcher_callback< const point &, const point & > signal_touch_motion
Used for events in event_category::touch_motion.
dispatcher_callback< const SDL_Keycode, const SDL_Keymod, const std::string & > signal_keyboard
Used for events in event_category::keyboard.
dispatcher_callback< const point & > signal_mouse
Used for events in event_category::mouse.
void connect_signal_mouse_left_double_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button double click.
Generic file dialog.
constexpr bool dependent_false_v
Workaround for the fact that static_assert(false) is invalid.
Definition general.hpp:39
Helper struct to generate the various event queues.
signal_queue(const signal_queue &)=delete
void disconnect_signal(const ui_event event, const queue_position position, const T &signal)
void connect_signal(const ui_event event, const queue_position position, const T &signal)
signal_queue & operator=(const signal_queue &)=delete
std::map< ui_event, signal_type< T > > queue
Helper struct to generate the various signal types.
bool empty(const dispatcher::event_queue_type queue_type) const
Checks whether the queue of a given type is empty.
The message callbacks hold a reference to a message.
Definition message.hpp:46
Holds a 2D point.
Definition point.hpp:25
The base template for associating string values with enum values.
Definition enum_base.hpp:33