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