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