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