The Battle for Wesnoth  1.19.26+dev
mouse_handler_base.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2025
3  by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
4  Copyright (C) 2003 by David White <dave@whitevine.net>
5  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
6 
7  This program is free software; you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation; either version 2 of the License, or
10  (at your option) any later version.
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY.
13 
14  See the COPYING file for more details.
15 */
16 
17 #include "mouse_handler_base.hpp"
18 
19 #include "cursor.hpp"
20 #include "display.hpp"
21 #include "gui/widgets/settings.hpp"
22 #include "log.hpp"
24 #include "sdl/rect.hpp"
25 #include "tooltips.hpp"
26 #include "sdl/input.hpp" // get_mouse_state
27 
28 static lg::log_domain log_display("display");
29 #define WRN_DP LOG_STREAM(warn, log_display)
30 
31 namespace events
32 {
34 {
36 }
37 
39 {
41 }
42 
44 
45 static bool command_active()
46 {
47 #ifdef __APPLE__
48  return (SDL_GetModState() & SDL_KMOD_CTRL) != 0;
49 #else
50  return false;
51 #endif
52 }
53 
55 {
56  return dragging_started_;
57 }
58 
60 {
62 }
63 
64 void mouse_handler_base::mouse_motion_event(const SDL_MouseMotionEvent& event, const bool browse)
65 {
66  mouse_motion(event.x, event.y, browse);
67 }
68 
69 void mouse_handler_base::touch_motion_event(const SDL_TouchFingerEvent& event, const bool browse)
70 {
71  // This is wrong (needs to be scaled from -1..1 to screen size), but it's discarded in touch_motion anyway.
72  // Let's not waste CPU cycles.
73  touch_motion(event.x, event.y, browse);
74 }
75 
77 {
78  auto [x, y] = sdl::get_mouse_location();
79  mouse_motion(x, y, browse, true, loc);
80 }
81 
82 bool mouse_handler_base::mouse_motion_default(int x, int y, bool /*update*/)
83 {
84  tooltips::process(x, y);
85 
86  if(simple_warp_) {
87  return true;
88  }
89 
90  if(minimap_scrolling_) {
91  // if the game is run in a window, we could miss a LMB/MMB up event
92  // if it occurs outside our window.
93  // thus, we need to check if the LMB/MMB is still down
94  minimap_scrolling_ = ((sdl::get_mouse_button_mask() & (SDL_BUTTON_MASK(SDL_BUTTON_LEFT) | SDL_BUTTON_MASK(SDL_BUTTON_MIDDLE))) != 0);
95  if(minimap_scrolling_) {
96  const map_location& loc = gui().minimap_location_on(x, y);
97  if(loc.valid()) {
98  if(loc != last_hex_) {
99  last_hex_ = loc;
100  gui().scroll_to_tile(loc, display::WARP, false);
101  }
102  } else {
103  // clicking outside of the minimap will end minimap scrolling
104  minimap_scrolling_ = false;
105  }
106  }
107 
108  if(minimap_scrolling_) {
109  return true;
110  }
111  }
112 
113  // Fire the drag & drop only after minimal drag distance
114  // While we check the mouse buttons state, we also grab fresh position data.
115 
116  if(is_dragging() && !dragging_started_) {
117  point pos = drag_from_; // some default value to prevent unlikely SDL bug
118  float mx = pos.x;
119  float my = pos.y;
120  uint32_t mouse_state = dragging_left_ || dragging_right_ ? sdl::get_mouse_state(&mx, &my) : 0;
121  pos.x = static_cast<int>(mx);
122  pos.y = static_cast<int>(my);
123 
124 #ifdef MOUSE_TOUCH_EMULATION
125  if(dragging_left_ && (mouse_state & SDL_BUTTON_MASK(SDL_BUTTON_RIGHT))) {
126  // Monkey-patch touch controls again to make them look like left button.
127  mouse_state = SDL_BUTTON_MASK(SDL_BUTTON_LEFT);
128  }
129 #endif
130  if((dragging_left_ && (mouse_state & SDL_BUTTON_MASK(SDL_BUTTON_LEFT)) != 0) ||
131  (dragging_right_ && (mouse_state & SDL_BUTTON_MASK(SDL_BUTTON_RIGHT)) != 0))
132  {
133  const double drag_distance =
134  std::pow(static_cast<double>(drag_from_.x - pos.x), 2) +
135  std::pow(static_cast<double>(drag_from_.y - pos.y), 2);
136 
137  if(drag_distance > drag_threshold() * drag_threshold()) {
138  dragging_started_ = true;
139  cursor::set_dragging(true);
140  }
141  }
142  }
143 
144  return false;
145 }
146 
148  const SDL_MouseButtonEvent& /*event*/, uint8_t /*button*/, map_location /*loc*/, bool /*click*/)
149 {
150  return false;
151 }
152 
153 void mouse_handler_base::mouse_press(const SDL_MouseButtonEvent& event, const bool browse)
154 {
155  if(is_middle_click(event) && !prefs::get().middle_click_scrolls()) {
156  simple_warp_ = true;
157  }
158 
159  show_menu_ = false;
160  map_location loc = gui().hex_clicked_on(event.x, event.y);
161  mouse_update(browse, loc);
162 
163  if(events::is_touch(event)) {
164  static std::chrono::steady_clock::time_point touch_timestamp;
165  const auto touch_time = gui2::settings::popup_show_delay;
166  const auto now = std::chrono::steady_clock::now();
167 
168  if(event.down == true) {
169  cancel_dragging();
170  touch_timestamp = now;
172  if (!mouse_button_event(event, SDL_BUTTON_LEFT, loc, true)) {
173  left_click(event.x, event.y, browse);
174  }
175  } else if(event.down == false) {
176  minimap_scrolling_ = false;
177 
178  if (!dragging_started_ && touch_timestamp != std::chrono::steady_clock::time_point{}) {
179  auto dt = now - touch_timestamp;
180  if (dt > touch_time) {
181  if (!mouse_button_event(event, SDL_BUTTON_RIGHT, loc, true)) {
182  // BUG: This function won't do anything in the game, need right_mouse_up()
183  right_click(event.x, event.y, browse); // show_menu_ = true;
184  }
185  }
186  } else {
187  touch_timestamp = {};
188  }
189 
190  clear_dragging(event, browse);
191  mouse_button_event(event, SDL_BUTTON_LEFT, loc);
192  left_mouse_up(event.x, event.y, browse);
194  }
195  } else if(is_left_click(event)) {
196  if(event.down == true) {
197  cancel_dragging();
199  if (!mouse_button_event(event, SDL_BUTTON_LEFT, loc, true)) {
200  left_click(event.x, event.y, browse);
201  }
202  } else if(event.down == false) {
203  minimap_scrolling_ = false;
204  clear_dragging(event, browse);
205  mouse_button_event(event, SDL_BUTTON_LEFT, loc);
206  left_mouse_up(event.x, event.y, browse);
208  }
209  } else if(is_right_click(event)) {
210  if(event.down == true) {
211  mouse_button_event(event, SDL_BUTTON_RIGHT, loc);
212  cancel_dragging();
214  right_click(event.x, event.y, browse);
215  } else if(event.down == false) {
216  minimap_scrolling_ = false;
217  clear_dragging(event, browse);
218  if (!mouse_button_event(event, SDL_BUTTON_RIGHT, loc, true)) {
219  right_mouse_up(event.x, event.y, browse);
220  }
222  }
223  } else if(is_middle_click(event)) {
224  if(event.down == true) {
226  set_scroll_start(event.x, event.y);
227  scroll_started_ = true;
228 
229  map_location minimap_loc = gui().minimap_location_on(event.x, event.y);
230  minimap_scrolling_ = false;
231  if(minimap_loc.valid()) {
232  simple_warp_ = false;
233  minimap_scrolling_ = true;
234  last_hex_ = minimap_loc;
235  gui().scroll_to_tile(minimap_loc, display::WARP, false);
236  } else if(mouse_button_event(event, SDL_BUTTON_MIDDLE, loc, true)) {
237  scroll_started_ = false;
238  simple_warp_ = false;
239  } else if(simple_warp_) {
240  // middle click not on minimap, check gamemap instead
241  if(loc.valid()) {
242  last_hex_ = loc;
243  gui().scroll_to_tile(loc, display::WARP, false);
244  }
245  } else {
246  // Deselect the current tile as we're scrolling
247  gui().highlight_hex({-1,-1});
248  }
249  } else if(!event.down) {
250  minimap_scrolling_ = false;
251  simple_warp_ = false;
252  scroll_started_ = false;
253  mouse_button_event(event, SDL_BUTTON_MIDDLE, loc);
255  }
256  } else if(event.button == SDL_BUTTON_X1 || event.button == SDL_BUTTON_X2) {
257  if(event.down) {
258  cancel_dragging();
259  // record mouse-down hex in drag_from_hex_
261  mouse_button_event(event, event.button, loc);
262  } else {
263  mouse_button_event(event, event.button, loc, true);
265  }
266  }
268  dragging_started_ = false;
269  cursor::set_dragging(false);
270  }
271 
272  mouse_update(browse, loc);
273 }
274 
275 bool mouse_handler_base::is_left_click(const SDL_MouseButtonEvent& event) const
276 {
277 #ifdef MOUSE_TOUCH_EMULATION
278  if(event.button == SDL_BUTTON_RIGHT) {
279  return true;
280  }
281 #endif
282  if(events::is_touch(event)) {
283  return false;
284  }
285  return event.button == SDL_BUTTON_LEFT && !command_active();
286 }
287 
288 bool mouse_handler_base::is_middle_click(const SDL_MouseButtonEvent& event) const
289 {
290  return event.button == SDL_BUTTON_MIDDLE;
291 }
292 
293 bool mouse_handler_base::is_right_click(const SDL_MouseButtonEvent& event) const
294 {
295 #ifdef MOUSE_TOUCH_EMULATION
296  (void) event;
297  return false;
298 #else
299  if(events::is_touch(event)) {
300  return false;
301  }
302  return event.button == SDL_BUTTON_RIGHT
303  || (event.button == SDL_BUTTON_LEFT && command_active());
304 #endif
305 }
306 
307 bool mouse_handler_base::left_click(int x, int y, const bool /*browse*/)
308 {
309  if(gui().view_locked()) {
310  return false;
311  }
312 
313  // clicked on a hex on the minimap? then initiate minimap scrolling
314  const map_location& loc = gui().minimap_location_on(x, y);
315  minimap_scrolling_ = false;
316  if(loc.valid()) {
317  minimap_scrolling_ = true;
318  last_hex_ = loc;
319  gui().scroll_to_tile(loc, display::WARP, false);
320  return true;
321  }
322 
323  return false;
324 }
325 
326 void mouse_handler_base::touch_action(const map_location /*hex*/, bool /*browse*/)
327 {
328 }
329 
330 void mouse_handler_base::left_drag_end(int /*x*/, int /*y*/, const bool browse)
331 {
332  move_action(browse);
333 }
334 
335 void mouse_handler_base::mouse_wheel(int scrollx, int scrolly, bool browse)
336 {
337  float x, y;
338  sdl::get_mouse_state(&x, &y);
339 
340  int movex = scrollx * prefs::get().scroll_speed();
341  int movey = scrolly * prefs::get().scroll_speed();
342 
343  // Don't scroll map if cursor is not in gamemap area
344  if(!gui().map_area().contains(x, y)) {
345  return;
346  }
347 
348  if(movex != 0 || movey != 0) {
349  CKey pressed;
350  // Alt + mousewheel do an 90° rotation on the scroll direction
351  if(pressed[SDLK_LALT] || pressed[SDLK_RALT]) {
352  gui().scroll(point{movey, movex});
353  } else {
354  gui().scroll(point{movex, movey});
355  }
356  }
357 
358  if(scrollx < 0) {
359  mouse_wheel_left(x, y, browse);
360  } else if(scrollx > 0) {
361  mouse_wheel_right(x, y, browse);
362  }
363 
364  if(scrolly < 0) {
365  mouse_wheel_up(x, y, browse);
366  } else if(scrolly > 0) {
367  mouse_wheel_down(x, y, browse);
368  }
369 }
370 
371 void mouse_handler_base::right_mouse_up(int x, int y, const bool browse)
372 {
373  if(!right_click_show_menu(x, y, browse)) {
374  return;
375  }
376 
377  const theme::menu* const m = gui().get_theme().context_menu();
378  if(m != nullptr) {
379  show_menu_ = true;
380  } else {
381  WRN_DP << "no context menu found...";
382  }
383 }
384 
385 void mouse_handler_base::init_dragging(bool& dragging_flag)
386 {
387  dragging_flag = true;
390 }
391 
393 {
394  dragging_started_ = false;
395  dragging_left_ = false;
396  dragging_touch_ = false;
397  dragging_right_ = false;
398  cursor::set_dragging(false);
399 }
400 
401 void mouse_handler_base::clear_dragging(const SDL_MouseButtonEvent& event, bool browse)
402 {
403  // we reset dragging info before calling functions
404  // because they may take time to return, and we
405  // could have started other drag&drop before that
406  cursor::set_dragging(false);
407 
408  if(dragging_started_) {
409  dragging_started_ = false;
410 
411  if(dragging_touch_) {
412  dragging_touch_ = false;
413  // Maybe to do: create touch_drag_end(). Do panning and what else there. OTOH, it's fine now.
414  left_drag_end(event.x, event.y, browse);
415  }
416 
417  if(dragging_left_) {
418  dragging_left_ = false;
419  left_drag_end(event.x, event.y, browse);
420  }
421 
422  if(dragging_right_) {
423  dragging_right_ = false;
424  right_drag_end(event.x, event.y, browse);
425  }
426  } else {
427  dragging_left_ = false;
428  dragging_right_ = false;
429  dragging_touch_ = false;
430  }
431 }
432 
434 {
436 }
437 
438 } // end namespace events
map_location loc
Definition: move.cpp:172
Class that keeps track of all the keys on the keyboard.
Definition: key.hpp:29
virtual void highlight_hex(map_location hex)
Definition: display.cpp:1383
void scroll_to_tile(const map_location &loc, SCROLL_TYPE scroll_type=ONSCREEN, bool check_fogged=true, bool force=true)
Scroll such that location loc is on-screen.
Definition: display.cpp:1858
map_location minimap_location_on(int x, int y)
given x,y co-ordinates of the mouse, will return the location of the hex in the minimap that the mous...
Definition: display.cpp:686
theme & get_theme()
Definition: display.hpp:379
map_location hex_clicked_on(int x, int y) const
given x,y co-ordinates of an onscreen pixel, will return the location of the hex that this pixel corr...
Definition: display.cpp:528
bool scroll(const point &amount, bool force=false)
Scrolls the display by amount pixels.
Definition: display.cpp:1583
virtual bool mouse_button_event(const SDL_MouseButtonEvent &event, uint8_t button, map_location loc, bool click=false)
bool show_menu_
Show context menu flag.
bool dragging_right_
RMB drag init flag.
virtual void left_drag_end(int, int, const bool)
Called whenever the left mouse drag has "ended".
bool minimap_scrolling_
minimap scrolling (scroll-drag) state flag
bool dragging_left_
LMB drag init flag.
map_location last_hex_
last highlighted hex
virtual void mouse_wheel_right(int, int, const bool)
Called when the mouse wheel is scrolled right.
bool scroll_started_
Scroll start flag.
virtual void mouse_wheel_down(int, int, const bool)
Called when the mouse wheel is scrolled down.
map_location drag_from_hex_
Drag start or mouse-down map location.
virtual int drag_threshold() const
Minimum dragging distance to fire the drag&drop.
virtual void right_drag_end(int, int, const bool)
Called whenever the right mouse drag has "ended".
void touch_motion_event(const SDL_TouchFingerEvent &event, const bool browse)
bool is_left_click(const SDL_MouseButtonEvent &event) const
virtual void mouse_press(const SDL_MouseButtonEvent &event, const bool browse)
bool dragging_started() const
If mouse/finger has moved far enough to consider it move/swipe, and not a click/touch.
virtual void mouse_wheel_up(int, int, const bool)
Called when the mouse wheel is scrolled up.
bool simple_warp_
MMB click (on game map) state flag.
bool is_middle_click(const SDL_MouseButtonEvent &event) const
void mouse_update(const bool browse, map_location loc)
Update the mouse with a fake mouse motion.
void init_dragging(bool &dragging_flag)
virtual void left_mouse_up(int, int, const bool)
Called when the left mouse button is up.
bool is_right_click(const SDL_MouseButtonEvent &event) const
bool mouse_motion_default(int x, int y, bool update)
This handles minimap scrolling and click-drag.
bool dragging_started_
Actual drag flag.
virtual bool right_click_show_menu(int, int, const bool)
Called in the default right_click when the context menu is about to be shown, can be used for preproc...
virtual display & gui()=0
Reference to the used display objects.
virtual bool right_click(int x, int y, const bool browse)
Overridden in derived classes, called on a right click (mousedown).
void mouse_motion_event(const SDL_MouseMotionEvent &event, const bool browse)
virtual void right_mouse_up(int, int, const bool)
Called when the right mouse button is up.
virtual void touch_motion(int x, int y, const bool browse, bool update=false, map_location new_loc=map_location::null_location())=0
virtual void touch_action(const map_location hex, bool browse)
bool dragging_touch_
Finger drag init flag.
virtual void mouse_wheel(int xscroll, int yscroll, bool browse)
Called when scrolling with the mouse wheel.
virtual void move_action(bool)
Overridden in derived class.
virtual bool left_click(int x, int y, const bool browse)
Overridden in derived classes, called on a left click (mousedown).
point drag_from_
Drag start position.
virtual void mouse_wheel_left(int, int, const bool)
Called when the mouse wheel is scrolled left.
void clear_dragging(const SDL_MouseButtonEvent &event, bool browse)
virtual void mouse_motion(int x, int y, const bool browse, bool update=false, map_location new_loc=map_location::null_location())=0
Called when a mouse motion event takes place.
void set_scroll_start(int x, int y)
Called when the middle click scrolling.
static prefs & get()
int scroll_speed()
const menu * context_menu() const
Definition: theme.hpp:260
map_display and display: classes which take care of displaying the map and game-data on the screen.
Contains functions for cleanly handling SDL input.
Standard logging facilities (interface).
#define WRN_DP
static lg::log_domain log_display("display")
void set_dragging(bool drag)
Definition: cursor.cpp:185
Handling of system events.
static bool command_active()
bool is_touch(const SDL_MouseButtonEvent &event)
Check if this mouse button event is caused by a touch.
Definition: events.cpp:763
std::chrono::milliseconds popup_show_delay
Delay before a popup shows.
Definition: settings.cpp:38
uint32_t get_mouse_button_mask()
Returns the current mouse button mask.
Definition: input.cpp:49
uint32_t get_mouse_state(float *x, float *y)
A wrapper for SDL_GetMouseState that gives coordinates in draw space.
Definition: input.cpp:27
point get_mouse_location()
Returns the current mouse location in draw space.
Definition: input.cpp:54
void process(int mousex, int mousey)
Definition: tooltips.cpp:334
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:87
Contains the SDL_Rect helper code.
This file contains the settings handling of the widget library.
Encapsulates the map of the game.
Definition: location.hpp:46
bool valid() const
Definition: location.hpp:111
static const map_location & null_location()
Definition: location.hpp:103
Holds a 2D point.
Definition: point.hpp:25