The Battle for Wesnoth  1.19.14+dev
events.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  by David White <dave@whitevine.net>
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 #include "events.hpp"
17 
18 #include "cursor.hpp"
19 #include "desktop/clipboard.hpp"
20 #include "log.hpp"
21 #include "draw_manager.hpp"
23 #include "quit_confirmation.hpp"
24 #include "sdl/userevent.hpp"
25 #include "utils/ranges.hpp"
26 #include "utils/general.hpp"
27 #include "video.hpp"
28 
29 #if defined _WIN32
31 #endif
32 
33 #include <algorithm>
34 #include <cassert>
35 #include <deque>
36 #include <future>
37 #include <iterator>
38 #include <thread>
39 #include <utility>
40 #include <vector>
41 
42 #include <SDL2/SDL.h>
43 
44 #define ERR_GEN LOG_STREAM(err, lg::general)
45 
46 static lg::log_domain log_display("display");
47 #define LOG_DP LOG_STREAM(info, log_display)
48 
49 static lg::log_domain log_event("event");
50 #define LOG_EV LOG_STREAM(info, log_event)
51 #define DBG_EV LOG_STREAM(debug, log_event)
52 
53 namespace
54 {
55 struct invoked_function_data
56 {
57  explicit invoked_function_data(const std::function<void(void)>& func)
58  : f(func)
59  , finished()
60  {
61  }
62 
63  /** The actual function to call. */
64  const std::function<void(void)>& f;
65 
66  /** Whether execution in the main thread is complete. */
67  std::promise<void> finished;
68 
69  void call()
70  {
71  try {
72  f();
73  } catch(const video::quit&) {
74  // Handle this exception in the main thread.
75  throw;
76  } catch(...) {
77  DBG_EV << "Caught exception in invoked function: " << utils::get_unknown_exception_type();
78  finished.set_exception(std::current_exception());
79  return;
80  }
81 
82  finished.set_value();
83  }
84 };
85 }
86 
87 namespace events
88 {
90 {
91  /* Add new handlers to the staging list initially.
92  * This ensures that if an event handler adds more handlers, the new handlers
93  * won't be called for the event that caused them to be added.
94  */
95  staging_handlers.push_back(ptr);
96 }
97 
98 bool context::has_handler(const sdl_handler* ptr) const
99 {
101 }
102 
104 {
105  static int depth = 0;
106  ++depth;
107 
108  // The handler is most likely on the back of the events list,
109  // so look there first, otherwise do a complete search.
110  if(!handlers.empty() && handlers.back() == ptr) {
111  if(focused_handler != handlers.end() && *focused_handler == ptr) {
112  focused_handler = handlers.end();
113  }
114 
115  handlers.pop_back();
116  } else {
117  const handler_list::iterator i = std::find(handlers.begin(), handlers.end(), ptr);
118 
119  if(i == handlers.end()) {
120  --depth;
121 
122  // The handler may be in the staging area. Search it from there.
123  auto j = std::find(staging_handlers.begin(), staging_handlers.end(), ptr);
124  if(j != staging_handlers.end()) {
125  staging_handlers.erase(j);
126  return true;
127  } else {
128  return false;
129  }
130  }
131 
132  if(i == focused_handler) {
134  }
135 
136  handlers.erase(i);
137  }
138 
139  --depth;
140 
141  if(depth == 0) {
142  cycle_focus();
143  } else {
144  focused_handler = handlers.end();
145  }
146 
147  return true;
148 }
149 
151 {
152  if(handlers.begin() == handlers.end()) {
153  return;
154  }
155 
158 
159  if(last != handlers.begin()) {
160  --last;
161  }
162 
163  if(current == handlers.end()) {
164  current = handlers.begin();
165  } else {
166  ++current;
167  }
168 
169  while(current != last) {
170  if(current != handlers.end() && (*current)->requires_event_focus()) {
171  focused_handler = current;
172  break;
173  }
174 
175  if(current == handlers.end()) {
176  current = handlers.begin();
177  } else {
178  ++current;
179  }
180  }
181 }
182 
184 {
185  const handler_list::iterator i = std::find(handlers.begin(), handlers.end(), ptr);
186  if(i != handlers.end() && (*i)->requires_event_focus()) {
187  focused_handler = i;
188  }
189 }
190 
192 {
193  std::copy(staging_handlers.begin(), staging_handlers.end(), std::back_inserter(handlers));
194  staging_handlers.clear();
195 }
196 
198 {
199  for(sdl_handler* h : handlers) {
200  if(h->has_joined()) {
201  h->has_joined_ = false;
202  }
203 
204  if(h->has_joined_global()) {
205  h->has_joined_global_ = false;
206  }
207  }
208 }
209 
210 // This object stores all the event handlers. It is a stack of event 'contexts'.
211 // a new event context is created when e.g. a modal dialog is opened, and then
212 // closed when that dialog is closed. Each context contains a list of the handlers
213 // in that context. The current context is the one on the top of the stack.
214 // The global context must always be in the first position.
215 std::deque<context> event_contexts;
216 
217 std::vector<pump_monitor*> pump_monitors;
218 
220 {
221  pump_monitors.push_back(this);
222 }
223 
225 {
227 }
228 
230 {
231  event_contexts.emplace_back();
232 }
233 
235 {
236  assert(event_contexts.empty() == false);
237  event_contexts.pop_back();
238 }
239 
240 sdl_handler::sdl_handler(const bool auto_join)
241  : has_joined_(false)
242  , has_joined_global_(false)
243 {
244  if(auto_join) {
245  assert(!event_contexts.empty());
246  event_contexts.back().add_handler(this);
247  has_joined_ = true;
248  }
249 }
250 
252  : has_joined_(that.has_joined_)
253  , has_joined_global_(that.has_joined_global_)
254 {
255  if(has_joined_global_) {
256  assert(!event_contexts.empty());
257  event_contexts.front().add_handler(this);
258  } else if(has_joined_) {
259  bool found_context = false;
261  if(context.has_handler(&that)) {
262  found_context = true;
263  context.add_handler(this);
264  break;
265  }
266  }
267 
268  if (!found_context) {
269  throw std::logic_error("Copy-constructing a sdl_handler that has_joined_ but can't be found by searching contexts");
270  }
271  }
272 }
273 
275 {
276  if(that.has_joined_global_) {
277  join_global();
278  } else if(that.has_joined_) {
280  if(context.has_handler(&that)) {
281  join(context);
282  break;
283  }
284  }
285  } else if(has_joined_) {
286  leave();
287  } else if(has_joined_global_) {
288  leave_global();
289  }
290 
291  return *this;
292 }
293 
295 {
296  if(has_joined_) {
297  leave();
298  }
299 
300  if(has_joined_global_) {
301  leave_global();
302  }
303 }
304 
306 {
307  // this assert will fire if someone will inadvertently try to join
308  // an event context but might end up in the global context instead.
309  assert(&event_contexts.back() != &event_contexts.front());
310 
311  join(event_contexts.back());
312 }
313 
315 {
316  if(has_joined_global_) {
317  leave_global();
318  }
319 
320  if(has_joined_) {
321  leave(); // should not be in multiple event contexts
322  }
323 
324  // join self
325  c.add_handler(this);
326  has_joined_ = true;
327 
328  // instruct members to join
329  for(auto member : handler_members()) {
330  member->join(c);
331  }
332 }
333 
335 {
336  if(has_joined_) {
337  leave(); // should not be in multiple event contexts
338  }
339 
341  if(context.has_handler(parent)) {
342  join(context);
343  return;
344  }
345  }
346 
347  join(event_contexts.back());
348 }
349 
351 {
353 
354  if(members.empty()) {
355  assert(event_contexts.empty() == false);
356  }
357 
358  for(auto member : members) {
359  member->leave();
360  }
361 
363  if(context.remove_handler(this)) {
364  break;
365  }
366  }
367 
368  has_joined_ = false;
369 }
370 
372 {
373  if(has_joined_) {
374  leave();
375  }
376 
377  if(has_joined_global_) {
378  leave_global(); // Should not be in multiple event contexts
379  }
380 
381  // Join self
382  event_contexts.front().add_handler(this);
383  has_joined_global_ = true;
384 
385  // Instruct members to join
386  for(auto member : handler_members()) {
387  member->join_global();
388  }
389 }
390 
392 {
393  for(auto member : handler_members()) {
394  member->leave_global();
395  }
396 
397  event_contexts.front().remove_handler(this);
398 
399  has_joined_global_ = false;
400 }
401 
402 void focus_handler(const sdl_handler* ptr)
403 {
404  if(event_contexts.empty() == false) {
405  event_contexts.back().set_focus(ptr);
406  }
407 }
408 
409 bool has_focus(const sdl_handler* hand, const SDL_Event* event)
410 {
411  if(event_contexts.empty()) {
412  return true;
413  }
414 
415  if(hand->requires_event_focus(event) == false) {
416  return true;
417  }
418 
419  const handler_list::iterator foc = event_contexts.back().focused_handler;
420  auto& handlers = event_contexts.back().handlers;
421 
422  // If no-one has focus at the moment, this handler obviously wants
423  // focus, so give it to it.
424  if(foc == handlers.end()) {
425  focus_handler(hand);
426  return true;
427  }
428 
429  sdl_handler* const foc_hand = *foc;
430  if(foc_hand == hand) {
431  return true;
432  } else if(!foc_hand->requires_event_focus(event)) {
433  // If the currently focused handler doesn't need focus for this event
434  // allow the most recent interested handler to take care of it
435  for(auto i = handlers.rbegin(); i != handlers.rend(); ++i) {
436  sdl_handler* const thief_hand = *i;
437 
438  if(thief_hand != foc_hand && thief_hand->requires_event_focus(event)) {
439  // Steal focus
440  focus_handler(thief_hand);
441 
442  // Position the previously focused handler to allow stealing back
443  handlers.splice(handlers.end(), handlers, foc);
444 
445  return thief_hand == hand;
446  }
447  }
448  }
449 
450  return false;
451 }
452 
453 static void raise_window_event(const SDL_Event& event)
454 {
455  for(auto& context : event_contexts) {
456  for(auto handler : context.handlers) {
457  handler->handle_window_event(event);
458  }
459  }
460 
461  for(auto global_handler : event_contexts.front().handlers) {
462  global_handler->handle_window_event(event);
463  }
464 }
465 
467 
469 {
470  main_thread = std::this_thread::get_id();
471 }
472 
473 // this should probably be elsewhere, but as the main thread is already
474 // being tracked here, this went here.
476 {
477  return std::this_thread::get_id() == main_thread;
478 }
479 
480 void pump()
481 {
482  if(!is_in_main_thread()) {
483  // Can only call this on the main thread!
484  return;
485  }
486 
487  SDL_Event temp_event;
488  int poll_count = 0;
489  int begin_ignoring = 0;
490 
491  std::vector<SDL_Event> events;
492  while(SDL_PollEvent(&temp_event)) {
493  if(temp_event.type == INVOKE_FUNCTION_EVENT) {
494  static_cast<invoked_function_data*>(temp_event.user.data1)->call();
495  continue;
496  }
497 
498  ++poll_count;
499 
500  if(!begin_ignoring && temp_event.type == SDL_WINDOWEVENT && (
501  temp_event.window.event == SDL_WINDOWEVENT_ENTER ||
502  temp_event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED)
503  ) {
504  begin_ignoring = poll_count;
505  } else if(begin_ignoring > 0 && is_input(temp_event)) {
506  // ignore user input events that occurred after the window was activated
507  continue;
508  }
509 
510  events.push_back(temp_event);
511  }
512 
513  auto ev_it = events.begin();
514  for(int i = 1; i < begin_ignoring; ++i) {
515  if(is_input(*ev_it)) {
516  // ignore user input events that occurred before the window was activated
517  ev_it = events.erase(ev_it);
518  } else {
519  ++ev_it;
520  }
521  }
522 
523  for(SDL_Event& event : events) {
524  for(context& c : event_contexts) {
525  c.add_staging_handlers();
526  }
527 
528 #ifdef MOUSE_TOUCH_EMULATION
529  switch (event.type) {
530  // TODO: Implement SDL_MULTIGESTURE. Some day.
531  case SDL_MOUSEMOTION:
532  if(!events::is_touch(event.motion) && event.motion.state == 0) {
533  return;
534  }
535 
536  if(event.motion.state & SDL_BUTTON(SDL_BUTTON_RIGHT))
537  {
538  // Events are given by SDL in draw space
540 
541  // TODO: Check if SDL_FINGERMOTION is actually signaled for COMPLETE motions (I doubt, but tbs)
542  SDL_Event touch_event;
543  touch_event.type = SDL_FINGERMOTION;
544  touch_event.tfinger.type = SDL_FINGERMOTION;
545  touch_event.tfinger.timestamp = event.motion.timestamp;
546  touch_event.tfinger.touchId = 1;
547  touch_event.tfinger.fingerId = 1;
548  touch_event.tfinger.dx = static_cast<float>(event.motion.xrel) / c.x;
549  touch_event.tfinger.dy = static_cast<float>(event.motion.yrel) / c.y;
550  touch_event.tfinger.x = static_cast<float>(event.motion.x) / c.x;
551  touch_event.tfinger.y = static_cast<float>(event.motion.y) / c.y;
552  touch_event.tfinger.pressure = 1;
553  ::SDL_PushEvent(&touch_event);
554 
555  event.motion.state = SDL_BUTTON(SDL_BUTTON_LEFT);
556  event.motion.which = SDL_TOUCH_MOUSEID;
557  }
558  break;
559  case SDL_MOUSEBUTTONDOWN:
560  case SDL_MOUSEBUTTONUP:
561  if(event.button.button == SDL_BUTTON_RIGHT)
562  {
563  event.button.button = SDL_BUTTON_LEFT;
564  event.button.which = SDL_TOUCH_MOUSEID;
565 
566  // Events are given by SDL in draw space
568 
569  SDL_Event touch_event;
570  touch_event.type = (event.type == SDL_MOUSEBUTTONDOWN) ? SDL_FINGERDOWN : SDL_FINGERUP;
571  touch_event.tfinger.type = touch_event.type;
572  touch_event.tfinger.timestamp = event.button.timestamp;
573  touch_event.tfinger.touchId = 1;
574  touch_event.tfinger.fingerId = 1;
575  touch_event.tfinger.dx = 0;
576  touch_event.tfinger.dy = 0;
577  touch_event.tfinger.x = static_cast<float>(event.button.x) / c.x;
578  touch_event.tfinger.y = static_cast<float>(event.button.y) / c.y;
579  touch_event.tfinger.pressure = 1;
580  ::SDL_PushEvent(&touch_event);
581 
582  }
583  break;
584  default:
585  break;
586  }
587 #endif
588 
589  switch(event.type) {
590  case SDL_WINDOWEVENT:
591  switch(event.window.event) {
592  case SDL_WINDOWEVENT_ENTER:
593  case SDL_WINDOWEVENT_FOCUS_GAINED:
595  break;
596 
597  case SDL_WINDOWEVENT_LEAVE:
598  case SDL_WINDOWEVENT_FOCUS_LOST:
600  break;
601 
602  // Size changed is called before resized.
603  // We can ensure the video framebuffer is valid here.
604  case SDL_WINDOWEVENT_SIZE_CHANGED:
605  LOG_DP << "events/SIZE_CHANGED "
606  << event.window.data1 << 'x' << event.window.data2;
607  video::update_buffers(false);
608  break;
609 
610  // Resized comes after size_changed.
611  // Here we can trigger any watchers for resize events.
612  // Video settings such as game_canvas_size() will be correct.
613  case SDL_WINDOWEVENT_RESIZED:
614  LOG_DP << "events/RESIZED "
615  << event.window.data1 << 'x' << event.window.data2;
617  break;
618 
619  // Once everything has had a chance to respond to the resize,
620  // an expose is triggered to display the changed content.
621  case SDL_WINDOWEVENT_EXPOSED:
622  LOG_DP << "events/EXPOSED";
624  break;
625 
626  case SDL_WINDOWEVENT_MAXIMIZED:
627  LOG_DP << "events/MAXIMIZED";
628  prefs::get().set_maximized(true);
629  break;
630  case SDL_WINDOWEVENT_RESTORED:
631  LOG_DP << "events/RESTORED";
632  prefs::get().set_maximized(prefs::get().fullscreen());
633  break;
634  case SDL_WINDOWEVENT_SHOWN:
635  case SDL_WINDOWEVENT_MOVED:
636  // Not used.
637  break;
638  }
639 
640  raise_window_event(event);
641 
642  // This event was just distributed, don't re-distribute.
643  continue;
644 
645  case SDL_MOUSEMOTION: {
646  // Always make sure a cursor is displayed if the mouse moves or if the user clicks
647  cursor::set_focus(true);
648  process_tooltip_strings(event.motion.x, event.motion.y);
649  break;
650  }
651 
652  case SDL_MOUSEBUTTONDOWN: {
653  // Always make sure a cursor is displayed if the mouse moves or if the user clicks
654  cursor::set_focus(true);
655  break;
656  }
657 
658 #ifndef __APPLE__
659  case SDL_KEYDOWN: {
660  if(event.key.keysym.sym == SDLK_F4 &&
661  (event.key.keysym.mod == KMOD_RALT || event.key.keysym.mod == KMOD_LALT)
662  ) {
664  continue; // this event is already handled
665  }
666  break;
667  }
668 #endif
669 
670 #if defined _WIN32
671  case SDL_SYSWMEVENT: {
673  break;
674  }
675 #endif
676 
677  case SDL_QUIT: {
679  continue; // this event is already handled.
680  }
681  }
682 
683  for(auto global_handler : event_contexts.front().handlers) {
684  global_handler->handle_event(event);
685  }
686 
687  if(event_contexts.empty() == false) {
688  // As pump() can recurse, pretty much anything can happen here
689  // including destroying handlers or the event context.
690  std::size_t ec_index = event_contexts.size();
691  context& c = event_contexts.back();
692  handler_list& h = c.handlers;
693  std::size_t h_size = h.size();
694  for(auto it = h.begin(); it != h.end(); ++it) {
695  // Pass the event on to the handler.
696  (*it)->handle_event(event);
697  // Escape if anything has changed.
698  if(event_contexts.size() != ec_index) {
699  LOG_EV << "ec size changed! bugging out";
700  break;
701  }
702  if(h_size != h.size()) {
703  LOG_EV << "h size changed! bugging out";
704  break;
705  }
706  }
707  }
708  }
709 
710  // Inform the pump monitors that an events::pump() has occurred
711  for(auto monitor : pump_monitors) {
712  monitor->process();
713  }
714 }
715 
716 void draw()
717 {
719 }
720 
722 {
723  if(event_contexts.empty() == false) {
724  event_contexts.back().add_staging_handlers();
725 
726  for(auto handler : event_contexts.back().handlers) {
727  handler->process_event();
728  }
729  }
730 }
731 
733 {
735  SDL_Event event;
736  event.window.type = SDL_WINDOWEVENT;
737  event.window.event = SDL_WINDOWEVENT_SIZE_CHANGED;
738  event.window.windowID = 0; // We don't check this anyway... I think...
739  event.window.data1 = size.x;
740  event.window.data2 = size.y;
741 
742  raise_window_event(event);
743 }
744 
745 void process_tooltip_strings(int mousex, int mousey)
746 {
747  if(event_contexts.empty() == false) {
748  for(auto handler : event_contexts.back().handlers) {
749  handler->process_tooltip_string(mousex, mousey);
750  }
751  }
752 }
753 
754 /* The constants for the minimum and maximum are picked from the headers. */
755 #define INPUT_MIN 0x300
756 #define INPUT_MAX 0x8FF
757 
758 bool is_input(const SDL_Event& event)
759 {
760  return event.type >= INPUT_MIN && event.type <= INPUT_MAX;
761 }
762 
764 {
765  SDL_FlushEvents(INPUT_MIN, INPUT_MAX);
766 }
767 
768 bool is_touch(const SDL_MouseButtonEvent &event)
769 {
770  return event.which == SDL_TOUCH_MOUSEID;
771 }
772 
773 bool is_touch(const SDL_MouseMotionEvent &event)
774 {
775  return event.which == SDL_TOUCH_MOUSEID;
776 }
777 
778 void call_in_main_thread(const std::function<void(void)>& f)
779 {
780  if(is_in_main_thread()) {
781  // nothing special to do if called from the main thread.
782  f();
783  return;
784  }
785 
786  invoked_function_data fdata{f};
787 
788  SDL_Event sdl_event;
789  sdl::UserEvent sdl_userevent(INVOKE_FUNCTION_EVENT, &fdata);
790 
791  sdl_event.type = INVOKE_FUNCTION_EVENT;
792  sdl_event.user = sdl_userevent;
793 
794  SDL_PushEvent(&sdl_event);
795 
796  // Block until execution is complete in the main thread. Rethrows any exceptions.
797  fdata.finished.get_future().wait();
798 }
799 
800 } // end events namespace
void set_focus(const sdl_handler *ptr)
Definition: events.cpp:183
bool has_handler(const sdl_handler *ptr) const
Returns true if ptr is found in either the handlers or staging_handlers lists.
Definition: events.cpp:98
void cycle_focus()
Definition: events.cpp:150
handler_list::iterator focused_handler
Definition: events.hpp:61
std::vector< sdl_handler * > staging_handlers
Definition: events.hpp:62
handler_list handlers
Definition: events.hpp:60
void add_handler(sdl_handler *ptr)
Definition: events.cpp:89
bool remove_handler(sdl_handler *ptr)
Definition: events.cpp:103
void add_staging_handlers()
Definition: events.cpp:191
virtual ~pump_monitor()
Definition: events.cpp:224
virtual bool requires_event_focus(const SDL_Event *=nullptr) const
Definition: events.hpp:80
virtual std::vector< sdl_handler * > handler_members()
Definition: events.hpp:108
virtual void join()
Definition: events.cpp:305
virtual void leave()
Definition: events.cpp:350
virtual void join_global()
Definition: events.cpp:371
virtual void join_same(sdl_handler *parent)
Definition: events.cpp:334
virtual void leave_global()
Definition: events.cpp:391
sdl_handler & operator=(sdl_handler &&)=delete
Moving would require two instances' context membership to be handled, it's simpler to delete these an...
virtual ~sdl_handler()
Definition: events.cpp:294
sdl_handler(sdl_handler &&)=delete
static prefs & get()
void set_resolution(const point &res)
static void quit_to_desktop()
Type that can be thrown as an exception to quit to desktop.
Definition: video.hpp:328
static void handle_system_event(const SDL_Event &event)
Frees resources when a notification disappears, switches user to the wesnoth window if the notificati...
#define INPUT_MAX
Definition: events.cpp:756
#define LOG_EV
Definition: events.cpp:50
#define INPUT_MIN
Definition: events.cpp:755
#define LOG_DP
Definition: events.cpp:47
static lg::log_domain log_display("display")
static lg::log_domain log_event("event")
#define DBG_EV
Definition: events.cpp:51
#define INVOKE_FUNCTION_EVENT
Definition: events.hpp:29
std::vector< events::sdl_handler * > sdl_handler_vector
Definition: events.hpp:197
std::size_t i
Definition: function.cpp:1032
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:199
Standard logging facilities (interface).
void set_focus(bool focus)
Definition: cursor.cpp:223
void invalidate_all()
Mark the entire screen as requiring redraw.
void sparkle()
Ensure that everything which needs to be drawn is drawn.
Handling of system events.
void raise_resize_event()
Definition: events.cpp:732
bool has_focus(const sdl_handler *hand, const SDL_Event *event)
Definition: events.cpp:409
void discard_input()
Discards all input events.
Definition: events.cpp:763
void draw()
Trigger a draw cycle.
Definition: events.cpp:716
static std::thread::id main_thread
Definition: events.cpp:466
static void raise_window_event(const SDL_Event &event)
Definition: events.cpp:453
bool is_input(const SDL_Event &event)
Is the event an input event?
Definition: events.cpp:758
void set_main_thread()
Definition: events.cpp:468
std::deque< context > event_contexts
Definition: events.cpp:215
std::vector< pump_monitor * > pump_monitors
Definition: events.cpp:217
void call_in_main_thread(const std::function< void(void)> &f)
Definition: events.cpp:778
void raise_process_event()
Definition: events.cpp:721
void process_tooltip_strings(int mousex, int mousey)
Triggered by mouse-motion, sends the cursor position to all handlers to check whether a tooltip shoul...
Definition: events.cpp:745
std::list< sdl_handler * > handler_list
Definition: events.hpp:34
bool is_in_main_thread()
Definition: events.cpp:475
void pump()
Process all events currently in the queue.
Definition: events.cpp:480
bool is_touch(const SDL_MouseButtonEvent &event)
Check if this mouse button event is caused by a touch.
Definition: events.cpp:768
void focus_handler(const sdl_handler *ptr)
Definition: events.cpp:402
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
constexpr auto reverse
Definition: ranges.hpp:40
std::size_t erase(Container &container, const Value &value)
Convenience wrapper for using std::remove on a container.
Definition: general.hpp:118
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:87
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:141
point game_canvas_size()
The size of the game canvas, in drawing coordinates / game pixels.
Definition: video.cpp:449
point window_size()
Returns the size of the window in display units / screen coordinates.
Definition: video.cpp:436
void update_buffers(bool autoupdate)
Update buffers to match current resolution and pixel scale settings.
Definition: video.cpp:868
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
Holds a 2D point.
Definition: point.hpp:25
mock_char c
#define h
#define f