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