The Battle for Wesnoth  1.19.0-dev
tooltips.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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"
20 #include "font/standard_colors.hpp"
21 #include "game_display.hpp"
22 #include "help/help.hpp"
23 #include "log.hpp"
24 #include "video.hpp"
25 
26 #include <SDL2/SDL_rect.h>
27 
28 static lg::log_domain log_font("font");
29 #define DBG_FT LOG_STREAM(debug, log_font)
30 #define LOG_FT LOG_STREAM(info, log_font)
31 
32 namespace {
33 
34 static const int font_size = font::SIZE_SMALL;
35 static const int text_width = 400;
36 
37 struct tooltip
38 {
39  tooltip(const SDL_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 
46  void init_label();
47  void update_label_pos();
48 };
49 
50 tooltip::tooltip(const SDL_Rect& r, const std::string& msg, const std::string& act)
51  : origin(r), message(msg), action(act), label(msg)
52 {
53  init_label();
54  DBG_FT << "created tooltip for " << origin << " at " << loc;
55 }
56 
57 void tooltip::init_label()
58 {
59  const color_t bgcolor {0,0,0,192};
61  unsigned int border = 10;
62 
63  label.set_font_size(font_size);
64  label.set_color(font::NORMAL_COLOR);
65  label.set_clip_rect(game_canvas);
66  label.set_width(text_width);
67  label.set_alignment(font::LEFT_ALIGN);
68  label.set_bg_color(bgcolor);
69  label.set_border_size(border);
70 
71  label.create_texture();
72 
73  update_label_pos();
74 }
75 
76 void tooltip::update_label_pos()
77 {
79 
80  point lsize = label.get_draw_size();
81  loc = {0, 0, lsize.x, lsize.y};
82 
83  // See if there is enough room to fit it above the tip area
84  if(origin.y > loc.h) {
85  loc.y = origin.y - loc.h;
86  } else {
87  loc.y = origin.y + origin.h;
88  }
89 
90  // Try to keep it within the screen
91  loc.x = origin.x;
92  if(loc.x < 0) {
93  loc.x = 0;
94  } else if(loc.x + loc.w > game_canvas.w) {
95  loc.x = game_canvas.w - loc.w;
96  }
97 
98  label.set_position(loc.x, loc.y);
99 }
100 
101 
102 std::map<int, tooltip> tips;
103 int active_tooltip = 0;
104 
105 int tooltip_id = 1;
106 
107 surface current_background = nullptr;
108 
109 // Is this a freaking singleton or is it not?
110 // This is horrible, but that's how the usage elsewhere is.
111 // If you want to fix this, either make it an actual singleton,
112 // or ensure that tooltips:: functions are called on an instance.
113 tooltips::manager* current_manager = nullptr;
114 
115 } // anon namespace
116 
117 /** Clear/hide the active tooltip. */
118 static void clear_active()
119 {
120  if(!active_tooltip) {
121  return;
122  }
123  DBG_FT << "clearing active tooltip " << active_tooltip;
124  tips.at(active_tooltip).label.undraw();
125  active_tooltip = 0;
126 }
127 
128 namespace tooltips
129 {
130 
132 {
133  clear_tooltips();
134  current_manager = this;
135 }
136 
138 {
139  try {
140  clear_tooltips();
141  } catch (...) {}
142  current_manager = nullptr;
143 }
144 
146 {
147  if(!active_tooltip) {
148  return;
149  }
150  // Update the active tooltip's draw state.
151  // This will trigger redraws if necessary.
152  tips.at(active_tooltip).label.update(SDL_GetTicks());
153 }
154 
155 bool manager::expose(const rect& region)
156 {
157  // Only the active tip is shown.
158  if(!active_tooltip) {
159  return false;
160  }
161  tooltip& tip = tips.at(active_tooltip);
162  if(!tip.loc.overlaps(region)) {
163  return false;
164  }
165  tip.label.draw();
166  return true;
167 }
168 
170 {
171  // Only the active tip, if any, should be visible.
172  if(!active_tooltip) {
173  return {};
174  } else {
175  return tips.at(active_tooltip).loc;
176  }
177 }
178 
180 {
181  LOG_FT << "clearing all tooltips";
182  clear_active();
183  tips.clear();
184 }
185 
186 void clear_tooltips(const SDL_Rect& r)
187 {
188  for(auto i = tips.begin(); i != tips.end(); ) {
189  if(i->second.origin.overlaps(r)) {
190  DBG_FT << "clearing tip " << i->first << " at "
191  << i->second.origin << " overlapping " << r;
192 
193  if (i->first == active_tooltip) {
194  i->second.label.undraw();
195  active_tooltip = 0;
196  }
197 
198  i = tips.erase(i);
199  } else {
200  ++i;
201  }
202  }
203 }
204 
205 bool update_tooltip(int id, const SDL_Rect& origin, const std::string& message)
206 {
208  if (it == tips.end() ) return false;
209  tooltip& tip = it->second;
210  if(tip.message == message && tip.origin == origin) {
211  return false;
212  }
213  if(tip.message != message) {
214  LOG_FT << "updating tooltip " << id << " message";
215  tip.message = message;
216  tip.label = font::floating_label(message);
217  tip.init_label();
218  }
219  if(tip.origin != origin) {
220  DBG_FT << "updating tooltip " << id << " origin " << origin;
221  tip.origin = origin;
222  tip.update_label_pos();
223  }
224  return true;
225 }
226 
227 void remove_tooltip(int id)
228 {
229  if(!id) { return; }
230  DBG_FT << "removing tooltip " << id;
231  if(id == active_tooltip) {
232  clear_active();
233  }
234  tips.erase(id);
235 }
236 
237 int add_tooltip(const SDL_Rect& origin, const std::string& message, const std::string& action)
238 {
239  // Because some other things are braindead, we have to check we're not
240  // just adding the same tooltip over and over every time the mouse moves.
241  for(auto& [id, tip] : tips) {
242  if(tip.origin == origin && tip.message == message && tip.action == action) {
243  return id;
244  }
245  }
246  DBG_FT << "adding tooltip for " << origin;
247 
248  // Clear any existing tooltips for this origin
249  clear_tooltips(origin);
250  // Create and add a new tooltip
251  int id = tooltip_id++;
252  tips.emplace(id, tooltip(origin, message, action));
253  return id;
254 }
255 
256 static void raise_to_top()
257 {
258  // Raise the current manager so it will display on top of everything.
259  if(!current_manager) {
260  throw game::error("trying to show tooltip with no tooltip manager");
261  }
262  draw_manager::raise_drawable(current_manager);
263 }
264 
265 static void select_active(int id)
266 {
267  if(active_tooltip == id) {
268  return;
269  }
270  tooltip& tip = tips.at(id);
271  LOG_FT << "showing tip " << id << " for " << tip.origin;
272  clear_active();
273  active_tooltip = id;
274  tip.label.update(SDL_GetTicks());
275  raise_to_top();
276 }
277 
278 void process(int mousex, int mousey)
279 {
280  point mouseloc{mousex, mousey};
281  for(auto& [id, tip] : tips) {
282  if(tip.origin.contains(mouseloc)) {
283  select_active(id);
284  return;
285  }
286  }
287 
288  if(active_tooltip) {
289  LOG_FT << "clearing tooltip because none hovered";
290  clear_active();
291  }
292 }
293 
294 bool click(int mousex, int mousey)
295 {
296  for(auto& [id, tip] : tips) { (void)id;
297  if(!tip.action.empty() && tip.origin.contains(mousex, mousey)) {
298  help::show_help(tip.action);
299  return true;
300  }
301  }
302  return false;
303 }
304 
305 } // namespace tooltips
virtual bool expose(const rect &region) override
Draw the portion of the drawable intersecting region to the screen.
Definition: tooltips.cpp:155
virtual void layout() override
Finalize the size and position of the drawable and its children, and invalidate any regions requiring...
Definition: tooltips.cpp:145
virtual rect screen_location() override
The location of the TLD on the screen, in drawing coordinates.
Definition: tooltips.cpp:169
std::size_t i
Definition: function.cpp:968
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:209
std::string tooltip
Shown when hovering over an entry in the filter's drop-down list.
Definition: manager.cpp:211
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:207
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
static std::unique_ptr< tooltip > tip
Definition: tooltip.cpp:77
std::vector< game_tip > tips
Definition: settings.cpp:55
void show_help(const std::string &show_topic, int xloc, int yloc)
Open the help browser, show topic with id show_topic.
Definition: help.cpp:140
tooltips.
Definition: tooltips.cpp:129
bool click(int mousex, int mousey)
Definition: tooltips.cpp:294
int add_tooltip(const SDL_Rect &origin, const std::string &message, const std::string &action)
Definition: tooltips.cpp:237
void clear_tooltips()
Definition: tooltips.cpp:179
void remove_tooltip(int id)
Definition: tooltips.cpp:227
static void select_active(int id)
Definition: tooltips.cpp:265
void process(int mousex, int mousey)
Definition: tooltips.cpp:278
static void raise_to_top()
Definition: tooltips.cpp:256
bool update_tooltip(int id, const SDL_Rect &origin, const std::string &message)
Definition: tooltips.cpp:205
rect game_canvas()
The game canvas area, in drawing coordinates.
Definition: video.cpp:429
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:59
Base class for all the errors encountered by the engine.
Definition: exceptions.hpp:29
Holds a 2D point.
Definition: point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:47
#define DBG_FT
Definition: tooltips.cpp:29
static void clear_active()
Clear/hide the active tooltip.
Definition: tooltips.cpp:118
#define LOG_FT
Definition: tooltips.cpp:30
static lg::log_domain log_font("font")