The Battle for Wesnoth  1.19.13+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() & KMOD_CTRL) != 0;
49 #else
50  return false;
51 #endif
52 }
53 
55  : simple_warp_(false)
56  , minimap_scrolling_(false)
57  , dragging_left_(false)
58  , dragging_touch_(false)
59  , dragging_started_(false)
60  , dragging_right_(false)
61  , drag_from_(0, 0)
62  , drag_from_hex_()
63  , last_hex_()
64  , show_menu_(false)
65  , scroll_start_x_(0)
66  , scroll_start_y_(0)
67  , scroll_started_(false)
68 {
69 }
70 
72 {
73  return dragging_started_;
74 }
75 
77 {
79 }
80 
81 void mouse_handler_base::mouse_motion_event(const SDL_MouseMotionEvent& event, const bool browse)
82 {
83  mouse_motion(event.x, event.y, browse);
84 }
85 
86 void mouse_handler_base::touch_motion_event(const SDL_TouchFingerEvent& event, const bool browse)
87 {
88  // This is wrong (needs to be scaled from -1..1 to screen size), but it's discarded in touch_motion anyway.
89  // Let's not waste CPU cycles.
90  touch_motion(event.x, event.y, browse);
91 }
92 
94 {
95  auto [x, y] = sdl::get_mouse_location();
96  mouse_motion(x, y, browse, true, loc);
97 }
98 
99 bool mouse_handler_base::mouse_motion_default(int x, int y, bool /*update*/)
100 {
101  tooltips::process(x, y);
102 
103  if(simple_warp_) {
104  return true;
105  }
106 
107  if(minimap_scrolling_) {
108  // if the game is run in a window, we could miss a LMB/MMB up event
109  // if it occurs outside our window.
110  // thus, we need to check if the LMB/MMB is still down
111  minimap_scrolling_ = ((sdl::get_mouse_button_mask() & (SDL_BUTTON(SDL_BUTTON_LEFT) | SDL_BUTTON(SDL_BUTTON_MIDDLE))) != 0);
112  if(minimap_scrolling_) {
113  const map_location& loc = gui().minimap_location_on(x, y);
114  if(loc.valid()) {
115  if(loc != last_hex_) {
116  last_hex_ = loc;
117  gui().scroll_to_tile(loc, display::WARP, false);
118  }
119  } else {
120  // clicking outside of the minimap will end minimap scrolling
121  minimap_scrolling_ = false;
122  }
123  }
124 
125  if(minimap_scrolling_) {
126  return true;
127  }
128  }
129 
130  // Fire the drag & drop only after minimal drag distance
131  // While we check the mouse buttons state, we also grab fresh position data.
132 
133  if(is_dragging() && !dragging_started_) {
134  point pos = drag_from_; // some default value to prevent unlikely SDL bug
135  uint32_t mouse_state = dragging_left_ || dragging_right_ ? sdl::get_mouse_state(&pos.x, &pos.y) : 0;
136 
137 #ifdef MOUSE_TOUCH_EMULATION
138  if(dragging_left_ && (mouse_state & SDL_BUTTON(SDL_BUTTON_RIGHT))) {
139  // Monkey-patch touch controls again to make them look like left button.
140  mouse_state = SDL_BUTTON(SDL_BUTTON_LEFT);
141  }
142 #endif
143  if((dragging_left_ && (mouse_state & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0) ||
144  (dragging_right_ && (mouse_state & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0))
145  {
146  const double drag_distance =
147  std::pow(static_cast<double>(drag_from_.x - pos.x), 2) +
148  std::pow(static_cast<double>(drag_from_.y - pos.y), 2);
149 
150  if(drag_distance > drag_threshold() * drag_threshold()) {
151  dragging_started_ = true;
152  cursor::set_dragging(true);
153  }
154  }
155  }
156 
157  return false;
158 }
159 
161  const SDL_MouseButtonEvent& /*event*/, uint8_t /*button*/, map_location /*loc*/, bool /*click*/)
162 {
163  return false;
164 }
165 
166 void mouse_handler_base::mouse_press(const SDL_MouseButtonEvent& event, const bool browse)
167 {
168  if(is_middle_click(event) && !prefs::get().middle_click_scrolls()) {
169  simple_warp_ = true;
170  }
171 
172  show_menu_ = false;
173  map_location loc = gui().hex_clicked_on(event.x, event.y);
174  mouse_update(browse, loc);
175 
176  if(events::is_touch(event)) {
177  static std::chrono::steady_clock::time_point touch_timestamp;
178  const auto touch_time = gui2::settings::popup_show_delay;
179  const auto now = std::chrono::steady_clock::now();
180 
181  if(event.state == SDL_PRESSED) {
182  cancel_dragging();
183  touch_timestamp = now;
185  if (!mouse_button_event(event, SDL_BUTTON_LEFT, loc, true)) {
186  left_click(event.x, event.y, browse);
187  }
188  } else if(event.state == SDL_RELEASED) {
189  minimap_scrolling_ = false;
190 
191  if (!dragging_started_ && touch_timestamp != std::chrono::steady_clock::time_point{}) {
192  auto dt = now - touch_timestamp;
193  if (dt > touch_time) {
194  if (!mouse_button_event(event, SDL_BUTTON_RIGHT, loc, true)) {
195  // BUG: This function won't do anything in the game, need right_mouse_up()
196  right_click(event.x, event.y, browse); // show_menu_ = true;
197  }
198  }
199  } else {
200  touch_timestamp = {};
201  }
202 
203  clear_dragging(event, browse);
204  mouse_button_event(event, SDL_BUTTON_LEFT, loc);
205  left_mouse_up(event.x, event.y, browse);
207  }
208  } else if(is_left_click(event)) {
209  if(event.state == SDL_PRESSED) {
210  cancel_dragging();
212  if (!mouse_button_event(event, SDL_BUTTON_LEFT, loc, true)) {
213  left_click(event.x, event.y, browse);
214  }
215  } else if(event.state == SDL_RELEASED) {
216  minimap_scrolling_ = false;
217  clear_dragging(event, browse);
218  mouse_button_event(event, SDL_BUTTON_LEFT, loc);
219  left_mouse_up(event.x, event.y, browse);
221  }
222  } else if(is_right_click(event)) {
223  if(event.state == SDL_PRESSED) {
224  mouse_button_event(event, SDL_BUTTON_RIGHT, loc);
225  cancel_dragging();
227  right_click(event.x, event.y, browse);
228  } else if(event.state == SDL_RELEASED) {
229  minimap_scrolling_ = false;
230  clear_dragging(event, browse);
231  if (!mouse_button_event(event, SDL_BUTTON_RIGHT, loc, true)) {
232  right_mouse_up(event.x, event.y, browse);
233  }
235  }
236  } else if(is_middle_click(event)) {
237  if(event.state == SDL_PRESSED) {
239  set_scroll_start(event.x, event.y);
240  scroll_started_ = true;
241 
242  map_location minimap_loc = gui().minimap_location_on(event.x, event.y);
243  minimap_scrolling_ = false;
244  if(minimap_loc.valid()) {
245  simple_warp_ = false;
246  minimap_scrolling_ = true;
247  last_hex_ = minimap_loc;
248  gui().scroll_to_tile(minimap_loc, display::WARP, false);
249  } else if(mouse_button_event(event, SDL_BUTTON_MIDDLE, loc, true)) {
250  scroll_started_ = false;
251  simple_warp_ = false;
252  } else if(simple_warp_) {
253  // middle click not on minimap, check gamemap instead
254  if(loc.valid()) {
255  last_hex_ = loc;
256  gui().scroll_to_tile(loc, display::WARP, false);
257  }
258  } else {
259  // Deselect the current tile as we're scrolling
260  gui().highlight_hex({-1,-1});
261  }
262  } else if(event.state == SDL_RELEASED) {
263  minimap_scrolling_ = false;
264  simple_warp_ = false;
265  scroll_started_ = false;
266  mouse_button_event(event, SDL_BUTTON_MIDDLE, loc);
268  }
269  } else if(event.button == SDL_BUTTON_X1 || event.button == SDL_BUTTON_X2) {
270  if(event.state == SDL_PRESSED) {
271  cancel_dragging();
272  // record mouse-down hex in drag_from_hex_
274  mouse_button_event(event, event.button, loc);
275  } else {
276  mouse_button_event(event, event.button, loc, true);
278  }
279  }
281  dragging_started_ = false;
282  cursor::set_dragging(false);
283  }
284 
285  mouse_update(browse, loc);
286 }
287 
288 bool mouse_handler_base::is_left_click(const SDL_MouseButtonEvent& event) const
289 {
290 #ifdef MOUSE_TOUCH_EMULATION
291  if(event.button == SDL_BUTTON_RIGHT) {
292  return true;
293  }
294 #endif
295  if(events::is_touch(event)) {
296  return false;
297  }
298  return event.button == SDL_BUTTON_LEFT && !command_active();
299 }
300 
301 bool mouse_handler_base::is_middle_click(const SDL_MouseButtonEvent& event) const
302 {
303  return event.button == SDL_BUTTON_MIDDLE;
304 }
305 
306 bool mouse_handler_base::is_right_click(const SDL_MouseButtonEvent& event) const
307 {
308 #ifdef MOUSE_TOUCH_EMULATION
309  (void) event;
310  return false;
311 #else
312  if(events::is_touch(event)) {
313  return false;
314  }
315  return event.button == SDL_BUTTON_RIGHT
316  || (event.button == SDL_BUTTON_LEFT && command_active());
317 #endif
318 }
319 
320 bool mouse_handler_base::left_click(int x, int y, const bool /*browse*/)
321 {
322  if(gui().view_locked()) {
323  return false;
324  }
325 
326  // clicked on a hex on the minimap? then initiate minimap scrolling
327  const map_location& loc = gui().minimap_location_on(x, y);
328  minimap_scrolling_ = false;
329  if(loc.valid()) {
330  minimap_scrolling_ = true;
331  last_hex_ = loc;
332  gui().scroll_to_tile(loc, display::WARP, false);
333  return true;
334  }
335 
336  return false;
337 }
338 
339 void mouse_handler_base::touch_action(const map_location /*hex*/, bool /*browse*/)
340 {
341 }
342 
343 void mouse_handler_base::left_drag_end(int /*x*/, int /*y*/, const bool browse)
344 {
345  move_action(browse);
346 }
347 
348 void mouse_handler_base::mouse_wheel(int scrollx, int scrolly, bool browse)
349 {
350  auto [x, y] = sdl::get_mouse_location();
351 
352  int movex = scrollx * prefs::get().scroll_speed();
353  int movey = scrolly * prefs::get().scroll_speed();
354 
355  // Don't scroll map if cursor is not in gamemap area
356  if(!gui().map_area().contains(x, y)) {
357  return;
358  }
359 
360  if(movex != 0 || movey != 0) {
361  CKey pressed;
362  // Alt + mousewheel do an 90° rotation on the scroll direction
363  if(pressed[SDLK_LALT] || pressed[SDLK_RALT]) {
364  gui().scroll(point{movey, movex});
365  } else {
366  gui().scroll(point{movex, movey});
367  }
368  }
369 
370  if(scrollx < 0) {
371  mouse_wheel_left(x, y, browse);
372  } else if(scrollx > 0) {
373  mouse_wheel_right(x, y, browse);
374  }
375 
376  if(scrolly < 0) {
377  mouse_wheel_up(x, y, browse);
378  } else if(scrolly > 0) {
379  mouse_wheel_down(x, y, browse);
380  }
381 }
382 
383 void mouse_handler_base::right_mouse_up(int x, int y, const bool browse)
384 {
385  if(!right_click_show_menu(x, y, browse)) {
386  return;
387  }
388 
389  const theme::menu* const m = gui().get_theme().context_menu();
390  if(m != nullptr) {
391  show_menu_ = true;
392  } else {
393  WRN_DP << "no context menu found...";
394  }
395 }
396 
397 void mouse_handler_base::init_dragging(bool& dragging_flag)
398 {
399  dragging_flag = true;
402 }
403 
405 {
406  dragging_started_ = false;
407  dragging_left_ = false;
408  dragging_touch_ = false;
409  dragging_right_ = false;
410  cursor::set_dragging(false);
411 }
412 
413 void mouse_handler_base::clear_dragging(const SDL_MouseButtonEvent& event, bool browse)
414 {
415  // we reset dragging info before calling functions
416  // because they may take time to return, and we
417  // could have started other drag&drop before that
418  cursor::set_dragging(false);
419 
420  if(dragging_started_) {
421  dragging_started_ = false;
422 
423  if(dragging_touch_) {
424  dragging_touch_ = false;
425  // Maybe to do: create touch_drag_end(). Do panning and what else there. OTOH, it's fine now.
426  left_drag_end(event.x, event.y, browse);
427  }
428 
429  if(dragging_left_) {
430  dragging_left_ = false;
431  left_drag_end(event.x, event.y, browse);
432  }
433 
434  if(dragging_right_) {
435  dragging_right_ = false;
436  right_drag_end(event.x, event.y, browse);
437  }
438  } else {
439  dragging_left_ = false;
440  dragging_right_ = false;
441  dragging_touch_ = false;
442  }
443 }
444 
446 {
448 }
449 
450 } // 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:1379
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:1854
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:682
theme & get_theme()
Definition: display.hpp:381
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:527
bool scroll(const point &amount, bool force=false)
Scrolls the display by amount pixels.
Definition: display.cpp:1579
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.
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:198
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:771
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(int *x, int *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:86
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:45
bool valid() const
Definition: location.hpp:110
static const map_location & null_location()
Definition: location.hpp:102
Holds a 2D point.
Definition: point.hpp:25