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