The Battle for Wesnoth  1.17.21+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  // Invalidate former draw loc
180 
181  // Invalidate new draw loc in preparation
183 
184  DBG_FT << "updating floating label from " << screen_loc_ << " to " << draw_loc;
185 
186  screen_loc_ = draw_loc;
187  alpha_ = new_alpha;
188 }
189 
191 {
192  if(!visible_) {
193  screen_loc_ = {};
194  return;
195  }
196 
197  if(screen_loc_.empty()) {
198  return;
199  }
200 
201  if(!tex_) {
202  ERR_DP << "trying to draw floating label with no texture!";
203  return;
204  }
205 
207  return;
208  }
209 
210  DBG_FT << "drawing floating label to " << screen_loc_;
211 
212  // Clip if appropriate.
213  auto clipper = draw::reduce_clip(clip_rect_);
214 
215  // Draw background, if appropriate
216  if(bgcolor_.a != 0) {
218  }
219 
220  // Apply the label texture to the screen.
223 }
224 
225 void floating_label::set_lifetime(int lifetime, int fadeout)
226 {
227  lifetime_ = lifetime;
228  fadeout_ = fadeout;
229  time_start_ = SDL_GetTicks();
230 }
231 
232 
234 {
235  int time_alive = get_time_alive(time);
236  return {
237  static_cast<int>(time_alive * xmove_ + xpos(tex_.w())),
238  static_cast<int>(time_alive * ymove_ + ypos_)
239  };
240 }
241 
242 uint8_t floating_label::get_alpha(int time)
243 {
244  if(lifetime_ >= 0 && fadeout_ > 0) {
245  int time_alive = get_time_alive(time);
246  if(time_alive >= lifetime_ && tex_ != nullptr) {
247  // fade out moving floating labels
248  int alpha_sub = 255 * (time_alive - lifetime_) / fadeout_;
249  if (alpha_sub >= 255) {
250  return 0;
251  } else {
252  return 255 - alpha_sub;
253  }
254  }
255  }
256  return 255;
257 }
258 
260 {
261  if(label_contexts.empty()) {
262  return 0;
263  }
264 
265  ++label_id;
266  labels.emplace(label_id, flabel);
267  label_contexts.top().insert(label_id);
268  return label_id;
269 }
270 
271 void move_floating_label(int handle, double xmove, double ymove)
272 {
273  const label_map::iterator i = labels.find(handle);
274  if(i != labels.end()) {
275  i->second.move(xmove, ymove);
276  }
277 }
278 
279 void scroll_floating_labels(double xmove, double ymove)
280 {
281  for(label_map::iterator i = labels.begin(); i != labels.end(); ++i) {
282  if(i->second.scroll() == ANCHOR_LABEL_MAP) {
283  i->second.move(xmove, ymove);
284  }
285  }
286 }
287 
288 void remove_floating_label(int handle, int fadeout)
289 {
290  const label_map::iterator i = labels.find(handle);
291  if(i != labels.end()) {
292  if(fadeout > 0) {
293  i->second.set_lifetime(0, fadeout);
294  return;
295  } else if(fadeout < 0) {
296  i->second.set_lifetime(0, i->second.get_fade_time());
297  return;
298  }
299  // Queue a redraw of where the label was.
300  i->second.undraw();
301  labels.erase(i);
302  }
303 
304  if(!label_contexts.empty()) {
305  label_contexts.top().erase(handle);
306  }
307 }
308 
309 void show_floating_label(int handle, bool value)
310 {
311  const label_map::iterator i = labels.find(handle);
312  if(i != labels.end()) {
313  i->second.show(value);
314  }
315 }
316 
318 {
319  const label_map::iterator i = labels.find(handle);
320  if(i != labels.end()) {
321  if (i->second.create_texture()) {
322  SDL_Point size = i->second.get_draw_size();
323  return {0, 0, size.x, size.y};
324  }
325  }
326  return sdl::empty_rect;
327 }
328 
330 {
331  //TODO: 'pause' floating labels in other contexrs
332  label_contexts.emplace();
333 }
334 
336 {
337  //TODO: 'pause' floating labels in other contexrs
338  const std::set<int>& context = label_contexts.top();
339 
340  while(!context.empty()) {
341  // Remove_floating_label removes the passed label from the context.
342  // This loop removes a different label in every iteration.
343  remove_floating_label(*context.begin());
344  }
345 
346  label_contexts.pop();
347 }
348 
350 {
351  if(label_contexts.empty()) {
352  return;
353  }
354 
355  const std::set<int>& context = label_contexts.top();
356 
357  // draw the labels in the order they were added, so later added labels (likely to be tooltips)
358  // are displayed over earlier added labels.
359  for(auto& [id, label] : labels) {
360  if(context.count(id) > 0) {
361  label.draw();
362  }
363  }
364 }
365 
367 {
368  if(label_contexts.empty()) {
369  return;
370  }
371  int time = SDL_GetTicks();
372 
373  std::set<int>& context = label_contexts.top();
374 
375  for(auto& [id, label] : labels) {
376  if(context.count(id) > 0) {
377  label.update(time);
378  }
379  }
380 
381  //remove expired labels
382  for(label_map::iterator j = labels.begin(); j != labels.end(); ) {
383  if(context.count(j->first) > 0 && j->second.expired(time)) {
384  DBG_FT << "removing expired floating label " << j->first;
385  context.erase(j->first);
386  labels.erase(j++);
387  } else {
388  ++j;
389  }
390  }
391 }
392 
393 }
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