The Battle for Wesnoth  1.15.2+dev
mouse_handler_base.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2018 by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
3  wesnoth playturn Copyright (C) 2003 by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "mouse_handler_base.hpp"
17 
18 #include "cursor.hpp"
19 #include "display.hpp"
20 #include "log.hpp"
21 #include "preferences/general.hpp"
22 #include "sdl/rect.hpp"
23 #include "tooltips.hpp"
24 
25 static lg::log_domain log_display("display");
26 #define WRN_DP LOG_STREAM(warn, log_display)
27 
28 namespace events
29 {
31 {
33 }
34 
36 {
38 }
39 
41 
42 static bool command_active()
43 {
44 #ifdef __APPLE__
45  return (SDL_GetModState() & KMOD_CTRL) != 0;
46 #else
47  return false;
48 #endif
49 }
50 
52  : simple_warp_(false)
53  , minimap_scrolling_(false)
54  , dragging_left_(false)
55  , dragging_touch_(false)
56  , dragging_started_(false)
57  , dragging_right_(false)
58  , drag_from_x_(0)
59  , drag_from_y_(0)
60  , drag_from_hex_()
61  , last_hex_()
62  , show_menu_(false)
63  , scroll_start_x_(0)
64  , scroll_start_y_(0)
65  , scroll_started_(false)
66 {
67 }
68 
70 {
71  return dragging_started_;
72 }
73 
75 {
77 }
78 
79 void mouse_handler_base::mouse_motion_event(const SDL_MouseMotionEvent& event, const bool browse)
80 {
81  mouse_motion(event.x, event.y, browse);
82 }
83 
84 void mouse_handler_base::touch_motion_event(const SDL_TouchFingerEvent& event, const bool browse)
85 {
86  // This is wrong (needs to be scaled from -1..1 to screen size), but it's discarded in touch_motion anyway.
87  // Let's not waste CPU cycles.
88  touch_motion(event.x, event.y, browse);
89 }
90 
91 void mouse_handler_base::mouse_update(const bool browse, map_location loc)
92 {
93  int x, y;
94  SDL_GetMouseState(&x, &y);
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_GetMouseState(nullptr, nullptr) & (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  int mx = drag_from_x_; // some default value to prevent unlikely SDL bug
132  int my = drag_from_y_;
133 
134  if(is_dragging() && !dragging_started_) {
135  Uint32 mouse_state = dragging_left_ || dragging_right_ ? SDL_GetMouseState(&mx, &my) : 0;
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_- mx), 2) +
147  std::pow(static_cast<double>(drag_from_y_- my), 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 void mouse_handler_base::mouse_press(const SDL_MouseButtonEvent& event, const bool browse)
160 {
162  simple_warp_ = true;
163  }
164 
165  show_menu_ = false;
166  map_location loc = gui().hex_clicked_on(event.x, event.y);
167  mouse_update(browse, loc);
168 
169  static clock_t touch_timestamp = 0;
170 
171  if(is_touch_click(event)) {
172  if (event.state == SDL_PRESSED) {
173  cancel_dragging();
174  touch_timestamp = clock();
176  left_click(event.x, event.y, browse);
177  } else if (event.state == SDL_RELEASED) {
178  minimap_scrolling_ = false;
179 
180  if (!dragging_started_ && touch_timestamp > 0) {
181  time_t dt = clock() - touch_timestamp;
182  if (dt > CLOCKS_PER_SEC * 3 / 10) {
183  right_click(event.x, event.y, browse); // show_menu_ = true;
184  }
185  } else {
186  touch_timestamp = 0;
187  }
188 
189  clear_dragging(event, browse);
190  left_mouse_up(event.x, event.y, browse);
191  }
192  } else if(is_left_click(event)) {
193  if(event.state == SDL_PRESSED) {
194  cancel_dragging();
196  left_click(event.x, event.y, browse);
197  } else if(event.state == SDL_RELEASED) {
198  minimap_scrolling_ = false;
199  clear_dragging(event, browse);
200  left_mouse_up(event.x, event.y, browse);
201  }
202  } else if(is_right_click(event)) {
203  if(event.state == SDL_PRESSED) {
204  cancel_dragging();
206  right_click(event.x, event.y, browse);
207  } else if(event.state == SDL_RELEASED) {
208  minimap_scrolling_ = false;
209  clear_dragging(event, browse);
210  right_mouse_up(event.x, event.y, browse);
211  }
212  } else if(is_middle_click(event)) {
213  if(event.state == SDL_PRESSED) {
214  set_scroll_start(event.x, event.y);
215  scroll_started_ = true;
216 
217  map_location minimap_loc = gui().minimap_location_on(event.x, event.y);
218  minimap_scrolling_ = false;
219  if(minimap_loc.valid()) {
220  simple_warp_ = false;
221  minimap_scrolling_ = true;
222  last_hex_ = minimap_loc;
223  gui().scroll_to_tile(minimap_loc, display::WARP, false);
224  } else if(simple_warp_) {
225  // middle click not on minimap, check gamemap instead
226  if(loc.valid()) {
227  last_hex_ = loc;
228  gui().scroll_to_tile(loc, display::WARP, false);
229  }
230  }
231  } else if(event.state == SDL_RELEASED) {
232  minimap_scrolling_ = false;
233  simple_warp_ = false;
234  scroll_started_ = false;
235  }
236  }
238  dragging_started_ = false;
239  cursor::set_dragging(false);
240  }
241 
242  mouse_update(browse, loc);
243 }
244 
245 bool mouse_handler_base::is_left_click(const SDL_MouseButtonEvent& event) const
246 {
247 #ifdef MOUSE_TOUCH_EMULATION
248  if(event.button == SDL_BUTTON_RIGHT) {
249  return true;
250  }
251 #endif
252  if(event.which == SDL_TOUCH_MOUSEID) {
253  return false;
254  }
255  return event.button == SDL_BUTTON_LEFT && !command_active();
256 }
257 
258 bool mouse_handler_base::is_middle_click(const SDL_MouseButtonEvent& event) const
259 {
260  return event.button == SDL_BUTTON_MIDDLE;
261 }
262 
263 bool mouse_handler_base::is_right_click(const SDL_MouseButtonEvent& event) const
264 {
265 #ifdef MOUSE_TOUCH_EMULATION
266  (void) event;
267  return false;
268 #else
269  if(event.which == SDL_TOUCH_MOUSEID) {
270  return false;
271  }
272  return event.button == SDL_BUTTON_RIGHT
273  || (event.button == SDL_BUTTON_LEFT && command_active());
274 #endif
275 }
276 
277 bool mouse_handler_base::is_touch_click(const SDL_MouseButtonEvent& event) const
278 {
279  return event.which == SDL_TOUCH_MOUSEID;
280 }
281 
282 bool mouse_handler_base::left_click(int x, int y, const bool /*browse*/)
283 {
284  if(gui().view_locked()) {
285  return false;
286  }
287 
288  // clicked on a hex on the minimap? then initiate minimap scrolling
289  const map_location& loc = gui().minimap_location_on(x, y);
290  minimap_scrolling_ = false;
291  if(loc.valid()) {
292  minimap_scrolling_ = true;
293  last_hex_ = loc;
294  gui().scroll_to_tile(loc, display::WARP, false);
295  return true;
296  }
297 
298  return false;
299 }
300 
301 void mouse_handler_base::touch_action(const map_location /*hex*/, bool /*browse*/)
302 {
303 }
304 
305 void mouse_handler_base::left_drag_end(int /*x*/, int /*y*/, const bool browse)
306 {
307  move_action(browse);
308 }
309 
310 void mouse_handler_base::mouse_wheel(int scrollx, int scrolly, bool browse)
311 {
312  int x, y;
313  SDL_GetMouseState(&x, &y);
314 
315  int movex = scrollx * preferences::scroll_speed();
316  int movey = scrolly * preferences::scroll_speed();
317 
318  // Don't scroll map if cursor is not in gamemap area
319  if(!sdl::point_in_rect(x, y, gui().map_area())) {
320  return;
321  }
322 
323  if(movex != 0 || movey != 0) {
324  CKey pressed;
325  // Alt + mousewheel do an 90° rotation on the scroll direction
326  if(pressed[SDLK_LALT] || pressed[SDLK_RALT]) {
327  gui().scroll(-movey, -movex);
328  } else {
329  gui().scroll(-movex, -movey);
330  }
331  }
332 
333  if(scrollx < 0) {
334  mouse_wheel_left(x, y, browse);
335  } else if(scrollx > 0) {
336  mouse_wheel_right(x, y, browse);
337  }
338 
339  if(scrolly < 0) {
340  mouse_wheel_down(x, y, browse);
341  } else if(scrolly > 0) {
342  mouse_wheel_up(x, y, browse);
343  }
344 }
345 
346 void mouse_handler_base::right_mouse_up(int x, int y, const bool browse)
347 {
348  if(!right_click_show_menu(x, y, browse)) {
349  return;
350  }
351 
352  const theme::menu* const m = gui().get_theme().context_menu();
353  if(m != nullptr) {
354  show_menu_ = true;
355  } else {
356  WRN_DP << "no context menu found..." << std::endl;
357  }
358 }
359 
360 void mouse_handler_base::init_dragging(bool& dragging_flag)
361 {
362  dragging_flag = true;
363  SDL_GetMouseState(&drag_from_x_, &drag_from_y_);
365 }
366 
368 {
369  dragging_started_ = false;
370  dragging_left_ = false;
371  dragging_touch_ = false;
372  dragging_right_ = false;
373  cursor::set_dragging(false);
374 }
375 
376 void mouse_handler_base::clear_dragging(const SDL_MouseButtonEvent& event, bool browse)
377 {
378  // we reset dragging info before calling functions
379  // because they may take time to return, and we
380  // could have started other drag&drop before that
381  cursor::set_dragging(false);
382 
383  if(dragging_started_) {
384  dragging_started_ = false;
385 
386  if(dragging_touch_) {
387  dragging_touch_ = false;
388  // Maybe to do: create touch_drag_end(). Do panning and what else there. OTOH, it's fine now.
389  left_drag_end(event.x, event.y, browse);
390  }
391 
392  if(dragging_left_) {
393  dragging_left_ = false;
394  left_drag_end(event.x, event.y, browse);
395  }
396 
397  if(dragging_right_) {
398  dragging_right_ = false;
399  right_drag_end(event.x, event.y, browse);
400  }
401  } else {
402  dragging_left_ = false;
403  dragging_right_ = false;
404  dragging_touch_ = false;
405  }
406 }
407 
408 } // 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:379
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:593
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:731
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:754
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:193
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:195
bool show_menu_
Show context menu flag.
bool valid() const
Definition: location.hpp:93
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:22
Encapsulates the map of the game.
Definition: location.hpp:42
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:2182
bool is_touch_click(const SDL_MouseButtonEvent &event) const
bool middle_click_scrolls()
Definition: general.cpp:745
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:41
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:255
Class that keeps track of all the keys on the keyboard.
Definition: key.hpp:27
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:1902
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.