The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
controller_base.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2003 - 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
17#include "controller_base.hpp"
18
19#include "display.hpp"
20#include "events.hpp"
24#include "log.hpp"
28#include "gui/core/event/handler.hpp" // gui2::is_in_dialog
29#include "soundsource.hpp"
30#include "gui/core/timer.hpp"
31#include "sdl/input.hpp" // get_mouse_state
32#include "video.hpp"
33
34static lg::log_domain log_display("display");
35#define ERR_DP LOG_STREAM(err, log_display)
36
37using namespace std::chrono_literals;
38
40 : game_config_(game_config_manager::get()->game_config())
41 , scrolling_(false)
42 , scroll_up_(false)
43 , scroll_down_(false)
44 , scroll_left_(false)
45 , scroll_right_(false)
46 , last_scroll_tick_()
47 , scroll_carry_x_(0.0)
48 , scroll_carry_y_(0.0)
49 , key_release_listener_(*this)
50 , long_touch_timer_(0)
51{
52}
53
61
63{
64 if(long_touch_timer_ != 0 && !get_mouse_handler_base().dragging_started()) {
65 float x_now;
66 float y_now;
67 uint32_t mouse_state = sdl::get_mouse_state(&x_now, &y_now);
68
69#ifdef MOUSE_TOUCH_EMULATION
70 if(mouse_state & SDL_BUTTON(SDL_BUTTON_RIGHT)) {
71 // Monkey-patch touch controls again to make them look like left button.
72 mouse_state = SDL_BUTTON(SDL_BUTTON_LEFT);
73 }
74#endif
75
76 // Workaround for double-menu b/c of slow events processing, or I don't know.
77 int dx = x - x_now;
78 int dy = y - y_now;
79 int threshold = get_mouse_handler_base().drag_threshold();
80 bool yes_actually_dragging = dx * dx + dy * dy >= threshold * threshold;
81
82 if(!yes_actually_dragging
83 && (mouse_state & SDL_BUTTON_MASK(SDL_BUTTON_LEFT)) != 0)
84 {
85 show_menu(get_display().get_theme().context_menu(), { static_cast<int>(x_now), static_cast<int>(y_now) }, true);
86 }
87 }
88
90}
91
92void controller_base::handle_event(const SDL_Event& event)
93{
94 if(gui2::is_in_dialog()) {
95 return;
96 }
97
99
100 SDL_Event new_event = {};
101
102 switch(event.type) {
103 case SDL_EVENT_TEXT_INPUT:
104 if(have_keyboard_focus()) {
106 }
107 break;
108
109 case SDL_EVENT_TEXT_EDITING:
110 // Ignore empty editing events: on Wayland, restarting text input makes the
111 // compositor send an empty preedit, which would otherwise trigger an endless
112 // stop/start loop until the connection breaks.
113 if(have_keyboard_focus() && event.edit.text && *event.edit.text) {
114 SDL_Event evt = event;
115 evt.type = SDL_EVENT_TEXT_INPUT;
117 SDL_StopTextInput(video::get_window());
118 SDL_StartTextInput(video::get_window());
119 }
120 break;
121
122 case SDL_EVENT_KEY_DOWN:
123 // Detect key press events, unless there something that has keyboard focus
124 // in which case the key press events should go only to it.
125 if(have_keyboard_focus()) {
126 if(event.key.key == SDLK_ESCAPE
127 || event.key.key == SDLK_AC_BACK)
128 {
130 break;
131 }
132
135 process_keyup_event(event);
136 } else {
138 }
139 break;
140
141 case SDL_EVENT_KEY_UP:
142 process_keyup_event(event);
144 break;
145
146 case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
148 break;
149
150 case SDL_EVENT_JOYSTICK_HAT_MOTION:
152 break;
153
154 case SDL_EVENT_MOUSE_MOTION:
155 // Ignore old mouse motion events in the event queue
156 if(SDL_PeepEvents(&new_event, 1, SDL_GETEVENT, SDL_EVENT_MOUSE_MOTION, SDL_EVENT_MOUSE_MOTION) > 0) {
157 while(SDL_PeepEvents(&new_event, 1, SDL_GETEVENT, SDL_EVENT_MOUSE_MOTION, SDL_EVENT_MOUSE_MOTION) > 0) {};
158
159 if(!events::is_touch(new_event.motion)) {
160 mh_base.mouse_motion_event(new_event.motion, is_browsing());
161 }
162 } else {
163 if(!events::is_touch(new_event.motion)) {
164 mh_base.mouse_motion_event(event.motion, is_browsing());
165 }
166 }
167 break;
168
169 case SDL_EVENT_FINGER_MOTION:
170 if(SDL_PeepEvents(&new_event, 1, SDL_GETEVENT, SDL_EVENT_FINGER_MOTION, SDL_EVENT_FINGER_MOTION) > 0) {
171 while(SDL_PeepEvents(&new_event, 1, SDL_GETEVENT, SDL_EVENT_FINGER_MOTION, SDL_EVENT_FINGER_MOTION) > 0) {
172 };
173 mh_base.touch_motion_event(new_event.tfinger, is_browsing());
174 } else {
175 mh_base.touch_motion_event(event.tfinger, is_browsing());
176 }
177 break;
178
179 case SDL_EVENT_MOUSE_BUTTON_DOWN:
180 if(events::is_touch(event.button)) {
181 int x = event.button.x;
182 int y = event.button.y;
183
184 if(long_touch_timer_ == 0) {
186 std::bind(&controller_base::long_touch_callback, this, x, y));
187 }
188 }
189
190 mh_base.mouse_press(event.button, is_browsing());
192 break;
193
194 case SDL_EVENT_FINGER_DOWN:
195 // handled by mouse case
196 break;
197
198 case SDL_EVENT_MOUSE_BUTTON_UP:
199 if(long_touch_timer_ != 0) {
202 }
203
204 mh_base.mouse_press(event.button, is_browsing());
205 if(mh_base.get_show_menu()) {
206 show_menu(get_display().get_theme().context_menu(), { static_cast<int>(event.button.x), static_cast<int>(event.button.y) }, true);
207 }
208 break;
209
210 case SDL_EVENT_FINGER_UP:
211 // handled by mouse case
212 break;
213
214 case SDL_EVENT_MOUSE_WHEEL:
215 // Right and down are positive in Wesnoth's map.
216 // Right and up are positive in SDL_MouseWheelEvent on all platforms
217#if defined(_WIN32) || defined(__APPLE__)
218 mh_base.mouse_wheel(event.wheel.x, -event.wheel.y, is_browsing());
219#else
220 {
221 mh_base.mouse_wheel(event.wheel.x, -event.wheel.y, is_browsing());
222 }
223#endif
224 break;
225
226 case TIMER_EVENT:
227 gui2::execute_timer(reinterpret_cast<std::size_t>(event.user.data1));
228 break;
229
230 default:
231 break;
232 }
233}
234
236{
237 if(gui2::is_in_dialog()) {
238 return;
239 }
240
242}
243
245{
246 if(event.type == SDL_EVENT_KEY_UP) {
248 }
249}
250
252{
253 return true;
254}
255
256bool controller_base::handle_scroll(int mousex, int mousey, int mouse_flags)
257{
258 const bool mouse_in_window =
261
262 int scroll_speed = prefs::get().scroll_speed();
263 double dx = 0.0, dy = 0.0;
264
265 int scroll_threshold = prefs::get().mouse_scrolling()
267 : 0;
268
269 for(const theme::menu& m : get_display().get_theme().menus()) {
270 if(m.get_location().contains(mousex, mousey)) {
271 scroll_threshold = 0;
272 }
273 }
274
275 // Scale scroll distance according to time passed
276 auto tick_now = std::chrono::steady_clock::now();
277
278 // If we weren't previously scrolling, start small.
279 auto dt = 1ms;
280 if (scrolling_) {
281 dt = std::chrono::duration_cast<std::chrono::milliseconds>(tick_now - last_scroll_tick_);
282 }
283
284 // scroll_speed is in percent. Ticks are in milliseconds.
285 // Let's assume the maximum speed (100) moves 50 hexes per second,
286 // i.e. 3600 pixels per 1000 ticks.
287 double scroll_amount = dt.count() * 0.036 * double(scroll_speed);
288 last_scroll_tick_ = tick_now;
289
290 // Apply keyboard scrolling
291 dy -= scroll_up_ * scroll_amount;
292 dy += scroll_down_ * scroll_amount;
293 dx -= scroll_left_ * scroll_amount;
294 dx += scroll_right_ * scroll_amount;
295
296 // Scroll if mouse is placed near the edge of the screen
297 if(mouse_in_window) {
298 if(mousey < scroll_threshold) {
299 dy -= scroll_amount;
300 }
301
302 if(mousey > video::game_canvas_size().y - scroll_threshold) {
303 dy += scroll_amount;
304 }
305
306 if(mousex < scroll_threshold) {
307 dx -= scroll_amount;
308 }
309
310 if(mousex > video::game_canvas_size().x - scroll_threshold) {
311 dx += scroll_amount;
312 }
313 }
314
316
317 // Scroll with middle-mouse if enabled
318 if((mouse_flags & SDL_BUTTON_MMASK) != 0 && prefs::get().middle_click_scrolls()) {
319 const point original_loc = mh_base.get_scroll_start();
320
321 if(mh_base.scroll_started()
322 && get_display().map_outside_area().contains(mousex, mousey))
323 {
324 // Scroll speed is proportional from the distance from the first
325 // middle click and scrolling speed preference.
326 const double speed = 0.01 * scroll_amount;
327 const double snap_dist = 16; // Snap to horizontal/vertical scrolling
328 const double x_diff = (mousex - original_loc.x);
329 const double y_diff = (mousey - original_loc.y);
330
331 if(std::fabs(x_diff) > snap_dist || std::fabs(y_diff) <= snap_dist) {
332 dx += speed * x_diff;
333 }
334
335 if(std::fabs(y_diff) > snap_dist || std::fabs(x_diff) <= snap_dist) {
336 dy += speed * y_diff;
337 }
338 } else { // Event may fire mouse down out of order with respect to initial click
339 mh_base.set_scroll_start(mousex, mousey);
340 }
341 }
342
343 // If nothing is scrolling, just return.
344 if (!dx && !dy) {
345 return false;
346 }
347
348 // If we are continuing a scroll, carry over any subpixel movement.
349 if (scrolling_) {
350 dx += scroll_carry_x_;
351 dy += scroll_carry_y_;
352 }
353 point dist{int(dx), int(dy)};
354 scroll_carry_x_ = dx - double(dist.x);
355 scroll_carry_y_ = dy - double(dist.y);
356
357 // Scroll the display
358 get_display().scroll(dist);
359
360 // Even if the integer parts are both zero, we are still scrolling.
361 // The subpixel amounts will add up.
362 return true;
363}
364
366{
368 l->play_slice();
369 }
370
371 events::pump();
373 events::draw();
374
375 // Update sound sources before scrolling
377 l->update();
378 }
379
380 const theme::menu* const m = get_display().menu_pressed();
381 if(m != nullptr) {
382 const rect& menu_loc = m->location(video::game_canvas());
383 show_menu(m, { menu_loc.x + 1, menu_loc.y + menu_loc.h + 1 }, false);
384 return;
385 }
386
387 const theme::action* const a = get_display().action_pressed();
388 if(a != nullptr) {
389 execute_action(a->items());
390 return;
391 }
392
393 auto str_vec = additional_actions_pressed();
394 if(!str_vec.empty()) {
395 execute_action(str_vec);
396 return;
397 }
398
399 bool was_scrolling = scrolling_;
400
401 float mousex, mousey;
402 uint8_t mouse_flags = sdl::get_mouse_state(&mousex, &mousey);
403
404 scrolling_ = handle_scroll(mousex, mousey, mouse_flags);
405
406 map_location highlighted_hex = get_display().mouseover_hex();
407
408 // Scrolling ended, update the cursor and the brightened hex
409 if(!scrolling_ && was_scrolling) {
410 get_mouse_handler_base().mouse_update(is_browsing(), highlighted_hex);
411 }
412}
413
414bool controller_base::show_menu(const theme::menu* menu, const point& loc, bool context_menu)
415{
417 if(!menu || !cmd_exec) {
418 return false;
419 }
420
421 // context menus cannot appear outside map area,
422 // but main top-panel menus can.
423 if(context_menu && !get_display().map_area().contains(loc)) {
424 return false;
425 }
426
427 cmd_exec->show_menu(menu->items(), loc, context_menu);
428 return true;
429}
430
431void controller_base::execute_action(const std::vector<std::string>& items)
432{
434 if(!cmd_exec) {
435 return;
436 }
437
438 cmd_exec->execute_action(items);
439}
map_location loc
Definition move.cpp:172
void handle_event(const SDL_Event &event) override
virtual display & get_display()=0
Get a reference to a display member a derived class uses.
virtual plugins_context * get_plugins_context()
Get (optionally) a plugins context a derived class uses.
bool handle_scroll(int mousex, int mousey, int mouse_flags)
Handle scrolling by keyboard, joystick and moving mouse near map edges.
virtual ~controller_base()
void handle_event(const SDL_Event &event) override
Process mouse- and keypress-events from SDL.
virtual void process_keyup_event(const SDL_Event &)
Process keyup (always).
bool show_menu(const theme::menu *menu, const point &loc, bool context_menu)
virtual soundsource::manager * get_soundsource_man()
Get (optionally) a soundsources manager a derived class uses.
virtual hotkey::command_executor * get_hotkey_command_executor()
Optionally get a command executor to handle context menu events.
virtual void process() override
virtual events::mouse_handler_base & get_mouse_handler_base()=0
Get a reference to a mouse handler member a derived class uses.
virtual void process_focus_keydown_event(const SDL_Event &)
Process keydown (only when the general map display does not have focus).
virtual bool have_keyboard_focus()
Derived classes should override this to return false when arrow keys should not scroll the map,...
virtual void execute_action(const std::vector< std::string > &items_arg)
virtual void process_keydown_event(const SDL_Event &)
Process keydown (always).
void long_touch_callback(int x, int y)
std::size_t long_touch_timer_
Context menu timer.
virtual void play_slice()
std::chrono::steady_clock::time_point last_scroll_tick_
virtual std::vector< std::string > additional_actions_pressed()
virtual bool is_browsing() const
const theme::action * action_pressed()
Definition display.cpp:1417
theme & get_theme()
Definition display.hpp:382
const theme::menu * menu_pressed()
Definition display.cpp:1433
bool scroll(const point &amount, bool force=false)
Scrolls the display by amount pixels.
Definition display.cpp:1540
const map_location & mouseover_hex() const
Definition display.hpp:302
virtual int drag_threshold() const
Minimum dragging distance to fire the drag&drop.
void touch_motion_event(const SDL_TouchFingerEvent &event, const bool browse)
virtual void mouse_press(const SDL_MouseButtonEvent &event, const bool browse)
void mouse_update(const bool browse, map_location loc)
Update the mouse with a fake mouse motion.
void mouse_motion_event(const SDL_MouseMotionEvent &event, const bool browse)
virtual void mouse_wheel(int xscroll, int yscroll, bool browse)
Called when scrolling with the mouse wheel.
void set_scroll_start(int x, int y)
Called when the middle click scrolling.
virtual void show_menu(const std::vector< config > &items_arg, const point &menu_loc, bool context_menu)
void execute_action(const std::vector< std::string > &items_arg)
static prefs & get()
int scroll_speed()
bool get_scroll_when_mouse_outside(bool def)
int mouse_scroll_threshold()
Gets the threshold for when to scroll.
const std::vector< std::string > & items() const
Definition theme.hpp:183
virtual rect & location(const rect &screen) const
Definition theme.cpp:333
const std::vector< config > & items() const
Definition theme.hpp:236
const std::vector< menu > & menus() const
Definition theme.hpp:256
static lg::log_domain log_display("display")
controller_base framework: controller_base is roughly analogous to a "dialog" class in a GUI toolkit ...
map_display and display: classes which take care of displaying the map and game-data on the screen.
#define TIMER_EVENT
Definition events.hpp:29
Contains functions for cleanly handling SDL input.
Standard logging facilities (interface).
void draw()
Trigger a draw cycle.
Definition events.cpp:712
void raise_process_event()
Definition events.cpp:717
void pump()
Process all events currently in the queue.
Definition events.cpp:480
bool is_touch(const SDL_MouseButtonEvent &event)
Check if this mouse button event is caused by a touch.
Definition events.cpp:763
Game configuration data as global variables.
std::chrono::milliseconds popup_show_delay
Delay before a popup shows.
Definition settings.cpp:38
bool is_in_dialog()
Is a dialog open?
Definition handler.cpp:1126
std::size_t add_timer(const std::chrono::milliseconds &interval, const std::function< void(std::size_t id)> &callback, const bool repeat)
Adds a new timer.
Definition timer.cpp:123
bool remove_timer(const std::size_t id)
Removes a timer.
Definition timer.cpp:164
bool execute_timer(const std::size_t id)
Executes a timer.
Definition timer.cpp:197
void jhat_event(const SDL_Event &event, command_executor *executor)
void key_event(const SDL_Event &event, command_executor *executor)
void mbutton_event(const SDL_Event &event, command_executor *executor)
void run_events(command_executor *executor)
void jbutton_event(const SDL_Event &event, command_executor *executor)
void keyup_event(const SDL_Event &, command_executor *executor)
uint32_t get_mouse_state(float *x, float *y)
A wrapper for SDL_GetMouseState that gives coordinates in draw space.
Definition input.cpp:28
bool window_has_mouse_focus()
True iff the window has mouse focus.
Definition video.cpp:760
point game_canvas_size()
The size of the game canvas, in drawing coordinates / game pixels.
Definition video.cpp:432
SDL_Window * get_window()
Definition video.cpp:712
rect game_canvas()
The game canvas area, in drawing coordinates.
Definition video.cpp:427
This file contains the settings handling of the widget library.
Encapsulates the map of the game.
Definition location.hpp:46
Holds a 2D point.
Definition point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition rect.hpp:49
Contains the gui2 timer routines.