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 <bitset>
12 #include <iterator>
13 #include <openrct2-ui/interface/Dropdown.h>
14 #include <openrct2-ui/interface/Widget.h>
15 #include <openrct2/Context.h>
16 #include <openrct2/Input.h>
17 #include <openrct2/drawing/Drawing.h>
18 #include <openrct2/localisation/Localisation.h>
19 #include <openrct2/sprites.h>
20
21 // The maximum number of rows to list before items overflow into new columns
22 constexpr int32_t DROPDOWN_TEXT_MAX_ROWS = 32;
23
24 constexpr int32_t DROPDOWN_ITEM_HEIGHT = 12;
25
26 static constexpr const uint8_t _appropriateImageDropdownItemsPerRow[34] = {
27 1, 1, 1, 1, 2, 2, 3, 3, 4, 3, 5, 4, 4, 5, 5, 5, 4, 5, 6, 5, 5, 7, 4, 5, 6, 5, 6, 6, 6, 6, 6, 8, 8, 8,
28 };
29
30 enum
31 {
32 WIDX_BACKGROUND,
33 };
34
35 static rct_widget window_dropdown_widgets[] = {
36 MakeWidget({ 0, 0 }, { 1, 1 }, WindowWidgetType::ImgBtn, WindowColour::Primary),
37 WIDGETS_END,
38 };
39
40 static int32_t _dropdown_num_columns;
41 static int32_t _dropdown_num_rows;
42 static int32_t _dropdown_item_width;
43 static int32_t _dropdown_item_height;
44 static bool _dropdown_list_vertically;
45
46 int32_t gDropdownNumItems;
47 rct_string_id gDropdownItemsFormat[Dropdown::ItemsMaxSize];
48 int64_t gDropdownItemsArgs[Dropdown::ItemsMaxSize];
49 static std::bitset<Dropdown::ItemsMaxSize> _dropdownItemsChecked = {};
50 static std::bitset<Dropdown::ItemsMaxSize> _dropdownItemsDisabled = {};
51 bool gDropdownIsColour;
52 int32_t gDropdownLastColourHover;
53 int32_t gDropdownHighlightedIndex;
54 int32_t gDropdownDefaultIndex;
55
IsChecked(int32_t index)56 bool Dropdown::IsChecked(int32_t index)
57 {
58 if (index < 0 || index >= static_cast<int32_t>(std::size(_dropdownItemsDisabled)))
59 {
60 return false;
61 }
62 return _dropdownItemsChecked[index];
63 }
64
IsDisabled(int32_t index)65 bool Dropdown::IsDisabled(int32_t index)
66 {
67 if (index < 0 || index >= static_cast<int32_t>(std::size(_dropdownItemsDisabled)))
68 {
69 return true;
70 }
71 return _dropdownItemsDisabled[index];
72 }
73
SetChecked(int32_t index,bool value)74 void Dropdown::SetChecked(int32_t index, bool value)
75 {
76 if (index < 0 || index >= static_cast<int32_t>(std::size(_dropdownItemsDisabled)))
77 {
78 return;
79 }
80 _dropdownItemsChecked[index] = value;
81 }
82
SetDisabled(int32_t index,bool value)83 void Dropdown::SetDisabled(int32_t index, bool value)
84 {
85 if (index < 0 || index >= static_cast<int32_t>(std::size(_dropdownItemsDisabled)))
86 {
87 return;
88 }
89 _dropdownItemsDisabled[index] = value;
90 }
91
92 static void window_dropdown_paint(rct_window* w, rct_drawpixelinfo* dpi);
93
94 // clang-format off
95 static rct_window_event_list window_dropdown_events([](auto& events)
__anonf3b9f9f40202(auto& events) 96 {
97 events.paint = &window_dropdown_paint;
98 });
99 // clang-format on
100
101 /**
102 * Shows a text dropdown menu.
103 * rct2: 0x006ECFB9
104 *
105 * @param x (cx)
106 * @param y (dx)
107 * @param extray (di)
108 * @param flags (bh)
109 * @param num_items (bx)
110 * @param colour (al)
111 */
WindowDropdownShowText(const ScreenCoordsXY & screenPos,int32_t extray,uint8_t colour,uint8_t flags,size_t num_items)112 void WindowDropdownShowText(const ScreenCoordsXY& screenPos, int32_t extray, uint8_t colour, uint8_t flags, size_t num_items)
113 {
114 int32_t string_width, max_string_width;
115 char buffer[256];
116
117 // Calculate the longest string width
118 max_string_width = 0;
119 for (size_t i = 0; i < num_items; i++)
120 {
121 format_string(buffer, 256, gDropdownItemsFormat[i], static_cast<void*>(&gDropdownItemsArgs[i]));
122 string_width = gfx_get_string_width(buffer, FontSpriteBase::MEDIUM);
123 max_string_width = std::max(string_width, max_string_width);
124 }
125
126 WindowDropdownShowTextCustomWidth(screenPos, extray, colour, 0, flags, num_items, max_string_width + 3);
127 }
128
129 /**
130 * Shows a text dropdown menu.
131 * rct2: 0x006ECFB9, although 0x006ECE50 is real version
132 *
133 * @param x (cx)
134 * @param y (dx)
135 * @param extray (di)
136 * @param flags (bh)
137 * @param num_items (bx)
138 * @param colour (al)
139 * @param custom_height (ah) requires flag set as well
140 */
WindowDropdownShowTextCustomWidth(const ScreenCoordsXY & screenPos,int32_t extray,uint8_t colour,uint8_t custom_height,uint8_t flags,size_t num_items,int32_t width)141 void WindowDropdownShowTextCustomWidth(
142 const ScreenCoordsXY& screenPos, int32_t extray, uint8_t colour, uint8_t custom_height, uint8_t flags, size_t num_items,
143 int32_t width)
144 {
145 rct_window* w;
146
147 input_set_flag(static_cast<INPUT_FLAGS>(INPUT_FLAG_DROPDOWN_STAY_OPEN | INPUT_FLAG_DROPDOWN_MOUSE_UP), false);
148 if (flags & Dropdown::Flag::StayOpen)
149 input_set_flag(INPUT_FLAG_DROPDOWN_STAY_OPEN, true);
150
151 WindowDropdownClose();
152
153 // Set and calculate num items, rows and columns
154 _dropdown_item_width = width;
155 _dropdown_item_height = (flags & Dropdown::Flag::CustomHeight) ? custom_height : DROPDOWN_ITEM_HEIGHT;
156 gDropdownNumItems = static_cast<int32_t>(num_items);
157 // There must always be at least one column to prevent dividing by zero
158 if (gDropdownNumItems == 0)
159 {
160 _dropdown_num_columns = 1;
161 _dropdown_num_rows = 1;
162 }
163 else
164 {
165 _dropdown_num_columns = (gDropdownNumItems + DROPDOWN_TEXT_MAX_ROWS - 1) / DROPDOWN_TEXT_MAX_ROWS;
166 _dropdown_num_rows = (gDropdownNumItems + _dropdown_num_columns - 1) / _dropdown_num_columns;
167 }
168
169 // Text dropdowns are listed horizontally
170 _dropdown_list_vertically = true;
171
172 width = _dropdown_item_width * _dropdown_num_columns + 3;
173 int32_t height = _dropdown_item_height * _dropdown_num_rows + 3;
174 int32_t screenWidth = context_get_width();
175 int32_t screenHeight = context_get_height();
176 auto boundedScreenPos = screenPos;
177 if (screenPos.x + width > screenWidth)
178 boundedScreenPos.x = std::max(0, screenWidth - width);
179 if (screenPos.y + height > screenHeight)
180 boundedScreenPos.y = std::max(0, screenHeight - height);
181
182 window_dropdown_widgets[WIDX_BACKGROUND].right = width;
183 window_dropdown_widgets[WIDX_BACKGROUND].bottom = height;
184
185 // Create the window
186 w = WindowCreate(
187 boundedScreenPos + ScreenCoordsXY{ 0, extray }, window_dropdown_widgets[WIDX_BACKGROUND].right + 1,
188 window_dropdown_widgets[WIDX_BACKGROUND].bottom + 1, &window_dropdown_events, WC_DROPDOWN, WF_STICK_TO_FRONT);
189 w->widgets = window_dropdown_widgets;
190 if (colour & COLOUR_FLAG_TRANSLUCENT)
191 w->flags |= WF_TRANSPARENT;
192 w->colours[0] = colour;
193
194 // Input state
195 gDropdownHighlightedIndex = -1;
196 _dropdownItemsDisabled.reset();
197 _dropdownItemsChecked.reset();
198 gDropdownIsColour = false;
199 gDropdownDefaultIndex = -1;
200 input_set_state(InputState::DropdownActive);
201 }
202
203 /**
204 * Shows an image dropdown menu.
205 * rct2: 0x006ECFB9
206 *
207 * @param x (cx)
208 * @param y (dx)
209 * @param extray (di)
210 * @param flags (bh)
211 * @param numItems (bx)
212 * @param colour (al)
213 * @param itemWidth (bp)
214 * @param itemHeight (ah)
215 * @param numColumns (bl)
216 */
WindowDropdownShowImage(int32_t x,int32_t y,int32_t extray,uint8_t colour,uint8_t flags,int32_t numItems,int32_t itemWidth,int32_t itemHeight,int32_t numColumns)217 void WindowDropdownShowImage(
218 int32_t x, int32_t y, int32_t extray, uint8_t colour, uint8_t flags, int32_t numItems, int32_t itemWidth,
219 int32_t itemHeight, int32_t numColumns)
220 {
221 int32_t width, height;
222 rct_window* w;
223
224 input_set_flag(static_cast<INPUT_FLAGS>(INPUT_FLAG_DROPDOWN_STAY_OPEN | INPUT_FLAG_DROPDOWN_MOUSE_UP), false);
225 if (flags & Dropdown::Flag::StayOpen)
226 input_set_flag(INPUT_FLAG_DROPDOWN_STAY_OPEN, true);
227
228 // Close existing dropdown
229 WindowDropdownClose();
230
231 // Set and calculate num items, rows and columns
232 _dropdown_item_width = itemWidth;
233 _dropdown_item_height = itemHeight;
234 gDropdownNumItems = numItems;
235 // There must always be at least one column and row to prevent dividing by zero
236 if (gDropdownNumItems == 0)
237 {
238 _dropdown_num_columns = 1;
239 _dropdown_num_rows = 1;
240 }
241 else
242 {
243 _dropdown_num_columns = std::max(1, numColumns);
244 _dropdown_num_rows = gDropdownNumItems / _dropdown_num_columns;
245 if (gDropdownNumItems % _dropdown_num_columns != 0)
246 _dropdown_num_rows++;
247 }
248
249 // image dropdowns are listed horizontally
250 _dropdown_list_vertically = false;
251
252 // Calculate position and size
253 width = _dropdown_item_width * _dropdown_num_columns + 3;
254 height = _dropdown_item_height * _dropdown_num_rows + 3;
255
256 int32_t screenWidth = context_get_width();
257 int32_t screenHeight = context_get_height();
258 if (x + width > screenWidth)
259 x = std::max(0, screenWidth - width);
260 if (y + height > screenHeight)
261 y = std::max(0, screenHeight - height);
262 window_dropdown_widgets[WIDX_BACKGROUND].right = width;
263 window_dropdown_widgets[WIDX_BACKGROUND].bottom = height;
264
265 // Create the window
266 w = WindowCreate(
267 ScreenCoordsXY(x, y + extray), window_dropdown_widgets[WIDX_BACKGROUND].right + 1,
268 window_dropdown_widgets[WIDX_BACKGROUND].bottom + 1, &window_dropdown_events, WC_DROPDOWN, WF_STICK_TO_FRONT);
269 w->widgets = window_dropdown_widgets;
270 if (colour & COLOUR_FLAG_TRANSLUCENT)
271 w->flags |= WF_TRANSPARENT;
272 w->colours[0] = colour;
273
274 // Input state
275 gDropdownHighlightedIndex = -1;
276 _dropdownItemsDisabled.reset();
277 _dropdownItemsChecked.reset();
278 gDropdownIsColour = false;
279 gDropdownDefaultIndex = -1;
280 input_set_state(InputState::DropdownActive);
281 }
282
WindowDropdownClose()283 void WindowDropdownClose()
284 {
285 window_close_by_class(WC_DROPDOWN);
286 }
287
window_dropdown_paint(rct_window * w,rct_drawpixelinfo * dpi)288 static void window_dropdown_paint(rct_window* w, rct_drawpixelinfo* dpi)
289 {
290 WindowDrawWidgets(w, dpi);
291
292 int32_t highlightedIndex = gDropdownHighlightedIndex;
293 for (int32_t i = 0; i < gDropdownNumItems; i++)
294 {
295 ScreenCoordsXY cellCoords;
296 if (_dropdown_list_vertically)
297 cellCoords = { i / _dropdown_num_rows, i % _dropdown_num_rows };
298 else
299 cellCoords = { i % _dropdown_num_columns, i / _dropdown_num_columns };
300
301 ScreenCoordsXY screenCoords = w->windowPos
302 + ScreenCoordsXY{ 2 + (cellCoords.x * _dropdown_item_width), 2 + (cellCoords.y * _dropdown_item_height) };
303
304 if (gDropdownItemsFormat[i] == Dropdown::SeparatorString)
305 {
306 const ScreenCoordsXY leftTop = screenCoords + ScreenCoordsXY{ 0, (_dropdown_item_height / 2) };
307 const ScreenCoordsXY rightBottom = leftTop + ScreenCoordsXY{ _dropdown_item_width - 1, 0 };
308 const ScreenCoordsXY shadowOffset{ 0, 1 };
309
310 if (w->colours[0] & COLOUR_FLAG_TRANSLUCENT)
311 {
312 translucent_window_palette palette = TranslucentWindowPalettes[BASE_COLOUR(w->colours[0])];
313 gfx_filter_rect(dpi, { leftTop, rightBottom }, palette.highlight);
314 gfx_filter_rect(dpi, { leftTop + shadowOffset, rightBottom + shadowOffset }, palette.shadow);
315 }
316 else
317 {
318 gfx_fill_rect(dpi, { leftTop, rightBottom }, ColourMapA[w->colours[0]].mid_dark);
319 gfx_fill_rect(dpi, { leftTop + shadowOffset, rightBottom + shadowOffset }, ColourMapA[w->colours[0]].lightest);
320 }
321 }
322 else
323 {
324 if (i == highlightedIndex)
325 {
326 // Darken the cell's background slightly when highlighted
327 const ScreenCoordsXY rightBottom = screenCoords
328 + ScreenCoordsXY{ _dropdown_item_width - 1, _dropdown_item_height - 1 };
329 gfx_filter_rect(dpi, { screenCoords, rightBottom }, FilterPaletteID::PaletteDarken3);
330 }
331
332 rct_string_id item = gDropdownItemsFormat[i];
333 if (item == Dropdown::FormatLandPicker || item == Dropdown::FormatColourPicker)
334 {
335 // Image item
336 auto image = static_cast<uint32_t>(gDropdownItemsArgs[i]);
337 if (item == Dropdown::FormatColourPicker && highlightedIndex == i)
338 image++;
339
340 gfx_draw_sprite(dpi, ImageId::FromUInt32(image), screenCoords);
341 }
342 else
343 {
344 // Text item
345 if (i < Dropdown::ItemsMaxSize && Dropdown::IsChecked(i))
346 item++;
347
348 // Calculate colour
349 colour_t colour = NOT_TRANSLUCENT(w->colours[0]);
350 if (i == highlightedIndex)
351 colour = COLOUR_WHITE;
352 if (i < Dropdown::ItemsMaxSize && Dropdown::IsDisabled(i))
353 colour = NOT_TRANSLUCENT(w->colours[0]) | COLOUR_FLAG_INSET;
354
355 // Draw item string
356 Formatter ft(reinterpret_cast<uint8_t*>(&gDropdownItemsArgs[i]));
357 DrawTextEllipsised(dpi, screenCoords, w->width - 5, item, ft, { colour });
358 }
359 }
360 }
361 }
362
363 /**
364 * New function based on 6e914e
365 * returns -1 if index is invalid
366 */
DropdownIndexFromPoint(const ScreenCoordsXY & loc,rct_window * w)367 int32_t DropdownIndexFromPoint(const ScreenCoordsXY& loc, rct_window* w)
368 {
369 int32_t top = loc.y - w->windowPos.y - 2;
370 if (top < 0)
371 return -1;
372
373 int32_t left = loc.x - w->windowPos.x;
374 if (left >= w->width)
375 return -1;
376 left -= 2;
377 if (left < 0)
378 return -1;
379
380 int32_t column_no = left / _dropdown_item_width;
381 if (column_no >= _dropdown_num_columns)
382 return -1;
383
384 int32_t row_no = top / _dropdown_item_height;
385 if (row_no >= _dropdown_num_rows)
386 return -1;
387
388 int32_t dropdown_index;
389 if (_dropdown_list_vertically)
390 dropdown_index = column_no * _dropdown_num_rows + row_no;
391 else
392 dropdown_index = row_no * _dropdown_num_columns + column_no;
393
394 if (dropdown_index >= gDropdownNumItems)
395 return -1;
396
397 return dropdown_index;
398 }
399
400 /**
401 * rct2: 0x006ED43D
402 */
WindowDropdownShowColour(rct_window * w,rct_widget * widget,uint8_t dropdownColour,uint8_t selectedColour)403 void WindowDropdownShowColour(rct_window* w, rct_widget* widget, uint8_t dropdownColour, uint8_t selectedColour)
404 {
405 int32_t defaultIndex = -1;
406 // Set items
407 for (uint64_t i = 0; i < COLOUR_COUNT; i++)
408 {
409 if (selectedColour == i)
410 defaultIndex = i;
411
412 gDropdownItemsFormat[i] = Dropdown::FormatColourPicker;
413 gDropdownItemsArgs[i] = (i << 32) | (SPRITE_ID_PALETTE_COLOUR_1(i) | SPR_PALETTE_BTN);
414 }
415
416 // Show dropdown
417 WindowDropdownShowImage(
418 w->windowPos.x + widget->left, w->windowPos.y + widget->top, widget->height() + 1, dropdownColour,
419 Dropdown::Flag::StayOpen, COLOUR_COUNT, 12, 12, _appropriateImageDropdownItemsPerRow[COLOUR_COUNT]);
420
421 gDropdownIsColour = true;
422 gDropdownLastColourHover = -1;
423 gDropdownDefaultIndex = defaultIndex;
424 }
425
DropdownGetAppropriateImageDropdownItemsPerRow(uint32_t numItems)426 uint32_t DropdownGetAppropriateImageDropdownItemsPerRow(uint32_t numItems)
427 {
428 return numItems < std::size(_appropriateImageDropdownItemsPerRow) ? _appropriateImageDropdownItemsPerRow[numItems] : 8;
429 }
430