The Battle for Wesnoth  1.19.5+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 using namespace std::chrono_literals;
49 
50 namespace font
51 {
52 floating_label::floating_label(const std::string& text)
53  : tex_()
54  , screen_loc_()
55  , alpha_(0)
56  , fadeout_(0)
57  , time_start_()
58  , text_(text)
59  , font_size_(SIZE_SMALL)
60  , color_(NORMAL_COLOR)
61  , bgcolor_(0, 0, 0, SDL_ALPHA_TRANSPARENT)
62  , xpos_(0)
63  , ypos_(0)
64  , xmove_(0)
65  , ymove_(0)
66  , lifetime_(-1)
67  , width_(-1)
68  , height_(-1)
69  , clip_rect_(video::game_canvas())
70  , visible_(true)
71  , align_(CENTER_ALIGN)
72  , border_(0)
73  , scroll_(ANCHOR_LABEL_SCREEN)
74  , use_markup_(true)
75 {
76 }
77 
78 void floating_label::move(double xmove, double ymove)
79 {
80  xpos_ += xmove;
81  ypos_ += ymove;
82 }
83 
84 int floating_label::xpos(std::size_t width) const
85 {
86  int xpos = int(xpos_);
87  if(align_ == font::CENTER_ALIGN) {
88  xpos -= width / 2;
89  } else if(align_ == font::RIGHT_ALIGN) {
90  xpos -= width;
91  }
92 
93  return xpos;
94 }
95 
96 rect floating_label::get_bg_rect(const rect& text_rect) const
97 {
98  return text_rect.padded_by(border_);
99 }
100 
102 {
103  tex_.reset();
104 }
105 
107 {
108  if(video::headless()) {
109  return false;
110  }
111 
112  if(tex_ != nullptr) {
113  // Already have a texture
114  return true;
115  }
116 
117  if(text_.empty()) {
118  // Empty labels are unfortunately still used sometimes
119  return false;
120  }
121 
122  DBG_FT << "creating floating label texture, text: " << text_.substr(0,15);
124 
125  text.set_link_aware(false)
129  .set_alignment(PANGO_ALIGN_LEFT)
132  .set_maximum_height(height_ < 0 ? clip_rect_.h : height_, true)
133  .set_ellipse_mode(PANGO_ELLIPSIZE_END)
135  .set_add_outline(bgcolor_.a == 0);
136 
137  // ignore last '\n'
138  if(!text_.empty() && *(text_.rbegin()) == '\n') {
139  text.set_text(std::string(text_.begin(), text_.end() - 1), use_markup_);
140  } else {
141  text.set_text(text_, use_markup_);
142  }
143 
144  tex_ = text.render_and_get_texture();
145  if(!tex_) {
146  ERR_FT << "could not create floating label's text";
147  return false;
148  }
149 
150  return true;
151 }
152 
154 {
155  DBG_FT << "undrawing floating label from " << screen_loc_;
157  screen_loc_ = {};
158 }
159 
160 void floating_label::update(const clock::time_point& time)
161 {
162  if(video::headless() || text_.empty()) {
163  return;
164  }
165 
166  if(!create_texture()) {
167  ERR_FT << "failed to create texture for floating label";
168  return;
169  }
170 
171  point new_pos = get_pos(time);
172  rect draw_loc {new_pos.x, new_pos.y, tex_.w(), tex_.h()};
173 
174  uint8_t new_alpha = get_alpha(time);
175 
176  // Invalidate former draw loc
178 
179  // Invalidate new draw loc in preparation
181 
182  DBG_FT << "updating floating label from " << screen_loc_ << " to " << draw_loc;
183 
184  screen_loc_ = draw_loc;
185  alpha_ = new_alpha;
186 }
187 
189 {
190  if(!visible_) {
191  screen_loc_ = {};
192  return;
193  }
194 
195  if(screen_loc_.empty()) {
196  return;
197  }
198 
199  if(!tex_) {
200  ERR_DP << "trying to draw floating label with no texture!";
201  return;
202  }
203 
205  return;
206  }
207 
208  DBG_FT << "drawing floating label to " << screen_loc_;
209 
210  // Clip if appropriate.
211  auto clipper = draw::reduce_clip(clip_rect_);
212 
213  // Draw background, if appropriate
214  if(bgcolor_.a != 0) {
216  }
217 
218  // Apply the label texture to the screen.
221 }
222 
223 void floating_label::set_lifetime(const std::chrono::milliseconds& lifetime, const std::chrono::milliseconds& fadeout)
224 {
225  lifetime_ = lifetime;
226  fadeout_ = fadeout;
227  time_start_ = std::chrono::steady_clock::now();
228 }
229 
230 std::chrono::milliseconds floating_label::get_time_alive(const clock::time_point& current_time) const
231 {
232  return std::chrono::duration_cast<std::chrono::milliseconds>(current_time - time_start_);
233 }
234 
235 point floating_label::get_pos(const clock::time_point& time)
236 {
237  auto time_alive = get_time_alive(time);
238  return {
239  static_cast<int>(time_alive.count() * xmove_ + xpos(tex_.w())),
240  static_cast<int>(time_alive.count() * ymove_ + ypos_)
241  };
242 }
243 
244 uint8_t floating_label::get_alpha(const clock::time_point& time)
245 {
246  if(lifetime_ >= 0ms && fadeout_ > 0ms) {
247  auto time_alive = get_time_alive(time);
248  if(time_alive >= lifetime_ && tex_ != nullptr) {
249  // fade out moving floating labels
250  int alpha_sub = 255 * (time_alive - lifetime_) / fadeout_;
251  if (alpha_sub >= 255) {
252  return 0;
253  } else {
254  return 255 - alpha_sub;
255  }
256  }
257  }
258  return 255;
259 }
260 
262 {
263  if(label_contexts.empty()) {
264  return 0;
265  }
266 
267  ++label_id;
268  labels.emplace(label_id, flabel);
269  label_contexts.top().insert(label_id);
270  return label_id;
271 }
272 
273 void move_floating_label(int handle, double xmove, double ymove)
274 {
275  const label_map::iterator i = labels.find(handle);
276  if(i != labels.end()) {
277  i->second.move(xmove, ymove);
278  }
279 }
280 
281 void scroll_floating_labels(double xmove, double ymove)
282 {
283  for(label_map::iterator i = labels.begin(); i != labels.end(); ++i) {
284  if(i->second.scroll() == ANCHOR_LABEL_MAP) {
285  i->second.move(xmove, ymove);
286  }
287  }
288 }
289 
290 void remove_floating_label(int handle, const std::chrono::milliseconds& fadeout)
291 {
292  const label_map::iterator i = labels.find(handle);
293  if(i != labels.end()) {
294  if(fadeout > 0ms) {
295  i->second.set_lifetime(0ms, fadeout);
296  return;
297  } else if(fadeout < 0ms) {
298  i->second.set_lifetime(0ms, i->second.get_fade_time());
299  return;
300  }
301  // Queue a redraw of where the label was.
302  i->second.undraw();
303  labels.erase(i);
304  }
305 
306  if(!label_contexts.empty()) {
307  label_contexts.top().erase(handle);
308  }
309 }
310 
311 void show_floating_label(int handle, bool value)
312 {
313  const label_map::iterator i = labels.find(handle);
314  if(i != labels.end()) {
315  i->second.show(value);
316  }
317 }
318 
320 {
321  const label_map::iterator i = labels.find(handle);
322  if(i != labels.end()) {
323  if (i->second.create_texture()) {
324  SDL_Point size = i->second.get_draw_size();
325  return {0, 0, size.x, size.y};
326  }
327  }
328  return sdl::empty_rect;
329 }
330 
332 {
333  // hacky but the whole floating label system needs to be redesigned...
334  for(auto& [id, label] : labels) {
335  if(label_contexts.top().count(id) > 0) {
336  label.undraw();
337  }
338  }
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  auto time = std::chrono::steady_clock::now();
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 }
std::chrono::milliseconds lifetime_
rect get_bg_rect(const rect &text_rect) const
clock::time_point time_start_
bool create_texture()
Ensure a texture for this floating label exists, creating one if needed.
point get_pos(const clock::time_point &time)
std::chrono::milliseconds get_time_alive(const clock::time_point &current_time) const
int xpos(std::size_t width) const
void update(const clock::time_point &time)
Finalize draw position and alpha, and queue redrawing if changed.
void set_lifetime(const std::chrono::milliseconds &lifetime, const std::chrono::milliseconds &fadeout=std::chrono::milliseconds{100})
std::chrono::milliseconds fadeout_
uint8_t get_alpha(const clock::time_point &time)
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:544
pango_text & set_characters_per_line(const unsigned characters_per_line)
Definition: text.cpp:579
pango_text & set_foreground_color(const color_t &color)
Definition: text.cpp:554
pango_text & set_family_class(font::family_class fclass)
Definition: text.cpp:522
pango_text & set_add_outline(bool do_add)
Definition: text.cpp:677
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:615
pango_text & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:635
pango_text & set_font_size(unsigned font_size)
Definition: text.cpp:532
pango_text & set_link_aware(bool b)
Definition: text.cpp:658
bool set_text(const std::string &text, const bool markedup)
Sets the text to render.
Definition: text.cpp:484
pango_text & set_maximum_height(int height, bool multiline)
Definition: text.cpp:590
pango_text & set_maximum_width(int width)
Definition: text.cpp:563
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:1028
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:200
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
Graphical text output.
@ FONT_SANS_SERIF
pango_text & get_text_renderer()
Returns a reference to a static pango_text object.
Definition: text.cpp:1136
int add_floating_label(const floating_label &flabel)
add a label floating on the screen above everything else.
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 remove_floating_label(int handle, const std::chrono::milliseconds &fadeout)
removes the floating label given by 'handle' from the screen
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:139
rect game_canvas()
The game canvas area, in drawing coordinates.
Definition: video.cpp:427
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:48
constexpr rect padded_by(int dx, int dy) const
Returns a new rectangle with dx horizontal padding and dy vertical padding.
Definition: rect.hpp:156
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:91
bool overlaps(const SDL_Rect &r) const
Whether the given rectangle and this rectangle overlap.
Definition: rect.cpp:73