From 1cc29016809d7d98f97cd3ab64deb6ade08ad965 Mon Sep 17 00:00:00 2001 From: Duncan Date: Sun, 29 May 2022 01:06:23 +0100 Subject: [PATCH] Dropdown Window Class Refactor (#17278) * Move dropdown window to class * Refactor and move locals into member vars * Deduplicate slightly * Apply review comments --- src/openrct2-ui/windows/Dropdown.cpp | 457 ++++++++++++++------------- 1 file changed, 232 insertions(+), 225 deletions(-) diff --git a/src/openrct2-ui/windows/Dropdown.cpp b/src/openrct2-ui/windows/Dropdown.cpp index 3f305dbf74..d465162b80 100644 --- a/src/openrct2-ui/windows/Dropdown.cpp +++ b/src/openrct2-ui/windows/Dropdown.cpp @@ -41,12 +41,6 @@ static rct_widget window_dropdown_widgets[] = { WIDGETS_END, }; -static int32_t _dropdown_num_columns; -static int32_t _dropdown_num_rows; -static int32_t _dropdown_item_width; -static int32_t _dropdown_item_height; -static bool _dropdown_list_vertically; - int32_t gDropdownNumItems; Dropdown::Item gDropdownItems[Dropdown::ItemsMaxSize]; static ImageId _dropdownItemsImages[Dropdown::ItemsMaxSize]; @@ -55,7 +49,6 @@ int32_t gDropdownLastColourHover; int32_t gDropdownHighlightedIndex; int32_t gDropdownDefaultIndex; static bool _dropdownPrepareUseImages; -static bool _dropdownUseImages; static void ResetDropdownFlags() { @@ -117,14 +110,225 @@ void Dropdown::SetImage(int32_t index, ImageId image) _dropdownPrepareUseImages = true; } -static void WindowDropdownPaint(rct_window* w, rct_drawpixelinfo* dpi); - -// clang-format off -static rct_window_event_list window_dropdown_events([](auto& events) +class DropdownWindow final : public Window { - events.paint = &WindowDropdownPaint; -}); -// clang-format on + bool UseImages; + int32_t NumColumns; + int32_t NumRows; + int32_t ItemWidth; + int32_t ItemHeight; + bool ListVertically; + +public: + void OnOpen() override + { + widgets = window_dropdown_widgets; + + // Input state + gDropdownHighlightedIndex = -1; + ResetDropdownFlags(); + gDropdownIsColour = false; + gDropdownDefaultIndex = -1; + input_set_state(InputState::DropdownActive); + } + + void OnDraw(rct_drawpixelinfo& dpi) override + { + DrawWidgets(dpi); + + int32_t highlightedIndex = gDropdownHighlightedIndex; + for (int32_t i = 0; i < gDropdownNumItems; i++) + { + ScreenCoordsXY cellCoords; + if (ListVertically) + cellCoords = { i / NumRows, i % NumRows }; + else + cellCoords = { i % NumColumns, i / NumColumns }; + + ScreenCoordsXY screenCoords = windowPos + + ScreenCoordsXY{ 2 + (cellCoords.x * ItemWidth), 2 + (cellCoords.y * ItemHeight) }; + + if (gDropdownItems[i].IsSeparator()) + { + const ScreenCoordsXY leftTop = screenCoords + ScreenCoordsXY{ 0, (ItemHeight / 2) }; + const ScreenCoordsXY rightBottom = leftTop + ScreenCoordsXY{ ItemWidth - 1, 0 }; + const ScreenCoordsXY shadowOffset{ 0, 1 }; + + if (colours[0] & COLOUR_FLAG_TRANSLUCENT) + { + translucent_window_palette palette = TranslucentWindowPalettes[BASE_COLOUR(colours[0])]; + gfx_filter_rect(&dpi, { leftTop, rightBottom }, palette.highlight); + gfx_filter_rect(&dpi, { leftTop + shadowOffset, rightBottom + shadowOffset }, palette.shadow); + } + else + { + gfx_fill_rect(&dpi, { leftTop, rightBottom }, ColourMapA[colours[0]].mid_dark); + gfx_fill_rect( + &dpi, { leftTop + shadowOffset, rightBottom + shadowOffset }, ColourMapA[colours[0]].lightest); + } + } + else + { + if (i == highlightedIndex) + { + // Darken the cell's background slightly when highlighted + const ScreenCoordsXY rightBottom = screenCoords + ScreenCoordsXY{ ItemWidth - 1, ItemHeight - 1 }; + gfx_filter_rect(&dpi, { screenCoords, rightBottom }, FilterPaletteID::PaletteDarken3); + } + + rct_string_id item = gDropdownItems[i].Format; + if (item == Dropdown::FormatLandPicker || item == Dropdown::FormatColourPicker) + { + // Image item + auto image = UseImages ? _dropdownItemsImages[i] + : ImageId::FromUInt32(static_cast(gDropdownItems[i].Args)); + if (item == Dropdown::FormatColourPicker && highlightedIndex == i) + image = image.WithIndexOffset(1); + gfx_draw_sprite(&dpi, image, screenCoords); + } + else + { + // Text item + if (i < Dropdown::ItemsMaxSize && Dropdown::IsChecked(i)) + item++; + + // Calculate colour + colour_t colour = NOT_TRANSLUCENT(colours[0]); + if (i == highlightedIndex) + colour = COLOUR_WHITE; + if (i < Dropdown::ItemsMaxSize && Dropdown::IsDisabled(i)) + colour = NOT_TRANSLUCENT(colours[0]) | COLOUR_FLAG_INSET; + + // Draw item string + Formatter ft(reinterpret_cast(&gDropdownItems[i].Args)); + DrawTextEllipsised(&dpi, screenCoords, width - 5, item, ft, { colour }); + } + } + } + } + + void SetTextItems( + const ScreenCoordsXY& screenPos, int32_t extraY, uint8_t colour, uint8_t customHeight, uint8_t txtFlags, + size_t numItems, int32_t itemWidth) + { + // Set and calculate num items, rows and columns + ItemWidth = itemWidth; + ItemHeight = (txtFlags & Dropdown::Flag::CustomHeight) ? customHeight : DROPDOWN_ITEM_HEIGHT; + gDropdownNumItems = static_cast(numItems); + // There must always be at least one column to prevent dividing by zero + if (gDropdownNumItems == 0) + { + NumColumns = 1; + NumRows = 1; + } + else + { + NumColumns = (gDropdownNumItems + DROPDOWN_TEXT_MAX_ROWS - 1) / DROPDOWN_TEXT_MAX_ROWS; + NumRows = (gDropdownNumItems + NumColumns - 1) / NumColumns; + } + + // Text dropdowns are listed horizontally + ListVertically = true; + + UpdateSizeAndPosition(screenPos, extraY); + + if (colour & COLOUR_FLAG_TRANSLUCENT) + flags |= WF_TRANSPARENT; + colours[0] = colour; + } + + void SetImageItems( + const ScreenCoordsXY& screenPos, int32_t extraY, uint8_t colour, int32_t numItems, int32_t itemWidth, + int32_t itemHeight, int32_t numColumns) + { + UseImages = _dropdownPrepareUseImages; + _dropdownPrepareUseImages = false; + + // Set and calculate num items, rows and columns + ItemWidth = itemWidth; + ItemHeight = itemHeight; + gDropdownNumItems = numItems; + // There must always be at least one column and row to prevent dividing by zero + if (gDropdownNumItems == 0) + { + NumColumns = 1; + NumRows = 1; + } + else + { + NumColumns = std::max(1, numColumns); + NumRows = gDropdownNumItems / NumColumns; + if (gDropdownNumItems % NumColumns != 0) + NumRows++; + } + + // image dropdowns are listed horizontally + ListVertically = false; + + UpdateSizeAndPosition(screenPos, extraY); + + if (colour & COLOUR_FLAG_TRANSLUCENT) + flags |= WF_TRANSPARENT; + colours[0] = colour; + } + + int32_t GetIndexFromPoint(const ScreenCoordsXY& loc) + { + int32_t top = loc.y - windowPos.y - 2; + if (top < 0) + return -1; + + int32_t left = loc.x - windowPos.x; + if (left >= width) + return -1; + left -= 2; + if (left < 0) + return -1; + + int32_t columnNum = left / ItemWidth; + if (columnNum >= NumColumns) + return -1; + + int32_t rowNum = top / ItemHeight; + if (rowNum >= NumRows) + return -1; + + int32_t dropdownIndex; + if (ListVertically) + dropdownIndex = columnNum * NumRows + rowNum; + else + dropdownIndex = rowNum * NumColumns + columnNum; + + if (dropdownIndex >= gDropdownNumItems) + return -1; + + return dropdownIndex; + } + +private: + void UpdateSizeAndPosition(const ScreenCoordsXY& screenPos, const int32_t extraY) + { + // Calculate position and size + const auto ddWidth = ItemWidth * NumColumns + 3; + const auto ddHeight = ItemHeight * NumRows + 3; + + int32_t screenWidth = context_get_width(); + int32_t screenHeight = context_get_height(); + auto boundedScreenPos = screenPos; + if (screenPos.x + ddWidth > screenWidth) + boundedScreenPos.x = std::max(0, screenWidth - ddWidth); + if (screenPos.y + ddHeight > screenHeight) + boundedScreenPos.y = std::max(0, screenHeight - ddHeight); + window_dropdown_widgets[WIDX_BACKGROUND].right = ddWidth; + window_dropdown_widgets[WIDX_BACKGROUND].bottom = ddHeight; + + Invalidate(); + width = ddWidth + 1; + height = ddHeight + 1; + windowPos = boundedScreenPos + ScreenCoordsXY{ 0, extraY }; + Invalidate(); + } +}; /** * Shows a text dropdown menu. @@ -170,61 +374,18 @@ void WindowDropdownShowTextCustomWidth( const ScreenCoordsXY& screenPos, int32_t extray, uint8_t colour, uint8_t custom_height, uint8_t flags, size_t num_items, int32_t width) { - rct_window* w; - input_set_flag(static_cast(INPUT_FLAG_DROPDOWN_STAY_OPEN | INPUT_FLAG_DROPDOWN_MOUSE_UP), false); if (flags & Dropdown::Flag::StayOpen) input_set_flag(INPUT_FLAG_DROPDOWN_STAY_OPEN, true); WindowDropdownClose(); - // Set and calculate num items, rows and columns - _dropdown_item_width = width; - _dropdown_item_height = (flags & Dropdown::Flag::CustomHeight) ? custom_height : DROPDOWN_ITEM_HEIGHT; - gDropdownNumItems = static_cast(num_items); - // There must always be at least one column to prevent dividing by zero - if (gDropdownNumItems == 0) + // Create the window (width/height position are set later) + auto* w = WindowCreate(WC_DROPDOWN, width, custom_height, WF_STICK_TO_FRONT); + if (w != nullptr) { - _dropdown_num_columns = 1; - _dropdown_num_rows = 1; + w->SetTextItems(screenPos, extray, colour, custom_height, flags, num_items, width); } - else - { - _dropdown_num_columns = (gDropdownNumItems + DROPDOWN_TEXT_MAX_ROWS - 1) / DROPDOWN_TEXT_MAX_ROWS; - _dropdown_num_rows = (gDropdownNumItems + _dropdown_num_columns - 1) / _dropdown_num_columns; - } - - // Text dropdowns are listed horizontally - _dropdown_list_vertically = true; - - width = _dropdown_item_width * _dropdown_num_columns + 3; - int32_t height = _dropdown_item_height * _dropdown_num_rows + 3; - int32_t screenWidth = context_get_width(); - int32_t screenHeight = context_get_height(); - auto boundedScreenPos = screenPos; - if (screenPos.x + width > screenWidth) - boundedScreenPos.x = std::max(0, screenWidth - width); - if (screenPos.y + height > screenHeight) - boundedScreenPos.y = std::max(0, screenHeight - height); - - window_dropdown_widgets[WIDX_BACKGROUND].right = width; - window_dropdown_widgets[WIDX_BACKGROUND].bottom = height; - - // Create the window - w = WindowCreate( - boundedScreenPos + ScreenCoordsXY{ 0, extray }, window_dropdown_widgets[WIDX_BACKGROUND].right + 1, - window_dropdown_widgets[WIDX_BACKGROUND].bottom + 1, &window_dropdown_events, WC_DROPDOWN, WF_STICK_TO_FRONT); - w->widgets = window_dropdown_widgets; - if (colour & COLOUR_FLAG_TRANSLUCENT) - w->flags |= WF_TRANSPARENT; - w->colours[0] = colour; - - // Input state - gDropdownHighlightedIndex = -1; - ResetDropdownFlags(); - gDropdownIsColour = false; - gDropdownDefaultIndex = -1; - input_set_state(InputState::DropdownActive); } /** @@ -245,9 +406,6 @@ void 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) { - int32_t width, height; - rct_window* w; - input_set_flag(static_cast(INPUT_FLAG_DROPDOWN_STAY_OPEN | INPUT_FLAG_DROPDOWN_MOUSE_UP), false); if (flags & Dropdown::Flag::StayOpen) input_set_flag(INPUT_FLAG_DROPDOWN_STAY_OPEN, true); @@ -255,65 +413,12 @@ void WindowDropdownShowImage( // Close existing dropdown WindowDropdownClose(); - if (_dropdownPrepareUseImages) + // Create the window (width/height position are set later) + auto* w = WindowCreate(WC_DROPDOWN, itemWidth, itemHeight, WF_STICK_TO_FRONT); + if (w != nullptr) { - _dropdownPrepareUseImages = false; - _dropdownUseImages = true; + w->SetImageItems({ x, y }, extray, colour, numItems, itemWidth, itemHeight, numColumns); } - else - { - _dropdownUseImages = false; - } - - // Set and calculate num items, rows and columns - _dropdown_item_width = itemWidth; - _dropdown_item_height = itemHeight; - gDropdownNumItems = numItems; - // There must always be at least one column and row to prevent dividing by zero - if (gDropdownNumItems == 0) - { - _dropdown_num_columns = 1; - _dropdown_num_rows = 1; - } - else - { - _dropdown_num_columns = std::max(1, numColumns); - _dropdown_num_rows = gDropdownNumItems / _dropdown_num_columns; - if (gDropdownNumItems % _dropdown_num_columns != 0) - _dropdown_num_rows++; - } - - // image dropdowns are listed horizontally - _dropdown_list_vertically = false; - - // Calculate position and size - width = _dropdown_item_width * _dropdown_num_columns + 3; - height = _dropdown_item_height * _dropdown_num_rows + 3; - - int32_t screenWidth = context_get_width(); - int32_t screenHeight = context_get_height(); - if (x + width > screenWidth) - x = std::max(0, screenWidth - width); - if (y + height > screenHeight) - y = std::max(0, screenHeight - height); - window_dropdown_widgets[WIDX_BACKGROUND].right = width; - window_dropdown_widgets[WIDX_BACKGROUND].bottom = height; - - // Create the window - w = WindowCreate( - ScreenCoordsXY(x, y + extray), window_dropdown_widgets[WIDX_BACKGROUND].right + 1, - window_dropdown_widgets[WIDX_BACKGROUND].bottom + 1, &window_dropdown_events, WC_DROPDOWN, WF_STICK_TO_FRONT); - w->widgets = window_dropdown_widgets; - if (colour & COLOUR_FLAG_TRANSLUCENT) - w->flags |= WF_TRANSPARENT; - w->colours[0] = colour; - - // Input state - gDropdownHighlightedIndex = -1; - ResetDropdownFlags(); - gDropdownIsColour = false; - gDropdownDefaultIndex = -1; - input_set_state(InputState::DropdownActive); } void WindowDropdownClose() @@ -321,116 +426,18 @@ void WindowDropdownClose() window_close_by_class(WC_DROPDOWN); } -static void WindowDropdownPaint(rct_window* w, rct_drawpixelinfo* dpi) -{ - WindowDrawWidgets(w, dpi); - - int32_t highlightedIndex = gDropdownHighlightedIndex; - for (int32_t i = 0; i < gDropdownNumItems; i++) - { - ScreenCoordsXY cellCoords; - if (_dropdown_list_vertically) - cellCoords = { i / _dropdown_num_rows, i % _dropdown_num_rows }; - else - cellCoords = { i % _dropdown_num_columns, i / _dropdown_num_columns }; - - ScreenCoordsXY screenCoords = w->windowPos - + ScreenCoordsXY{ 2 + (cellCoords.x * _dropdown_item_width), 2 + (cellCoords.y * _dropdown_item_height) }; - - if (gDropdownItems[i].IsSeparator()) - { - const ScreenCoordsXY leftTop = screenCoords + ScreenCoordsXY{ 0, (_dropdown_item_height / 2) }; - const ScreenCoordsXY rightBottom = leftTop + ScreenCoordsXY{ _dropdown_item_width - 1, 0 }; - const ScreenCoordsXY shadowOffset{ 0, 1 }; - - if (w->colours[0] & COLOUR_FLAG_TRANSLUCENT) - { - translucent_window_palette palette = TranslucentWindowPalettes[BASE_COLOUR(w->colours[0])]; - gfx_filter_rect(dpi, { leftTop, rightBottom }, palette.highlight); - gfx_filter_rect(dpi, { leftTop + shadowOffset, rightBottom + shadowOffset }, palette.shadow); - } - else - { - gfx_fill_rect(dpi, { leftTop, rightBottom }, ColourMapA[w->colours[0]].mid_dark); - gfx_fill_rect(dpi, { leftTop + shadowOffset, rightBottom + shadowOffset }, ColourMapA[w->colours[0]].lightest); - } - } - else - { - if (i == highlightedIndex) - { - // Darken the cell's background slightly when highlighted - const ScreenCoordsXY rightBottom = screenCoords - + ScreenCoordsXY{ _dropdown_item_width - 1, _dropdown_item_height - 1 }; - gfx_filter_rect(dpi, { screenCoords, rightBottom }, FilterPaletteID::PaletteDarken3); - } - - rct_string_id item = gDropdownItems[i].Format; - if (item == Dropdown::FormatLandPicker || item == Dropdown::FormatColourPicker) - { - // Image item - auto image = _dropdownUseImages ? _dropdownItemsImages[i] - : ImageId::FromUInt32(static_cast(gDropdownItems[i].Args)); - if (item == Dropdown::FormatColourPicker && highlightedIndex == i) - image = image.WithIndexOffset(1); - gfx_draw_sprite(dpi, image, screenCoords); - } - else - { - // Text item - if (i < Dropdown::ItemsMaxSize && Dropdown::IsChecked(i)) - item++; - - // Calculate colour - colour_t colour = NOT_TRANSLUCENT(w->colours[0]); - if (i == highlightedIndex) - colour = COLOUR_WHITE; - if (i < Dropdown::ItemsMaxSize && Dropdown::IsDisabled(i)) - colour = NOT_TRANSLUCENT(w->colours[0]) | COLOUR_FLAG_INSET; - - // Draw item string - Formatter ft(reinterpret_cast(&gDropdownItems[i].Args)); - DrawTextEllipsised(dpi, screenCoords, w->width - 5, item, ft, { colour }); - } - } - } -} - /** * New function based on 6e914e * returns -1 if index is invalid */ int32_t DropdownIndexFromPoint(const ScreenCoordsXY& loc, rct_window* w) { - int32_t top = loc.y - w->windowPos.y - 2; - if (top < 0) - return -1; - - int32_t left = loc.x - w->windowPos.x; - if (left >= w->width) - return -1; - left -= 2; - if (left < 0) - return -1; - - int32_t column_no = left / _dropdown_item_width; - if (column_no >= _dropdown_num_columns) - return -1; - - int32_t row_no = top / _dropdown_item_height; - if (row_no >= _dropdown_num_rows) - return -1; - - int32_t dropdown_index; - if (_dropdown_list_vertically) - dropdown_index = column_no * _dropdown_num_rows + row_no; - else - dropdown_index = row_no * _dropdown_num_columns + column_no; - - if (dropdown_index >= gDropdownNumItems) - return -1; - - return dropdown_index; + if (w->classification == WC_DROPDOWN) + { + auto* ddWnd = static_cast(w); + return ddWnd->GetIndexFromPoint(loc); + } + return -1; } /**