The Battle for Wesnoth  1.19.0-dev
dispatcher_private.hpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2024
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 
19 
20 #include "gui/widgets/widget.hpp"
21 #include "utils/ranges.hpp"
22 
23 #include <SDL2/SDL_events.h>
24 
25 #include <cassert>
26 
27 namespace gui2::event
28 {
30 {
31  /**
32  * Returns the appropriate signal queue for an event by category.
33  *
34  * @tparam C For example, general.
35  * @param dispatcher The dispatcher whose signal queue is used.
36  * @param event The event to get the signal for.
37  *
38  * @returns The signal of the type dispatcher::signal_type<T>
39  */
40  template<event_category C>
41  static auto& event_signal(dispatcher& dispatcher, const ui_event event)
42  {
43  return dispatcher.get_signal_queue<C>().queue[event];
44  }
45 
46  /**
47  * A helper to test whether dispatcher has an handler for a certain event.
48  *
49  * @param dispatcher The dispatcher whose signal queue is used.
50  * @param queue_type The type of event to look for.
51  * @param event The event to get the signal for.
52  *
53  * @returns Whether or not the handler is found.
54  */
56  {
57  const auto queue_check = [&](auto& queue_set) {
58  return !queue_set.queue[event].empty(queue_type);
59  };
60 
61  // We can't just use get_signal_queue since there's no way to know the event at compile time.
62  switch(get_event_category(event)) {
64  return queue_check(dispatcher.signal_queue_);
66  return queue_check(dispatcher.signal_mouse_queue_);
68  return queue_check(dispatcher.signal_keyboard_queue_);
70  return queue_check(dispatcher.signal_touch_motion_queue_);
72  return queue_check(dispatcher.signal_touch_gesture_queue_);
74  return queue_check(dispatcher.signal_notification_queue_);;
76  return queue_check(dispatcher.signal_message_queue_);
78  return queue_check(dispatcher.signal_raw_event_queue_);
80  return queue_check(dispatcher.signal_text_input_queue_);
81  default:
82  throw std::invalid_argument("Event is not categorized");
83  }
84  }
85 };
86 
87 namespace implementation
88 {
89 
90 /*
91  * Small sample to illustrate the effects of the various build_event_chain
92  * functions. Assume the widgets are in an window with the following widgets:
93  *
94  * -----------------------
95  * | dispatcher |
96  * | ------------------- |
97  * | | container 1 | |
98  * | | --------------- | |
99  * | | | container 2 | | |
100  * | | | ----------- | | |
101  * | | | | widget | | | |
102  * | | | ----------- | | |
103  * | | --------------- | |
104  * | ------------------- |
105  * -----------------------
106  *
107  * Note that the firing routine fires the events from:
108  * - pre child for chain.end() - > chain.begin()
109  * - child for widget
110  * - post child for chain.begin() -> chain.end()
111  */
112 
113 /**
114  * Build the event chain.
115  *
116  * The event chain is a chain of events starting from the first parent of the
117  * widget until (and including) the wanted parent. For all these widgets it
118  * will be tested whether they have either a pre or post handler for the event.
119  * This ways there will be list of widgets to try to send the events to.
120  * If there's no line from widget to parent the result is undefined.
121  * (If widget == dispatcher the result will always be empty.)
122  *
123  * @pre dispatcher != nullptr
124  * @pre widget != nullptr
125  *
126  * @param event The event to test.
127  * @param dispatcher The final widget to test, this is also the
128  * dispatcher the sends the event.
129  * @param w The widget should parent(s) to check.
130  *
131  * @returns The list of widgets with a handler.
132  * The order will be (assuming all have a
133  * handler):
134  * * container 2
135  * * container 1
136  * * dispatcher
137  */
138 template<event_category C>
139 std::vector<std::pair<widget*, ui_event>>
141 {
142  assert(dispatcher);
143  assert(w);
144 
145  std::vector<std::pair<widget*, ui_event>> result;
146 
147  while(true) {
149  result.emplace_back(w, event);
150  }
151 
152  if(w == dispatcher) {
153  break;
154  }
155 
156  w = w->parent();
157  assert(w);
158  }
159 
160  return result;
161 }
162 
163 /**
164  * Build the event chain for signal_notification.
165  *
166  * The notification is only send to the receiver it returns an empty chain.
167  * Since the pre and post queues are unused, it validates whether they are
168  * empty (using asserts).
169  *
170  * @returns An empty vector.
171  */
172 template<>
173 std::vector<std::pair<widget*, ui_event>>
174 build_event_chain<event_category::notification>(const ui_event event, widget* dispatcher, widget* w)
175 {
176  assert(dispatcher);
177  assert(w);
178 
179  assert(!w->has_event(event, dispatcher::event_queue_type(dispatcher::pre | dispatcher::post)));
180 
181  return {};
182 }
183 
184 /**
185  * Build the event chain for signal_message.
186  *
187  * This function expects that the widget sending it is also the receiver. This
188  * assumption might change, but is valid for now. The function doesn't build an
189  * event chain from @p dispatcher to @p widget but from @p widget to its
190  * toplevel item (the first one without a parent) which we call @p window.
191  *
192  * @pre dispatcher == widget
193  *
194  * @returns The list of widgets with a handler.
195  * The order will be (assuming all have a
196  * handler):
197  * * window
198  * * container 1
199  * * container 2
200  */
201 template<>
202 std::vector<std::pair<widget*, ui_event>>
203 build_event_chain<event_category::message>(const ui_event event, widget* dispatcher, widget* w)
204 {
205  assert(dispatcher);
206  assert(w);
207  assert(w == dispatcher);
208 
209  std::vector<std::pair<widget*, ui_event>> result;
210 
211  /* We only should add the parents of the widget to the chain. */
212  while((w = w->parent())) {
213  assert(w);
214 
216  result.emplace(result.begin(), w, event);
217  }
218  }
219 
220  return result;
221 }
222 
223 /**
224  * Helper function for fire_event.
225  *
226  * This is called with the same parameters as fire_event except for the
227  * event_chain, which contains the widgets with the events to call for them.
228  */
229 template<event_category C, typename... F>
230 bool fire_event(const ui_event event,
231  const std::vector<std::pair<widget*, ui_event>>& event_chain,
233  widget* w,
234  F&&... params)
235 {
236  bool handled = false;
237  bool halt = false;
238 
239  /***** ***** ***** Pre ***** ***** *****/
240  for(const auto& [chain_target, chain_event] : utils::reversed_view(event_chain)) {
241  const auto& signal = dispatcher_implementation::event_signal<C>(*chain_target, chain_event);
242 
243  for(const auto& pre_func : signal.pre_child) {
244  pre_func(*dispatcher, chain_event, handled, halt, std::forward<F>(params)...);
245 
246  if(halt) {
247  assert(handled);
248  break;
249  }
250  }
251 
252  if(handled) {
253  return true;
254  }
255  }
256 
257  /***** ***** ***** Child ***** ***** *****/
258  if(w->has_event(event, dispatcher::child)) {
259  const auto& signal = dispatcher_implementation::event_signal<C>(*w, event);
260 
261  for(const auto& func : signal.child) {
262  func(*dispatcher, event, handled, halt, std::forward<F>(params)...);
263 
264  if(halt) {
265  assert(handled);
266  break;
267  }
268  }
269 
270  if(handled) {
271  return true;
272  }
273  }
274 
275  /***** ***** ***** Post ***** ***** *****/
276  for(const auto& [chain_target, chain_event] : event_chain) {
277  const auto& signal = dispatcher_implementation::event_signal<C>(*chain_target, chain_event);
278 
279  for(const auto& post_func : signal.post_child) {
280  post_func(*dispatcher, chain_event, handled, halt, std::forward<F>(params)...);
281 
282  if(halt) {
283  assert(handled);
284  break;
285  }
286  }
287 
288  if(handled) {
289  return true;
290  }
291  }
292 
293  /**** ***** ***** Unhandled ***** ***** *****/
294  assert(handled == false);
295  return false;
296 }
297 
298 } // namespace implementation
299 
300 /**
301  * Fires an event.
302  *
303  * A helper to allow the common event firing code to be shared between the
304  * different signal function types.
305  *
306  * @pre d != nullptr
307  * @pre w != nullptr
308  *
309  * @tparam C The category of the event to handle.
310  * @tparam F The parameter pack type.
311  *
312  *
313  * @param event The event to fire.
314  * @param d The dispatcher that handles the event.
315  * @param w The widget that should receive the event.
316  * @param params Zero or more additional arguments to pass
317  * to the signal function when it's executed.
318  *
319  * @returns Whether or not the event was handled.
320  */
321 template<event_category C, typename... F>
322 bool fire_event(const ui_event event, dispatcher* d, widget* w, F&&... params)
323 {
324  assert(d);
325  assert(w);
326 
327  widget* dispatcher_w = dynamic_cast<widget*>(d);
328 
329  std::vector<std::pair<widget*, ui_event>> event_chain =
330  implementation::build_event_chain<C>(event, dispatcher_w, w);
331 
332  return implementation::fire_event<C>(event, event_chain, dispatcher_w, w, std::forward<F>(params)...);
333 }
334 
335 template<ui_event click,
336  ui_event double_click,
337  bool (event_executor::*wants_double_click)() const,
338  typename... F>
339 bool fire_event_double_click(dispatcher* dsp, widget* wgt, F&&... params)
340 {
341  assert(dsp);
342  assert(wgt);
343 
344  std::vector<std::pair<widget*, ui_event>> event_chain;
345  widget* w = wgt;
346  widget* d = dynamic_cast<widget*>(dsp);
347 
348  while(w != d) {
349  w = w->parent();
350  assert(w);
351 
352  if(std::invoke(wants_double_click, w)) {
353  if(w->has_event(double_click, dispatcher::event_queue_type(dispatcher::pre | dispatcher::post))) {
354  event_chain.emplace_back(w, double_click);
355  }
356  } else {
358  event_chain.emplace_back(w, click);
359  }
360  }
361  }
362 
363  if(std::invoke(wants_double_click, wgt)) {
364  constexpr auto C = get_event_category(double_click);
365  return implementation::fire_event<C>(double_click, event_chain, d, wgt, std::forward<F>(params)...);
366  } else {
367  constexpr auto C = get_event_category(click);
368  return implementation::fire_event<C>(click, event_chain, d, wgt, std::forward<F>(params)...);
369  }
370 }
371 
372 } // namespace gui2::event
Base class for event handling.
Definition: dispatcher.hpp:150
signal_queue< signal_touch_gesture > signal_touch_gesture_queue_
Signal queue for callbacks in event_category::touch_gesture.
Definition: dispatcher.hpp:593
signal_queue< signal_message > signal_message_queue_
Signal queue for callbacks in event_category::message.
Definition: dispatcher.hpp:599
signal_queue< signal_text_input > signal_text_input_queue_
Signal queue for callbacks in event_category::text_input.
Definition: dispatcher.hpp:605
signal_queue< signal_raw_event > signal_raw_event_queue_
Signal queue for callbacks in event_category::raw_event.
Definition: dispatcher.hpp:602
signal_queue< signal_notification > signal_notification_queue_
Signal queue for callbacks in event_category::notification.
Definition: dispatcher.hpp:596
signal_queue< signal > signal_queue_
Signal queue for callbacks in event_category::general.
Definition: dispatcher.hpp:581
signal_queue< signal_touch_motion > signal_touch_motion_queue_
Signal queue for callbacks in event_category::touch_motion.
Definition: dispatcher.hpp:590
signal_queue< signal_keyboard > signal_keyboard_queue_
Signal queue for callbacks in event_category::keyboard.
Definition: dispatcher.hpp:587
signal_queue< signal_mouse > signal_mouse_queue_
Signal queue for callbacks in event_category::mouse.
Definition: dispatcher.hpp:584
Event execution calls.
Base class for all widgets.
Definition: widget.hpp:53
int w
std::vector< std::pair< widget *, ui_event > > build_event_chain(const ui_event event, widget *dispatcher, widget *w)
Build the event chain.
bool fire_event(const ui_event event, const std::vector< std::pair< widget *, ui_event >> &event_chain, widget *dispatcher, widget *w, F &&... params)
Helper function for fire_event.
ui_event
The event sent to the dispatcher.
Definition: handler.hpp:115
bool fire_event_double_click(dispatcher *dsp, widget *wgt, F &&... params)
constexpr event_category get_event_category(const ui_event event)
Returns the category of a given event.
Definition: handler.hpp:192
bool fire_event(const ui_event event, dispatcher *d, widget *w, F &&... params)
Fires an event.
dispatcher_callback<> signal
Used for events in event_category::general.
Definition: dispatcher.hpp:56
event_category
Event category masks.
Definition: handler.hpp:55
@ 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.
Contains the implementation details for lexical_cast and shouldn't be used directly.
bool click(int mousex, int mousey)
Definition: tooltips.cpp:294
auto reversed_view(T &container)
Definition: ranges.hpp:28
static auto & event_signal(dispatcher &dispatcher, const ui_event event)
Returns the appropriate signal queue for an event by category.
static bool has_handler(dispatcher &dispatcher, const dispatcher::event_queue_type queue_type, ui_event event)
A helper to test whether dispatcher has an handler for a certain event.
#define d