1 /*
2 Copyright (C) 2003 by David White <dave@whitevine.net>
3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY.
11
12 See the COPYING file for more details.
13 */
14
15 #include "floating_label.hpp"
16
17 #include "display.hpp"
18 #include "font/text.hpp"
19 #include "log.hpp"
20 #include "video.hpp"
21
22 #include <map>
23 #include <set>
24 #include <stack>
25
26 static lg::log_domain log_font("font");
27 #define DBG_FT LOG_STREAM(debug, log_font)
28 #define LOG_FT LOG_STREAM(info, log_font)
29 #define WRN_FT LOG_STREAM(warn, log_font)
30 #define ERR_FT LOG_STREAM(err, log_font)
31
32 namespace
33 {
34 typedef std::map<int, font::floating_label> label_map;
35 label_map labels;
36 int label_id = 1;
37
38 std::stack<std::set<int>> label_contexts;
39 }
40
41 namespace font
42 {
floating_label(const std::string & text,const surface & surf)43 floating_label::floating_label(const std::string& text, const surface& surf)
44 #if 0
45 : img_(),
46 #else
47 : surf_(surf)
48 , buf_(nullptr)
49 #endif
50 , text_(text)
51 , font_size_(SIZE_NORMAL)
52 , color_(NORMAL_COLOR)
53 , bgcolor_()
54 , bgalpha_(0)
55 , xpos_(0)
56 , ypos_(0)
57 , xmove_(0)
58 , ymove_(0)
59 , lifetime_(-1)
60 , width_(-1)
61 , height_(-1)
62 , clip_rect_(CVideo::get_singleton().screen_area())
63 , alpha_change_(0)
64 , visible_(true)
65 , align_(CENTER_ALIGN)
66 , border_(0)
67 , scroll_(ANCHOR_LABEL_SCREEN)
68 , use_markup_(true)
69 {
70 }
71
move(double xmove,double ymove)72 void floating_label::move(double xmove, double ymove)
73 {
74 xpos_ += xmove;
75 ypos_ += ymove;
76 }
77
xpos(size_t width) const78 int floating_label::xpos(size_t width) const
79 {
80 int xpos = int(xpos_);
81 if(align_ == font::CENTER_ALIGN) {
82 xpos -= width / 2;
83 } else if(align_ == font::RIGHT_ALIGN) {
84 xpos -= width;
85 }
86
87 return xpos;
88 }
89
create_surface()90 surface floating_label::create_surface()
91 {
92 if(!surf_) {
93 font::pango_text text;
94 text.set_foreground_color(color_);
95 text.set_font_size(font_size_);
96 text.set_maximum_width(width_ < 0 ? clip_rect_.w : width_);
97 text.set_maximum_height(height_ < 0 ? clip_rect_.h : height_, true);
98
99 // ignore last '\n'
100 if(!text_.empty() && *(text_.rbegin()) == '\n') {
101 text.set_text(std::string(text_.begin(), text_.end() - 1), use_markup_);
102 } else {
103 text.set_text(text_, use_markup_);
104 }
105
106 surface foreground = text.render();
107
108 if(foreground == nullptr) {
109 ERR_FT << "could not create floating label's text" << std::endl;
110 return nullptr;
111 }
112
113 // combine foreground text with its background
114 if(bgalpha_ != 0) {
115 // background is a dark tooltip box
116 surface background(foreground->w + border_ * 2, foreground->h + border_ * 2);
117
118 if(background == nullptr) {
119 ERR_FT << "could not create tooltip box" << std::endl;
120 return surf_ = foreground;
121 }
122
123 uint32_t color = SDL_MapRGBA(foreground->format, bgcolor_.r, bgcolor_.g, bgcolor_.b, bgalpha_);
124 sdl::fill_surface_rect(background, nullptr, color);
125
126 // we make the text less transparent, because the blitting on the
127 // dark background will darken the anti-aliased part.
128 // This 1.13 value seems to restore the brightness of version 1.4
129 // (where the text was blitted directly on screen)
130 adjust_surface_alpha(foreground, ftofxp(1.13));
131
132 SDL_Rect r{border_, border_, 0, 0};
133 adjust_surface_alpha(foreground, SDL_ALPHA_OPAQUE);
134 sdl_blit(foreground, nullptr, background, &r);
135
136 surf_ = background;
137 } else {
138 // background is blurred shadow of the text
139 surface background(foreground->w + 4, foreground->h + 4);
140 sdl::fill_surface_rect(background, nullptr, 0);
141 SDL_Rect r{2, 2, 0, 0};
142 sdl_blit(foreground, nullptr, background, &r);
143 background = shadow_image(background);
144
145 if(background == nullptr) {
146 ERR_FT << "could not create floating label's shadow" << std::endl;
147 return surf_ = foreground;
148 }
149 sdl_blit(foreground, nullptr, background, &r);
150 surf_ = background;
151 }
152 }
153
154 return surf_;
155 }
156
draw(surface screen)157 void floating_label::draw(surface screen)
158 {
159 if(!visible_) {
160 buf_ = nullptr;
161 return;
162 }
163
164 if(screen == nullptr) {
165 return;
166 }
167
168 create_surface();
169 if(surf_ == nullptr) {
170 return;
171 }
172
173 if(buf_ == nullptr) {
174 buf_ = surface(surf_->w, surf_->h);
175 if(buf_ == nullptr) {
176 return;
177 }
178 }
179
180 SDL_Rect rect = sdl::create_rect(xpos(surf_->w), ypos_, surf_->w, surf_->h);
181 const clip_rect_setter clip_setter(screen, &clip_rect_);
182 sdl_copy_portion(screen,&rect,buf_,nullptr);
183 sdl_blit(surf_,nullptr,screen,&rect);
184 }
185
undraw(surface screen)186 void floating_label::undraw(surface screen)
187 {
188 if(screen == nullptr || buf_ == nullptr) {
189 return;
190 }
191 SDL_Rect rect = sdl::create_rect(xpos(surf_->w), ypos_, surf_->w, surf_->h);
192 const clip_rect_setter clip_setter(screen, &clip_rect_);
193 sdl_blit(buf_,nullptr,screen,&rect);
194
195 move(xmove_,ymove_);
196 if(lifetime_ > 0) {
197 --lifetime_;
198 if(alpha_change_ != 0 && (xmove_ != 0.0 || ymove_ != 0.0) && surf_ != nullptr) {
199 // fade out moving floating labels
200 surf_ = adjust_surface_alpha_add(surf_,alpha_change_);
201 }
202 }
203 }
204
add_floating_label(const floating_label & flabel)205 int add_floating_label(const floating_label& flabel)
206 {
207 if(label_contexts.empty()) {
208 return 0;
209 }
210
211 ++label_id;
212 labels.emplace(label_id, flabel);
213 label_contexts.top().insert(label_id);
214 return label_id;
215 }
216
move_floating_label(int handle,double xmove,double ymove)217 void move_floating_label(int handle, double xmove, double ymove)
218 {
219 const label_map::iterator i = labels.find(handle);
220 if(i != labels.end()) {
221 i->second.move(xmove, ymove);
222 }
223 }
224
scroll_floating_labels(double xmove,double ymove)225 void scroll_floating_labels(double xmove, double ymove)
226 {
227 for(label_map::iterator i = labels.begin(); i != labels.end(); ++i) {
228 if(i->second.scroll() == ANCHOR_LABEL_MAP) {
229 i->second.move(xmove, ymove);
230 }
231 }
232 }
233
remove_floating_label(int handle)234 void remove_floating_label(int handle)
235 {
236 const label_map::iterator i = labels.find(handle);
237 if(i != labels.end()) {
238 labels.erase(i);
239 }
240
241 if(!label_contexts.empty()) {
242 label_contexts.top().erase(handle);
243 }
244 }
245
show_floating_label(int handle,bool value)246 void show_floating_label(int handle, bool value)
247 {
248 const label_map::iterator i = labels.find(handle);
249 if(i != labels.end()) {
250 i->second.show(value);
251 }
252 }
253
get_floating_label_rect(int handle)254 SDL_Rect get_floating_label_rect(int handle)
255 {
256 const label_map::iterator i = labels.find(handle);
257 if(i != labels.end()) {
258 const surface surf = i->second.create_surface();
259 if(surf != nullptr) {
260 return {0, 0, surf->w, surf->h};
261 }
262 }
263 return sdl::empty_rect;
264 }
265
floating_label_context()266 floating_label_context::floating_label_context()
267 {
268 label_contexts.emplace();
269 }
270
~floating_label_context()271 floating_label_context::~floating_label_context()
272 {
273 const std::set<int>& context = label_contexts.top();
274
275 while(!context.empty()) {
276 // Remove_floating_label removes the passed label from the context.
277 // This loop removes a different label in every iteration.
278 remove_floating_label(*context.begin());
279 }
280
281 label_contexts.pop();
282 }
283
draw_floating_labels(surface screen)284 void draw_floating_labels(surface screen)
285 {
286 if(label_contexts.empty()) {
287 return;
288 }
289
290 const std::set<int>& context = label_contexts.top();
291
292 // draw the labels in the order they were added, so later added labels (likely to be tooltips)
293 // are displayed over earlier added labels.
294 for(label_map::iterator i = labels.begin(); i != labels.end(); ++i) {
295 if(context.count(i->first) > 0) {
296 i->second.draw(screen);
297 }
298 }
299 }
300
undraw_floating_labels(surface screen)301 void undraw_floating_labels(surface screen)
302 {
303 if(label_contexts.empty()) {
304 return;
305 }
306
307 std::set<int>& context = label_contexts.top();
308
309 //undraw labels in reverse order, so that a LIFO process occurs, and the screen is restored
310 //into the exact state it started in.
311 for(label_map::reverse_iterator i = labels.rbegin(); i != labels.rend(); ++i) {
312 if(context.count(i->first) > 0) {
313 i->second.undraw(screen);
314 }
315 }
316
317 //remove expired labels
318 for(label_map::iterator j = labels.begin(); j != labels.end(); ) {
319 if(context.count(j->first) > 0 && j->second.expired()) {
320 context.erase(j->first);
321 labels.erase(j++);
322 } else {
323 ++j;
324 }
325 }
326 }
327 }
328