The Battle for Wesnoth  1.19.25+dev
cursor.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  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 /**
17  * @file
18  * Support for different cursors-shapes.
19  */
20 
21 #include "cursor.hpp"
22 
23 #include "picture.hpp"
25 #include "sdl/utils.hpp"
26 
27 #include <boost/logic/tribool.hpp>
28 
29 #include <SDL3/SDL_mouse.h>
30 
31 #include <array>
32 #include <memory>
33 
34 namespace cursor
35 {
36 namespace
37 {
38 struct cursor_data
39 {
40  std::string image_bw;
41  std::string image_color;
42 
43  int hot_x{0};
44  int hot_y{0};
45 
46  boost::tribool is_color{boost::indeterminate};
47 
48  using cursor_ptr = std::unique_ptr<SDL_Cursor, void(*)(SDL_Cursor*)>;
49  cursor_ptr cursor{nullptr, SDL_DestroyCursor};
50 };
51 
52 //
53 // Array with each available cursor type.
54 // macOS needs 16x16 b&w cursors. TODO: is that still the case?
55 //
56 auto available_cursors = std::array
57 {
58 #ifdef __APPLE__
59  cursor_data{ "normal.png", "normal.png", 0, 0 },
60  cursor_data{ "wait-alt.png", "wait.png", 0, 0 },
61  cursor_data{ "ibeam.png", "ibeam.png", 14, 14 },
62  cursor_data{ "move.png", "move.png", 0, 0 },
63  cursor_data{ "attack.png", "attack.png", 0, 0 },
64  cursor_data{ "select.png", "select.png", 0, 0 },
65  cursor_data{ "move_drag_alt.png", "move_drag.png", 2, 20 },
66  cursor_data{ "attack_drag_alt.png", "attack_drag.png", 3, 22 },
67  cursor_data{ "no_cursor.png", "", 0, 0 }
68 #else
69  cursor_data{ "normal.png", "normal.png", 0, 0 },
70  cursor_data{ "wait.png", "wait.png", 0, 0 },
71  cursor_data{ "ibeam.png", "ibeam.png", 14, 14 },
72  cursor_data{ "move.png", "move.png", 0, 0 },
73  cursor_data{ "attack.png", "attack.png", 0, 0 },
74  cursor_data{ "select.png", "select.png", 0, 0 },
75  cursor_data{ "move_drag.png", "move_drag.png", 2, 20 },
76  cursor_data{ "attack_drag.png", "attack_drag.png", 3, 22 },
77  cursor_data{ "no_cursor.png", "", 0, 0 }
78 #endif
79 };
80 
81 static_assert(available_cursors.size() == cursor::NUM_CURSORS);
82 
83 cursor::CURSOR_TYPE current_cursor = cursor::NORMAL;
84 bool have_focus = true;
85 
86 bool use_color_cursors()
87 {
88  return game_config::editor == false && prefs::get().use_color_cursors();
89 }
90 
91 SDL_Cursor* create_cursor(const surface& surf)
92 {
93  if(surf == nullptr) {
94  return nullptr;
95  }
96 
97  // The width must be a multiple of 8 (SDL requirement)
98 
99 #ifdef __APPLE__
100  std::size_t cursor_width = 16;
101 #else
102  std::size_t cursor_width = surf->w;
103  if((cursor_width % 8) != 0) {
104  cursor_width += 8 - (cursor_width % 8);
105  }
106 #endif
107 
108  std::vector<uint8_t> data((cursor_width * surf->h) / 8, 0);
109  std::vector<uint8_t> mask(data.size(), 0);
110 
111  // See https://wiki.libsdl.org/SDL_CreateCursor for documentation
112  // on the format that data has to be in to pass to SDL_CreateCursor
113  const_surface_lock lock(surf);
114  const utils::span pixels = lock.pixel_span();
115 
116  for(int y = 0; y != surf->h; ++y) {
117  for(int x = 0; x != surf->w; ++x) {
118  if(static_cast<std::size_t>(x) < cursor_width) {
119  uint8_t r, g, b, a;
120  SDL_GetRGBA(pixels[y * surf->w + x], SDL_GetPixelFormatDetails(surf->format), SDL_GetSurfacePalette(surf), &r, &g, &b, &a);
121 
122  const std::size_t index = y * cursor_width + x;
123  const std::size_t shift = 7 - (index % 8);
124 
125  const uint8_t trans = (a < 128 ? 0 : 1) << shift;
126  const uint8_t black = (trans == 0 || (r + g + b) / 3 > 128 ? 0 : 1) << shift;
127 
128  data[index / 8] |= black;
129  mask[index / 8] |= trans;
130  }
131  }
132  }
133 
134  return SDL_CreateCursor(&data[0], &mask[0], cursor_width, surf->h, 0, 0);
135 }
136 
137 SDL_Cursor* get_cursor(cursor::CURSOR_TYPE type)
138 {
139  const bool use_color = use_color_cursors();
140  cursor_data& data = available_cursors[type];
141 
142  if(data.cursor == nullptr || boost::indeterminate(data.is_color) || data.is_color != use_color) {
143  static const std::string color_prefix = "cursors/";
144  static const std::string bw_prefix = "cursors-bw/";
145 
146  if(use_color) {
147  surface surf = image::get_surface(color_prefix + data.image_color);
148  data.cursor.reset(SDL_CreateColorCursor(surf, data.hot_x, data.hot_y));
149  } else {
150  surface surf = image::get_surface(bw_prefix + data.image_bw);
151  data.cursor.reset(create_cursor(surf));
152  }
153 
154  data.is_color = use_color;
155  }
156 
157  return data.cursor.get();
158 }
159 
160 } // end anon namespace
161 
163 {
164  set();
165 }
166 
168 {
169 }
170 
172 {
173  // Change only if it's a valid cursor
174  if(type != NUM_CURSORS) {
175  current_cursor = type;
176  } else if(current_cursor == NUM_CURSORS) {
177  // Except if the current one is also invalid.
178  // In this case, change to a valid one.
179  current_cursor = NORMAL;
180  }
181 
182  SDL_SetCursor(get_cursor(current_cursor));
183 }
184 
185 void set_dragging(bool drag)
186 {
187  switch(current_cursor) {
188  case MOVE:
189  if(drag) cursor::set(MOVE_DRAG);
190  break;
191  case ATTACK:
192  if(drag) cursor::set(ATTACK_DRAG);
193  break;
194  case MOVE_DRAG:
195  if(!drag) cursor::set(MOVE);
196  break;
197  case ATTACK_DRAG:
198  if(!drag) cursor::set(ATTACK);
199  break;
200  default:
201  break;
202  }
203 }
204 
206 {
207  return current_cursor;
208 }
209 
210 void set_focus(bool focus)
211 {
212  have_focus = focus;
213 
214  if(!focus) {
215  set();
216  }
217 }
218 
220  : old_(current_cursor)
221 {
222  set(type);
223 }
224 
226 {
227  set(old_);
228 }
229 
230 } // end namespace cursor
double g
Definition: astarsearch.cpp:63
static prefs & get()
bool use_color_cursors()
Helper class for pinning SDL surfaces into memory.
Definition: surface.hpp:98
std::string image_bw
Definition: cursor.cpp:40
boost::tribool is_color
Definition: cursor.cpp:46
int hot_x
Definition: cursor.cpp:43
std::string image_color
Definition: cursor.cpp:41
int hot_y
Definition: cursor.cpp:44
CURSOR_TYPE get()
Definition: cursor.cpp:205
void set_dragging(bool drag)
Definition: cursor.cpp:185
CURSOR_TYPE
Definition: cursor.hpp:28
@ ATTACK
Definition: cursor.hpp:28
@ MOVE_DRAG
Definition: cursor.hpp:28
@ NORMAL
Definition: cursor.hpp:28
@ ATTACK_DRAG
Definition: cursor.hpp:28
@ NUM_CURSORS
Definition: cursor.hpp:28
@ MOVE
Definition: cursor.hpp:28
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition: cursor.cpp:171
void set_focus(bool focus)
Definition: cursor.cpp:210
surface get_surface(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image surface suitable for software manipulation.
Definition: picture.cpp:691
std::size_t index(std::string_view str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
std::string_view data
Definition: picture.cpp:188
surface surf
Image.
setter(CURSOR_TYPE type)
Definition: cursor.cpp:219
CURSOR_TYPE old_
Definition: cursor.hpp:46
#define b