1 /*****************************************************************************
2 * Copyright (c) 2014-2020 OpenRCT2 developers
3 *
4 * For a complete list of all authors, please refer to contributors.md
5 * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6 *
7 * OpenRCT2 is licensed under the GNU General Public License version 3.
8 *****************************************************************************/
9
10 #include <algorithm>
11 #include <openrct2-ui/interface/Widget.h>
12 #include <openrct2-ui/windows/Window.h>
13 #include <openrct2/Context.h>
14 #include <openrct2/Input.h>
15 #include <openrct2/drawing/Drawing.h>
16 #include <openrct2/localisation/Localisation.h>
17
18 // clang-format off
19 enum {
20 WIDX_BACKGROUND
21 };
22
23 static rct_widget window_tooltip_widgets[] = {
24 MakeWidget({0, 0}, {200, 32}, WindowWidgetType::ImgBtn, WindowColour::Primary),
25 WIDGETS_END,
26 };
27
28 static void window_tooltip_update(rct_window *w);
29 static void window_tooltip_paint(rct_window *w, rct_drawpixelinfo *dpi);
30
31 static rct_window_event_list window_tooltip_events([](auto& events)
__anon51871dd20202(auto& events) 32 {
33 events.update = &window_tooltip_update;
34 events.paint = &window_tooltip_paint;
35 });
36 // clang-format on
37
38 static utf8 _tooltipText[sizeof(gCommonStringFormatBuffer)];
39 static int16_t _tooltipNumLines;
40
window_tooltip_reset(const ScreenCoordsXY & screenCoords)41 void window_tooltip_reset(const ScreenCoordsXY& screenCoords)
42 {
43 gTooltipCursor = screenCoords;
44 gTooltipTimeout = 0;
45 gTooltipWidget.window_classification = 255;
46 input_set_state(InputState::Normal);
47 input_set_flag(INPUT_FLAG_4, false);
48 }
49
50 // Returns the width of the new tooltip text
FormatTextForTooltip(const OpenRCT2String & message)51 static int32_t FormatTextForTooltip(const OpenRCT2String& message)
52 {
53 utf8 tempBuffer[sizeof(gCommonStringFormatBuffer)];
54 format_string(tempBuffer, sizeof(tempBuffer), message.str, message.args.Data());
55
56 OpenRCT2String formattedMessage{ STR_STRING_TOOLTIP, Formatter() };
57 formattedMessage.args.Add<const char*>(tempBuffer);
58 format_string(_tooltipText, sizeof(_tooltipText), formattedMessage.str, formattedMessage.args.Data());
59
60 auto textWidth = gfx_get_string_width_new_lined(_tooltipText, FontSpriteBase::SMALL);
61 textWidth = std::min(textWidth, 196);
62
63 int32_t numLines;
64 textWidth = gfx_wrap_string(_tooltipText, textWidth + 1, FontSpriteBase::SMALL, &numLines);
65 _tooltipNumLines = numLines;
66 return textWidth;
67 }
68
window_tooltip_show(const OpenRCT2String & message,ScreenCoordsXY screenCoords)69 void window_tooltip_show(const OpenRCT2String& message, ScreenCoordsXY screenCoords)
70 {
71 auto* w = window_find_by_class(WC_ERROR);
72 if (w != nullptr)
73 return;
74
75 int32_t textWidth = FormatTextForTooltip(message);
76 int32_t width = textWidth + 3;
77 int32_t height = ((_tooltipNumLines + 1) * font_get_line_height(FontSpriteBase::SMALL)) + 4;
78 window_tooltip_widgets[WIDX_BACKGROUND].right = width;
79 window_tooltip_widgets[WIDX_BACKGROUND].bottom = height;
80
81 int32_t screenWidth = context_get_width();
82 int32_t screenHeight = context_get_height();
83 screenCoords.x = std::clamp(screenCoords.x - (width / 2), 0, screenWidth - width);
84
85 // TODO The cursor size will be relative to the window DPI.
86 // The amount to offset the y should be adjusted.
87
88 int32_t max_y = screenHeight - height;
89 screenCoords.y += 26; // Normally, we'd display the tooltip 26 lower
90 if (screenCoords.y > max_y)
91 // If y is too large, the tooltip could be forced below the cursor if we'd just clamped y,
92 // so we'll subtract a bit more
93 screenCoords.y -= height + 40;
94 screenCoords.y = std::clamp(screenCoords.y, 22, max_y);
95
96 w = WindowCreate(screenCoords, width, height, &window_tooltip_events, WC_TOOLTIP, WF_TRANSPARENT | WF_STICK_TO_FRONT);
97 w->widgets = window_tooltip_widgets;
98
99 reset_tooltip_not_shown();
100 }
101
102 /**
103 *
104 * rct2: 0x006EA10D
105 */
window_tooltip_open(rct_window * widgetWindow,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCords)106 void window_tooltip_open(rct_window* widgetWindow, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCords)
107 {
108 if (widgetWindow == nullptr || widgetIndex == -1)
109 return;
110
111 auto widget = &widgetWindow->widgets[widgetIndex];
112 window_event_invalidate_call(widgetWindow);
113
114 OpenRCT2String result;
115 if (widget->flags & WIDGET_FLAGS::TOOLTIP_IS_STRING)
116 {
117 result.str = STR_STRING_TOOLTIP;
118 result.args = Formatter();
119 result.args.Add<const char*>(widget->sztooltip);
120
121 gTooltipWidget.window_classification = widgetWindow->classification;
122 gTooltipWidget.window_number = widgetWindow->number;
123 gTooltipWidget.widget_index = widgetIndex;
124 }
125 else
126 {
127 auto stringId = widget->tooltip;
128 if (stringId == STR_NONE)
129 return;
130
131 gTooltipWidget.window_classification = widgetWindow->classification;
132 gTooltipWidget.window_number = widgetWindow->number;
133 gTooltipWidget.widget_index = widgetIndex;
134 result = window_event_tooltip_call(widgetWindow, widgetIndex, stringId);
135 if (result.str == STR_NONE)
136 return;
137 }
138
139 window_tooltip_show(result, screenCords);
140 }
141
142 /**
143 *
144 * rct2: 0x006E98C6
145 */
window_tooltip_close()146 void window_tooltip_close()
147 {
148 window_close_by_class(WC_TOOLTIP);
149 gTooltipTimeout = 0;
150 gTooltipWidget.window_classification = 255;
151 }
152
153 /**
154 *
155 * rct2: 0x006EA580
156 */
window_tooltip_update(rct_window * w)157 static void window_tooltip_update(rct_window* w)
158 {
159 reset_tooltip_not_shown();
160 }
161
162 /**
163 *
164 * rct2: 0x006EA41D
165 */
window_tooltip_paint(rct_window * w,rct_drawpixelinfo * dpi)166 static void window_tooltip_paint(rct_window* w, rct_drawpixelinfo* dpi)
167 {
168 int32_t left = w->windowPos.x;
169 int32_t top = w->windowPos.y;
170 int32_t right = w->windowPos.x + w->width - 1;
171 int32_t bottom = w->windowPos.y + w->height - 1;
172
173 // Background
174 gfx_filter_rect(dpi, { { left + 1, top + 1 }, { right - 1, bottom - 1 } }, FilterPaletteID::Palette45);
175 gfx_filter_rect(dpi, { { left + 1, top + 1 }, { right - 1, bottom - 1 } }, FilterPaletteID::PaletteGlassLightOrange);
176
177 // Sides
178 gfx_filter_rect(dpi, { { left + 0, top + 2 }, { left + 0, bottom - 2 } }, FilterPaletteID::PaletteDarken3);
179 gfx_filter_rect(dpi, { { right + 0, top + 2 }, { right + 0, bottom - 2 } }, FilterPaletteID::PaletteDarken3);
180 gfx_filter_rect(dpi, { { left + 2, bottom + 0 }, { right - 2, bottom + 0 } }, FilterPaletteID::PaletteDarken3);
181 gfx_filter_rect(dpi, { { left + 2, top + 0 }, { right - 2, top + 0 } }, FilterPaletteID::PaletteDarken3);
182
183 // Corners
184 gfx_filter_pixel(dpi, { left + 1, top + 1 }, FilterPaletteID::PaletteDarken3);
185 gfx_filter_pixel(dpi, { right - 1, top + 1 }, FilterPaletteID::PaletteDarken3);
186 gfx_filter_pixel(dpi, { left + 1, bottom - 1 }, FilterPaletteID::PaletteDarken3);
187 gfx_filter_pixel(dpi, { right - 1, bottom - 1 }, FilterPaletteID::PaletteDarken3);
188
189 // Text
190 left = w->windowPos.x + ((w->width + 1) / 2) - 1;
191 top = w->windowPos.y + 1;
192 draw_string_centred_raw(dpi, { left, top }, _tooltipNumLines, _tooltipText, FontSpriteBase::SMALL);
193 }
194