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