The Battle for Wesnoth  1.19.3+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)
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 }
75 
76 void floating_label::move(double xmove, double ymove)
77 {
78  xpos_ += xmove;
79  ypos_ += ymove;
80 }
81 
82 int floating_label::xpos(std::size_t width) const
83 {
84  int xpos = int(xpos_);
85  if(align_ == font::CENTER_ALIGN) {
86  xpos -= width / 2;
87  } else if(align_ == font::RIGHT_ALIGN) {
88  xpos -= width;
89  }
90 
91  return xpos;
92 }
93 
94 rect floating_label::get_bg_rect(const rect& text_rect) const
95 {
96  return {
97  text_rect.x - border_,
98  text_rect.y - border_,
99  text_rect.w + (border_ * 2),
100  text_rect.h + (border_ * 2)
101  };
102 }
103 
105 {
106  tex_.reset();
107 }
108 
110 {
111  if(video::headless()) {
112  return false;
113  }
114 
115  if(tex_ != nullptr) {
116  // Already have a texture
117  return true;
118  }
119 
120  if(text_.empty()) {
121  // Empty labels are unfortunately still used sometimes
122  return false;
123  }
124 
125  DBG_FT << "creating floating label texture, text: " << text_.substr(0,15);
127 
128  text.set_link_aware(false)
132  .set_alignment(PANGO_ALIGN_LEFT)
135  .set_maximum_height(height_ < 0 ? clip_rect_.h : height_, true)
136  .set_ellipse_mode(PANGO_ELLIPSIZE_END)
138  .set_add_outline(bgcolor_.a == 0);
139 
140  // ignore last '\n'
141  if(!text_.empty() && *(text_.rbegin()) == '\n') {
142  text.set_text(std::string(text_.begin(), text_.end() - 1), use_markup_);
143  } else {
144  text.set_text(text_, use_markup_);
145  }
146 
147  tex_ = text.render_and_get_texture();
148  if(!tex_) {
149  ERR_FT << "could not create floating label's text";
150  return false;
151  }
152 
153  return true;
154 }
155 
157 {
158  DBG_FT << "undrawing floating label from " << screen_loc_;
160  screen_loc_ = {};
161 }
162 
164 {
165  if(video::headless() || text_.empty()) {
166  return;
167  }
168 
169  if(!create_texture()) {
170  ERR_FT << "failed to create texture for floating label";
171  return;
172  }
173 
174  point new_pos = get_pos(time);
175  rect draw_loc {new_pos.x, new_pos.y, tex_.w(), tex_.h()};
176 
177  uint8_t new_alpha = get_alpha(time);
178 
179  // Invalidate former draw loc
181 
182  // Invalidate new draw loc in preparation
184 
185  DBG_FT << "updating floating label from " << screen_loc_ << " to " << draw_loc;
186 
187  screen_loc_ = draw_loc;
188  alpha_ = new_alpha;
189 }
190 
192 {
193  if(!visible_) {
194  screen_loc_ = {};
195  return;
196  }
197 
198  if(screen_loc_.empty()) {
199  return;
200  }
201 
202  if(!tex_) {
203  ERR_DP << "trying to draw floating label with no texture!";
204  return;
205  }
206 
208  return;
209  }
210 
211  DBG_FT << "drawing floating label to " << screen_loc_;
212 
213  // Clip if appropriate.
214  auto clipper = draw::reduce_clip(clip_rect_);
215 
216  // Draw background, if appropriate
217  if(bgcolor_.a != 0) {
219  }
220 
221  // Apply the label texture to the screen.
224 }
225 
226 void floating_label::set_lifetime(int lifetime, int fadeout)
227 {
228  lifetime_ = lifetime;
229  fadeout_ = fadeout;
230  time_start_ = SDL_GetTicks();
231 }
232 
233 
235 {
236  int time_alive = get_time_alive(time);
237  return {
238  static_cast<int>(time_alive * xmove_ + xpos(tex_.w())),
239  static_cast<int>(time_alive * ymove_ + ypos_)
240  };
241 }
242 
243 uint8_t floating_label::get_alpha(int time)
244 {
245  if(lifetime_ >= 0 && fadeout_ > 0) {
246  int time_alive = get_time_alive(time);
247  if(time_alive >= lifetime_ && tex_ != nullptr) {
248  // fade out moving floating labels
249  int alpha_sub = 255 * (time_alive - lifetime_) / fadeout_;
250  if (alpha_sub >= 255) {
251  return 0;
252  } else {
253  return 255 - alpha_sub;
254  }
255  }
256  }
257  return 255;
258 }
259 
261 {
262  if(label_contexts.empty()) {
263  return 0;
264  }
265 
266  ++label_id;
267  labels.emplace(label_id, flabel);
268  label_contexts.top().insert(label_id);
269  return label_id;
270 }
271 
272 void move_floating_label(int handle, double xmove, double ymove)
273 {
274  const label_map::iterator i = labels.find(handle);
275  if(i != labels.end()) {
276  i->second.move(xmove, ymove);
277  }
278 }
279 
280 void scroll_floating_labels(double xmove, double ymove)
281 {
282  for(label_map::iterator i = labels.begin(); i != labels.end(); ++i) {
283  if(i->second.scroll() == ANCHOR_LABEL_MAP) {
284  i->second.move(xmove, ymove);
285  }
286  }
287 }
288 
289 void remove_floating_label(int handle, int fadeout)
290 {
291  const label_map::iterator i = labels.find(handle);
292  if(i != labels.end()) {
293  if(fadeout > 0) {
294  i->second.set_lifetime(0, fadeout);
295  return;
296  } else if(fadeout < 0) {
297  i->second.set_lifetime(0, i->second.get_fade_time());
298  return;
299  }
300  // Queue a redraw of where the label was.
301  i->second.undraw();
302  labels.erase(i);
303  }
304 
305  if(!label_contexts.empty()) {
306  label_contexts.top().erase(handle);
307  }
308 }
309 
310 void show_floating_label(int handle, bool value)
311 {
312  const label_map::iterator i = labels.find(handle);
313  if(i != labels.end()) {
314  i->second.show(value);
315  }
316 }
317 
319 {
320  const label_map::iterator i = labels.find(handle);
321  if(i != labels.end()) {
322  if (i->second.create_texture()) {
323  SDL_Point size = i->second.get_draw_size();
324  return {0, 0, size.x, size.y};
325  }
326  }
327  return sdl::empty_rect;
328 }
329 
331 {
332  // hacky but the whole floating label system needs to be redesigned...
333  for(auto& [id, label] : labels) {
334  if(label_contexts.top().count(id) > 0) {
335  label.undraw();
336  }
337  }
338 
339  //TODO: 'pause' floating labels in other contexrs
340  label_contexts.emplace();
341 }
342 
344 {
345  //TODO: 'pause' floating labels in other contexrs
346  const std::set<int>& context = label_contexts.top();
347 
348  while(!context.empty()) {
349  // Remove_floating_label removes the passed label from the context.
350  // This loop removes a different label in every iteration.
351  remove_floating_label(*context.begin());
352  }
353 
354  label_contexts.pop();
355 }
356 
358 {
359  if(label_contexts.empty()) {
360  return;
361  }
362 
363  const std::set<int>& context = label_contexts.top();
364 
365  // draw the labels in the order they were added, so later added labels (likely to be tooltips)
366  // are displayed over earlier added labels.
367  for(auto& [id, label] : labels) {
368  if(context.count(id) > 0) {
369  label.draw();
370  }
371  }
372 }
373 
375 {
376  if(label_contexts.empty()) {
377  return;
378  }
379  int time = SDL_GetTicks();
380 
381  std::set<int>& context = label_contexts.top();
382 
383  for(auto& [id, label] : labels) {
384  if(context.count(id) > 0) {
385  label.update(time);
386  }
387  }
388 
389  //remove expired labels
390  for(label_map::iterator j = labels.begin(); j != labels.end(); ) {
391  if(context.count(j->first) > 0 && j->second.expired(time)) {
392  DBG_FT << "removing expired floating label " << j->first;
393  context.erase(j->first);
394  labels.erase(j++);
395  } else {
396  ++j;
397  }
398  }
399 }
400 
401 }
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.
floating_label(const std::string &text)
int xpos(std::size_t width) const
void move(double xmove, double ymove)
Change the floating label's position.
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:529
pango_text & set_characters_per_line(const unsigned characters_per_line)
Definition: text.cpp:564
pango_text & set_foreground_color(const color_t &color)
Definition: text.cpp:539
pango_text & set_family_class(font::family_class fclass)
Definition: text.cpp:507
pango_text & set_add_outline(bool do_add)
Definition: text.cpp:662
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:600
pango_text & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:620
pango_text & set_font_size(unsigned font_size)
Definition: text.cpp:517
pango_text & set_link_aware(bool b)
Definition: text.cpp:643
bool set_text(const std::string &text, const bool markedup)
Sets the text to render.
Definition: text.cpp:462
pango_text & set_maximum_height(int height, bool multiline)
Definition: text.cpp:575
pango_text & set_maximum_width(int width)
Definition: text.cpp:548
texture render_and_get_texture()
Returns the cached texture, or creates a new one otherwise.
Definition: text.cpp:118
int w() const
The draw-space width of the texture, in pixels.
Definition: texture.hpp:105
void reset()
Releases ownership of the managed texture and resets the ptr to null.
Definition: texture.cpp:208
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:965
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:207
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:502
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:522
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:1118
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