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