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