The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
floating_label.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2003 - 2025
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"
21#include "font/text.hpp"
22#include "log.hpp"
24#include "video.hpp"
25
26#include <map>
27#include <set>
28#include <stack>
29
30static 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
36static lg::log_domain log_display("display");
37#define ERR_DP LOG_STREAM(err, log_display)
38
39namespace
40{
41typedef std::map<int, font::floating_label> label_map;
42label_map labels;
43int label_id = 1;
44
45std::stack<std::set<int>> label_contexts;
46
47}
48
49using namespace std::chrono_literals;
50
51namespace font
52{
53floating_label::floating_label(const std::string& text)
54 : tex_()
55 , screen_loc_()
56 , alpha_(0)
57 , fadeout_(0)
58 , time_start_()
59 , text_((!text.empty() && text.back() == '\n') ? std::string(text.begin(), text.end() - 1) : text) // remove unwanted trailing newline
60 , font_size_(SIZE_SMALL)
61 , color_(NORMAL_COLOR)
62 , bgcolor_(0, 0, 0, SDL_ALPHA_TRANSPARENT)
63 , xpos_(0)
64 , ypos_(0)
65 , xmove_(0)
66 , ymove_(0)
67 , lifetime_(-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}
76
77void floating_label::move(double xmove, double ymove)
78{
79 xpos_ += xmove;
80 ypos_ += ymove;
81}
82
83int floating_label::xpos(std::size_t width) const
84{
85 int xpos = int(xpos_);
87 xpos -= width / 2;
88 } else if(align_ == font::RIGHT_ALIGN) {
89 xpos -= width;
90 }
91
92 return xpos;
93}
94
95rect floating_label::get_bg_rect(const rect& text_rect) const
96{
97 return text_rect.padded_by(border_);
98}
99
104
106{
107 if(video::headless()) {
108 return false;
109 }
110
111 if(tex_ != nullptr) {
112 // Already have a texture
113 return true;
114 }
115
116 if(text_.empty()) {
117 // Empty labels are unfortunately still used sometimes
118 return false;
119 }
120
121 DBG_FT << "creating floating label texture, text: " << text_.substr(0,15);
123
124 text.set_link_aware(false)
128 .set_alignment(PANGO_ALIGN_LEFT)
131 .set_maximum_height(height_.value_or(clip_rect_.h), true)
132 .set_ellipse_mode(PANGO_ELLIPSIZE_END)
134 .set_add_outline(bgcolor_.a == 0);
135
137
139 if(!tex_) {
140 ERR_FT << "could not create floating label's text";
141 return false;
142 }
143
144 return true;
145}
146
148{
149 DBG_FT << "undrawing floating label from " << screen_loc_;
151 screen_loc_ = {};
152}
153
154void floating_label::update(const clock::time_point& time)
155{
156 if(video::headless() || text_.empty()) {
157 return;
158 }
159
160 if(!create_texture()) {
161 ERR_FT << "failed to create texture for floating label";
162 return;
163 }
164
165 rect draw_loc{get_pos(time), tex_.draw_size()};
166 uint8_t new_alpha = get_alpha(time);
167
168 // Nothing has changed
169 // FIXME: we consider border too since otherwise we get flickering under floating
170 // labels with a background (see bug #7700). This isn't ideal for chat messages or
171 // tooltips, but it optimizes the usual case (map labels).
172 if(screen_loc_ == draw_loc && alpha_ == new_alpha && border_ == 0) {
173 return;
174 }
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
223void 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
230auto floating_label::get_time_alive(const clock::time_point& current_time) const -> clock::duration
231{
232 return current_time - time_start_;
233}
234
235point floating_label::get_pos(const clock::time_point& time) const
236{
237 double new_x = xpos(tex_.w());
238 double new_y = ypos_;
239
240 if(xmove_ != 0.0 || ymove_ != 0.0) {
241 auto time_alive = std::chrono::duration_cast<std::chrono::milliseconds>(get_time_alive(time));
242 new_x += time_alive.count() * xmove_;
243 new_y += time_alive.count() * ymove_;
244 }
245
246 // TODO: return a floating point point
247 return {static_cast<int>(new_x), static_cast<int>(new_y)};
248}
249
250uint8_t floating_label::get_alpha(const clock::time_point& time) const
251{
252 if(lifetime_ >= 0ms && fadeout_ > 0ms) {
253 auto time_alive = get_time_alive(time);
254 if(time_alive >= lifetime_) {
255 double progress = chrono::normalize_progress(time_alive - lifetime_, fadeout_);
256 return float_to_color(1.0 - progress);
257 }
258 }
259 return ALPHA_OPAQUE;
260}
261
263{
264 if(label_contexts.empty()) {
265 return 0;
266 }
267
268 ++label_id;
269 labels.emplace(label_id, flabel);
270 label_contexts.top().insert(label_id);
271 return label_id;
272}
273
274void move_floating_label(int handle, double xmove, double ymove)
275{
276 const label_map::iterator i = labels.find(handle);
277 if(i != labels.end()) {
278 i->second.move(xmove, ymove);
279 }
280}
281
282void scroll_floating_labels(double xmove, double ymove)
283{
284 for(auto& [id, label] : labels) {
285 if(label.scroll() == ANCHOR_LABEL_MAP) {
286 label.move(xmove, ymove);
287 }
288 }
289}
290
291void remove_floating_label(int handle, const std::chrono::milliseconds& fadeout)
292{
293 const label_map::iterator i = labels.find(handle);
294 if(i != labels.end()) {
295 if(fadeout > 0ms) {
296 i->second.set_lifetime(0ms, fadeout);
297 return;
298 } else if(fadeout < 0ms) {
299 i->second.set_lifetime(0ms, i->second.get_fade_time());
300 return;
301 }
302 // Queue a redraw of where the label was.
303 i->second.undraw();
304 labels.erase(i);
305 }
306
307 if(!label_contexts.empty()) {
308 label_contexts.top().erase(handle);
309 }
310}
311
312void show_floating_label(int handle, bool value)
313{
314 const label_map::iterator i = labels.find(handle);
315 if(i != labels.end()) {
316 i->second.show(value);
317 }
318}
319
321{
322 const label_map::iterator i = labels.find(handle);
323 if(i != labels.end()) {
324 if (i->second.create_texture()) {
325 point size = i->second.get_draw_size();
326 return {0, 0, size.x, size.y};
327 }
328 }
329 return {};
330}
331
333{
334 // hacky but the whole floating label system needs to be redesigned...
335 for(auto& [id, label] : labels) {
336 if(label_contexts.top().count(id) > 0) {
337 label.undraw();
338 }
339 }
340
341 //TODO: 'pause' floating labels in other contexrs
342 label_contexts.emplace();
343}
344
346{
347 //TODO: 'pause' floating labels in other contexrs
348 const std::set<int>& context = label_contexts.top();
349
350 while(!context.empty()) {
351 // Remove_floating_label removes the passed label from the context.
352 // This loop removes a different label in every iteration.
353 remove_floating_label(*context.begin());
354 }
355
356 label_contexts.pop();
357}
358
360{
361 if(label_contexts.empty()) {
362 return;
363 }
364
365 const std::set<int>& context = label_contexts.top();
366
367 // draw the labels in the order they were added, so later added labels (likely to be tooltips)
368 // are displayed over earlier added labels.
369 for(auto& [id, label] : labels) {
370 if(context.count(id) > 0) {
371 label.draw();
372 }
373 }
374}
375
377{
378 if(label_contexts.empty()) {
379 return;
380 }
381
382 auto time = std::chrono::steady_clock::now();
383 std::set<int>& context = label_contexts.top();
384
385 for(auto iter = labels.begin(); iter!= labels.end();) {
386 auto& [id, label] = *iter;
387
388 if(context.count(id) > 0) {
389 label.update(time);
390
391 if(label.expired(time)) {
392 context.erase(id);
393 iter = labels.erase(iter);
394 continue;
395 }
396 }
397
398 ++iter;
399 }
400}
401
402}
utils::optional< int > width_
std::chrono::milliseconds lifetime_
uint8_t get_alpha(const clock::time_point &time) const
rect get_bg_rect(const rect &text_rect) const
utils::optional< int > height_
point get_pos(const clock::time_point &time) const
clock::time_point time_start_
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 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_
void move(double xmove, double ymove)
Change the floating label's position.
clock::duration get_time_alive(const clock::time_point &current_time) const
void draw()
Draw the label to the screen.
void undraw()
Mark the last drawn location as requiring redraw.
Text class.
Definition text.hpp:78
pango_text & set_font_style(const FONT_STYLE font_style)
Definition text.cpp:381
pango_text & set_characters_per_line(const unsigned characters_per_line)
Definition text.cpp:416
pango_text & set_foreground_color(const color_t &color)
Definition text.cpp:391
pango_text & set_family_class(font::family_class fclass)
Definition text.cpp:359
pango_text & set_add_outline(bool do_add)
Definition text.cpp:514
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition text.cpp:452
pango_text & set_alignment(const PangoAlignment alignment)
Definition text.cpp:472
pango_text & set_font_size(unsigned font_size)
Definition text.cpp:369
pango_text & set_link_aware(bool b)
Definition text.cpp:495
bool set_text(const std::string &text, const bool markedup)
Sets the text to render.
Definition text.cpp:333
pango_text & set_maximum_height(int height, bool multiline)
Definition text.cpp:427
pango_text & set_maximum_width(int width)
Definition text.cpp:400
texture render_and_get_texture()
Returns the cached texture, or creates a new one otherwise.
Definition text.cpp:122
int w() const
The draw-space width of the texture, in pixels.
Definition texture.hpp:103
void reset()
Releases ownership of the managed texture and resets the ptr to null.
Definition texture.cpp:202
point draw_size() const
The size of the texture in draw-space.
Definition texture.hpp:120
void set_alpha_mod(uint8_t alpha)
Alpha modifier.
Definition texture.cpp:146
constexpr uint8_t ALPHA_OPAQUE
Definition color.hpp:37
constexpr uint8_t float_to_color(double n)
Convert a double in the range [0.0,1.0] to an 8-bit colour value.
Definition color.hpp:274
#define ERR_DP
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_FT
std::string label
What to show in the filter's drop-down list.
Definition manager.cpp:201
std::string id
Text to match against addon_info.tags()
Definition manager.cpp:199
Standard logging facilities (interface).
constexpr double normalize_progress(const std::chrono::duration< RepE, PeriodE > &elapsed, const std::chrono::duration< RepD, PeriodD > &duration)
Definition chrono.hpp:88
void invalidate_region(const rect &region)
Mark a region of the screen as requiring redraw.
clip_setter reduce_clip(const ::rect &clip)
Set the clipping area to the intersection of the current clipping area and the given rectangle.
Definition draw.cpp:595
void fill()
Definition draw.cpp:115
::rect get_clip()
Get the current clipping area, in draw coordinates.
Definition draw.cpp:615
void blit(const texture &tex, const ::rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition draw.cpp:409
Graphical text output.
pango_text & get_text_renderer()
Returns a reference to a static pango_text object.
Definition text.cpp:952
int add_floating_label(const floating_label &flabel)
add a label floating on the screen above everything else.
rect get_floating_label_rect(int handle)
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
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
@ ANCHOR_LABEL_SCREEN
@ ANCHOR_LABEL_MAP
STL namespace.
bool headless()
The game is running headless.
Definition video.cpp:146
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
Holds a 2D point.
Definition point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition rect.hpp:49
bool empty() const
False if both w and h are > 0, true otherwise.
Definition rect.cpp:49
constexpr rect padded_by(int dx, int dy) const
Returns a new rectangle with dx horizontal padding and dy vertical padding.
Definition rect.hpp:158
rect intersect(const 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 rect &r) const
Whether the given rectangle and this rectangle overlap.
Definition rect.cpp:74
#define i