The Battle for Wesnoth  1.19.4+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_x_(0)
61  , drag_from_y_(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 
93 void mouse_handler_base::mouse_update(const bool browse, map_location loc)
94 {
95  int x, y;
96  sdl::get_mouse_state(&x, &y);
97  mouse_motion(x, y, browse, true, loc);
98 }
99 
100 bool mouse_handler_base::mouse_motion_default(int x, int y, bool /*update*/)
101 {
102  tooltips::process(x, y);
103 
104  if(simple_warp_) {
105  return true;
106  }
107 
108  if(minimap_scrolling_) {
109  // if the game is run in a window, we could miss a LMB/MMB up event
110  // if it occurs outside our window.
111  // thus, we need to check if the LMB/MMB is still down
112  minimap_scrolling_ = ((sdl::get_mouse_button_mask() & (SDL_BUTTON(SDL_BUTTON_LEFT) | SDL_BUTTON(SDL_BUTTON_MIDDLE))) != 0);
113  if(minimap_scrolling_) {
114  const map_location& loc = gui().minimap_location_on(x, y);
115  if(loc.valid()) {
116  if(loc != last_hex_) {
117  last_hex_ = loc;
118  gui().scroll_to_tile(loc, display::WARP, false);
119  }
120  } else {
121  // clicking outside of the minimap will end minimap scrolling
122  minimap_scrolling_ = false;
123  }
124  }
125 
126  if(minimap_scrolling_) {
127  return true;
128  }
129  }
130 
131  // Fire the drag & drop only after minimal drag distance
132  // While we check the mouse buttons state, we also grab fresh position data.
133  int mx = drag_from_x_; // some default value to prevent unlikely SDL bug
134  int my = drag_from_y_;
135 
136  if(is_dragging() && !dragging_started_) {
137  Uint32 mouse_state = dragging_left_ || dragging_right_ ? sdl::get_mouse_state(&mx, &my) : 0;
138 #ifdef MOUSE_TOUCH_EMULATION
139  if(dragging_left_ && (mouse_state & SDL_BUTTON(SDL_BUTTON_RIGHT))) {
140  // Monkey-patch touch controls again to make them look like left button.
141  mouse_state = SDL_BUTTON(SDL_BUTTON_LEFT);
142  }
143 #endif
144  if((dragging_left_ && (mouse_state & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0) ||
145  (dragging_right_ && (mouse_state & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0))
146  {
147  const double drag_distance =
148  std::pow(static_cast<double>(drag_from_x_- mx), 2) +
149  std::pow(static_cast<double>(drag_from_y_- my), 2);
150 
151  if(drag_distance > drag_threshold() * drag_threshold()) {
152  dragging_started_ = true;
153  cursor::set_dragging(true);
154  }
155  }
156  }
157 
158  return false;
159 }
160 
161 bool mouse_handler_base::mouse_button_event(const SDL_MouseButtonEvent& event, uint8_t button,
162  map_location loc, bool click)
163 {
164  (void)event;
165  (void)button;
166  (void)loc;
167  (void)click;
168 
169  return false;
170 }
171 
172 void mouse_handler_base::mouse_press(const SDL_MouseButtonEvent& event, const bool browse)
173 {
174  if(is_middle_click(event) && !prefs::get().middle_click_scrolls()) {
175  simple_warp_ = true;
176  }
177 
178  show_menu_ = false;
179  map_location loc = gui().hex_clicked_on(event.x, event.y);
180  mouse_update(browse, loc);
181 
182  static clock_t touch_timestamp = 0;
183 
184  if(is_touch_click(event)) {
185  if (event.state == SDL_PRESSED) {
186  cancel_dragging();
187  touch_timestamp = clock();
189  if (!mouse_button_event(event, SDL_BUTTON_LEFT, loc, true)) {
190  left_click(event.x, event.y, browse);
191  }
192  } else if (event.state == SDL_RELEASED) {
193  minimap_scrolling_ = false;
194 
195  if (!dragging_started_ && touch_timestamp > 0) {
196  clock_t dt = clock() - touch_timestamp;
197  if (dt > CLOCKS_PER_SEC * 3 / 10) {
198  if (!mouse_button_event(event, SDL_BUTTON_RIGHT, loc, true)) {
199  // BUG: This function won't do anything in the game, need right_mouse_up()
200  right_click(event.x, event.y, browse); // show_menu_ = true;
201  }
202  }
203  } else {
204  touch_timestamp = 0;
205  }
206 
207  clear_dragging(event, browse);
208  mouse_button_event(event, SDL_BUTTON_LEFT, loc);
209  left_mouse_up(event.x, event.y, browse);
211  }
212  } else if(is_left_click(event)) {
213  if(event.state == SDL_PRESSED) {
214  cancel_dragging();
216  if (!mouse_button_event(event, SDL_BUTTON_LEFT, loc, true)) {
217  left_click(event.x, event.y, browse);
218  }
219  } else if(event.state == SDL_RELEASED) {
220  minimap_scrolling_ = false;
221  clear_dragging(event, browse);
222  mouse_button_event(event, SDL_BUTTON_LEFT, loc);
223  left_mouse_up(event.x, event.y, browse);
225  }
226  } else if(is_right_click(event)) {
227  if(event.state == SDL_PRESSED) {
228  mouse_button_event(event, SDL_BUTTON_RIGHT, loc);
229  cancel_dragging();
231  right_click(event.x, event.y, browse);
232  } else if(event.state == SDL_RELEASED) {
233  minimap_scrolling_ = false;
234  clear_dragging(event, browse);
235  if (!mouse_button_event(event, SDL_BUTTON_RIGHT, loc, true)) {
236  right_mouse_up(event.x, event.y, browse);
237  }
239  }
240  } else if(is_middle_click(event)) {
241  if(event.state == SDL_PRESSED) {
242  drag_from_hex_ = loc;
243  set_scroll_start(event.x, event.y);
244  scroll_started_ = true;
245 
246  map_location minimap_loc = gui().minimap_location_on(event.x, event.y);
247  minimap_scrolling_ = false;
248  if(minimap_loc.valid()) {
249  simple_warp_ = false;
250  minimap_scrolling_ = true;
251  last_hex_ = minimap_loc;
252  gui().scroll_to_tile(minimap_loc, display::WARP, false);
253  } else if(mouse_button_event(event, SDL_BUTTON_MIDDLE, loc, true)) {
254  scroll_started_ = false;
255  simple_warp_ = false;
256  } else if(simple_warp_) {
257  // middle click not on minimap, check gamemap instead
258  if(loc.valid()) {
259  last_hex_ = loc;
260  gui().scroll_to_tile(loc, display::WARP, false);
261  }
262  } else {
263  // Deselect the current tile as we're scrolling
264  gui().highlight_hex({-1,-1});
265  }
266  } else if(event.state == SDL_RELEASED) {
267  minimap_scrolling_ = false;
268  simple_warp_ = false;
269  scroll_started_ = false;
270  mouse_button_event(event, SDL_BUTTON_MIDDLE, loc);
272  }
273  } else if(event.button == SDL_BUTTON_X1 || event.button == SDL_BUTTON_X2) {
274  if(event.state == SDL_PRESSED) {
275  cancel_dragging();
276  // record mouse-down hex in drag_from_hex_
277  drag_from_hex_ = loc;
278  mouse_button_event(event, event.button, loc);
279  } else {
280  mouse_button_event(event, event.button, loc, true);
282  }
283  }
285  dragging_started_ = false;
286  cursor::set_dragging(false);
287  }
288 
289  mouse_update(browse, loc);
290 }
291 
292 bool mouse_handler_base::is_left_click(const SDL_MouseButtonEvent& event) const
293 {
294 #ifdef MOUSE_TOUCH_EMULATION
295  if(event.button == SDL_BUTTON_RIGHT) {
296  return true;
297  }
298 #endif
299  if(event.which == SDL_TOUCH_MOUSEID) {
300  return false;
301  }
302  return event.button == SDL_BUTTON_LEFT && !command_active();
303 }
304 
305 bool mouse_handler_base::is_middle_click(const SDL_MouseButtonEvent& event) const
306 {
307  return event.button == SDL_BUTTON_MIDDLE;
308 }
309 
310 bool mouse_handler_base::is_right_click(const SDL_MouseButtonEvent& event) const
311 {
312 #ifdef MOUSE_TOUCH_EMULATION
313  (void) event;
314  return false;
315 #else
316  if(event.which == SDL_TOUCH_MOUSEID) {
317  return false;
318  }
319  return event.button == SDL_BUTTON_RIGHT
320  || (event.button == SDL_BUTTON_LEFT && command_active());
321 #endif
322 }
323 
324 bool mouse_handler_base::is_touch_click(const SDL_MouseButtonEvent& event) const
325 {
326  return event.which == SDL_TOUCH_MOUSEID;
327 }
328 
329 bool mouse_handler_base::left_click(int x, int y, const bool /*browse*/)
330 {
331  if(gui().view_locked()) {
332  return false;
333  }
334 
335  // clicked on a hex on the minimap? then initiate minimap scrolling
336  const map_location& loc = gui().minimap_location_on(x, y);
337  minimap_scrolling_ = false;
338  if(loc.valid()) {
339  minimap_scrolling_ = true;
340  last_hex_ = loc;
341  gui().scroll_to_tile(loc, display::WARP, false);
342  return true;
343  }
344 
345  return false;
346 }
347 
348 void mouse_handler_base::touch_action(const map_location /*hex*/, bool /*browse*/)
349 {
350 }
351 
352 void mouse_handler_base::left_drag_end(int /*x*/, int /*y*/, const bool browse)
353 {
354  move_action(browse);
355 }
356 
357 void mouse_handler_base::mouse_wheel(int scrollx, int scrolly, bool browse)
358 {
359  int x, y;
360  sdl::get_mouse_state(&x, &y);
361 
362  int movex = scrollx * prefs::get().scroll_speed();
363  int movey = scrolly * prefs::get().scroll_speed();
364 
365  // Don't scroll map if cursor is not in gamemap area
366  if(!gui().map_area().contains(x, y)) {
367  return;
368  }
369 
370  if(movex != 0 || movey != 0) {
371  CKey pressed;
372  // Alt + mousewheel do an 90° rotation on the scroll direction
373  if(pressed[SDLK_LALT] || pressed[SDLK_RALT]) {
374  gui().scroll(movey, movex);
375  } else {
376  gui().scroll(movex, movey);
377  }
378  }
379 
380  if(scrollx < 0) {
381  mouse_wheel_left(x, y, browse);
382  } else if(scrollx > 0) {
383  mouse_wheel_right(x, y, browse);
384  }
385 
386  if(scrolly < 0) {
387  mouse_wheel_up(x, y, browse);
388  } else if(scrolly > 0) {
389  mouse_wheel_down(x, y, browse);
390  }
391 }
392 
393 void mouse_handler_base::right_mouse_up(int x, int y, const bool browse)
394 {
395  if(!right_click_show_menu(x, y, browse)) {
396  return;
397  }
398 
399  const theme::menu* const m = gui().get_theme().context_menu();
400  if(m != nullptr) {
401  show_menu_ = true;
402  } else {
403  WRN_DP << "no context menu found...";
404  }
405 }
406 
407 void mouse_handler_base::init_dragging(bool& dragging_flag)
408 {
409  dragging_flag = true;
412 }
413 
415 {
416  dragging_started_ = false;
417  dragging_left_ = false;
418  dragging_touch_ = false;
419  dragging_right_ = false;
420  cursor::set_dragging(false);
421 }
422 
423 void mouse_handler_base::clear_dragging(const SDL_MouseButtonEvent& event, bool browse)
424 {
425  // we reset dragging info before calling functions
426  // because they may take time to return, and we
427  // could have started other drag&drop before that
428  cursor::set_dragging(false);
429 
430  if(dragging_started_) {
431  dragging_started_ = false;
432 
433  if(dragging_touch_) {
434  dragging_touch_ = false;
435  // Maybe to do: create touch_drag_end(). Do panning and what else there. OTOH, it's fine now.
436  left_drag_end(event.x, event.y, browse);
437  }
438 
439  if(dragging_left_) {
440  dragging_left_ = false;
441  left_drag_end(event.x, event.y, browse);
442  }
443 
444  if(dragging_right_) {
445  dragging_right_ = false;
446  right_drag_end(event.x, event.y, browse);
447  }
448  } else {
449  dragging_left_ = false;
450  dragging_right_ = false;
451  dragging_touch_ = false;
452  }
453 }
454 
456 {
458 }
459 
460 } // end namespace events
Class that keeps track of all the keys on the keyboard.
Definition: key.hpp:29
const 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:543
bool scroll(int xmov, int ymov, bool force=false)
Scrolls the display by xmov,ymov pixels.
Definition: display.cpp:1728
virtual void highlight_hex(map_location hex)
Definition: display.cpp:1507
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:2015
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:712
theme & get_theme()
Definition: display.hpp:385
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
int drag_from_y_
Drag start position y.
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.
int drag_from_x_
Drag start position x.
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).
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
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:83
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