The Battle for Wesnoth  1.17.10+dev
floating_label.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2022
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 #include "floating_label.hpp"
17 
18 #include "display.hpp"
19 #include "draw.hpp"
20 #include "draw_manager.hpp"
21 #include "font/sdl_ttf_compat.hpp" // pango_line_width
22 #include "font/text.hpp"
23 #include "log.hpp"
24 #include "sdl/utils.hpp"
25 #include "video.hpp"
26 
27 #include <map>
28 #include <set>
29 #include <stack>
30 
31 static lg::log_domain log_font("font");
32 #define DBG_FT LOG_STREAM(debug, log_font)
33 #define LOG_FT LOG_STREAM(info, log_font)
34 #define WRN_FT LOG_STREAM(warn, log_font)
35 #define ERR_FT LOG_STREAM(err, log_font)
36 
37 static lg::log_domain log_display("display");
38 #define ERR_DP LOG_STREAM(err, log_display)
39 
40 namespace
41 {
42 typedef std::map<int, font::floating_label> label_map;
43 label_map labels;
44 int label_id = 1;
45 
46 std::stack<std::set<int>> label_contexts;
47 
48 /** Curent ID of the help string. */
49 int help_string_ = 0;
50 }
51 
52 namespace font
53 {
54 floating_label::floating_label(const std::string& text, const surface& surf)
55  : tex_()
56  , screen_loc_()
57  , alpha_(0)
58  , fadeout_(0)
59  , time_start_(0)
60  , text_(text)
61  , font_size_(SIZE_SMALL)
62  , color_(NORMAL_COLOR)
63  , bgcolor_(0, 0, 0, SDL_ALPHA_TRANSPARENT)
64  , xpos_(0)
65  , ypos_(0)
66  , xmove_(0)
67  , ymove_(0)
68  , lifetime_(-1)
69  , width_(-1)
70  , height_(-1)
71  , clip_rect_(video::game_canvas())
72  , visible_(true)
73  , align_(CENTER_ALIGN)
74  , border_(0)
75  , scroll_(ANCHOR_LABEL_SCREEN)
76  , use_markup_(true)
77 {
78  if (surf.get()) {
79  tex_ = texture(surf);
80  }
81 }
82 
83 void floating_label::move(double xmove, double ymove)
84 {
85  xpos_ += xmove;
86  ypos_ += ymove;
87 }
88 
89 int floating_label::xpos(std::size_t width) const
90 {
91  int xpos = int(xpos_);
92  if(align_ == font::CENTER_ALIGN) {
93  xpos -= width / 2;
94  } else if(align_ == font::RIGHT_ALIGN) {
95  xpos -= width;
96  }
97 
98  return xpos;
99 }
100 
101 rect floating_label::get_bg_rect(const rect& text_rect) const
102 {
103  const int zf = display::get_singleton()->get_zoom_factor();
104  return {
105  text_rect.x - (border_ * zf),
106  text_rect.y - (border_ * zf),
107  text_rect.w + (border_ * zf * 2),
108  text_rect.h + (border_ * zf * 2)
109  };
110 }
111 
113 {
114  if(video::headless()) {
115  return false;
116  }
117 
118  if(tex_ != nullptr) {
119  // Already have a texture
120  return true;
121  }
122 
123  if(text_.empty()) {
124  // Empty labels are unfortunately still used sometimes
125  return false;
126  }
127 
128  DBG_FT << "creating floating label texture";
130 
131  text.set_link_aware(false)
135  .set_alignment(PANGO_ALIGN_LEFT)
138  .set_maximum_height(height_ < 0 ? clip_rect_.h : height_, true)
139  .set_ellipse_mode(PANGO_ELLIPSIZE_END)
141  .set_add_outline(bgcolor_.a == 0);
142 
143  // ignore last '\n'
144  if(!text_.empty() && *(text_.rbegin()) == '\n') {
145  text.set_text(std::string(text_.begin(), text_.end() - 1), use_markup_);
146  } else {
147  text.set_text(text_, use_markup_);
148  }
149 
150  tex_ = text.render_and_get_texture();
151  if(!tex_) {
152  ERR_FT << "could not create floating label's text";
153  return false;
154  }
155 
156  return true;
157 }
158 
160 {
161  DBG_FT << "undrawing floating label from " << screen_loc_;
163  screen_loc_ = {};
164 }
165 
167 {
168  if(video::headless() || text_.empty()) {
169  return;
170  }
171 
172  if(!create_texture()) {
173  ERR_FT << "failed to create texture for floating label";
174  return;
175  }
176 
177  point new_pos = get_pos(time);
178  rect draw_loc {new_pos.x, new_pos.y, tex_.w(), tex_.h()};
179 
180  uint8_t new_alpha = get_alpha(time);
181 
182  if(screen_loc_ == draw_loc && alpha_ == new_alpha) {
183  // nothing has changed
184  return;
185  }
186 
187  // Invalidate former draw loc
189 
190  // Invalidate new draw loc in preparation
192 
193  DBG_FT << "updating floating label from " << screen_loc_ << " to " << draw_loc;
194 
195  screen_loc_ = draw_loc;
196  alpha_ = new_alpha;
197 }
198 
200 {
201  if(!visible_) {
202  screen_loc_ = {};
203  return;
204  }
205 
206  if(screen_loc_.empty()) {
207  return;
208  }
209 
210  if(!tex_) {
211  ERR_DP << "trying to draw floating label with no texture!";
212  return;
213  }
214 
216  return;
217  }
218 
219  DBG_FT << "drawing floating label to " << screen_loc_;
220 
221  // Clip if appropriate.
222  auto clipper = draw::reduce_clip(clip_rect_);
223 
224  // Draw background, if appropriate
225  if(bgcolor_.a != 0) {
226  draw::fill(get_bg_rect(screen_loc_), bgcolor_);
227  }
228 
229  // Apply the label texture to the screen.
231  draw::blit(tex_, screen_loc_);
232 }
233 
234 void floating_label::set_lifetime(int lifetime, int fadeout)
235 {
236  lifetime_ = lifetime;
237  fadeout_ = fadeout;
238  time_start_ = SDL_GetTicks();
239 }
240 
241 
243 {
244  int time_alive = get_time_alive(time);
245  return {
246  static_cast<int>(time_alive * xmove_ + xpos(tex_.w())),
247  static_cast<int>(time_alive * ymove_ + ypos_)
248  };
249 }
250 
251 uint8_t floating_label::get_alpha(int time)
252 {
253  if(lifetime_ >= 0 && fadeout_ > 0) {
254  int time_alive = get_time_alive(time);
255  if(time_alive >= lifetime_ && tex_ != nullptr) {
256  // fade out moving floating labels
257  int alpha_sub = 255 * (time_alive - lifetime_) / fadeout_;
258  if (alpha_sub >= 255) {
259  return 0;
260  } else {
261  return 255 - alpha_sub;
262  }
263  }
264  }
265  return 255;
266 }
267 
269 {
270  if(label_contexts.empty()) {
271  return 0;
272  }
273 
274  ++label_id;
275  labels.emplace(label_id, flabel);
276  label_contexts.top().insert(label_id);
277  return label_id;
278 }
279 
280 void move_floating_label(int handle, double xmove, double ymove)
281 {
282  const label_map::iterator i = labels.find(handle);
283  if(i != labels.end()) {
284  i->second.move(xmove, ymove);
285  }
286 }
287 
288 void scroll_floating_labels(double xmove, double ymove)
289 {
290  for(label_map::iterator i = labels.begin(); i != labels.end(); ++i) {
291  if(i->second.scroll() == ANCHOR_LABEL_MAP) {
292  i->second.move(xmove, ymove);
293  }
294  }
295 }
296 
297 void remove_floating_label(int handle, int fadeout)
298 {
299  const label_map::iterator i = labels.find(handle);
300  if(i != labels.end()) {
301  if(fadeout > 0) {
302  i->second.set_lifetime(0, fadeout);
303  return;
304  } else if(fadeout < 0) {
305  i->second.set_lifetime(0, i->second.get_fade_time());
306  return;
307  }
308  // Queue a redraw of where the label was.
309  i->second.undraw();
310  labels.erase(i);
311  }
312 
313  if(!label_contexts.empty()) {
314  label_contexts.top().erase(handle);
315  }
316 }
317 
318 void show_floating_label(int handle, bool value)
319 {
320  const label_map::iterator i = labels.find(handle);
321  if(i != labels.end()) {
322  i->second.show(value);
323  }
324 }
325 
327 {
328  const label_map::iterator i = labels.find(handle);
329  if(i != labels.end()) {
330  if (i->second.create_texture()) {
331  SDL_Point size = i->second.get_draw_size();
332  return {0, 0, size.x, size.y};
333  }
334  }
335  return sdl::empty_rect;
336 }
337 
339 {
340  //TODO: 'pause' floating labels in other contexrs
341  label_contexts.emplace();
342 }
343 
345 {
346  //TODO: 'pause' floating labels in other contexrs
347  const std::set<int>& context = label_contexts.top();
348 
349  while(!context.empty()) {
350  // Remove_floating_label removes the passed label from the context.
351  // This loop removes a different label in every iteration.
352  remove_floating_label(*context.begin());
353  }
354 
355  label_contexts.pop();
356 }
357 
359 {
360  if(label_contexts.empty()) {
361  return;
362  }
363 
364  const std::set<int>& context = label_contexts.top();
365 
366  // draw the labels in the order they were added, so later added labels (likely to be tooltips)
367  // are displayed over earlier added labels.
368  for(auto& [id, label] : labels) {
369  if(context.count(id) > 0) {
370  label.draw();
371  }
372  }
373 }
374 
376 {
377  if(label_contexts.empty()) {
378  return;
379  }
380  int time = SDL_GetTicks();
381 
382  std::set<int>& context = label_contexts.top();
383 
384  for(auto& [id, label] : labels) {
385  if(context.count(id) > 0) {
386  label.update(time);
387  }
388  }
389 
390  //remove expired labels
391  for(label_map::iterator j = labels.begin(); j != labels.end(); ) {
392  if(context.count(j->first) > 0 && j->second.expired(time)) {
393  DBG_FT << "removing expired floating label " << j->first;
394  context.erase(j->first);
395  labels.erase(j++);
396  } else {
397  ++j;
398  }
399  }
400 }
401 
402 void set_help_string(const std::string& str)
403 {
404  remove_floating_label(help_string_);
405 
406  const color_t color{0, 0, 0, 0xbb};
407 
408  int size = font::SIZE_LARGE;
409  point canvas_size = video::game_canvas_size();
410 
411  while(size > 0) {
412  if(pango_line_width(str, size) > canvas_size.x) {
413  size--;
414  } else {
415  break;
416  }
417  }
418 
419  const int border = 5;
420 
421  floating_label flabel(str);
422  flabel.set_font_size(size);
423  flabel.set_position(canvas_size.x / 2, canvas_size.y);
424  flabel.set_bg_color(color);
425  flabel.set_border_size(border);
426 
427  help_string_ = add_floating_label(flabel);
428 
429  const rect& r = get_floating_label_rect(help_string_);
430  move_floating_label(help_string_, 0.0, -double(r.h));
431 }
432 
434 {
435  remove_floating_label(help_string_);
436  help_string_ = 0;
437 }
438 
439 }
floating_label(const std::string &text, const surface &surface=nullptr)
Drawing functions, for drawing things on the screen.
int xpos(std::size_t width) const
SDL_Surface * get() const
Definition: surface.hpp:91
void draw_floating_labels()
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:98
static lg::log_domain log_font("font")
void remove_floating_label(int handle, int fadeout)
removes the floating label given by &#39;handle&#39; from the screen
void set_alpha_mod(uint8_t alpha)
Alpha modifier.
Definition: texture.cpp:151
point get_pos(int time)
void update(int time)
Finalize draw position and alpha, and queue redrawing if changed.
int w() const
The draw-space width of the texture, in pixels.
Definition: texture.hpp:105
bool set_text(const std::string &text, const bool markedup)
Sets the text to render.
Definition: text.cpp:333
Collection of helper functions relating to Pango formatting.
#define ERR_DP
static double get_zoom_factor()
Returns the current zoom factor.
Definition: display.hpp:256
texture render_and_get_texture()
Returns the cached texture, or creates a new one otherwise.
Definition: text.cpp:116
void scroll_floating_labels(double xmove, double ymove)
moves all floating labels that have &#39;scroll_mode&#39; set to ANCHOR_LABEL_MAP
void show_floating_label(int handle, bool value)
hides or shows a floating label
int h() const
The draw-space height of the texture, in pixels.
Definition: texture.hpp:114
void update_floating_labels()
void set_lifetime(int lifetime, int fadeout=100)
pango_text & set_link_aware(bool b)
Definition: text.cpp:505
void set_font_size(int font_size)
void move(double xmove, double ymove)
Change the floating label&#39;s position.
pango_text & set_font_style(const FONT_STYLE font_style)
Definition: text.cpp:391
pango_text & get_text_renderer()
Returns a reference to a static pango_text object.
Definition: text.cpp:968
int get_time_alive(int current_time) const
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:462
void set_help_string(const std::string &str)
Displays a help string with the given text.
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:32
void move_floating_label(int handle, double xmove, double ymove)
moves the floating label given by &#39;handle&#39; by (xmove,ymove)
pango_text & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:482
#define DBG_FT
void draw()
Draw the label to the screen.
rect game_canvas()
The game canvas area, in drawing coordinates.
Definition: video.cpp:417
void clear_help_string()
Removes the help string.
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
std::string label
What to show in the filter&#39;s drop-down list.
Definition: manager.cpp:217
::rect get_clip()
Get the current clipping area, in draw coordinates.
Definition: draw.cpp:468
void set_bg_color(const color_t &bg_color)
#define ERR_FT
rect intersect(const SDL_Rect &r) const
Calculates the intersection of this rectangle and another; that is, the maximal rectangle that is con...
Definition: rect.cpp:92
pango_text & set_characters_per_line(const unsigned characters_per_line)
Definition: text.cpp:426
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:58
bool headless()
The game is running headless.
Definition: video.cpp:143
void undraw()
Mark the last drawn location as requiring redraw.
void set_position(double xpos, double ypos)
map_display and display: classes which take care of displaying the map and game-data on the screen...
bool create_texture()
Ensure a texture for this floating label exists, creating one if needed.
bool overlaps(const SDL_Rect &r) const
Whether the given rectangle and this rectangle overlap.
Definition: rect.cpp:74
const color_t NORMAL_COLOR
pango_text & set_family_class(font::family_class fclass)
Definition: text.cpp:369
bool empty() const
False if both w and h are > 0, true otherwise.
Definition: rect.cpp:49
clip_setter reduce_clip(const SDL_Rect &clip)
Set the clipping area to the intersection of the current clipping area and the given rectangle...
Definition: draw.cpp:448
std::size_t i
Definition: function.cpp:967
pango_text & set_font_size(unsigned font_size)
Definition: text.cpp:379
static lg::log_domain log_display("display")
point game_canvas_size()
The size of the game canvas, in drawing coordinates / game pixels.
Definition: video.cpp:422
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:46
int add_floating_label(const floating_label &flabel)
add a label floating on the screen above everything else.
Holds a 2D point.
Definition: point.hpp:24
SDL_Rect get_floating_label_rect(int handle)
uint8_t get_alpha(int time)
void set_border_size(int border)
void blit(const texture &tex, const SDL_Rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition: draw.cpp:301
Text class.
Definition: text.hpp:79
const int SIZE_LARGE
Definition: constants.cpp:30
pango_text & set_maximum_width(int width)
Definition: text.cpp:410
constexpr const SDL_Rect empty_rect
Definition: rect.hpp:30
rect get_bg_rect(const rect &text_rect) const
Standard logging facilities (interface).
pango_text & set_maximum_height(int height, bool multiline)
Definition: text.cpp:437
pango_text & set_foreground_color(const color_t &color)
Definition: text.cpp:401
std::shared_ptr< halo_record > handle
Definition: halo.hpp:31
Transitional API for porting SDL_ttf-based code to Pango.
void fill(const SDL_Rect &rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Fill an area with the given colour.
Definition: draw.cpp:41
void invalidate_region(const rect &region)
Mark a region of the screen as requiring redraw.
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
const int SIZE_SMALL
Definition: constants.cpp:24
pango_text & set_add_outline(bool do_add)
Definition: text.cpp:524
int pango_line_width(const std::string &line, int font_size, font::pango_text::FONT_STYLE font_style=font::pango_text::STYLE_NORMAL)
Determine the width of a line of text given a certain font size.