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