The Battle for Wesnoth  1.17.17+dev
floating_label.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2023
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 "sdl/utils.hpp"
24 #include "video.hpp"
25 
26 #include <map>
27 #include <set>
28 #include <stack>
29 
30 static lg::log_domain log_font("font");
31 #define DBG_FT LOG_STREAM(debug, log_font)
32 #define LOG_FT LOG_STREAM(info, log_font)
33 #define WRN_FT LOG_STREAM(warn, log_font)
34 #define ERR_FT LOG_STREAM(err, log_font)
35 
36 static lg::log_domain log_display("display");
37 #define ERR_DP LOG_STREAM(err, log_display)
38 
39 namespace
40 {
41 typedef std::map<int, font::floating_label> label_map;
42 label_map labels;
43 int label_id = 1;
44 
45 std::stack<std::set<int>> label_contexts;
46 
47 }
48 
49 namespace font
50 {
51 floating_label::floating_label(const std::string& text, const surface& surf)
52  : tex_()
53  , screen_loc_()
54  , alpha_(0)
55  , fadeout_(0)
56  , time_start_(0)
57  , text_(text)
58  , font_size_(SIZE_SMALL)
59  , color_(NORMAL_COLOR)
60  , bgcolor_(0, 0, 0, SDL_ALPHA_TRANSPARENT)
61  , xpos_(0)
62  , ypos_(0)
63  , xmove_(0)
64  , ymove_(0)
65  , lifetime_(-1)
66  , width_(-1)
67  , height_(-1)
68  , clip_rect_(video::game_canvas())
69  , visible_(true)
70  , align_(CENTER_ALIGN)
71  , border_(0)
72  , scroll_(ANCHOR_LABEL_SCREEN)
73  , use_markup_(true)
74 {
75  if (surf.get()) {
76  tex_ = texture(surf);
77  }
78 }
79 
80 void floating_label::move(double xmove, double ymove)
81 {
82  xpos_ += xmove;
83  ypos_ += ymove;
84 }
85 
86 int floating_label::xpos(std::size_t width) const
87 {
88  int xpos = int(xpos_);
89  if(align_ == font::CENTER_ALIGN) {
90  xpos -= width / 2;
91  } else if(align_ == font::RIGHT_ALIGN) {
92  xpos -= width;
93  }
94 
95  return xpos;
96 }
97 
98 rect floating_label::get_bg_rect(const rect& text_rect) const
99 {
100  return {
101  text_rect.x - border_,
102  text_rect.y - border_,
103  text_rect.w + (border_ * 2),
104  text_rect.h + (border_ * 2)
105  };
106 }
107 
109 {
110  if(video::headless()) {
111  return false;
112  }
113 
114  if(tex_ != nullptr) {
115  // Already have a texture
116  return true;
117  }
118 
119  if(text_.empty()) {
120  // Empty labels are unfortunately still used sometimes
121  return false;
122  }
123 
124  DBG_FT << "creating floating label texture";
126 
127  text.set_link_aware(false)
131  .set_alignment(PANGO_ALIGN_LEFT)
134  .set_maximum_height(height_ < 0 ? clip_rect_.h : height_, true)
135  .set_ellipse_mode(PANGO_ELLIPSIZE_END)
137  .set_add_outline(bgcolor_.a == 0);
138 
139  // ignore last '\n'
140  if(!text_.empty() && *(text_.rbegin()) == '\n') {
141  text.set_text(std::string(text_.begin(), text_.end() - 1), use_markup_);
142  } else {
143  text.set_text(text_, use_markup_);
144  }
145 
146  tex_ = text.render_and_get_texture();
147  if(!tex_) {
148  ERR_FT << "could not create floating label's text";
149  return false;
150  }
151 
152  return true;
153 }
154 
156 {
157  DBG_FT << "undrawing floating label from " << screen_loc_;
159  screen_loc_ = {};
160 }
161 
163 {
164  if(video::headless() || text_.empty()) {
165  return;
166  }
167 
168  if(!create_texture()) {
169  ERR_FT << "failed to create texture for floating label";
170  return;
171  }
172 
173  point new_pos = get_pos(time);
174  rect draw_loc {new_pos.x, new_pos.y, tex_.w(), tex_.h()};
175 
176  uint8_t new_alpha = get_alpha(time);
177 
178  if(screen_loc_ == draw_loc && alpha_ == new_alpha) {
179  // nothing has changed
180  return;
181  }
182 
183  // Invalidate former draw loc
185 
186  // Invalidate new draw loc in preparation
188 
189  DBG_FT << "updating floating label from " << screen_loc_ << " to " << draw_loc;
190 
191  screen_loc_ = draw_loc;
192  alpha_ = new_alpha;
193 }
194 
196 {
197  if(!visible_) {
198  screen_loc_ = {};
199  return;
200  }
201 
202  if(screen_loc_.empty()) {
203  return;
204  }
205 
206  if(!tex_) {
207  ERR_DP << "trying to draw floating label with no texture!";
208  return;
209  }
210 
212  return;
213  }
214 
215  DBG_FT << "drawing floating label to " << screen_loc_;
216 
217  // Clip if appropriate.
218  auto clipper = draw::reduce_clip(clip_rect_);
219 
220  // Draw background, if appropriate
221  if(bgcolor_.a != 0) {
223  }
224 
225  // Apply the label texture to the screen.
228 }
229 
230 void floating_label::set_lifetime(int lifetime, int fadeout)
231 {
232  lifetime_ = lifetime;
233  fadeout_ = fadeout;
234  time_start_ = SDL_GetTicks();
235 }
236 
237 
239 {
240  int time_alive = get_time_alive(time);
241  return {
242  static_cast<int>(time_alive * xmove_ + xpos(tex_.w())),
243  static_cast<int>(time_alive * ymove_ + ypos_)
244  };
245 }
246 
247 uint8_t floating_label::get_alpha(int time)
248 {
249  if(lifetime_ >= 0 && fadeout_ > 0) {
250  int time_alive = get_time_alive(time);
251  if(time_alive >= lifetime_ && tex_ != nullptr) {
252  // fade out moving floating labels
253  int alpha_sub = 255 * (time_alive - lifetime_) / fadeout_;
254  if (alpha_sub >= 255) {
255  return 0;
256  } else {
257  return 255 - alpha_sub;
258  }
259  }
260  }
261  return 255;
262 }
263 
265 {
266  if(label_contexts.empty()) {
267  return 0;
268  }
269 
270  ++label_id;
271  labels.emplace(label_id, flabel);
272  label_contexts.top().insert(label_id);
273  return label_id;
274 }
275 
276 void move_floating_label(int handle, double xmove, double ymove)
277 {
278  const label_map::iterator i = labels.find(handle);
279  if(i != labels.end()) {
280  i->second.move(xmove, ymove);
281  }
282 }
283 
284 void scroll_floating_labels(double xmove, double ymove)
285 {
286  for(label_map::iterator i = labels.begin(); i != labels.end(); ++i) {
287  if(i->second.scroll() == ANCHOR_LABEL_MAP) {
288  i->second.move(xmove, ymove);
289  }
290  }
291 }
292 
293 void remove_floating_label(int handle, int fadeout)
294 {
295  const label_map::iterator i = labels.find(handle);
296  if(i != labels.end()) {
297  if(fadeout > 0) {
298  i->second.set_lifetime(0, fadeout);
299  return;
300  } else if(fadeout < 0) {
301  i->second.set_lifetime(0, i->second.get_fade_time());
302  return;
303  }
304  // Queue a redraw of where the label was.
305  i->second.undraw();
306  labels.erase(i);
307  }
308 
309  if(!label_contexts.empty()) {
310  label_contexts.top().erase(handle);
311  }
312 }
313 
314 void show_floating_label(int handle, bool value)
315 {
316  const label_map::iterator i = labels.find(handle);
317  if(i != labels.end()) {
318  i->second.show(value);
319  }
320 }
321 
323 {
324  const label_map::iterator i = labels.find(handle);
325  if(i != labels.end()) {
326  if (i->second.create_texture()) {
327  SDL_Point size = i->second.get_draw_size();
328  return {0, 0, size.x, size.y};
329  }
330  }
331  return sdl::empty_rect;
332 }
333 
335 {
336  //TODO: 'pause' floating labels in other contexrs
337  label_contexts.emplace();
338 }
339 
341 {
342  //TODO: 'pause' floating labels in other contexrs
343  const std::set<int>& context = label_contexts.top();
344 
345  while(!context.empty()) {
346  // Remove_floating_label removes the passed label from the context.
347  // This loop removes a different label in every iteration.
348  remove_floating_label(*context.begin());
349  }
350 
351  label_contexts.pop();
352 }
353 
355 {
356  if(label_contexts.empty()) {
357  return;
358  }
359 
360  const std::set<int>& context = label_contexts.top();
361 
362  // draw the labels in the order they were added, so later added labels (likely to be tooltips)
363  // are displayed over earlier added labels.
364  for(auto& [id, label] : labels) {
365  if(context.count(id) > 0) {
366  label.draw();
367  }
368  }
369 }
370 
372 {
373  if(label_contexts.empty()) {
374  return;
375  }
376  int time = SDL_GetTicks();
377 
378  std::set<int>& context = label_contexts.top();
379 
380  for(auto& [id, label] : labels) {
381  if(context.count(id) > 0) {
382  label.update(time);
383  }
384  }
385 
386  //remove expired labels
387  for(label_map::iterator j = labels.begin(); j != labels.end(); ) {
388  if(context.count(j->first) > 0 && j->second.expired(time)) {
389  DBG_FT << "removing expired floating label " << j->first;
390  context.erase(j->first);
391  labels.erase(j++);
392  } else {
393  ++j;
394  }
395  }
396 }
397 
398 }
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:80
pango_text & set_font_style(const FONT_STYLE font_style)
Definition: text.cpp:384
pango_text & set_characters_per_line(const unsigned characters_per_line)
Definition: text.cpp:419
pango_text & set_foreground_color(const color_t &color)
Definition: text.cpp:394
pango_text & set_family_class(font::family_class fclass)
Definition: text.cpp:362
pango_text & set_add_outline(bool do_add)
Definition: text.cpp:517
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:455
pango_text & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:475
pango_text & set_font_size(unsigned font_size)
Definition: text.cpp:372
pango_text & set_link_aware(bool b)
Definition: text.cpp:498
bool set_text(const std::string &text, const bool markedup)
Sets the text to render.
Definition: text.cpp:326
pango_text & set_maximum_height(int height, bool multiline)
Definition: text.cpp:430
pango_text & set_maximum_width(int width)
Definition: text.cpp:403
texture render_and_get_texture()
Returns the cached texture, or creates a new one otherwise.
Definition: text.cpp:116
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:217
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:448
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 blit(const texture &tex, const SDL_Rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition: draw.cpp:301
::rect get_clip()
Get the current clipping area, in draw coordinates.
Definition: draw.cpp:468
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:961
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:87
bool headless()
The game is running headless.
Definition: video.cpp:142
rect game_canvas()
The game canvas area, in drawing coordinates.
Definition: video.cpp:421
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:49
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
bool overlaps(const SDL_Rect &r) const
Whether the given rectangle and this rectangle overlap.
Definition: rect.cpp:74