The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
tooltips.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 "tooltips.hpp"
17
18#include "draw_manager.hpp"
19#include "floating_label.hpp"
21#include "game_display.hpp"
22#include "help/help.hpp"
23#include "log.hpp"
24#include "sdl/rect.hpp"
25#include "video.hpp"
26
27static lg::log_domain log_font("font");
28#define DBG_FT LOG_STREAM(debug, log_font)
29#define LOG_FT LOG_STREAM(info, log_font)
30
31namespace {
32
33static const int font_size = font::SIZE_SMALL;
34static const int text_width = 400;
35static const double height_fudge = 0.95; // An artificial "border" to keep tip text from crowding lower edge of viewing area
36
37struct tooltip
38{
39 tooltip(const rect& r, const std::string& msg, const std::string& act = "");
40 rect origin;
41 rect loc = {};
42 std::string message;
43 std::string action;
45 bool label_initialized = false;
46
47 void init_label();
48 void update_label_pos();
49};
50
51tooltip::tooltip(const rect& r, const std::string& msg, const std::string& act)
52 : origin(r), message(msg), action(act), label(msg)
53{
54 DBG_FT << "created tooltip for " << origin << " at " << loc;
55}
56
57void tooltip::init_label()
58{
59 const color_t bgcolor {0,0,0,192};
60 rect game_canvas = video::game_canvas();
61 unsigned int border = 10;
62
63 label.set_font_size(font_size);
64 label.set_color(font::NORMAL_COLOR);
65 label.set_width(text_width); // If tooltip will be too tall for game_canvas, this could be scaled up appropriately
66 label.set_height(1000000); // Functionally unbounded on first pass
67 label.set_alignment(font::LEFT_ALIGN);
68 label.set_bg_color(bgcolor);
69 label.set_border_size(border);
70
71 label.clear_texture();
72 label.create_texture();
73
74 point lsize = label.get_draw_size();
75 int new_text_width = text_width * static_cast<float>(lsize.y)/game_canvas.h; // If necessary, scale width to reduce height while preserving area of label
76 while((lsize.y > game_canvas.h*height_fudge) && (lsize.x < game_canvas.w)) {
77 // Scaling the tip to reduce height is hard, since making a texture wider is no guarantee that there will be fewer lines of text:
78 //
79 // This block of text is just
80 // as tall as the other one.
81 //
82 // This block of text is just as tall as the other
83 // one.
84 //
85 // Creating this over and over may not be the most efficient route, but it will work and will be quite rare (tip taller than screen).
86 bool wont_fit = false;
87 if(new_text_width>game_canvas.w) {
88 new_text_width=game_canvas.w;
89 wont_fit = true;
90 }
91 DBG_FT << "lsize.x,y = " << lsize.x << "," << lsize.y << ", new_text_width = " << new_text_width;
92
93 label.set_width(new_text_width);
94 label.clear_texture();
95 label.create_texture();
96
97 lsize = label.get_draw_size();
98 DBG_FT << "new label lsize.x,y = " << lsize.x << "," << lsize.y;
99 if(wont_fit) {
100 break;
101 }
102 new_text_width *= 1.3;
103 }
104
105 update_label_pos();
106 label_initialized = true;
107}
108
109void tooltip::update_label_pos()
110{
111 rect game_canvas = video::game_canvas();
112
113 point lsize = label.get_draw_size();
114 loc = {0, 0, lsize.x, lsize.y};
115
116 DBG_FT << "\nupdate_label_pos() Start: loc = " << loc.x << "," << loc.y << " origin = " << origin.x << "," << origin.y;
117
118 if(origin.y > loc.h) {
119 // There is enough room to fit it above the tip area
120 loc.y = origin.y - loc.h;
121 DBG_FT << "\tAbove: loc = " << loc.x << "," << loc.y << " origin = " << origin.x << "," << origin.y;
122 } else if((origin.y + origin.h + loc.h) <= game_canvas.h*height_fudge) {
123 // There is enough room to fit it below the tip area
124 loc.y = origin.y + origin.h;
125 DBG_FT << "\tBelow: loc = " << loc.x << "," << loc.y << " origin = " << origin.x << "," << origin.y;
126 } else if(((origin.y + origin.h/2 - loc.h/2) >= 0) &&
127 ((origin.y + origin.h/2 + loc.h/2) <= game_canvas.h*height_fudge)) {
128 // There is enough room to center it at the tip area
129 loc.y = origin.y + origin.h/2 - loc.h/2;
130 DBG_FT << "\tCenter: loc = " << loc.x << "," << loc.y << " origin = " << origin.x << "," << origin.y;
131 } else if(loc.h <= game_canvas.h*0.95) {
132 // There is enough room to center it
133 loc.y = game_canvas.h/2 - loc.h/2;
134 DBG_FT << "\tScreen Center: loc = " << loc.x << "," << loc.y << " origin = " << origin.x << "," << origin.y;
135 } else {
136 // It doesn't fit
137 loc.y = 0;
138 DBG_FT << "\tToo big: loc = " << loc.x << "," << loc.y << " origin = " << origin.x << "," << origin.y;
139 }
140
141 DBG_FT << "\tBefore x adjust: loc.x,y,w,h = " << loc.x << "," << loc.y << "," << loc.w << "," << loc.h << " origin = " << origin.x << "," << origin.y;
142 // Try to keep it within the screen
143 loc.x = origin.x;
144 if(loc.x + loc.w > game_canvas.w) {
145 loc.x = game_canvas.w - loc.w;
146 }
147 if(loc.x < 0) {
148 loc.x = 0;
149 }
150
151 DBG_FT << "\tFinal: loc.x,y,w,h = " << loc.x << "," << loc.y << "," << loc.w << "," << loc.h << " origin = " << origin.x << "," << origin.y;
152 label.set_position(loc.x, loc.y);
153}
154
155
156std::map<int, tooltip> tips;
157int active_tooltip = 0;
158
159int tooltip_id = 1;
160
161// Is this a freaking singleton or is it not?
162// This is horrible, but that's how the usage elsewhere is.
163// If you want to fix this, either make it an actual singleton,
164// or ensure that tooltips:: functions are called on an instance.
165tooltips::manager* current_manager = nullptr;
166
167} // anon namespace
168
169/** Clear/hide the active tooltip. */
170static void clear_active()
171{
172 if(!active_tooltip) {
173 return;
174 }
175 DBG_FT << "clearing active tooltip " << active_tooltip;
176 tips.at(active_tooltip).label.undraw();
177 active_tooltip = 0;
178}
179
180namespace tooltips
181{
182
184{
186 current_manager = this;
187}
188
190{
191 try {
193 } catch (...) {}
194 current_manager = nullptr;
195}
196
198{
199 if(!active_tooltip) {
200 return;
201 }
202 tooltip& tip = tips.at(active_tooltip);
203 if(!tip.label_initialized) {
204 tip.init_label();
205 }
206 // Update the active tooltip's draw state.
207 // This will trigger redraws if necessary.
208 tip.label.update(std::chrono::steady_clock::now());
209}
210
211bool manager::expose(const rect& region)
212{
213 // Only the active tip is shown.
214 if(!active_tooltip) {
215 return false;
216 }
217 tooltip& tip = tips.at(active_tooltip);
218 if(!tip.loc.overlaps(region)) {
219 return false;
220 }
221 tip.label.draw();
222 return true;
223}
224
226{
227 // Only the active tip, if any, should be visible.
228 if(!active_tooltip) {
229 return {};
230 } else {
231 return tips.at(active_tooltip).loc;
232 }
233}
234
236{
237 LOG_FT << "clearing all tooltips";
238 clear_active();
239 tips.clear();
240}
241
242void clear_tooltips(const rect& r)
243{
244 for(auto i = tips.begin(); i != tips.end(); ) {
245 if(i->second.origin.overlaps(r)) {
246 DBG_FT << "clearing tip " << i->first << " at "
247 << i->second.origin << " overlapping " << r;
248
249 if (i->first == active_tooltip) {
250 i->second.label.undraw();
251 active_tooltip = 0;
252 }
253
254 i = tips.erase(i);
255 } else {
256 ++i;
257 }
258 }
259}
260
261bool update_tooltip(int id, const rect& origin, const std::string& message)
262{
263 std::map<int, tooltip>::iterator it = tips.find(id);
264 if (it == tips.end() ) return false;
265 tooltip& tip = it->second;
266 if(tip.message == message && tip.origin == origin) {
267 return false;
268 }
269 if(tip.message != message) {
270 LOG_FT << "updating tooltip " << id << " message";
271 tip.message = message;
272 tip.label = font::floating_label(message);
273 tip.init_label();
274 }
275 if(tip.origin != origin) {
276 DBG_FT << "updating tooltip " << id << " origin " << origin;
277 tip.origin = origin;
278 tip.update_label_pos();
279 }
280 return true;
281}
282
283void remove_tooltip(int id)
284{
285 if(!id) { return; }
286 DBG_FT << "removing tooltip " << id;
287 if(id == active_tooltip) {
288 clear_active();
289 }
290 tips.erase(id);
291}
292
293int add_tooltip(const rect& origin, const std::string& message, const std::string& action)
294{
295 // Because some other things are braindead, we have to check we're not
296 // just adding the same tooltip over and over every time the mouse moves.
297 for(auto& [id, tip] : tips) {
298 if(tip.origin == origin && tip.message == message && tip.action == action) {
299 return id;
300 }
301 }
302 DBG_FT << "adding tooltip for " << origin;
303
304 // Clear any existing tooltips for this origin
305 clear_tooltips(origin);
306 // Create and add a new tooltip
307 int id = tooltip_id++;
308 tips.try_emplace(id, origin, message, action);
309 return id;
310}
311
312static void raise_to_top()
313{
314 // Raise the current manager so it will display on top of everything.
315 if(!current_manager) {
316 throw game::error("trying to show tooltip with no tooltip manager");
317 }
318 draw_manager::raise_drawable(current_manager);
319}
320
321static void select_active(int id)
322{
323 if(active_tooltip == id) {
324 return;
325 }
326 tooltip& tip = tips.at(id);
327 LOG_FT << "showing tip " << id << " for " << tip.origin;
328 clear_active();
329 active_tooltip = id;
330 tip.label.update(std::chrono::steady_clock::now());
331 raise_to_top();
332}
333
334void process(int mousex, int mousey)
335{
336 point mouseloc{mousex, mousey};
337 for(auto& [id, tip] : tips) {
338 if(tip.origin.contains(mouseloc)) {
339 select_active(id);
340 return;
341 }
342 }
343
344 if(active_tooltip) {
345 LOG_FT << "clearing tooltip because none hovered";
346 clear_active();
347 }
348}
349
350bool click(int mousex, int mousey)
351{
352 for(auto& [id, tip] : tips) { (void)id;
353 if(!tip.action.empty() && tip.origin.contains(mousex, mousey)) {
354 help::show_help(tip.action);
355 return true;
356 }
357 }
358 return false;
359}
360
361} // namespace tooltips
map_location loc
Definition move.cpp:172
virtual bool expose(const rect &region) override
Draw the portion of the drawable intersecting region to the screen.
Definition tooltips.cpp:211
virtual void layout() override
Finalize the size and position of the drawable and its children, and invalidate any regions requiring...
Definition tooltips.cpp:197
virtual rect screen_location() override
The location of the TLD on the screen, in drawing coordinates.
Definition tooltips.cpp:225
@ border
The border of the map.
#define DBG_FT
#define LOG_FT
std::string label
What to show in the filter's drop-down list.
Definition manager.cpp:201
std::string tooltip
Shown when hovering over an entry in the filter's drop-down list.
Definition manager.cpp:203
std::string id
Text to match against addon_info.tags()
Definition manager.cpp:199
Standard logging facilities (interface).
void raise_drawable(top_level_drawable *tld)
Raise a TLD to the top of the drawing stack.
const int SIZE_SMALL
Definition constants.cpp:24
const color_t NORMAL_COLOR
void show_help(const std::string &show_topic)
Open the help browser.
Definition help.cpp:84
tooltips.
Definition tooltips.cpp:181
int add_tooltip(const rect &origin, const std::string &message, const std::string &action)
Definition tooltips.cpp:293
bool click(int mousex, int mousey)
Definition tooltips.cpp:350
void clear_tooltips()
Definition tooltips.cpp:235
void remove_tooltip(int id)
Definition tooltips.cpp:283
static void select_active(int id)
Definition tooltips.cpp:321
void process(int mousex, int mousey)
Definition tooltips.cpp:334
static void raise_to_top()
Definition tooltips.cpp:312
bool update_tooltip(int id, const rect &origin, const std::string &message)
Definition tooltips.cpp:261
rect game_canvas()
The game canvas area, in drawing coordinates.
Definition video.cpp:427
Contains the SDL_Rect helper code.
The basic class for representing 8-bit RGB or RGBA colour values.
Definition color.hpp:51
Base class for all the errors encountered by the engine.
Holds a 2D point.
Definition point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition rect.hpp:49
#define DBG_FT
Definition tooltips.cpp:28
static void clear_active()
Clear/hide the active tooltip.
Definition tooltips.cpp:170
static lg::log_domain log_font("font")
#define i