The Battle for Wesnoth  1.17.0-dev
mouse_handler_base.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2021
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"
22 #include "preferences/general.hpp"
23 #include "sdl/rect.hpp"
24 #include "tooltips.hpp"
25 
26 static lg::log_domain log_display("display");
27 #define WRN_DP LOG_STREAM(warn, log_display)
28 
29 namespace events
30 {
32 {
34 }
35 
37 {
39 }
40 
42 
43 static bool command_active()
44 {
45 #ifdef __APPLE__
46  return (SDL_GetModState() & KMOD_CTRL) != 0;
47 #else
48  return false;
49 #endif
50 }
51 
53  : simple_warp_(false)
54  , minimap_scrolling_(false)
55  , dragging_left_(false)
56  , dragging_touch_(false)
57  , dragging_started_(false)
58  , dragging_right_(false)
59  , drag_from_x_(0)
60  , drag_from_y_(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 
92 void mouse_handler_base::mouse_update(const bool browse, map_location loc)
93 {
94  int x, y;
95  SDL_GetMouseState(&x, &y);
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_GetMouseState(nullptr, nullptr) & (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  int mx = drag_from_x_; // some default value to prevent unlikely SDL bug
133  int my = drag_from_y_;
134 
135  if(is_dragging() && !dragging_started_) {
136  Uint32 mouse_state = dragging_left_ || dragging_right_ ? SDL_GetMouseState(&mx, &my) : 0;
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_- mx), 2) +
148  std::pow(static_cast<double>(drag_from_y_- my), 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 
160 void mouse_handler_base::mouse_press(const SDL_MouseButtonEvent& event, const bool browse)
161 {
163  simple_warp_ = true;
164  }
165 
166  show_menu_ = false;
167  map_location loc = gui().hex_clicked_on(event.x, event.y);
168  mouse_update(browse, loc);
169 
170  static clock_t touch_timestamp = 0;
171 
172  if(is_touch_click(event)) {
173  if (event.state == SDL_PRESSED) {
174  cancel_dragging();
175  touch_timestamp = clock();
177  left_click(event.x, event.y, browse);
178  } else if (event.state == SDL_RELEASED) {
179  minimap_scrolling_ = false;
180 
181  if (!dragging_started_ && touch_timestamp > 0) {
182  time_t dt = clock() - touch_timestamp;
183  if (dt > CLOCKS_PER_SEC * 3 / 10) {
184  right_click(event.x, event.y, browse); // show_menu_ = true;
185  }
186  } else {
187  touch_timestamp = 0;
188  }
189 
190  clear_dragging(event, browse);
191  left_mouse_up(event.x, event.y, browse);
192  }
193  } else if(is_left_click(event)) {
194  if(event.state == SDL_PRESSED) {
195  cancel_dragging();
197  left_click(event.x, event.y, browse);
198  } else if(event.state == SDL_RELEASED) {
199  minimap_scrolling_ = false;
200  clear_dragging(event, browse);
201  left_mouse_up(event.x, event.y, browse);
202  }
203  } else if(is_right_click(event)) {
204  if(event.state == SDL_PRESSED) {
205  cancel_dragging();
207  right_click(event.x, event.y, browse);
208  } else if(event.state == SDL_RELEASED) {
209  minimap_scrolling_ = false;
210  clear_dragging(event, browse);
211  right_mouse_up(event.x, event.y, browse);
212  }
213  } else if(is_middle_click(event)) {
214  if(event.state == SDL_PRESSED) {
215  set_scroll_start(event.x, event.y);
216  scroll_started_ = true;
217 
218  map_location minimap_loc = gui().minimap_location_on(event.x, event.y);
219  minimap_scrolling_ = false;
220  if(minimap_loc.valid()) {
221  simple_warp_ = false;
222  minimap_scrolling_ = true;
223  last_hex_ = minimap_loc;
224  gui().scroll_to_tile(minimap_loc, display::WARP, false);
225  } else if(simple_warp_) {
226  // middle click not on minimap, check gamemap instead
227  if(loc.valid()) {
228  last_hex_ = loc;
229  gui().scroll_to_tile(loc, display::WARP, false);
230  }
231  }
232  } else if(event.state == SDL_RELEASED) {
233  minimap_scrolling_ = false;
234  simple_warp_ = false;
235  scroll_started_ = false;
236  }
237  }
239  dragging_started_ = false;
240  cursor::set_dragging(false);
241  }
242 
243  mouse_update(browse, loc);
244 }
245 
246 bool mouse_handler_base::is_left_click(const SDL_MouseButtonEvent& event) const
247 {
248 #ifdef MOUSE_TOUCH_EMULATION
249  if(event.button == SDL_BUTTON_RIGHT) {
250  return true;
251  }
252 #endif
253  if(event.which == SDL_TOUCH_MOUSEID) {
254  return false;
255  }
256  return event.button == SDL_BUTTON_LEFT && !command_active();
257 }
258 
259 bool mouse_handler_base::is_middle_click(const SDL_MouseButtonEvent& event) const
260 {
261  return event.button == SDL_BUTTON_MIDDLE;
262 }
263 
264 bool mouse_handler_base::is_right_click(const SDL_MouseButtonEvent& event) const
265 {
266 #ifdef MOUSE_TOUCH_EMULATION
267  (void) event;
268  return false;
269 #else
270  if(event.which == SDL_TOUCH_MOUSEID) {
271  return false;
272  }
273  return event.button == SDL_BUTTON_RIGHT
274  || (event.button == SDL_BUTTON_LEFT && command_active());
275 #endif
276 }
277 
278 bool mouse_handler_base::is_touch_click(const SDL_MouseButtonEvent& event) const
279 {
280  return event.which == SDL_TOUCH_MOUSEID;
281 }
282 
283 bool mouse_handler_base::left_click(int x, int y, const bool /*browse*/)
284 {
285  if(gui().view_locked()) {
286  return false;
287  }
288 
289  // clicked on a hex on the minimap? then initiate minimap scrolling
290  const map_location& loc = gui().minimap_location_on(x, y);
291  minimap_scrolling_ = false;
292  if(loc.valid()) {
293  minimap_scrolling_ = true;
294  last_hex_ = loc;
295  gui().scroll_to_tile(loc, display::WARP, false);
296  return true;
297  }
298 
299  return false;
300 }
301 
302 void mouse_handler_base::touch_action(const map_location /*hex*/, bool /*browse*/)
303 {
304 }
305 
306 void mouse_handler_base::left_drag_end(int /*x*/, int /*y*/, const bool browse)
307 {
308  move_action(browse);
309 }
310 
311 void mouse_handler_base::mouse_wheel(int scrollx, int scrolly, bool browse)
312 {
313  int x, y;
314  SDL_GetMouseState(&x, &y);
315 
316  int movex = scrollx * preferences::scroll_speed();
317  int movey = scrolly * preferences::scroll_speed();
318 
319  // Don't scroll map if cursor is not in gamemap area
320  if(!sdl::point_in_rect(x, y, gui().map_area())) {
321  return;
322  }
323 
324  if(movex != 0 || movey != 0) {
325  CKey pressed;
326  // Alt + mousewheel do an 90° rotation on the scroll direction
327  if(pressed[SDLK_LALT] || pressed[SDLK_RALT]) {
328  gui().scroll(-movey, -movex);
329  } else {
330  gui().scroll(-movex, -movey);
331  }
332  }
333 
334  if(scrollx < 0) {
335  mouse_wheel_left(x, y, browse);
336  } else if(scrollx > 0) {
337  mouse_wheel_right(x, y, browse);
338  }
339 
340  if(scrolly < 0) {
341  mouse_wheel_down(x, y, browse);
342  } else if(scrolly > 0) {
343  mouse_wheel_up(x, y, browse);
344  }
345 }
346 
347 void mouse_handler_base::right_mouse_up(int x, int y, const bool browse)
348 {
349  if(!right_click_show_menu(x, y, browse)) {
350  return;
351  }
352 
353  const theme::menu* const m = gui().get_theme().context_menu();
354  if(m != nullptr) {
355  show_menu_ = true;
356  } else {
357  WRN_DP << "no context menu found..." << std::endl;
358  }
359 }
360 
361 void mouse_handler_base::init_dragging(bool& dragging_flag)
362 {
363  dragging_flag = true;
364  SDL_GetMouseState(&drag_from_x_, &drag_from_y_);
366 }
367 
369 {
370  dragging_started_ = false;
371  dragging_left_ = false;
372  dragging_touch_ = false;
373  dragging_right_ = false;
374  cursor::set_dragging(false);
375 }
376 
377 void mouse_handler_base::clear_dragging(const SDL_MouseButtonEvent& event, bool browse)
378 {
379  // we reset dragging info before calling functions
380  // because they may take time to return, and we
381  // could have started other drag&drop before that
382  cursor::set_dragging(false);
383 
384  if(dragging_started_) {
385  dragging_started_ = false;
386 
387  if(dragging_touch_) {
388  dragging_touch_ = false;
389  // Maybe to do: create touch_drag_end(). Do panning and what else there. OTOH, it's fine now.
390  left_drag_end(event.x, event.y, browse);
391  }
392 
393  if(dragging_left_) {
394  dragging_left_ = false;
395  left_drag_end(event.x, event.y, browse);
396  }
397 
398  if(dragging_right_) {
399  dragging_right_ = false;
400  right_drag_end(event.x, event.y, browse);
401  }
402  } else {
403  dragging_left_ = false;
404  dragging_right_ = false;
405  dragging_touch_ = false;
406  }
407 }
408 
409 } // end namespace events
bool mouse_motion_default(int x, int y, bool update)
This handles minimap scrolling and click-drag.
int drag_from_x_
Drag start position x.
void clear_dragging(const SDL_MouseButtonEvent &event, bool browse)
#define WRN_DP
bool dragging_right_
RMB drag init flag.
theme & get_theme()
Definition: display.hpp:384
void set_scroll_start(int x, int y)
Called when the middle click scrolling.
bool dragging_touch_
Finger drag init flag.
virtual int drag_threshold() const
Minimum dragging distance to fire the drag&drop.
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:599
bool dragging_started_
Actual drag flag.
bool is_middle_click(const SDL_MouseButtonEvent &event) const
virtual void mouse_wheel_right(int, int, const bool)
Called when the mouse wheel is scrolled right.
virtual bool right_click(int x, int y, const bool browse)
Overridden in derived classes, called on a right click (mousedown).
int scroll_speed()
Definition: general.cpp:751
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:760
void touch_motion_event(const SDL_TouchFingerEvent &event, const 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 mouse_motion_event(const SDL_MouseMotionEvent &event, const bool browse)
virtual void mouse_wheel_down(int, int, const bool)
Called when the mouse wheel is scrolled down.
bool is_right_click(const SDL_MouseButtonEvent &event) const
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 void mouse_press(const SDL_MouseButtonEvent &event, const bool browse)
virtual bool left_click(int x, int y, const bool browse)
Overridden in derived classes, called on a left click (mousedown).
void process(int mousex, int mousey)
Definition: tooltips.cpp:194
bool minimap_scrolling_
minimap scrolling (scroll-drag) state flag
bool is_left_click(const SDL_MouseButtonEvent &event) const
void set_dragging(bool drag)
Definition: cursor.cpp:196
bool show_menu_
Show context menu flag.
bool valid() const
Definition: location.hpp:89
virtual void mouse_wheel_up(int, int, const bool)
Called when the mouse wheel is scrolled up.
bool dragging_started() const
If mouse/finger has moved far enough to consider it move/swipe, and not a click/touch.
map_location drag_from_hex_
Drag start map location.
virtual void left_drag_end(int, int, const bool)
Called whenever the left mouse drag has "ended".
virtual void right_mouse_up(int, int, const bool)
Called when the right mouse button is up.
map_display and display: classes which take care of displaying the map and game-data on the screen...
bool dragging_left_
LMB drag init flag.
static lg::log_domain log_display("display")
virtual void move_action(bool)
Overridden in derived class.
bool point_in_rect(int x, int y, const SDL_Rect &rect)
Tests whether a point is inside a rectangle.
Definition: rect.cpp:23
Encapsulates the map of the game.
Definition: location.hpp:38
virtual void right_drag_end(int, int, const bool)
Called whenever the right mouse drag has "ended".
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:2203
bool is_touch_click(const SDL_MouseButtonEvent &event) const
bool middle_click_scrolls()
Definition: general.cpp:765
virtual void touch_action(const map_location hex, bool browse)
static bool command_active()
virtual void mouse_wheel(int xscroll, int yscroll, bool browse)
Called when scrolling with the mouse wheel.
Handling of system events.
Definition: manager.hpp:43
map_location last_hex_
last highlighted hex
Contains the SDL_Rect helper code.
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 mouse_wheel_left(int, int, const bool)
Called when the mouse wheel is scrolled left.
void init_dragging(bool &dragging_flag)
virtual display & gui()=0
Reference to the used display objects.
Standard logging facilities (interface).
const menu * context_menu() const
Definition: theme.hpp:259
Class that keeps track of all the keys on the keyboard.
Definition: key.hpp:28
void mouse_update(const bool browse, map_location loc)
Update the mouse with a fake mouse motion.
bool scroll(int xmov, int ymov, bool force=false)
Scrolls the display by xmov,ymov pixels.
Definition: display.cpp:1926
bool simple_warp_
MMB click (on game map) state flag.
int drag_from_y_
Drag start position y.
virtual void left_mouse_up(int, int, const bool)
Called when the left mouse button is up.