From 681b54dc876cdb174dd141f9baf7900007c63eef Mon Sep 17 00:00:00 2001 From: Duncan Date: Wed, 31 Jul 2024 05:26:53 +0100 Subject: [PATCH] Add Some Items to the OpenRCT2::Ui namespace (#22423) * Put widget in the Ui namespace * Put theme in the Ui namespace * Put objective in Ui namespace --- src/openrct2-ui/input/MouseInput.cpp | 1 + src/openrct2-ui/interface/Dropdown.h | 2 - src/openrct2-ui/interface/Objective.cpp | 91 +- src/openrct2-ui/interface/Objective.h | 7 +- src/openrct2-ui/interface/Theme.cpp | 1341 +++++++------- src/openrct2-ui/interface/Theme.h | 61 +- src/openrct2-ui/interface/Widget.cpp | 2253 ++++++++++++----------- src/openrct2-ui/interface/Widget.h | 275 +-- src/openrct2-ui/interface/Window.cpp | 1 + src/openrct2-ui/scripting/ScWidget.hpp | 28 +- src/openrct2-ui/windows/Dropdown.cpp | 1 + 11 files changed, 2043 insertions(+), 2018 deletions(-) diff --git a/src/openrct2-ui/input/MouseInput.cpp b/src/openrct2-ui/input/MouseInput.cpp index ac74161ad7..9c1ce3bb63 100644 --- a/src/openrct2-ui/input/MouseInput.cpp +++ b/src/openrct2-ui/input/MouseInput.cpp @@ -36,6 +36,7 @@ #include using namespace OpenRCT2; +using namespace OpenRCT2::Ui; using namespace OpenRCT2::Ui::Windows; struct RCTMouseData diff --git a/src/openrct2-ui/interface/Dropdown.h b/src/openrct2-ui/interface/Dropdown.h index 2610b92352..949a3ab87f 100644 --- a/src/openrct2-ui/interface/Dropdown.h +++ b/src/openrct2-ui/interface/Dropdown.h @@ -14,8 +14,6 @@ #include #include -using namespace OpenRCT2; - namespace OpenRCT2::Dropdown { struct Item; diff --git a/src/openrct2-ui/interface/Objective.cpp b/src/openrct2-ui/interface/Objective.cpp index 564c77c211..7850707fe0 100644 --- a/src/openrct2-ui/interface/Objective.cpp +++ b/src/openrct2-ui/interface/Objective.cpp @@ -16,53 +16,56 @@ #include #include -const StringId kObjectiveNames[] = { - STR_OBJECTIVE_NONE, - STR_OBJECTIVE_GUESTS_BY, - STR_OBJECTIVE_PARK_VALUE_BY, - STR_OBJECTIVE_HAVE_FUN, - STR_OBJECTIVE_BUILD_THE_BEST, - STR_OBJECTIVE_10_ROLLERCOASTERS, - STR_OBJECTIVE_GUESTS_AND_RATING, - STR_OBJECTIVE_MONTHLY_RIDE_INCOME, - STR_OBJECTIVE_10_ROLLERCOASTERS_LENGTH, - STR_OBJECTIVE_FINISH_5_ROLLERCOASTERS, - STR_OBJECTIVE_REPLAY_LOAN_AND_PARK_VALUE, - STR_OBJECTIVE_MONTHLY_FOOD_INCOME, -}; - -void formatObjective(Formatter& ft, Objective objective) +namespace OpenRCT2::Ui { - if (objective.Type == OBJECTIVE_BUILD_THE_BEST) + const StringId kObjectiveNames[] = { + STR_OBJECTIVE_NONE, + STR_OBJECTIVE_GUESTS_BY, + STR_OBJECTIVE_PARK_VALUE_BY, + STR_OBJECTIVE_HAVE_FUN, + STR_OBJECTIVE_BUILD_THE_BEST, + STR_OBJECTIVE_10_ROLLERCOASTERS, + STR_OBJECTIVE_GUESTS_AND_RATING, + STR_OBJECTIVE_MONTHLY_RIDE_INCOME, + STR_OBJECTIVE_10_ROLLERCOASTERS_LENGTH, + STR_OBJECTIVE_FINISH_5_ROLLERCOASTERS, + STR_OBJECTIVE_REPLAY_LOAN_AND_PARK_VALUE, + STR_OBJECTIVE_MONTHLY_FOOD_INCOME, + }; + + void formatObjective(Formatter& ft, Objective objective) { - StringId rideTypeString = STR_NONE; - auto rideTypeId = objective.RideId; - if (rideTypeId != RIDE_TYPE_NULL && rideTypeId < RIDE_TYPE_COUNT) + if (objective.Type == OBJECTIVE_BUILD_THE_BEST) { - rideTypeString = GetRideTypeDescriptor(rideTypeId).Naming.Name; + StringId rideTypeString = STR_NONE; + auto rideTypeId = objective.RideId; + if (rideTypeId != RIDE_TYPE_NULL && rideTypeId < RIDE_TYPE_COUNT) + { + rideTypeString = GetRideTypeDescriptor(rideTypeId).Naming.Name; + } + ft.Add(rideTypeString); + } + else if (objective.Type == OBJECTIVE_GUESTS_BY) + { + ft.Add(objective.NumGuests); + ft.Add(DateGetTotalMonths(MONTH_OCTOBER, objective.Year)); + } + else if (objective.Type == OBJECTIVE_GUESTS_AND_RATING) + { + ft.Add(objective.NumGuests); + } + else if (objective.Type == OBJECTIVE_10_ROLLERCOASTERS_LENGTH) + { + ft.Add(objective.MinimumLength); } - ft.Add(rideTypeString); - } - else if (objective.Type == OBJECTIVE_GUESTS_BY) - { - ft.Add(objective.NumGuests); - ft.Add(DateGetTotalMonths(MONTH_OCTOBER, objective.Year)); - } - else if (objective.Type == OBJECTIVE_GUESTS_AND_RATING) - { - ft.Add(objective.NumGuests); - } - else if (objective.Type == OBJECTIVE_10_ROLLERCOASTERS_LENGTH) - { - ft.Add(objective.MinimumLength); - } - else - { - ft.Add(objective.NumGuests); - ft.Add(DateGetTotalMonths(MONTH_OCTOBER, objective.Year)); - if (objective.Type == OBJECTIVE_FINISH_5_ROLLERCOASTERS) - ft.Add(objective.MinimumExcitement); else - ft.Add(objective.Currency); + { + ft.Add(objective.NumGuests); + ft.Add(DateGetTotalMonths(MONTH_OCTOBER, objective.Year)); + if (objective.Type == OBJECTIVE_FINISH_5_ROLLERCOASTERS) + ft.Add(objective.MinimumExcitement); + else + ft.Add(objective.Currency); + } } -} +} // namespace OpenRCT2::Ui diff --git a/src/openrct2-ui/interface/Objective.h b/src/openrct2-ui/interface/Objective.h index 6fdaa7e0ac..495d36f3ca 100644 --- a/src/openrct2-ui/interface/Objective.h +++ b/src/openrct2-ui/interface/Objective.h @@ -15,6 +15,9 @@ class Formatter; struct Objective; -void formatObjective(Formatter& ft, Objective objective); +namespace OpenRCT2::Ui +{ + void formatObjective(Formatter& ft, Objective objective); -extern const StringId kObjectiveNames[12]; + extern const StringId kObjectiveNames[12]; +} // namespace OpenRCT2::Ui diff --git a/src/openrct2-ui/interface/Theme.cpp b/src/openrct2-ui/interface/Theme.cpp index fb4c0e16cf..3434b4cde5 100644 --- a/src/openrct2-ui/interface/Theme.cpp +++ b/src/openrct2-ui/interface/Theme.cpp @@ -32,80 +32,80 @@ #include #include -using namespace OpenRCT2; - -static constexpr uint8_t kCurrentThemeVersion = 1; - -struct WindowThemeDesc; - -/** - * Represents a window theming style such as the colour scheme. - */ -struct WindowTheme +namespace OpenRCT2::Ui { - ColourWithFlags Colours[6]; -}; + static constexpr uint8_t kCurrentThemeVersion = 1; -/** - * Represents the style for a particular type of window. - */ -struct UIThemeWindowEntry -{ - WindowClass Class; - WindowTheme Theme; + struct WindowThemeDesc; - json_t ToJson() const; /** - * @note json is deliberately left non-const: json_t behaviour changes when const + * Represents a window theming style such as the colour scheme. */ - static UIThemeWindowEntry FromJson(const WindowThemeDesc* wtDesc, json_t& json, uint8_t version); -}; - -/** - * Represents a user interface theme. Contains window colour schemes and appearance features. - */ -class UITheme -{ -public: - std::string Name; - std::vector Entries; - uint8_t Flags = 0; - - explicit UITheme(const std::string& name) - : Name(name) + struct WindowTheme { - } - - const UIThemeWindowEntry* GetEntry(WindowClass windowClass) const; - void SetEntry(const UIThemeWindowEntry* entry); - void RemoveEntry(WindowClass windowClass); - - json_t ToJson() const; - bool WriteToFile(const std::string& path) const; + ColourWithFlags Colours[6]; + }; /** - * @note json is deliberately left non-const: json_t behaviour changes when const + * Represents the style for a particular type of window. */ - static UITheme* FromJson(json_t& json); - static UITheme* FromFile(const std::string& path); - static UITheme CreatePredefined(const std::string& name, const UIThemeWindowEntry* entries, uint8_t flags); -}; + struct UIThemeWindowEntry + { + WindowClass Class; + WindowTheme Theme; -/** - * Represents the theme descriptor for a specific window type including the default colour scheme. - */ -struct WindowThemeDesc -{ - ::WindowClass WindowClass; - const utf8* WindowClassSZ; - StringId WindowName; - uint8_t NumColours; - WindowTheme DefaultTheme; -}; + json_t ToJson() const; + /** + * @note json is deliberately left non-const: json_t behaviour changes when const + */ + static UIThemeWindowEntry FromJson(const WindowThemeDesc* wtDesc, json_t& json, uint8_t version); + }; + + /** + * Represents a user interface theme. Contains window colour schemes and appearance features. + */ + class UITheme + { + public: + std::string Name; + std::vector Entries; + uint8_t Flags = 0; + + explicit UITheme(const std::string& name) + : Name(name) + { + } + + const UIThemeWindowEntry* GetEntry(WindowClass windowClass) const; + void SetEntry(const UIThemeWindowEntry* entry); + void RemoveEntry(WindowClass windowClass); + + json_t ToJson() const; + bool WriteToFile(const std::string& path) const; + + /** + * @note json is deliberately left non-const: json_t behaviour changes when const + */ + static UITheme* FromJson(json_t& json); + static UITheme* FromFile(const std::string& path); + static UITheme CreatePredefined(const std::string& name, const UIThemeWindowEntry* entries, uint8_t flags); + }; + + /** + * Represents the theme descriptor for a specific window type including the default colour scheme. + */ + struct WindowThemeDesc + { + ::WindowClass WindowClass; + const utf8* WindowClassSZ; + StringId WindowName; + uint8_t NumColours; + WindowTheme DefaultTheme; + }; #pragma region Window Theme Descriptors -// clang-format off + // clang-format off #define COLOURS_1(c0) 1, { { (c0), 0, 0, 0, 0, 0 } } #define COLOURS_2(c0, c1) 2, { { (c0), (c1), 0, 0, 0, 0 } } #define COLOURS_3(c0, c1, c2) 3, { { (c0), (c1), (c2), 0, 0, 0 } } @@ -230,712 +230,713 @@ static constexpr UIThemeWindowEntry PredefinedThemeRCT1_Entries[] = { WindowClass::Changelog, COLOURS_RCT1(opaque(COLOUR_DARK_BROWN), opaque(COLOUR_DARK_BROWN), opaque(COLOUR_WHITE), opaque(COLOUR_BLACK), opaque(COLOUR_BLACK), opaque(COLOUR_BLACK)) }, THEME_DEF_END, }; -// clang-format on + // clang-format on -static constexpr UIThemeWindowEntry PredefinedThemeRCT2_Entries[] = { - THEME_DEF_END, -}; + static constexpr UIThemeWindowEntry PredefinedThemeRCT2_Entries[] = { + THEME_DEF_END, + }; -const UITheme PredefinedThemeRCT1 = UITheme::CreatePredefined( - "*RCT1", PredefinedThemeRCT1_Entries, - UITHEME_FLAG_USE_LIGHTS_RIDE | UITHEME_FLAG_USE_LIGHTS_PARK | UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT - | UITHEME_FLAG_USE_FULL_BOTTOM_TOOLBAR); + const UITheme PredefinedThemeRCT1 = UITheme::CreatePredefined( + "*RCT1", PredefinedThemeRCT1_Entries, + UITHEME_FLAG_USE_LIGHTS_RIDE | UITHEME_FLAG_USE_LIGHTS_PARK | UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT + | UITHEME_FLAG_USE_FULL_BOTTOM_TOOLBAR); -const UITheme PredefinedThemeRCT2 = UITheme::CreatePredefined("*RCT2", PredefinedThemeRCT2_Entries, 0); + const UITheme PredefinedThemeRCT2 = UITheme::CreatePredefined("*RCT2", PredefinedThemeRCT2_Entries, 0); -struct PredefinedTheme -{ - const UITheme* Theme; - StringId Name; -}; + struct PredefinedTheme + { + const UITheme* Theme; + StringId Name; + }; -static constexpr PredefinedTheme PredefinedThemes[] = { - { &PredefinedThemeRCT1, STR_TITLE_SEQUENCE_RCT1 }, - { &PredefinedThemeRCT2, STR_TITLE_SEQUENCE_RCT2 }, -}; + static constexpr PredefinedTheme PredefinedThemes[] = { + { &PredefinedThemeRCT1, STR_TITLE_SEQUENCE_RCT1 }, + { &PredefinedThemeRCT2, STR_TITLE_SEQUENCE_RCT2 }, + }; #pragma endregion -static const WindowThemeDesc* GetWindowThemeDescriptor(WindowClass windowClass) -{ - for (const auto& desc : WindowThemeDescriptors) + static const WindowThemeDesc* GetWindowThemeDescriptor(WindowClass windowClass) { - if (desc.WindowClass == windowClass) + for (const auto& desc : WindowThemeDescriptors) { - return &desc; + if (desc.WindowClass == windowClass) + { + return &desc; + } } - } - return nullptr; -} - -static const WindowThemeDesc* GetWindowThemeDescriptor(const utf8* windowClassSZ) -{ - for (const auto& desc : WindowThemeDescriptors) - { - if (String::Equals(desc.WindowClassSZ, windowClassSZ)) - { - return &desc; - } - } - return nullptr; -} - -static void ThrowThemeLoadException() -{ - throw std::runtime_error("Invalid JSON UI theme entry."); -} - -#pragma region UIThemeEntry - -json_t UIThemeWindowEntry::ToJson() const -{ - const WindowThemeDesc* wtDesc = GetWindowThemeDescriptor(Class); - if (wtDesc == nullptr) - { return nullptr; } - json_t jsonColours = json_t::array(); - for (uint8_t i = 0; i < wtDesc->NumColours; i++) + static const WindowThemeDesc* GetWindowThemeDescriptor(const utf8* windowClassSZ) { - json_t jsonEntry = { { "colour", Colour::ToString(Theme.Colours[i].colour) }, - { "translucent", Theme.Colours[i].hasFlag(ColourFlag::translucent) } }; - - jsonColours.emplace_back(jsonEntry); - } - - json_t colourSettingsEntry = { - { "colours", jsonColours }, - }; - - return colourSettingsEntry; -} - -UIThemeWindowEntry UIThemeWindowEntry::FromJson(const WindowThemeDesc* wtDesc, json_t& jsonData, uint8_t version) -{ - Guard::Assert(jsonData.is_object(), "UIThemeWindowEntry::FromJson expects parameter jsonData to be object"); - - auto jsonColours = Json::AsArray(jsonData["colours"]); - - if (jsonColours.empty()) - { - ThrowThemeLoadException(); - } - - UIThemeWindowEntry result{}; - result.Class = wtDesc->WindowClass; - result.Theme = wtDesc->DefaultTheme; - - // result.Theme.Colours only has 6 values - size_t colourCount = std::min(jsonColours.size(), static_cast(wtDesc->NumColours)); - - for (size_t i = 0; i < colourCount; i++) - { - if (version == 0) + for (const auto& desc : WindowThemeDescriptors) { - auto number = Json::GetNumber(jsonColours[i]); - result.Theme.Colours[i] = ColourWithFlags::fromLegacy(number); - } - else - { - auto colourObject = Json::AsObject(jsonColours[i]); - auto colour = Colour::FromString(Json::GetString(colourObject["colour"]), COLOUR_BLACK); - auto isTranslucent = Json::GetBoolean(colourObject["translucent"], false); - uint8_t flags = isTranslucent ? EnumToFlag(ColourFlag::translucent) : 0; - - result.Theme.Colours[i] = { colour, flags }; + if (String::Equals(desc.WindowClassSZ, windowClassSZ)) + { + return &desc; + } } + return nullptr; } - return result; -} - -#pragma endregion - -#pragma region UITheme - -const UIThemeWindowEntry* UITheme::GetEntry(WindowClass windowClass) const -{ - for (const auto& entry : Entries) + static void ThrowThemeLoadException() { - if (entry.Class == windowClass) - { - return &entry; - } - } - return nullptr; -} - -void UITheme::SetEntry(const UIThemeWindowEntry* newEntry) -{ - // Try to replace existing entry - for (auto& entry : Entries) - { - if (entry.Class == newEntry->Class) - { - entry = *newEntry; - return; - } + throw std::runtime_error("Invalid JSON UI theme entry."); } - Entries.push_back(*newEntry); -} +#pragma region UIThemeEntry -void UITheme::RemoveEntry(WindowClass windowClass) -{ - // Remove existing entry - for (size_t i = 0; i < Entries.size(); i++) + json_t UIThemeWindowEntry::ToJson() const { - UIThemeWindowEntry* entry = &Entries[i]; - if (entry->Class == windowClass) - { - Entries.erase(Entries.begin() + i); - break; - } - } -} - -json_t UITheme::ToJson() const -{ - // Create entries - json_t jsonEntries; - for (const UIThemeWindowEntry& entry : Entries) - { - const WindowThemeDesc* wtDesc = GetWindowThemeDescriptor(entry.Class); + const WindowThemeDesc* wtDesc = GetWindowThemeDescriptor(Class); if (wtDesc == nullptr) { return nullptr; } - jsonEntries[wtDesc->WindowClassSZ] = entry.ToJson(); - } - // Create theme object - json_t jsonTheme = { - { "name", Name }, - { "version", kCurrentThemeVersion }, - { "entries", jsonEntries }, - { "useLightsRide", (Flags & UITHEME_FLAG_USE_LIGHTS_RIDE) != 0 }, - { "useLightsPark", (Flags & UITHEME_FLAG_USE_LIGHTS_PARK) != 0 }, - { "useAltScenarioSelectFont", (Flags & UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT) != 0 }, - { "useFullBottomToolbar", (Flags & UITHEME_FLAG_USE_FULL_BOTTOM_TOOLBAR) != 0 }, - }; - - return jsonTheme; -} - -bool UITheme::WriteToFile(const std::string& path) const -{ - auto jsonTheme = ToJson(); - bool result; - try - { - Json::WriteToFile(path.c_str(), jsonTheme); - result = true; - } - catch (const std::exception& ex) - { - LOG_ERROR("Unable to save %s: %s", path.c_str(), ex.what()); - result = false; - } - - return result; -} - -UITheme* UITheme::FromJson(json_t& jsonObj) -{ - Guard::Assert(jsonObj.is_object(), "UITheme::FromJson expects parameter jsonObj to be object"); - - const std::string themeName = Json::GetString(jsonObj["name"]); - if (themeName.empty()) - { - ThrowThemeLoadException(); - } - auto version = Json::GetNumber(jsonObj["version"], 0); - - json_t jsonEntries = jsonObj["entries"]; - - UITheme* result = nullptr; - try - { - result = new UITheme(themeName); - - result->Flags = Json::GetFlags( - jsonObj, - { - { "useLightsRide", UITHEME_FLAG_USE_LIGHTS_RIDE }, - { "useLightsPark", UITHEME_FLAG_USE_LIGHTS_PARK }, - { "useAltScenarioSelectFont", UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT }, - { "useFullBottomToolbar", UITHEME_FLAG_USE_FULL_BOTTOM_TOOLBAR }, - }); - - if (jsonEntries.is_object()) + json_t jsonColours = json_t::array(); + for (uint8_t i = 0; i < wtDesc->NumColours; i++) { - for (auto& [jsonKey, jsonValue] : jsonEntries.items()) - { - if (jsonValue.is_object()) - { - const WindowThemeDesc* wtDesc = GetWindowThemeDescriptor(jsonKey.data()); - if (wtDesc == nullptr) - { - continue; - } + json_t jsonEntry = { { "colour", Colour::ToString(Theme.Colours[i].colour) }, + { "translucent", Theme.Colours[i].hasFlag(ColourFlag::translucent) } }; - UIThemeWindowEntry entry = UIThemeWindowEntry::FromJson(wtDesc, jsonValue, version); - result->SetEntry(&entry); - } + jsonColours.emplace_back(jsonEntry); + } + + json_t colourSettingsEntry = { + { "colours", jsonColours }, + }; + + return colourSettingsEntry; + } + + UIThemeWindowEntry UIThemeWindowEntry::FromJson(const WindowThemeDesc* wtDesc, json_t& jsonData, uint8_t version) + { + Guard::Assert(jsonData.is_object(), "UIThemeWindowEntry::FromJson expects parameter jsonData to be object"); + + auto jsonColours = Json::AsArray(jsonData["colours"]); + + if (jsonColours.empty()) + { + ThrowThemeLoadException(); + } + + UIThemeWindowEntry result{}; + result.Class = wtDesc->WindowClass; + result.Theme = wtDesc->DefaultTheme; + + // result.Theme.Colours only has 6 values + size_t colourCount = std::min(jsonColours.size(), static_cast(wtDesc->NumColours)); + + for (size_t i = 0; i < colourCount; i++) + { + if (version == 0) + { + auto number = Json::GetNumber(jsonColours[i]); + result.Theme.Colours[i] = ColourWithFlags::fromLegacy(number); + } + else + { + auto colourObject = Json::AsObject(jsonColours[i]); + auto colour = Colour::FromString(Json::GetString(colourObject["colour"]), COLOUR_BLACK); + auto isTranslucent = Json::GetBoolean(colourObject["translucent"], false); + uint8_t flags = isTranslucent ? EnumToFlag(ColourFlag::translucent) : 0; + + result.Theme.Colours[i] = { colour, flags }; } } return result; } - catch (const std::exception&) - { - delete result; - throw; - } -} - -UITheme* UITheme::FromFile(const std::string& path) -{ - UITheme* result = nullptr; - json_t json; - try - { - json = Json::ReadFromFile(path.c_str()); - result = UITheme::FromJson(json); - } - catch (const std::exception&) - { - LOG_ERROR("Unable to read theme: %s", path.c_str()); - result = nullptr; - } - return result; -} - -UITheme UITheme::CreatePredefined(const std::string& name, const UIThemeWindowEntry* entries, uint8_t flags) -{ - auto theme = UITheme(name); - theme.Flags = flags | UITHEME_FLAG_PREDEFINED; - - size_t numEntries = 0; - for (const UIThemeWindowEntry* entry = entries; entry->Class != WindowClass::Null; entry++) - { - numEntries++; - } - - theme.Entries = std::vector(entries, entries + numEntries); - return theme; -} #pragma endregion -namespace OpenRCT2::ThemeManager -{ - struct AvailableTheme +#pragma region UITheme + + const UIThemeWindowEntry* UITheme::GetEntry(WindowClass windowClass) const { - std::string Path; - std::string Name; - }; - - static std::string CurrentThemePath; - static UITheme* CurrentTheme; - static std::vector AvailableThemes; - static size_t ActiveAvailableThemeIndex = SIZE_MAX; - static size_t NumPredefinedThemes = 0; - - std::string GetThemeFileName(const std::string& name); - bool EnsureThemeDirectoryExists(); - std::string GetThemePath(); - - static void GetAvailableThemes(std::vector* outThemes) - { - Guard::ArgumentNotNull(outThemes, GUARD_LINE); - - outThemes->clear(); - - NumPredefinedThemes = 0; - for (auto predefinedTheme : PredefinedThemes) + for (const auto& entry : Entries) { - AvailableTheme theme{}; - theme.Name = predefinedTheme.Theme->Name; - outThemes->push_back(std::move(theme)); + if (entry.Class == windowClass) + { + return &entry; + } + } + return nullptr; + } - NumPredefinedThemes++; + void UITheme::SetEntry(const UIThemeWindowEntry* newEntry) + { + // Try to replace existing entry + for (auto& entry : Entries) + { + if (entry.Class == newEntry->Class) + { + entry = *newEntry; + return; + } } - auto themesPattern = Path::Combine(GetThemePath(), u8"*.json"); - auto scanner = Path::ScanDirectory(themesPattern, true); - while (scanner->Next()) + Entries.push_back(*newEntry); + } + + void UITheme::RemoveEntry(WindowClass windowClass) + { + // Remove existing entry + for (size_t i = 0; i < Entries.size(); i++) { - const auto& fileInfo = scanner->GetFileInfo(); - auto name = Path::GetFileNameWithoutExtension(fileInfo.Name); - - AvailableTheme theme{}; - theme.Name = name; - theme.Path = GetThemeFileName(name); - outThemes->push_back(std::move(theme)); - - if (Path::Equals(CurrentThemePath, scanner->GetPath())) + UIThemeWindowEntry* entry = &Entries[i]; + if (entry->Class == windowClass) { - ActiveAvailableThemeIndex = outThemes->size() - 1; + Entries.erase(Entries.begin() + i); + break; } } } - static void LoadTheme(UITheme* theme) + json_t UITheme::ToJson() const { - if (CurrentTheme == theme) + // Create entries + json_t jsonEntries; + for (const UIThemeWindowEntry& entry : Entries) { - return; - } - - if (CurrentTheme != nullptr) - { - if (!(CurrentTheme->Flags & UITHEME_FLAG_PREDEFINED)) + const WindowThemeDesc* wtDesc = GetWindowThemeDescriptor(entry.Class); + if (wtDesc == nullptr) { - delete CurrentTheme; + return nullptr; } + jsonEntries[wtDesc->WindowClassSZ] = entry.ToJson(); } - CurrentTheme = theme; - CurrentThemePath.clear(); + // Create theme object + json_t jsonTheme = { + { "name", Name }, + { "version", kCurrentThemeVersion }, + { "entries", jsonEntries }, + { "useLightsRide", (Flags & UITHEME_FLAG_USE_LIGHTS_RIDE) != 0 }, + { "useLightsPark", (Flags & UITHEME_FLAG_USE_LIGHTS_PARK) != 0 }, + { "useAltScenarioSelectFont", (Flags & UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT) != 0 }, + { "useFullBottomToolbar", (Flags & UITHEME_FLAG_USE_FULL_BOTTOM_TOOLBAR) != 0 }, + }; - GfxInvalidateScreen(); + return jsonTheme; } - static void LoadTheme(const std::string& path) - { - auto theme = UITheme::FromFile(path); - if (theme == nullptr) - { - // Fall-back to default - theme = const_cast(&PredefinedThemeRCT2); - LoadTheme(theme); - } - else - { - LoadTheme(theme); - CurrentThemePath = path; - } - } - - static bool LoadThemeByConfigName(const utf8* name) - { - for (size_t i = 0; i < ThemeManager::AvailableThemes.size(); i++) - { - const auto& theme = ThemeManager::AvailableThemes[i]; - if (String::Equals(name, theme.Name)) - { - if (theme.Path.empty()) - { - LoadTheme(const_cast(PredefinedThemes[i].Theme)); - } - else - { - LoadTheme(theme.Path); - } - ActiveAvailableThemeIndex = i; - return true; - } - } - return false; - } - - static void Initialise() - { - ThemeManager::GetAvailableThemes(&ThemeManager::AvailableThemes); - LoadTheme(const_cast(&PredefinedThemeRCT2)); - ActiveAvailableThemeIndex = 1; - - bool configValid = false; - if (!Config::Get().interface.CurrentThemePreset.empty()) - { - if (LoadThemeByConfigName(Config::Get().interface.CurrentThemePreset.c_str())) - { - configValid = true; - } - } - - if (!configValid) - { - Config::Get().interface.CurrentThemePreset = ThemeManagerGetAvailableThemeConfigName(1); - } - } - - std::string GetThemeFileName(const std::string& name) - { - auto themeDirectory = GetThemePath(); - auto themePath = Path::Combine(themeDirectory, name + u8".json"); - return themePath; - } - - bool EnsureThemeDirectoryExists() + bool UITheme::WriteToFile(const std::string& path) const { + auto jsonTheme = ToJson(); + bool result; try { - auto path = GetThemePath(); - Path::CreateDirectory(path); - return true; + Json::WriteToFile(path.c_str(), jsonTheme); + result = true; + } + catch (const std::exception& ex) + { + LOG_ERROR("Unable to save %s: %s", path.c_str(), ex.what()); + result = false; + } + + return result; + } + + UITheme* UITheme::FromJson(json_t& jsonObj) + { + Guard::Assert(jsonObj.is_object(), "UITheme::FromJson expects parameter jsonObj to be object"); + + const std::string themeName = Json::GetString(jsonObj["name"]); + if (themeName.empty()) + { + ThrowThemeLoadException(); + } + auto version = Json::GetNumber(jsonObj["version"], 0); + + json_t jsonEntries = jsonObj["entries"]; + + UITheme* result = nullptr; + try + { + result = new UITheme(themeName); + + result->Flags = Json::GetFlags( + jsonObj, + { + { "useLightsRide", UITHEME_FLAG_USE_LIGHTS_RIDE }, + { "useLightsPark", UITHEME_FLAG_USE_LIGHTS_PARK }, + { "useAltScenarioSelectFont", UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT }, + { "useFullBottomToolbar", UITHEME_FLAG_USE_FULL_BOTTOM_TOOLBAR }, + }); + + if (jsonEntries.is_object()) + { + for (auto& [jsonKey, jsonValue] : jsonEntries.items()) + { + if (jsonValue.is_object()) + { + const WindowThemeDesc* wtDesc = GetWindowThemeDescriptor(jsonKey.data()); + if (wtDesc == nullptr) + { + continue; + } + + UIThemeWindowEntry entry = UIThemeWindowEntry::FromJson(wtDesc, jsonValue, version); + result->SetEntry(&entry); + } + } + } + + return result; } catch (const std::exception&) { + delete result; + throw; + } + } + + UITheme* UITheme::FromFile(const std::string& path) + { + UITheme* result = nullptr; + json_t json; + try + { + json = Json::ReadFromFile(path.c_str()); + result = UITheme::FromJson(json); + } + catch (const std::exception&) + { + LOG_ERROR("Unable to read theme: %s", path.c_str()); + result = nullptr; + } + return result; + } + + UITheme UITheme::CreatePredefined(const std::string& name, const UIThemeWindowEntry* entries, uint8_t flags) + { + auto theme = UITheme(name); + theme.Flags = flags | UITHEME_FLAG_PREDEFINED; + + size_t numEntries = 0; + for (const UIThemeWindowEntry* entry = entries; entry->Class != WindowClass::Null; entry++) + { + numEntries++; + } + + theme.Entries = std::vector(entries, entries + numEntries); + return theme; + } + +#pragma endregion + + namespace ThemeManager + { + struct AvailableTheme + { + std::string Path; + std::string Name; + }; + + static std::string CurrentThemePath; + static UITheme* CurrentTheme; + static std::vector AvailableThemes; + static size_t ActiveAvailableThemeIndex = SIZE_MAX; + static size_t NumPredefinedThemes = 0; + + std::string GetThemeFileName(const std::string& name); + bool EnsureThemeDirectoryExists(); + std::string GetThemePath(); + + static void GetAvailableThemes(std::vector* outThemes) + { + Guard::ArgumentNotNull(outThemes, GUARD_LINE); + + outThemes->clear(); + + NumPredefinedThemes = 0; + for (auto predefinedTheme : PredefinedThemes) + { + AvailableTheme theme{}; + theme.Name = predefinedTheme.Theme->Name; + outThemes->push_back(std::move(theme)); + + NumPredefinedThemes++; + } + + auto themesPattern = Path::Combine(GetThemePath(), u8"*.json"); + auto scanner = Path::ScanDirectory(themesPattern, true); + while (scanner->Next()) + { + const auto& fileInfo = scanner->GetFileInfo(); + auto name = Path::GetFileNameWithoutExtension(fileInfo.Name); + + AvailableTheme theme{}; + theme.Name = name; + theme.Path = GetThemeFileName(name); + outThemes->push_back(std::move(theme)); + + if (Path::Equals(CurrentThemePath, scanner->GetPath())) + { + ActiveAvailableThemeIndex = outThemes->size() - 1; + } + } + } + + static void LoadTheme(UITheme* theme) + { + if (CurrentTheme == theme) + { + return; + } + + if (CurrentTheme != nullptr) + { + if (!(CurrentTheme->Flags & UITHEME_FLAG_PREDEFINED)) + { + delete CurrentTheme; + } + } + + CurrentTheme = theme; + CurrentThemePath.clear(); + + GfxInvalidateScreen(); + } + + static void LoadTheme(const std::string& path) + { + auto theme = UITheme::FromFile(path); + if (theme == nullptr) + { + // Fall-back to default + theme = const_cast(&PredefinedThemeRCT2); + LoadTheme(theme); + } + else + { + LoadTheme(theme); + CurrentThemePath = path; + } + } + + static bool LoadThemeByConfigName(const utf8* name) + { + for (size_t i = 0; i < ThemeManager::AvailableThemes.size(); i++) + { + const auto& theme = ThemeManager::AvailableThemes[i]; + if (String::Equals(name, theme.Name)) + { + if (theme.Path.empty()) + { + LoadTheme(const_cast(PredefinedThemes[i].Theme)); + } + else + { + LoadTheme(theme.Path); + } + ActiveAvailableThemeIndex = i; + return true; + } + } return false; } - } - std::string GetThemePath() - { - auto context = GetContext(); - auto env = context->GetPlatformEnvironment(); - return env->GetDirectoryPath(DIRBASE::USER, DIRID::THEME); - } -} // namespace OpenRCT2::ThemeManager - -void ThemeManagerLoadAvailableThemes() -{ - ThemeManager::GetAvailableThemes(&ThemeManager::AvailableThemes); -} - -size_t ThemeManagerGetNumAvailableThemes() -{ - return ThemeManager::AvailableThemes.size(); -} - -const utf8* ThemeManagerGetAvailableThemePath(size_t index) -{ - return ThemeManager::AvailableThemes[index].Path.c_str(); -} - -const utf8* ThemeManagerGetAvailableThemeConfigName(size_t index) -{ - return ThemeManager::AvailableThemes[index].Name.c_str(); -} -const utf8* ThemeManagerGetAvailableThemeName(size_t index) -{ - if (index < ThemeManager::NumPredefinedThemes) - return LanguageGetString(PredefinedThemes[index].Name); - return ThemeManager::AvailableThemes[index].Name.c_str(); -} - -size_t ThemeManagerGetAvailableThemeIndex() -{ - return ThemeManager::ActiveAvailableThemeIndex; -} - -void ThemeManagerSetActiveAvailableTheme(size_t index) -{ - if (index < ThemeManager::NumPredefinedThemes) - { - ThemeManager::LoadTheme(const_cast(PredefinedThemes[index].Theme)); - } - else - { - auto path = ThemeManager::AvailableThemes[index].Path; - ThemeManager::LoadTheme(path); - - // HACK Check if theme load failed and fell back to RCT2 - if (ThemeManager::CurrentThemePath.empty()) + static void Initialise() { - index = 1; + ThemeManager::GetAvailableThemes(&ThemeManager::AvailableThemes); + LoadTheme(const_cast(&PredefinedThemeRCT2)); + ActiveAvailableThemeIndex = 1; + + bool configValid = false; + if (!Config::Get().interface.CurrentThemePreset.empty()) + { + if (LoadThemeByConfigName(Config::Get().interface.CurrentThemePreset.c_str())) + { + configValid = true; + } + } + + if (!configValid) + { + Config::Get().interface.CurrentThemePreset = ThemeManagerGetAvailableThemeConfigName(1); + } + } + + std::string GetThemeFileName(const std::string& name) + { + auto themeDirectory = GetThemePath(); + auto themePath = Path::Combine(themeDirectory, name + u8".json"); + return themePath; + } + + bool EnsureThemeDirectoryExists() + { + try + { + auto path = GetThemePath(); + Path::CreateDirectory(path); + return true; + } + catch (const std::exception&) + { + return false; + } + } + + std::string GetThemePath() + { + auto context = GetContext(); + auto env = context->GetPlatformEnvironment(); + return env->GetDirectoryPath(DIRBASE::USER, DIRID::THEME); + } + } // namespace ThemeManager + + void ThemeManagerLoadAvailableThemes() + { + ThemeManager::GetAvailableThemes(&ThemeManager::AvailableThemes); + } + + size_t ThemeManagerGetNumAvailableThemes() + { + return ThemeManager::AvailableThemes.size(); + } + + const utf8* ThemeManagerGetAvailableThemePath(size_t index) + { + return ThemeManager::AvailableThemes[index].Path.c_str(); + } + + const utf8* ThemeManagerGetAvailableThemeConfigName(size_t index) + { + return ThemeManager::AvailableThemes[index].Name.c_str(); + } + const utf8* ThemeManagerGetAvailableThemeName(size_t index) + { + if (index < ThemeManager::NumPredefinedThemes) + return LanguageGetString(PredefinedThemes[index].Name); + return ThemeManager::AvailableThemes[index].Name.c_str(); + } + + size_t ThemeManagerGetAvailableThemeIndex() + { + return ThemeManager::ActiveAvailableThemeIndex; + } + + void ThemeManagerSetActiveAvailableTheme(size_t index) + { + if (index < ThemeManager::NumPredefinedThemes) + { + ThemeManager::LoadTheme(const_cast(PredefinedThemes[index].Theme)); + } + else + { + auto path = ThemeManager::AvailableThemes[index].Path; + ThemeManager::LoadTheme(path); + + // HACK Check if theme load failed and fell back to RCT2 + if (ThemeManager::CurrentThemePath.empty()) + { + index = 1; + } + } + ThemeManager::ActiveAvailableThemeIndex = index; + Config::Get().interface.CurrentThemePreset = ThemeManagerGetAvailableThemeConfigName(index); + + ColourSchemeUpdateAll(); + } + + size_t ThemeGetIndexForName(const utf8* name) + { + size_t count = ThemeManager::AvailableThemes.size(); + for (size_t i = 0; i < count; i++) + { + const utf8* tn = ThemeManagerGetAvailableThemeName(i); + if (String::IEquals(tn, name)) + { + return i; + } + } + return SIZE_MAX; + } + + ColourWithFlags ThemeGetColour(WindowClass wc, uint8_t index) + { + const UIThemeWindowEntry* entry = ThemeManager::CurrentTheme->GetEntry(wc); + if (entry == nullptr) + { + const WindowThemeDesc* desc = GetWindowThemeDescriptor(wc); + if (desc == nullptr) + { + return {}; + } + return desc->DefaultTheme.Colours[index]; + } + + return entry->Theme.Colours[index]; + } + + void ThemeSetColour(WindowClass wc, uint8_t index, ColourWithFlags colour) + { + UIThemeWindowEntry entry{}; + entry.Class = wc; + + auto currentEntry = const_cast(ThemeManager::CurrentTheme->GetEntry(wc)); + if (currentEntry != nullptr) + { + entry.Theme = currentEntry->Theme; + } + else + { + const WindowThemeDesc* desc = GetWindowThemeDescriptor(wc); + if (desc == nullptr) + { + return; + } + entry.Theme = desc->DefaultTheme; + } + + entry.Theme.Colours[index] = colour; + ThemeManager::CurrentTheme->SetEntry(&entry); + + ThemeSave(); + } + + uint8_t ThemeGetFlags() + { + return ThemeManager::CurrentTheme->Flags; + } + + void ThemeSetFlags(uint8_t flags) + { + ThemeManager::CurrentTheme->Flags = flags; + ThemeSave(); + } + + void ThemeSave() + { + ThemeManager::EnsureThemeDirectoryExists(); + ThemeManager::CurrentTheme->WriteToFile(ThemeManager::CurrentThemePath); + } + + void ThemeRename(const utf8* name) + { + const auto oldPath = ThemeManager::CurrentThemePath; + + ThemeManager::EnsureThemeDirectoryExists(); + auto newPath = ThemeManager::GetThemeFileName(name); + File::Move(oldPath, newPath); + ThemeManager::CurrentThemePath = newPath; + + ThemeManager::CurrentTheme->Name = name; + ThemeManager::CurrentTheme->WriteToFile(ThemeManager::CurrentThemePath); + + ThemeManagerLoadAvailableThemes(); + for (size_t i = 0; i < ThemeManager::AvailableThemes.size(); i++) + { + if (Path::Equals(newPath, ThemeManager::AvailableThemes[i].Path)) + { + ThemeManager::ActiveAvailableThemeIndex = i; + Config::Get().interface.CurrentThemePreset = ThemeManagerGetAvailableThemeConfigName(1); + break; + } } } - ThemeManager::ActiveAvailableThemeIndex = index; - Config::Get().interface.CurrentThemePreset = ThemeManagerGetAvailableThemeConfigName(index); - ColourSchemeUpdateAll(); -} - -size_t ThemeGetIndexForName(const utf8* name) -{ - size_t count = ThemeManager::AvailableThemes.size(); - for (size_t i = 0; i < count; i++) + void ThemeDuplicate(const utf8* name) { - const utf8* tn = ThemeManagerGetAvailableThemeName(i); - if (String::IEquals(tn, name)) + ThemeManager::EnsureThemeDirectoryExists(); + auto newPath = ThemeManager::GetThemeFileName(name); + + // Copy the theme, save it and then load it back in + auto newTheme = std::make_unique(*ThemeManager::CurrentTheme); + newTheme->Name = name; + newTheme->Flags &= ~UITHEME_FLAG_PREDEFINED; + newTheme->WriteToFile(newPath); + + ThemeManager::LoadTheme(newPath); + + ThemeManagerLoadAvailableThemes(); + for (size_t i = 0; i < ThemeManager::AvailableThemes.size(); i++) { - return i; + if (Path::Equals(newPath, ThemeManager::AvailableThemes[i].Path)) + { + ThemeManager::ActiveAvailableThemeIndex = i; + Config::Get().interface.CurrentThemePreset = ThemeManagerGetAvailableThemeConfigName(i); + break; + } } } - return SIZE_MAX; -} -ColourWithFlags ThemeGetColour(WindowClass wc, uint8_t index) -{ - const UIThemeWindowEntry* entry = ThemeManager::CurrentTheme->GetEntry(wc); - if (entry == nullptr) + void ThemeDelete() + { + File::Delete(ThemeManager::CurrentThemePath); + ThemeManager::LoadTheme(const_cast(&PredefinedThemeRCT2)); + ThemeManager::ActiveAvailableThemeIndex = 1; + Config::Get().interface.CurrentThemePreset = ThemeManagerGetAvailableThemeConfigName(1); + } + + void ThemeManagerInitialise() + { + ThemeManager::Initialise(); + } + + uint8_t ThemeDescGetNumColours(WindowClass wc) { const WindowThemeDesc* desc = GetWindowThemeDescriptor(wc); if (desc == nullptr) { - return {}; + return 0; } - return desc->DefaultTheme.Colours[index]; + return desc->NumColours; } - return entry->Theme.Colours[index]; -} - -void ThemeSetColour(WindowClass wc, uint8_t index, ColourWithFlags colour) -{ - UIThemeWindowEntry entry{}; - entry.Class = wc; - - auto currentEntry = const_cast(ThemeManager::CurrentTheme->GetEntry(wc)); - if (currentEntry != nullptr) - { - entry.Theme = currentEntry->Theme; - } - else + StringId ThemeDescGetName(WindowClass wc) { const WindowThemeDesc* desc = GetWindowThemeDescriptor(wc); if (desc == nullptr) { - return; + return STR_EMPTY; } - entry.Theme = desc->DefaultTheme; + return desc->WindowName; } - entry.Theme.Colours[index] = colour; - ThemeManager::CurrentTheme->SetEntry(&entry); - - ThemeSave(); -} - -uint8_t ThemeGetFlags() -{ - return ThemeManager::CurrentTheme->Flags; -} - -void ThemeSetFlags(uint8_t flags) -{ - ThemeManager::CurrentTheme->Flags = flags; - ThemeSave(); -} - -void ThemeSave() -{ - ThemeManager::EnsureThemeDirectoryExists(); - ThemeManager::CurrentTheme->WriteToFile(ThemeManager::CurrentThemePath); -} - -void ThemeRename(const utf8* name) -{ - const auto oldPath = ThemeManager::CurrentThemePath; - - ThemeManager::EnsureThemeDirectoryExists(); - auto newPath = ThemeManager::GetThemeFileName(name); - File::Move(oldPath, newPath); - ThemeManager::CurrentThemePath = newPath; - - ThemeManager::CurrentTheme->Name = name; - ThemeManager::CurrentTheme->WriteToFile(ThemeManager::CurrentThemePath); - - ThemeManagerLoadAvailableThemes(); - for (size_t i = 0; i < ThemeManager::AvailableThemes.size(); i++) + void ColourSchemeUpdateAll() { - if (Path::Equals(newPath, ThemeManager::AvailableThemes[i].Path)) + WindowVisitEach([](WindowBase* w) { ColourSchemeUpdate(w); }); + } + + void ColourSchemeUpdate(WindowBase* window) + { + ColourSchemeUpdateByClass(window, window->classification); + } + + void ColourSchemeUpdateByClass(WindowBase* window, WindowClass classification) + { + const WindowTheme* windowTheme; + const UIThemeWindowEntry* entry = ThemeManager::CurrentTheme->GetEntry(classification); + if (entry != nullptr) { - ThemeManager::ActiveAvailableThemeIndex = i; - Config::Get().interface.CurrentThemePreset = ThemeManagerGetAvailableThemeConfigName(1); - break; + windowTheme = &entry->Theme; } - } -} - -void ThemeDuplicate(const utf8* name) -{ - ThemeManager::EnsureThemeDirectoryExists(); - auto newPath = ThemeManager::GetThemeFileName(name); - - // Copy the theme, save it and then load it back in - auto newTheme = std::make_unique(*ThemeManager::CurrentTheme); - newTheme->Name = name; - newTheme->Flags &= ~UITHEME_FLAG_PREDEFINED; - newTheme->WriteToFile(newPath); - - ThemeManager::LoadTheme(newPath); - - ThemeManagerLoadAvailableThemes(); - for (size_t i = 0; i < ThemeManager::AvailableThemes.size(); i++) - { - if (Path::Equals(newPath, ThemeManager::AvailableThemes[i].Path)) + else { - ThemeManager::ActiveAvailableThemeIndex = i; - Config::Get().interface.CurrentThemePreset = ThemeManagerGetAvailableThemeConfigName(i); - break; + const WindowThemeDesc* desc = GetWindowThemeDescriptor(classification); + + // Some windows don't have a theme set (e.g. main window, title screen) + if (desc == nullptr) + { + return; + } + + windowTheme = &desc->DefaultTheme; } - } -} -void ThemeDelete() -{ - File::Delete(ThemeManager::CurrentThemePath); - ThemeManager::LoadTheme(const_cast(&PredefinedThemeRCT2)); - ThemeManager::ActiveAvailableThemeIndex = 1; - Config::Get().interface.CurrentThemePreset = ThemeManagerGetAvailableThemeConfigName(1); -} - -void ThemeManagerInitialise() -{ - ThemeManager::Initialise(); -} - -uint8_t ThemeDescGetNumColours(WindowClass wc) -{ - const WindowThemeDesc* desc = GetWindowThemeDescriptor(wc); - if (desc == nullptr) - { - return 0; - } - return desc->NumColours; -} - -StringId ThemeDescGetName(WindowClass wc) -{ - const WindowThemeDesc* desc = GetWindowThemeDescriptor(wc); - if (desc == nullptr) - { - return STR_EMPTY; - } - return desc->WindowName; -} - -void ColourSchemeUpdateAll() -{ - WindowVisitEach([](WindowBase* w) { ColourSchemeUpdate(w); }); -} - -void ColourSchemeUpdate(WindowBase* window) -{ - ColourSchemeUpdateByClass(window, window->classification); -} - -void ColourSchemeUpdateByClass(WindowBase* window, WindowClass classification) -{ - const WindowTheme* windowTheme; - const UIThemeWindowEntry* entry = ThemeManager::CurrentTheme->GetEntry(classification); - if (entry != nullptr) - { - windowTheme = &entry->Theme; - } - else - { - const WindowThemeDesc* desc = GetWindowThemeDescriptor(classification); - - // Some windows don't have a theme set (e.g. main window, title screen) - if (desc == nullptr) + for (int32_t i = 0; i < 6; i++) { - return; + window->colours[i] = windowTheme->Colours[i]; } - - windowTheme = &desc->DefaultTheme; + // Some windows need to be transparent even if the colours aren't. + // There doesn't seem to be any side-effects for all windows being transparent + window->flags |= WF_TRANSPARENT; } - - for (int32_t i = 0; i < 6; i++) - { - window->colours[i] = windowTheme->Colours[i]; - } - // Some windows need to be transparent even if the colours aren't. - // There doesn't seem to be any side-effects for all windows being transparent - window->flags |= WF_TRANSPARENT; -} +} // namespace OpenRCT2::Ui diff --git a/src/openrct2-ui/interface/Theme.h b/src/openrct2-ui/interface/Theme.h index 963ba8c23a..45fb6da141 100644 --- a/src/openrct2-ui/interface/Theme.h +++ b/src/openrct2-ui/interface/Theme.h @@ -12,37 +12,40 @@ #include #include -enum +namespace OpenRCT2::Ui { - UITHEME_FLAG_PREDEFINED = 1 << 0, - UITHEME_FLAG_USE_LIGHTS_RIDE = 1 << 1, - UITHEME_FLAG_USE_LIGHTS_PARK = 1 << 2, - UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT = 1 << 3, - UITHEME_FLAG_USE_FULL_BOTTOM_TOOLBAR = 1 << 4, -}; + enum + { + UITHEME_FLAG_PREDEFINED = 1 << 0, + UITHEME_FLAG_USE_LIGHTS_RIDE = 1 << 1, + UITHEME_FLAG_USE_LIGHTS_PARK = 1 << 2, + UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT = 1 << 3, + UITHEME_FLAG_USE_FULL_BOTTOM_TOOLBAR = 1 << 4, + }; -void ColourSchemeUpdate(WindowBase* window); -void ColourSchemeUpdateAll(); -void ColourSchemeUpdateByClass(WindowBase* window, WindowClass classification); + void ColourSchemeUpdate(WindowBase* window); + void ColourSchemeUpdateAll(); + void ColourSchemeUpdateByClass(WindowBase* window, WindowClass classification); -void ThemeManagerInitialise(); -void ThemeManagerLoadAvailableThemes(); -size_t ThemeManagerGetNumAvailableThemes(); -const utf8* ThemeManagerGetAvailableThemePath(size_t index); -const utf8* ThemeManagerGetAvailableThemeConfigName(size_t index); -const utf8* ThemeManagerGetAvailableThemeName(size_t index); -size_t ThemeManagerGetAvailableThemeIndex(); -void ThemeManagerSetActiveAvailableTheme(size_t index); -size_t ThemeGetIndexForName(const utf8* name); + void ThemeManagerInitialise(); + void ThemeManagerLoadAvailableThemes(); + size_t ThemeManagerGetNumAvailableThemes(); + const utf8* ThemeManagerGetAvailableThemePath(size_t index); + const utf8* ThemeManagerGetAvailableThemeConfigName(size_t index); + const utf8* ThemeManagerGetAvailableThemeName(size_t index); + size_t ThemeManagerGetAvailableThemeIndex(); + void ThemeManagerSetActiveAvailableTheme(size_t index); + size_t ThemeGetIndexForName(const utf8* name); -ColourWithFlags ThemeGetColour(WindowClass wc, uint8_t index); -void ThemeSetColour(WindowClass wc, uint8_t index, ColourWithFlags colour); -uint8_t ThemeGetFlags(); -void ThemeSetFlags(uint8_t flags); -void ThemeSave(); -void ThemeRename(const utf8* name); -void ThemeDuplicate(const utf8* name); -void ThemeDelete(); + ColourWithFlags ThemeGetColour(WindowClass wc, uint8_t index); + void ThemeSetColour(WindowClass wc, uint8_t index, ColourWithFlags colour); + uint8_t ThemeGetFlags(); + void ThemeSetFlags(uint8_t flags); + void ThemeSave(); + void ThemeRename(const utf8* name); + void ThemeDuplicate(const utf8* name); + void ThemeDelete(); -uint8_t ThemeDescGetNumColours(WindowClass wc); -StringId ThemeDescGetName(WindowClass wc); + uint8_t ThemeDescGetNumColours(WindowClass wc); + StringId ThemeDescGetName(WindowClass wc); +} // namespace OpenRCT2::Ui diff --git a/src/openrct2-ui/interface/Widget.cpp b/src/openrct2-ui/interface/Widget.cpp index efcf620fb6..e26e95c8d2 100644 --- a/src/openrct2-ui/interface/Widget.cpp +++ b/src/openrct2-ui/interface/Widget.cpp @@ -25,1208 +25,1221 @@ using namespace OpenRCT2; -static void WidgetFrameDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); -static void WidgetResizeDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); -static void WidgetButtonDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); -static void WidgetTabDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); -static void WidgetFlatButtonDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); -static void WidgetTextButton(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); -static void WidgetTextCentred(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); -static void WidgetText(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); -static void WidgetTextInset(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); -static void WidgetTextBoxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); -static void WidgetProgressBarDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); -static void WidgetGroupboxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); -static void WidgetCaptionDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); -static void WidgetCheckboxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); -static void WidgetCloseboxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); -static void WidgetScrollDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); -static void WidgetHScrollbarDraw( - DrawPixelInfo& dpi, const ScrollBar& scroll, int32_t l, int32_t t, int32_t r, int32_t b, ColourWithFlags colour); -static void WidgetVScrollbarDraw( - DrawPixelInfo& dpi, const ScrollBar& scroll, int32_t l, int32_t t, int32_t r, int32_t b, ColourWithFlags colour); -static void WidgetDrawImage(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); - -/** - * - * rct2: 0x006EB2A8 - */ -void WidgetDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) +namespace OpenRCT2::Ui { - const auto* widget = GetWidgetByIndex(w, widgetIndex); - if (widget == nullptr) + static void WidgetFrameDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + static void WidgetResizeDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + static void WidgetButtonDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + static void WidgetTabDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + static void WidgetFlatButtonDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + static void WidgetTextButton(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + static void WidgetTextCentred(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + static void WidgetText(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + static void WidgetTextInset(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + static void WidgetTextBoxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + static void WidgetProgressBarDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + static void WidgetGroupboxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + static void WidgetCaptionDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + static void WidgetCheckboxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + static void WidgetCloseboxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + static void WidgetScrollDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + static void WidgetHScrollbarDraw( + DrawPixelInfo& dpi, const ScrollBar& scroll, int32_t l, int32_t t, int32_t r, int32_t b, ColourWithFlags colour); + static void WidgetVScrollbarDraw( + DrawPixelInfo& dpi, const ScrollBar& scroll, int32_t l, int32_t t, int32_t r, int32_t b, ColourWithFlags colour); + static void WidgetDrawImage(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + + /** + * + * rct2: 0x006EB2A8 + */ + void WidgetDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) { - LOG_ERROR("Tried drawing an out-of-bounds widget index!"); - return; - } - - switch (widget->type) - { - case WindowWidgetType::Frame: - WidgetFrameDraw(dpi, w, widgetIndex); - break; - case WindowWidgetType::Resize: - WidgetResizeDraw(dpi, w, widgetIndex); - break; - case WindowWidgetType::ImgBtn: - WidgetButtonDraw(dpi, w, widgetIndex); - break; - case WindowWidgetType::ColourBtn: - case WindowWidgetType::TrnBtn: - case WindowWidgetType::Tab: - WidgetTabDraw(dpi, w, widgetIndex); - break; - case WindowWidgetType::FlatBtn: - WidgetFlatButtonDraw(dpi, w, widgetIndex); - break; - case WindowWidgetType::Button: - case WindowWidgetType::TableHeader: - WidgetTextButton(dpi, w, widgetIndex); - break; - case WindowWidgetType::LabelCentred: - WidgetTextCentred(dpi, w, widgetIndex); - break; - case WindowWidgetType::Label: - WidgetText(dpi, w, widgetIndex); - break; - case WindowWidgetType::Spinner: - case WindowWidgetType::DropdownMenu: - case WindowWidgetType::Viewport: - WidgetTextInset(dpi, w, widgetIndex); - break; - case WindowWidgetType::Groupbox: - WidgetGroupboxDraw(dpi, w, widgetIndex); - break; - case WindowWidgetType::Caption: - WidgetCaptionDraw(dpi, w, widgetIndex); - break; - case WindowWidgetType::CloseBox: - WidgetCloseboxDraw(dpi, w, widgetIndex); - break; - case WindowWidgetType::Scroll: - WidgetScrollDraw(dpi, w, widgetIndex); - break; - case WindowWidgetType::Checkbox: - WidgetCheckboxDraw(dpi, w, widgetIndex); - break; - case WindowWidgetType::TextBox: - WidgetTextBoxDraw(dpi, w, widgetIndex); - break; - case WindowWidgetType::ProgressBar: - WidgetProgressBarDraw(dpi, w, widgetIndex); - break; - default: - break; - } -} - -/** - * - * rct2: 0x006EB6CE - */ -static void WidgetFrameDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) -{ - // Get the widget - const auto& widget = w.widgets[widgetIndex]; - - // Resolve the absolute ltrb - auto leftTop = w.windowPos + ScreenCoordsXY{ widget.left, widget.top }; - int32_t r = w.windowPos.x + widget.right; - int32_t b = w.windowPos.y + widget.bottom; - - // - uint8_t press = ((w.flags & WF_10) ? INSET_RECT_FLAG_FILL_MID_LIGHT : 0); - - auto colour = w.colours[widget.colour]; - - // Draw the frame - GfxFillRectInset(dpi, { leftTop, { r, b } }, colour, press); - - // Check if the window can be resized - if (!(w.flags & WF_RESIZABLE)) - return; - if (w.min_width == w.max_width && w.min_height == w.max_height) - return; - - // Draw the resize sprite at the bottom right corner - leftTop = w.windowPos + ScreenCoordsXY{ widget.right - 18, widget.bottom - 18 }; - GfxDrawSprite(dpi, ImageId(SPR_RESIZE, colour.colour), leftTop); -} - -/** - * - * rct2: 0x006EB765 - */ -static void WidgetResizeDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) -{ - // Get the widget - const auto& widget = w.widgets[widgetIndex]; - - // Resolve the absolute ltrb - auto leftTop = w.windowPos + ScreenCoordsXY{ widget.left, widget.top }; - int32_t r = w.windowPos.x + widget.right; - int32_t b = w.windowPos.y + widget.bottom; - - auto colour = w.colours[widget.colour]; - - // Draw the panel - GfxFillRectInset(dpi, { leftTop, { r, b } }, colour, 0); - - // Check if the window can be resized - if (!(w.flags & WF_RESIZABLE)) - return; - if (w.min_width == w.max_width && w.min_height == w.max_height) - return; - - // Draw the resize sprite at the bottom right corner - leftTop = w.windowPos + ScreenCoordsXY{ widget.right - 18, widget.bottom - 18 }; - GfxDrawSprite(dpi, ImageId(SPR_RESIZE, colour.colour), leftTop); -} - -/** - * - * rct2: 0x006EB8E5 - */ -static void WidgetButtonDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) -{ - // Get the widget - const auto& widget = w.widgets[widgetIndex]; - - // Resolve the absolute ltrb - ScreenRect rect{ w.windowPos + ScreenCoordsXY{ widget.left, widget.top }, - w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom } }; - - // Check if the button is pressed down - uint8_t press = WidgetIsPressed(w, widgetIndex) || WidgetIsActiveTool(w, widgetIndex) ? INSET_RECT_FLAG_BORDER_INSET : 0; - - auto colour = w.colours[widget.colour]; - - if (static_cast(widget.image.ToUInt32()) == -2) - { - // Draw border with no fill - GfxFillRectInset(dpi, rect, colour, press | INSET_RECT_FLAG_FILL_NONE); - return; - } - - // Draw the border with fill - GfxFillRectInset(dpi, rect, colour, press); - - WidgetDrawImage(dpi, w, widgetIndex); -} - -/** - * - * rct2: 0x006EB806 - */ -static void WidgetTabDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) -{ - // Get the widget - auto& widget = w.widgets[widgetIndex]; - - if (widget.type != WindowWidgetType::Tab && widget.image.GetIndex() == ImageIndexUndefined) - return; - - if (widget.type == WindowWidgetType::Tab) - { - if (WidgetIsDisabled(w, widgetIndex)) - return; - - if (widget.image.GetIndex() == ImageIndexUndefined) + const auto* widget = GetWidgetByIndex(w, widgetIndex); + if (widget == nullptr) { - // Set standard tab sprite to use. - widget.image = ImageId(SPR_TAB, FilterPaletteID::PaletteNull); + LOG_ERROR("Tried drawing an out-of-bounds widget index!"); + return; + } + + switch (widget->type) + { + case WindowWidgetType::Frame: + WidgetFrameDraw(dpi, w, widgetIndex); + break; + case WindowWidgetType::Resize: + WidgetResizeDraw(dpi, w, widgetIndex); + break; + case WindowWidgetType::ImgBtn: + WidgetButtonDraw(dpi, w, widgetIndex); + break; + case WindowWidgetType::ColourBtn: + case WindowWidgetType::TrnBtn: + case WindowWidgetType::Tab: + WidgetTabDraw(dpi, w, widgetIndex); + break; + case WindowWidgetType::FlatBtn: + WidgetFlatButtonDraw(dpi, w, widgetIndex); + break; + case WindowWidgetType::Button: + case WindowWidgetType::TableHeader: + WidgetTextButton(dpi, w, widgetIndex); + break; + case WindowWidgetType::LabelCentred: + WidgetTextCentred(dpi, w, widgetIndex); + break; + case WindowWidgetType::Label: + WidgetText(dpi, w, widgetIndex); + break; + case WindowWidgetType::Spinner: + case WindowWidgetType::DropdownMenu: + case WindowWidgetType::Viewport: + WidgetTextInset(dpi, w, widgetIndex); + break; + case WindowWidgetType::Groupbox: + WidgetGroupboxDraw(dpi, w, widgetIndex); + break; + case WindowWidgetType::Caption: + WidgetCaptionDraw(dpi, w, widgetIndex); + break; + case WindowWidgetType::CloseBox: + WidgetCloseboxDraw(dpi, w, widgetIndex); + break; + case WindowWidgetType::Scroll: + WidgetScrollDraw(dpi, w, widgetIndex); + break; + case WindowWidgetType::Checkbox: + WidgetCheckboxDraw(dpi, w, widgetIndex); + break; + case WindowWidgetType::TextBox: + WidgetTextBoxDraw(dpi, w, widgetIndex); + break; + case WindowWidgetType::ProgressBar: + WidgetProgressBarDraw(dpi, w, widgetIndex); + break; + default: + break; } } - // Draw widgets that aren't explicitly disabled. - if (!WidgetIsDisabled(w, widgetIndex)) + /** + * + * rct2: 0x006EB6CE + */ + static void WidgetFrameDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) { - WidgetDrawImage(dpi, w, widgetIndex); - return; + // Get the widget + const auto& widget = w.widgets[widgetIndex]; + + // Resolve the absolute ltrb + auto leftTop = w.windowPos + ScreenCoordsXY{ widget.left, widget.top }; + int32_t r = w.windowPos.x + widget.right; + int32_t b = w.windowPos.y + widget.bottom; + + // + uint8_t press = ((w.flags & WF_10) ? INSET_RECT_FLAG_FILL_MID_LIGHT : 0); + + auto colour = w.colours[widget.colour]; + + // Draw the frame + GfxFillRectInset(dpi, { leftTop, { r, b } }, colour, press); + + // Check if the window can be resized + if (!(w.flags & WF_RESIZABLE)) + return; + if (w.min_width == w.max_width && w.min_height == w.max_height) + return; + + // Draw the resize sprite at the bottom right corner + leftTop = w.windowPos + ScreenCoordsXY{ widget.right - 18, widget.bottom - 18 }; + GfxDrawSprite(dpi, ImageId(SPR_RESIZE, colour.colour), leftTop); } - if (widget.type != WindowWidgetType::TrnBtn) + /** + * + * rct2: 0x006EB765 + */ + static void WidgetResizeDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) { - WidgetDrawImage(dpi, w, widgetIndex); - return; + // Get the widget + const auto& widget = w.widgets[widgetIndex]; + + // Resolve the absolute ltrb + auto leftTop = w.windowPos + ScreenCoordsXY{ widget.left, widget.top }; + int32_t r = w.windowPos.x + widget.right; + int32_t b = w.windowPos.y + widget.bottom; + + auto colour = w.colours[widget.colour]; + + // Draw the panel + GfxFillRectInset(dpi, { leftTop, { r, b } }, colour, 0); + + // Check if the window can be resized + if (!(w.flags & WF_RESIZABLE)) + return; + if (w.min_width == w.max_width && w.min_height == w.max_height) + return; + + // Draw the resize sprite at the bottom right corner + leftTop = w.windowPos + ScreenCoordsXY{ widget.right - 18, widget.bottom - 18 }; + GfxDrawSprite(dpi, ImageId(SPR_RESIZE, colour.colour), leftTop); } - // Resolve the absolute ltrb - auto leftTop = w.windowPos + ScreenCoordsXY{ widget.left, widget.top }; - - // Get the colour and disabled image - auto colour = w.colours[widget.colour].colour; - const auto newIndex = widget.image.GetIndex() + 2; - auto image = widget.image.WithIndex(newIndex).WithPrimary(colour); - - // Draw disabled image - GfxDrawSprite(dpi, image, leftTop); -} - -/** - * - * rct2: 0x006EB861 - */ -static void WidgetFlatButtonDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) -{ - if (!WidgetIsDisabled(w, widgetIndex) && WidgetIsHighlighted(w, widgetIndex)) + /** + * + * rct2: 0x006EB8E5 + */ + static void WidgetButtonDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) { - WidgetButtonDraw(dpi, w, widgetIndex); - return; - } + // Get the widget + const auto& widget = w.widgets[widgetIndex]; - // Get the widget - const auto& widget = w.widgets[widgetIndex]; + // Resolve the absolute ltrb + ScreenRect rect{ w.windowPos + ScreenCoordsXY{ widget.left, widget.top }, + w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom } }; - // Resolve the absolute ltrb - ScreenRect rect{ w.windowPos + ScreenCoordsXY{ widget.left, widget.top }, - w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom } }; + // Check if the button is pressed down + uint8_t press = WidgetIsPressed(w, widgetIndex) || WidgetIsActiveTool(w, widgetIndex) ? INSET_RECT_FLAG_BORDER_INSET + : 0; - auto colour = w.colours[widget.colour]; + auto colour = w.colours[widget.colour]; - // Check if the button is pressed down - if (WidgetIsPressed(w, widgetIndex) || WidgetIsActiveTool(w, widgetIndex)) - { if (static_cast(widget.image.ToUInt32()) == -2) { // Draw border with no fill - GfxFillRectInset(dpi, rect, colour, INSET_RECT_FLAG_BORDER_INSET | INSET_RECT_FLAG_FILL_NONE); + GfxFillRectInset(dpi, rect, colour, press | INSET_RECT_FLAG_FILL_NONE); return; } // Draw the border with fill - GfxFillRectInset(dpi, rect, colour, INSET_RECT_FLAG_BORDER_INSET); + GfxFillRectInset(dpi, rect, colour, press); + + WidgetDrawImage(dpi, w, widgetIndex); } - // Draw image - WidgetDrawImage(dpi, w, widgetIndex); -} - -/** - * - * rct2: 0x006EBBEB - */ -static void WidgetTextButton(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) -{ - // Get the widget - const auto& widget = w.widgets[widgetIndex]; - - // Resolve the absolute ltrb - ScreenRect rect{ w.windowPos + ScreenCoordsXY{ widget.left, widget.top }, - w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom } }; - - auto colour = w.colours[widget.colour]; - - // Border - uint8_t press = WidgetIsPressed(w, widgetIndex) || WidgetIsActiveTool(w, widgetIndex) ? INSET_RECT_FLAG_BORDER_INSET : 0; - GfxFillRectInset(dpi, rect, colour, press); - - // Button caption - if (widget.type != WindowWidgetType::TableHeader) + /** + * + * rct2: 0x006EB806 + */ + static void WidgetTabDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) { - WidgetTextCentred(dpi, w, widgetIndex); - } - else - { - WidgetText(dpi, w, widgetIndex); - } -} + // Get the widget + auto& widget = w.widgets[widgetIndex]; -/** - * - * rct2: 0x006EBC41 - */ -static void WidgetTextCentred(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) -{ - // Get the widget - const auto& widget = w.widgets[widgetIndex]; + if (widget.type != WindowWidgetType::Tab && widget.image.GetIndex() == ImageIndexUndefined) + return; - if (widget.text == STR_NONE) - return; - - auto colour = w.colours[widget.colour]; - colour.setFlag(ColourFlag::translucent, false); - if (WidgetIsDisabled(w, widgetIndex)) - colour.setFlag(ColourFlag::inset, true); - - // Resolve the absolute ltrb - auto topLeft = w.windowPos + ScreenCoordsXY{ widget.left, 0 }; - int32_t r = w.windowPos.x + widget.right; - - if (widget.type == WindowWidgetType::Button || widget.type == WindowWidgetType::TableHeader) - topLeft.y += widget.textTop(); - else - topLeft.y += widget.top; - - auto stringId = widget.text; - auto ft = Formatter::Common(); - if (widget.flags & WIDGET_FLAGS::TEXT_IS_STRING) - { - stringId = STR_STRING; - ft.Add(widget.string); - } - - ScreenCoordsXY coords = { (topLeft.x + r + 1) / 2 - 1, topLeft.y }; - if (widget.type == WindowWidgetType::LabelCentred) - { - DrawTextWrapped(dpi, coords, widget.width() - 2, stringId, ft, { colour, TextAlignment::CENTRE }); - } - else - { - DrawTextEllipsised(dpi, coords, widget.width() - 2, stringId, ft, { colour, TextAlignment::CENTRE }); - } -} - -/** - * - * rct2: 0x006EBD52 - */ -static void WidgetText(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) -{ - // Get the widget - const auto& widget = w.widgets[widgetIndex]; - - if (widget.text == STR_NONE || widget.content == kWidgetContentEmpty) - return; - - auto colour = w.colours[widget.colour]; - if (WidgetIsDisabled(w, widgetIndex)) - colour.setFlag(ColourFlag::inset, true); - - // Resolve the absolute ltrb - int32_t l = w.windowPos.x + widget.left; - int32_t r = w.windowPos.x + widget.right; - int32_t t; - - if (widget.type == WindowWidgetType::Button || widget.type == WindowWidgetType::DropdownMenu - || widget.type == WindowWidgetType::Spinner || widget.type == WindowWidgetType::TableHeader) - { - t = w.windowPos.y + widget.textTop(); - } - else - t = w.windowPos.y + widget.top; - - auto stringId = widget.text; - auto ft = Formatter::Common(); - if (widget.flags & WIDGET_FLAGS::TEXT_IS_STRING) - { - stringId = STR_STRING; - ft.Add(widget.string); - } - - ScreenCoordsXY coords = { l + 1, t }; - if (widget.type == WindowWidgetType::LabelCentred) - { - DrawTextWrapped(dpi, coords, r - l, stringId, ft, { colour, TextAlignment::CENTRE }); - } - else - { - DrawTextEllipsised(dpi, coords, r - l, stringId, ft, colour); - } -} - -/** - * - * rct2: 0x006EBD1F - */ -static void WidgetTextInset(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) -{ - // Get the widget - const auto& widget = w.widgets[widgetIndex]; - - // Resolve the absolute ltrb - ScreenRect rect{ w.windowPos + ScreenCoordsXY{ widget.left, widget.top }, - w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom } }; - - auto colour = w.colours[widget.colour]; - - GfxFillRectInset(dpi, rect, colour, INSET_RECT_F_60); - WidgetText(dpi, w, widgetIndex); -} - -static std::pair WidgetGetStringidAndArgs(const Widget& widget) -{ - auto stringId = widget.text; - void* formatArgs = gCommonFormatArgs; - if (widget.flags & WIDGET_FLAGS::TEXT_IS_STRING) - { - if (widget.string == nullptr || widget.string[0] == '\0') + if (widget.type == WindowWidgetType::Tab) { - stringId = STR_NONE; - formatArgs = nullptr; + if (WidgetIsDisabled(w, widgetIndex)) + return; + + if (widget.image.GetIndex() == ImageIndexUndefined) + { + // Set standard tab sprite to use. + widget.image = ImageId(SPR_TAB, FilterPaletteID::PaletteNull); + } + } + + // Draw widgets that aren't explicitly disabled. + if (!WidgetIsDisabled(w, widgetIndex)) + { + WidgetDrawImage(dpi, w, widgetIndex); + return; + } + + if (widget.type != WindowWidgetType::TrnBtn) + { + WidgetDrawImage(dpi, w, widgetIndex); + return; + } + + // Resolve the absolute ltrb + auto leftTop = w.windowPos + ScreenCoordsXY{ widget.left, widget.top }; + + // Get the colour and disabled image + auto colour = w.colours[widget.colour].colour; + const auto newIndex = widget.image.GetIndex() + 2; + auto image = widget.image.WithIndex(newIndex).WithPrimary(colour); + + // Draw disabled image + GfxDrawSprite(dpi, image, leftTop); + } + + /** + * + * rct2: 0x006EB861 + */ + static void WidgetFlatButtonDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) + { + if (!WidgetIsDisabled(w, widgetIndex) && WidgetIsHighlighted(w, widgetIndex)) + { + WidgetButtonDraw(dpi, w, widgetIndex); + return; + } + + // Get the widget + const auto& widget = w.widgets[widgetIndex]; + + // Resolve the absolute ltrb + ScreenRect rect{ w.windowPos + ScreenCoordsXY{ widget.left, widget.top }, + w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom } }; + + auto colour = w.colours[widget.colour]; + + // Check if the button is pressed down + if (WidgetIsPressed(w, widgetIndex) || WidgetIsActiveTool(w, widgetIndex)) + { + if (static_cast(widget.image.ToUInt32()) == -2) + { + // Draw border with no fill + GfxFillRectInset(dpi, rect, colour, INSET_RECT_FLAG_BORDER_INSET | INSET_RECT_FLAG_FILL_NONE); + return; + } + + // Draw the border with fill + GfxFillRectInset(dpi, rect, colour, INSET_RECT_FLAG_BORDER_INSET); + } + + // Draw image + WidgetDrawImage(dpi, w, widgetIndex); + } + + /** + * + * rct2: 0x006EBBEB + */ + static void WidgetTextButton(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) + { + // Get the widget + const auto& widget = w.widgets[widgetIndex]; + + // Resolve the absolute ltrb + ScreenRect rect{ w.windowPos + ScreenCoordsXY{ widget.left, widget.top }, + w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom } }; + + auto colour = w.colours[widget.colour]; + + // Border + uint8_t press = WidgetIsPressed(w, widgetIndex) || WidgetIsActiveTool(w, widgetIndex) ? INSET_RECT_FLAG_BORDER_INSET + : 0; + GfxFillRectInset(dpi, rect, colour, press); + + // Button caption + if (widget.type != WindowWidgetType::TableHeader) + { + WidgetTextCentred(dpi, w, widgetIndex); } else { - stringId = STR_STRING; - formatArgs = const_cast(reinterpret_cast(&widget.string)); + WidgetText(dpi, w, widgetIndex); } } - return std::make_pair(stringId, formatArgs); -} -/** - * - * rct2: 0x006EB535 - */ -static void WidgetGroupboxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) -{ - // Get the widget - const auto& widget = w.widgets[widgetIndex]; - - // Resolve the absolute ltrb - auto l = w.windowPos.x + widget.left + 5; - auto t = w.windowPos.y + widget.top; - auto textRight = l; - - // Text - auto [stringId, formatArgs] = WidgetGetStringidAndArgs(widget); - if (stringId != STR_NONE) + /** + * + * rct2: 0x006EBC41 + */ + static void WidgetTextCentred(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) { - auto colour = w.colours[widget.colour].withFlag(ColourFlag::translucent, false); + // Get the widget + const auto& widget = w.widgets[widgetIndex]; + + if (widget.text == STR_NONE) + return; + + auto colour = w.colours[widget.colour]; + colour.setFlag(ColourFlag::translucent, false); if (WidgetIsDisabled(w, widgetIndex)) colour.setFlag(ColourFlag::inset, true); - utf8 buffer[512] = { 0 }; - OpenRCT2::FormatStringLegacy(buffer, sizeof(buffer), stringId, formatArgs); - auto ft = Formatter(); - ft.Add(buffer); - DrawTextBasic(dpi, { l, t }, STR_STRING, ft, { colour }); - textRight = l + GfxGetStringWidth(buffer, FontStyle::Medium) + 1; - } + // Resolve the absolute ltrb + auto topLeft = w.windowPos + ScreenCoordsXY{ widget.left, 0 }; + int32_t r = w.windowPos.x + widget.right; - // Border - // Resolve the absolute ltrb - l = w.windowPos.x + widget.left; - t = w.windowPos.y + widget.top + 4; - const auto r = w.windowPos.x + widget.right; - const auto b = w.windowPos.y + widget.bottom; - - uint8_t colour = w.colours[widget.colour].colour; - - // Border left of text - GfxFillRect(dpi, { { l, t }, { l + 4, t } }, ColourMapA[colour].mid_dark); - GfxFillRect(dpi, { { l + 1, t + 1 }, { l + 4, t + 1 } }, ColourMapA[colour].lighter); - - // Border right of text - GfxFillRect(dpi, { { textRight, t }, { r - 1, t } }, ColourMapA[colour].mid_dark); - GfxFillRect(dpi, { { textRight, t + 1 }, { r - 2, t + 1 } }, ColourMapA[colour].lighter); - - // Border right - GfxFillRect(dpi, { { r - 1, t + 1 }, { r - 1, b - 1 } }, ColourMapA[colour].mid_dark); - GfxFillRect(dpi, { { r, t }, { r, b } }, ColourMapA[colour].lighter); - - // Border bottom - GfxFillRect(dpi, { { l, b - 1 }, { r - 2, b - 1 } }, ColourMapA[colour].mid_dark); - GfxFillRect(dpi, { { l, b }, { r - 1, b } }, ColourMapA[colour].lighter); - - // Border left - GfxFillRect(dpi, { { l, t + 1 }, { l, b - 2 } }, ColourMapA[colour].mid_dark); - GfxFillRect(dpi, { { l + 1, t + 2 }, { l + 1, b - 2 } }, ColourMapA[colour].lighter); -} - -/** - * - * rct2: 0x006EB2F9 - */ -static void WidgetCaptionDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) -{ - // Get the widget - const auto* widget = &w.widgets[widgetIndex]; - - // Resolve the absolute ltrb - auto topLeft = w.windowPos + ScreenCoordsXY{ widget->left, widget->top }; - auto bottomRight = w.windowPos + ScreenCoordsXY{ widget->right, widget->bottom }; - - auto colour = w.colours[widget->colour]; - - uint8_t press = INSET_RECT_F_60; - if (w.flags & WF_10) - press |= INSET_RECT_FLAG_FILL_MID_LIGHT; - - GfxFillRectInset(dpi, { topLeft, bottomRight }, colour, press); - - // Black caption bars look slightly green, this fixes that - if (colour.colour == COLOUR_BLACK) - GfxFillRect( - dpi, { { topLeft + ScreenCoordsXY{ 1, 1 } }, { bottomRight - ScreenCoordsXY{ 1, 1 } } }, - ColourMapA[colour.colour].dark); - else - GfxFilterRect( - dpi, { { topLeft + ScreenCoordsXY{ 1, 1 } }, { bottomRight - ScreenCoordsXY{ 1, 1 } } }, - FilterPaletteID::PaletteDarken3); - - // Draw text - if (widget->text == STR_NONE) - return; - - topLeft = w.windowPos + ScreenCoordsXY{ widget->left + 2, widget->top + 1 }; - int32_t width = widget->width() - 4; - if ((widget + 1)->type == WindowWidgetType::CloseBox) - { - width -= kCloseButtonWidth; - if ((widget + 2)->type == WindowWidgetType::CloseBox) - width -= kCloseButtonWidth; - } - topLeft.x += width / 2; - if (Config::Get().interface.WindowButtonsOnTheLeft) - topLeft.x += kCloseButtonWidth; - - DrawTextEllipsised( - dpi, topLeft, width, widget->text, Formatter::Common(), - { ColourWithFlags{ COLOUR_WHITE }.withFlag(ColourFlag::withOutline, true), TextAlignment::CENTRE }); -} - -/** - * - * rct2: 0x006EBB85 - */ -static void WidgetCloseboxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) -{ - // Get the widget - const auto& widget = w.widgets[widgetIndex]; - - // Resolve the absolute ltrb - auto topLeft = w.windowPos + ScreenCoordsXY{ widget.left, widget.top }; - auto bottomRight = w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom }; - - // Check if the button is pressed down - uint8_t press = 0; - if (w.flags & WF_10) - press |= INSET_RECT_FLAG_FILL_MID_LIGHT; - if (WidgetIsPressed(w, widgetIndex) || WidgetIsActiveTool(w, widgetIndex)) - press |= INSET_RECT_FLAG_BORDER_INSET; - - auto colour = w.colours[widget.colour]; - - // Draw the button - GfxFillRectInset(dpi, { topLeft, bottomRight }, colour, press); - - if (widget.text == STR_NONE) - return; - - topLeft = w.windowPos + ScreenCoordsXY{ widget.midX() - 1, std::max(widget.top, widget.midY() - 5) }; - - if (WidgetIsDisabled(w, widgetIndex)) - colour.setFlag(ColourFlag::inset, true); - ; - - DrawTextEllipsised(dpi, topLeft, widget.width() - 2, widget.text, Formatter::Common(), { colour, TextAlignment::CENTRE }); -} - -/** - * - * rct2: 0x006EBAD9 - */ -static void WidgetCheckboxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) -{ - // Get the widget - const auto& widget = w.widgets[widgetIndex]; - - // Resolve the absolute ltb - ScreenCoordsXY topLeft = w.windowPos + ScreenCoordsXY{ widget.left, widget.top }; - ScreenCoordsXY bottomRight = w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom }; - ScreenCoordsXY midLeft = { topLeft.x, (topLeft.y + bottomRight.y) / 2 }; - - auto colour = w.colours[widget.colour]; - - // checkbox - GfxFillRectInset(dpi, { midLeft - ScreenCoordsXY{ 0, 5 }, midLeft + ScreenCoordsXY{ 9, 4 } }, colour, INSET_RECT_F_60); - - if (WidgetIsDisabled(w, widgetIndex)) - { - colour.setFlag(ColourFlag::inset, true); - } - - // fill it when checkbox is pressed - if (WidgetIsPressed(w, widgetIndex)) - { - DrawText( - dpi, { midLeft - ScreenCoordsXY{ 0, 5 } }, { colour.withFlag(ColourFlag::translucent, false) }, kCheckMarkString); - } - - // draw the text - if (widget.text == STR_NONE) - return; - - auto [stringId, formatArgs] = WidgetGetStringidAndArgs(widget); - GfxDrawStringLeftCentred(dpi, stringId, formatArgs, colour, { midLeft + ScreenCoordsXY{ 14, 0 } }); -} - -/** - * - * rct2: 0x006EBD96 - */ -static void WidgetScrollDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) -{ - // Get the widget - int32_t scrollIndex = WindowGetScrollDataIndex(w, widgetIndex); - const auto& widget = w.widgets[widgetIndex]; - const auto& scroll = w.scrolls[scrollIndex]; - - // Resolve the absolute ltrb - ScreenCoordsXY topLeft = w.windowPos + ScreenCoordsXY{ widget.left, widget.top }; - ScreenCoordsXY bottomRight = w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom }; - - auto colour = w.colours[widget.colour]; - - // Draw the border - GfxFillRectInset(dpi, { topLeft, bottomRight }, colour, INSET_RECT_F_60); - - // Inflate by -1 - topLeft.x++; - topLeft.y++; - bottomRight.x--; - bottomRight.y--; - - // Horizontal scrollbar - if (scroll.flags & HSCROLLBAR_VISIBLE) - WidgetHScrollbarDraw( - dpi, scroll, topLeft.x, bottomRight.y - kScrollBarWidth, - ((scroll.flags & VSCROLLBAR_VISIBLE) ? bottomRight.x - (kScrollBarWidth + 1) : bottomRight.x), bottomRight.y, - colour); - - // Vertical scrollbar - if (scroll.flags & VSCROLLBAR_VISIBLE) - WidgetVScrollbarDraw( - dpi, scroll, bottomRight.x - kScrollBarWidth, topLeft.y, bottomRight.x, - ((scroll.flags & HSCROLLBAR_VISIBLE) ? bottomRight.y - (kScrollBarWidth + 1) : bottomRight.y), colour); - - // Contents - if (scroll.flags & HSCROLLBAR_VISIBLE) - bottomRight.y -= (kScrollBarWidth + 1); - if (scroll.flags & VSCROLLBAR_VISIBLE) - bottomRight.x -= (kScrollBarWidth + 1); - - bottomRight.y++; - bottomRight.x++; - - // Create a new inner scroll dpi - DrawPixelInfo scroll_dpi = dpi; - - // Clip the scroll dpi against the outer dpi - int32_t cl = std::max(dpi.x, topLeft.x); - int32_t ct = std::max(dpi.y, topLeft.y); - int32_t cr = std::min(dpi.x + dpi.width, bottomRight.x); - int32_t cb = std::min(dpi.y + dpi.height, bottomRight.y); - - // Set the respective dpi attributes - scroll_dpi.x = cl - topLeft.x + scroll.h_left; - scroll_dpi.y = ct - topLeft.y + scroll.v_top; - scroll_dpi.width = cr - cl; - scroll_dpi.height = cb - ct; - scroll_dpi.bits += cl - dpi.x; - scroll_dpi.bits += (ct - dpi.y) * (dpi.width + dpi.pitch); - scroll_dpi.pitch = (dpi.width + dpi.pitch) - scroll_dpi.width; - - // Draw the scroll contents - if (scroll_dpi.width > 0 && scroll_dpi.height > 0) - w.OnScrollDraw(scrollIndex, scroll_dpi); -} - -static void WidgetHScrollbarDraw( - DrawPixelInfo& dpi, const ScrollBar& scroll, int32_t l, int32_t t, int32_t r, int32_t b, ColourWithFlags colour) -{ - colour.setFlag(ColourFlag::translucent, false); - - // Trough - GfxFillRect(dpi, { { l + kScrollBarWidth, t }, { r - kScrollBarWidth, b } }, ColourMapA[colour.colour].lighter); - GfxFillRect( - dpi, { { l + kScrollBarWidth, t }, { r - kScrollBarWidth, b } }, 0x1000000 | ColourMapA[colour.colour].mid_dark); - GfxFillRect(dpi, { { l + kScrollBarWidth, t + 2 }, { r - kScrollBarWidth, t + 2 } }, ColourMapA[colour.colour].mid_dark); - GfxFillRect(dpi, { { l + kScrollBarWidth, t + 3 }, { r - kScrollBarWidth, t + 3 } }, ColourMapA[colour.colour].lighter); - GfxFillRect(dpi, { { l + kScrollBarWidth, t + 7 }, { r - kScrollBarWidth, t + 7 } }, ColourMapA[colour.colour].mid_dark); - GfxFillRect(dpi, { { l + kScrollBarWidth, t + 8 }, { r - kScrollBarWidth, t + 8 } }, ColourMapA[colour.colour].lighter); - - // Left button - { - uint8_t flags = (scroll.flags & HSCROLLBAR_LEFT_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0; - - GfxFillRectInset(dpi, { { l, t }, { l + (kScrollBarWidth - 1), b } }, colour, flags); - DrawText(dpi, { l + 1, t }, {}, kBlackLeftArrowString); - } - - // Thumb - { - int16_t left = std::max(l + kScrollBarWidth, l + scroll.h_thumb_left - 1); - int16_t right = std::min(r - kScrollBarWidth, l + scroll.h_thumb_right - 1); - uint8_t flags = (scroll.flags & HSCROLLBAR_THUMB_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0; - - GfxFillRectInset(dpi, { { left, t }, { right, b } }, colour, flags); - } - - // Right button - { - uint8_t flags = (scroll.flags & HSCROLLBAR_RIGHT_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0; - - GfxFillRectInset(dpi, { { r - (kScrollBarWidth - 1), t }, { r, b } }, colour, flags); - DrawText(dpi, { r - 6, t }, {}, kBlackRightArrowString); - } -} - -static void WidgetVScrollbarDraw( - DrawPixelInfo& dpi, const ScrollBar& scroll, int32_t l, int32_t t, int32_t r, int32_t b, ColourWithFlags colour) -{ - colour.setFlag(ColourFlag::translucent, false); - - // Trough - GfxFillRect(dpi, { { l, t + kScrollBarWidth }, { r, b - kScrollBarWidth } }, ColourMapA[colour.colour].lighter); - GfxFillRect( - dpi, { { l, t + kScrollBarWidth }, { r, b - kScrollBarWidth } }, 0x1000000 | ColourMapA[colour.colour].mid_dark); - GfxFillRect(dpi, { { l + 2, t + kScrollBarWidth }, { l + 2, b - kScrollBarWidth } }, ColourMapA[colour.colour].mid_dark); - GfxFillRect(dpi, { { l + 3, t + kScrollBarWidth }, { l + 3, b - kScrollBarWidth } }, ColourMapA[colour.colour].lighter); - GfxFillRect(dpi, { { l + 7, t + kScrollBarWidth }, { l + 7, b - kScrollBarWidth } }, ColourMapA[colour.colour].mid_dark); - GfxFillRect(dpi, { { l + 8, t + kScrollBarWidth }, { l + 8, b - kScrollBarWidth } }, ColourMapA[colour.colour].lighter); - - // Up button - GfxFillRectInset( - dpi, { { l, t }, { r, t + (kScrollBarWidth - 1) } }, colour, - ((scroll.flags & VSCROLLBAR_UP_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0)); - DrawText(dpi, { l + 1, t - 1 }, {}, kBlackUpArrowString); - - // Thumb - GfxFillRectInset( - dpi, - { { l, std::max(t + kScrollBarWidth, t + scroll.v_thumb_top - 1) }, - { r, std::min(b - kScrollBarWidth, t + scroll.v_thumb_bottom - 1) } }, - { colour }, ((scroll.flags & VSCROLLBAR_THUMB_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0)); - - // Down button - GfxFillRectInset( - dpi, { { l, b - (kScrollBarWidth - 1) }, { r, b } }, colour, - ((scroll.flags & VSCROLLBAR_DOWN_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0)); - DrawText(dpi, { l + 1, b - (kScrollBarWidth - 1) }, {}, kBlackDownArrowString); -} - -/** - * - * rct2: 0x006EB951 - */ -static void WidgetDrawImage(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) -{ - // Get the widget - const auto& widget = w.widgets[widgetIndex]; - - // Get the image - if (static_cast(widget.image.ToUInt32()) == SPR_NONE) - return; - auto image = widget.image; - - // Resolve the absolute ltrb - auto screenCoords = w.windowPos + ScreenCoordsXY{ widget.left, widget.top }; - - if (widget.type == WindowWidgetType::ColourBtn || widget.type == WindowWidgetType::TrnBtn - || widget.type == WindowWidgetType::Tab) - if (WidgetIsPressed(w, widgetIndex) || WidgetIsActiveTool(w, widgetIndex)) - image = image.WithIndexOffset(1); - - const auto colour = w.colours[widget.colour].colour; - if (WidgetIsDisabled(w, widgetIndex)) - { - // Draw greyed out (light border bottom right shadow) - auto mappedColour = ColourMapA[colour].lighter; - GfxDrawSpriteSolid(dpi, image, screenCoords + ScreenCoordsXY{ 1, 1 }, mappedColour); - - // Draw greyed out (dark) - mappedColour = ColourMapA[colour].mid_light; - GfxDrawSpriteSolid(dpi, image, screenCoords, mappedColour); - } - else - { - if (image.HasSecondary()) - { - // ? - } - - if (image.IsBlended()) - image = image.WithBlended(false); + if (widget.type == WindowWidgetType::Button || widget.type == WindowWidgetType::TableHeader) + topLeft.y += widget.textTop(); else - image = image.WithPrimary(colour); + topLeft.y += widget.top; - GfxDrawSprite(dpi, image, screenCoords); - } -} - -bool WidgetIsDisabled(const WindowBase& w, WidgetIndex widgetIndex) -{ - if (w.classification == WindowClass::Custom) - return w.widgets[widgetIndex].flags & WIDGET_FLAGS::IS_DISABLED; - return (w.disabled_widgets & (1LL << widgetIndex)) != 0; -} - -bool WidgetIsHoldable(const WindowBase& w, WidgetIndex widgetIndex) -{ - if (w.classification == WindowClass::Custom) - return w.widgets[widgetIndex].flags & WIDGET_FLAGS::IS_HOLDABLE; - return (w.hold_down_widgets & (1LL << widgetIndex)) != 0; -} - -bool WidgetIsVisible(const WindowBase& w, WidgetIndex widgetIndex) -{ - return w.widgets[widgetIndex].IsVisible(); -} - -bool WidgetIsPressed(const WindowBase& w, WidgetIndex widgetIndex) -{ - if (w.classification == WindowClass::Custom) - { - if (w.widgets[widgetIndex].flags & WIDGET_FLAGS::IS_PRESSED) + auto stringId = widget.text; + auto ft = Formatter::Common(); + if (widget.flags & WIDGET_FLAGS::TEXT_IS_STRING) { - return true; + stringId = STR_STRING; + ft.Add(widget.string); } - } - else - { - if (w.pressed_widgets & (1LL << widgetIndex)) + + ScreenCoordsXY coords = { (topLeft.x + r + 1) / 2 - 1, topLeft.y }; + if (widget.type == WindowWidgetType::LabelCentred) { - return true; + DrawTextWrapped(dpi, coords, widget.width() - 2, stringId, ft, { colour, TextAlignment::CENTRE }); + } + else + { + DrawTextEllipsised(dpi, coords, widget.width() - 2, stringId, ft, { colour, TextAlignment::CENTRE }); } } - if (InputGetState() == InputState::WidgetPressed || InputGetState() == InputState::DropdownActive) + /** + * + * rct2: 0x006EBD52 + */ + static void WidgetText(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) { - if (!(InputTestFlag(INPUT_FLAG_WIDGET_PRESSED))) + // Get the widget + const auto& widget = w.widgets[widgetIndex]; + + if (widget.text == STR_NONE || widget.content == kWidgetContentEmpty) + return; + + auto colour = w.colours[widget.colour]; + if (WidgetIsDisabled(w, widgetIndex)) + colour.setFlag(ColourFlag::inset, true); + + // Resolve the absolute ltrb + int32_t l = w.windowPos.x + widget.left; + int32_t r = w.windowPos.x + widget.right; + int32_t t; + + if (widget.type == WindowWidgetType::Button || widget.type == WindowWidgetType::DropdownMenu + || widget.type == WindowWidgetType::Spinner || widget.type == WindowWidgetType::TableHeader) + { + t = w.windowPos.y + widget.textTop(); + } + else + t = w.windowPos.y + widget.top; + + auto stringId = widget.text; + auto ft = Formatter::Common(); + if (widget.flags & WIDGET_FLAGS::TEXT_IS_STRING) + { + stringId = STR_STRING; + ft.Add(widget.string); + } + + ScreenCoordsXY coords = { l + 1, t }; + if (widget.type == WindowWidgetType::LabelCentred) + { + DrawTextWrapped(dpi, coords, r - l, stringId, ft, { colour, TextAlignment::CENTRE }); + } + else + { + DrawTextEllipsised(dpi, coords, r - l, stringId, ft, colour); + } + } + + /** + * + * rct2: 0x006EBD1F + */ + static void WidgetTextInset(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) + { + // Get the widget + const auto& widget = w.widgets[widgetIndex]; + + // Resolve the absolute ltrb + ScreenRect rect{ w.windowPos + ScreenCoordsXY{ widget.left, widget.top }, + w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom } }; + + auto colour = w.colours[widget.colour]; + + GfxFillRectInset(dpi, rect, colour, INSET_RECT_F_60); + WidgetText(dpi, w, widgetIndex); + } + + static std::pair WidgetGetStringidAndArgs(const Widget& widget) + { + auto stringId = widget.text; + void* formatArgs = gCommonFormatArgs; + if (widget.flags & WIDGET_FLAGS::TEXT_IS_STRING) + { + if (widget.string == nullptr || widget.string[0] == '\0') + { + stringId = STR_NONE; + formatArgs = nullptr; + } + else + { + stringId = STR_STRING; + formatArgs = const_cast(reinterpret_cast(&widget.string)); + } + } + return std::make_pair(stringId, formatArgs); + } + + /** + * + * rct2: 0x006EB535 + */ + static void WidgetGroupboxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) + { + // Get the widget + const auto& widget = w.widgets[widgetIndex]; + + // Resolve the absolute ltrb + auto l = w.windowPos.x + widget.left + 5; + auto t = w.windowPos.y + widget.top; + auto textRight = l; + + // Text + auto [stringId, formatArgs] = WidgetGetStringidAndArgs(widget); + if (stringId != STR_NONE) + { + auto colour = w.colours[widget.colour].withFlag(ColourFlag::translucent, false); + if (WidgetIsDisabled(w, widgetIndex)) + colour.setFlag(ColourFlag::inset, true); + + utf8 buffer[512] = { 0 }; + OpenRCT2::FormatStringLegacy(buffer, sizeof(buffer), stringId, formatArgs); + auto ft = Formatter(); + ft.Add(buffer); + DrawTextBasic(dpi, { l, t }, STR_STRING, ft, { colour }); + textRight = l + GfxGetStringWidth(buffer, FontStyle::Medium) + 1; + } + + // Border + // Resolve the absolute ltrb + l = w.windowPos.x + widget.left; + t = w.windowPos.y + widget.top + 4; + const auto r = w.windowPos.x + widget.right; + const auto b = w.windowPos.y + widget.bottom; + + uint8_t colour = w.colours[widget.colour].colour; + + // Border left of text + GfxFillRect(dpi, { { l, t }, { l + 4, t } }, ColourMapA[colour].mid_dark); + GfxFillRect(dpi, { { l + 1, t + 1 }, { l + 4, t + 1 } }, ColourMapA[colour].lighter); + + // Border right of text + GfxFillRect(dpi, { { textRight, t }, { r - 1, t } }, ColourMapA[colour].mid_dark); + GfxFillRect(dpi, { { textRight, t + 1 }, { r - 2, t + 1 } }, ColourMapA[colour].lighter); + + // Border right + GfxFillRect(dpi, { { r - 1, t + 1 }, { r - 1, b - 1 } }, ColourMapA[colour].mid_dark); + GfxFillRect(dpi, { { r, t }, { r, b } }, ColourMapA[colour].lighter); + + // Border bottom + GfxFillRect(dpi, { { l, b - 1 }, { r - 2, b - 1 } }, ColourMapA[colour].mid_dark); + GfxFillRect(dpi, { { l, b }, { r - 1, b } }, ColourMapA[colour].lighter); + + // Border left + GfxFillRect(dpi, { { l, t + 1 }, { l, b - 2 } }, ColourMapA[colour].mid_dark); + GfxFillRect(dpi, { { l + 1, t + 2 }, { l + 1, b - 2 } }, ColourMapA[colour].lighter); + } + + /** + * + * rct2: 0x006EB2F9 + */ + static void WidgetCaptionDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) + { + // Get the widget + const auto* widget = &w.widgets[widgetIndex]; + + // Resolve the absolute ltrb + auto topLeft = w.windowPos + ScreenCoordsXY{ widget->left, widget->top }; + auto bottomRight = w.windowPos + ScreenCoordsXY{ widget->right, widget->bottom }; + + auto colour = w.colours[widget->colour]; + + uint8_t press = INSET_RECT_F_60; + if (w.flags & WF_10) + press |= INSET_RECT_FLAG_FILL_MID_LIGHT; + + GfxFillRectInset(dpi, { topLeft, bottomRight }, colour, press); + + // Black caption bars look slightly green, this fixes that + if (colour.colour == COLOUR_BLACK) + GfxFillRect( + dpi, { { topLeft + ScreenCoordsXY{ 1, 1 } }, { bottomRight - ScreenCoordsXY{ 1, 1 } } }, + ColourMapA[colour.colour].dark); + else + GfxFilterRect( + dpi, { { topLeft + ScreenCoordsXY{ 1, 1 } }, { bottomRight - ScreenCoordsXY{ 1, 1 } } }, + FilterPaletteID::PaletteDarken3); + + // Draw text + if (widget->text == STR_NONE) + return; + + topLeft = w.windowPos + ScreenCoordsXY{ widget->left + 2, widget->top + 1 }; + int32_t width = widget->width() - 4; + if ((widget + 1)->type == WindowWidgetType::CloseBox) + { + width -= kCloseButtonWidth; + if ((widget + 2)->type == WindowWidgetType::CloseBox) + width -= kCloseButtonWidth; + } + topLeft.x += width / 2; + if (Config::Get().interface.WindowButtonsOnTheLeft) + topLeft.x += kCloseButtonWidth; + + DrawTextEllipsised( + dpi, topLeft, width, widget->text, Formatter::Common(), + { ColourWithFlags{ COLOUR_WHITE }.withFlag(ColourFlag::withOutline, true), TextAlignment::CENTRE }); + } + + /** + * + * rct2: 0x006EBB85 + */ + static void WidgetCloseboxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) + { + // Get the widget + const auto& widget = w.widgets[widgetIndex]; + + // Resolve the absolute ltrb + auto topLeft = w.windowPos + ScreenCoordsXY{ widget.left, widget.top }; + auto bottomRight = w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom }; + + // Check if the button is pressed down + uint8_t press = 0; + if (w.flags & WF_10) + press |= INSET_RECT_FLAG_FILL_MID_LIGHT; + if (WidgetIsPressed(w, widgetIndex) || WidgetIsActiveTool(w, widgetIndex)) + press |= INSET_RECT_FLAG_BORDER_INSET; + + auto colour = w.colours[widget.colour]; + + // Draw the button + GfxFillRectInset(dpi, { topLeft, bottomRight }, colour, press); + + if (widget.text == STR_NONE) + return; + + topLeft = w.windowPos + ScreenCoordsXY{ widget.midX() - 1, std::max(widget.top, widget.midY() - 5) }; + + if (WidgetIsDisabled(w, widgetIndex)) + colour.setFlag(ColourFlag::inset, true); + ; + + DrawTextEllipsised( + dpi, topLeft, widget.width() - 2, widget.text, Formatter::Common(), { colour, TextAlignment::CENTRE }); + } + + /** + * + * rct2: 0x006EBAD9 + */ + static void WidgetCheckboxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) + { + // Get the widget + const auto& widget = w.widgets[widgetIndex]; + + // Resolve the absolute ltb + ScreenCoordsXY topLeft = w.windowPos + ScreenCoordsXY{ widget.left, widget.top }; + ScreenCoordsXY bottomRight = w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom }; + ScreenCoordsXY midLeft = { topLeft.x, (topLeft.y + bottomRight.y) / 2 }; + + auto colour = w.colours[widget.colour]; + + // checkbox + GfxFillRectInset(dpi, { midLeft - ScreenCoordsXY{ 0, 5 }, midLeft + ScreenCoordsXY{ 9, 4 } }, colour, INSET_RECT_F_60); + + if (WidgetIsDisabled(w, widgetIndex)) + { + colour.setFlag(ColourFlag::inset, true); + } + + // fill it when checkbox is pressed + if (WidgetIsPressed(w, widgetIndex)) + { + DrawText( + dpi, { midLeft - ScreenCoordsXY{ 0, 5 } }, { colour.withFlag(ColourFlag::translucent, false) }, + kCheckMarkString); + } + + // draw the text + if (widget.text == STR_NONE) + return; + + auto [stringId, formatArgs] = WidgetGetStringidAndArgs(widget); + GfxDrawStringLeftCentred(dpi, stringId, formatArgs, colour, { midLeft + ScreenCoordsXY{ 14, 0 } }); + } + + /** + * + * rct2: 0x006EBD96 + */ + static void WidgetScrollDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) + { + // Get the widget + int32_t scrollIndex = WindowGetScrollDataIndex(w, widgetIndex); + const auto& widget = w.widgets[widgetIndex]; + const auto& scroll = w.scrolls[scrollIndex]; + + // Resolve the absolute ltrb + ScreenCoordsXY topLeft = w.windowPos + ScreenCoordsXY{ widget.left, widget.top }; + ScreenCoordsXY bottomRight = w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom }; + + auto colour = w.colours[widget.colour]; + + // Draw the border + GfxFillRectInset(dpi, { topLeft, bottomRight }, colour, INSET_RECT_F_60); + + // Inflate by -1 + topLeft.x++; + topLeft.y++; + bottomRight.x--; + bottomRight.y--; + + // Horizontal scrollbar + if (scroll.flags & HSCROLLBAR_VISIBLE) + WidgetHScrollbarDraw( + dpi, scroll, topLeft.x, bottomRight.y - kScrollBarWidth, + ((scroll.flags & VSCROLLBAR_VISIBLE) ? bottomRight.x - (kScrollBarWidth + 1) : bottomRight.x), bottomRight.y, + colour); + + // Vertical scrollbar + if (scroll.flags & VSCROLLBAR_VISIBLE) + WidgetVScrollbarDraw( + dpi, scroll, bottomRight.x - kScrollBarWidth, topLeft.y, bottomRight.x, + ((scroll.flags & HSCROLLBAR_VISIBLE) ? bottomRight.y - (kScrollBarWidth + 1) : bottomRight.y), colour); + + // Contents + if (scroll.flags & HSCROLLBAR_VISIBLE) + bottomRight.y -= (kScrollBarWidth + 1); + if (scroll.flags & VSCROLLBAR_VISIBLE) + bottomRight.x -= (kScrollBarWidth + 1); + + bottomRight.y++; + bottomRight.x++; + + // Create a new inner scroll dpi + DrawPixelInfo scroll_dpi = dpi; + + // Clip the scroll dpi against the outer dpi + int32_t cl = std::max(dpi.x, topLeft.x); + int32_t ct = std::max(dpi.y, topLeft.y); + int32_t cr = std::min(dpi.x + dpi.width, bottomRight.x); + int32_t cb = std::min(dpi.y + dpi.height, bottomRight.y); + + // Set the respective dpi attributes + scroll_dpi.x = cl - topLeft.x + scroll.h_left; + scroll_dpi.y = ct - topLeft.y + scroll.v_top; + scroll_dpi.width = cr - cl; + scroll_dpi.height = cb - ct; + scroll_dpi.bits += cl - dpi.x; + scroll_dpi.bits += (ct - dpi.y) * (dpi.width + dpi.pitch); + scroll_dpi.pitch = (dpi.width + dpi.pitch) - scroll_dpi.width; + + // Draw the scroll contents + if (scroll_dpi.width > 0 && scroll_dpi.height > 0) + w.OnScrollDraw(scrollIndex, scroll_dpi); + } + + static void WidgetHScrollbarDraw( + DrawPixelInfo& dpi, const ScrollBar& scroll, int32_t l, int32_t t, int32_t r, int32_t b, ColourWithFlags colour) + { + colour.setFlag(ColourFlag::translucent, false); + + // Trough + GfxFillRect(dpi, { { l + kScrollBarWidth, t }, { r - kScrollBarWidth, b } }, ColourMapA[colour.colour].lighter); + GfxFillRect( + dpi, { { l + kScrollBarWidth, t }, { r - kScrollBarWidth, b } }, 0x1000000 | ColourMapA[colour.colour].mid_dark); + GfxFillRect( + dpi, { { l + kScrollBarWidth, t + 2 }, { r - kScrollBarWidth, t + 2 } }, ColourMapA[colour.colour].mid_dark); + GfxFillRect(dpi, { { l + kScrollBarWidth, t + 3 }, { r - kScrollBarWidth, t + 3 } }, ColourMapA[colour.colour].lighter); + GfxFillRect( + dpi, { { l + kScrollBarWidth, t + 7 }, { r - kScrollBarWidth, t + 7 } }, ColourMapA[colour.colour].mid_dark); + GfxFillRect(dpi, { { l + kScrollBarWidth, t + 8 }, { r - kScrollBarWidth, t + 8 } }, ColourMapA[colour.colour].lighter); + + // Left button + { + uint8_t flags = (scroll.flags & HSCROLLBAR_LEFT_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0; + + GfxFillRectInset(dpi, { { l, t }, { l + (kScrollBarWidth - 1), b } }, colour, flags); + DrawText(dpi, { l + 1, t }, {}, kBlackLeftArrowString); + } + + // Thumb + { + int16_t left = std::max(l + kScrollBarWidth, l + scroll.h_thumb_left - 1); + int16_t right = std::min(r - kScrollBarWidth, l + scroll.h_thumb_right - 1); + uint8_t flags = (scroll.flags & HSCROLLBAR_THUMB_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0; + + GfxFillRectInset(dpi, { { left, t }, { right, b } }, colour, flags); + } + + // Right button + { + uint8_t flags = (scroll.flags & HSCROLLBAR_RIGHT_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0; + + GfxFillRectInset(dpi, { { r - (kScrollBarWidth - 1), t }, { r, b } }, colour, flags); + DrawText(dpi, { r - 6, t }, {}, kBlackRightArrowString); + } + } + + static void WidgetVScrollbarDraw( + DrawPixelInfo& dpi, const ScrollBar& scroll, int32_t l, int32_t t, int32_t r, int32_t b, ColourWithFlags colour) + { + colour.setFlag(ColourFlag::translucent, false); + + // Trough + GfxFillRect(dpi, { { l, t + kScrollBarWidth }, { r, b - kScrollBarWidth } }, ColourMapA[colour.colour].lighter); + GfxFillRect( + dpi, { { l, t + kScrollBarWidth }, { r, b - kScrollBarWidth } }, 0x1000000 | ColourMapA[colour.colour].mid_dark); + GfxFillRect( + dpi, { { l + 2, t + kScrollBarWidth }, { l + 2, b - kScrollBarWidth } }, ColourMapA[colour.colour].mid_dark); + GfxFillRect(dpi, { { l + 3, t + kScrollBarWidth }, { l + 3, b - kScrollBarWidth } }, ColourMapA[colour.colour].lighter); + GfxFillRect( + dpi, { { l + 7, t + kScrollBarWidth }, { l + 7, b - kScrollBarWidth } }, ColourMapA[colour.colour].mid_dark); + GfxFillRect(dpi, { { l + 8, t + kScrollBarWidth }, { l + 8, b - kScrollBarWidth } }, ColourMapA[colour.colour].lighter); + + // Up button + GfxFillRectInset( + dpi, { { l, t }, { r, t + (kScrollBarWidth - 1) } }, colour, + ((scroll.flags & VSCROLLBAR_UP_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0)); + DrawText(dpi, { l + 1, t - 1 }, {}, kBlackUpArrowString); + + // Thumb + GfxFillRectInset( + dpi, + { { l, std::max(t + kScrollBarWidth, t + scroll.v_thumb_top - 1) }, + { r, std::min(b - kScrollBarWidth, t + scroll.v_thumb_bottom - 1) } }, + { colour }, ((scroll.flags & VSCROLLBAR_THUMB_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0)); + + // Down button + GfxFillRectInset( + dpi, { { l, b - (kScrollBarWidth - 1) }, { r, b } }, colour, + ((scroll.flags & VSCROLLBAR_DOWN_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0)); + DrawText(dpi, { l + 1, b - (kScrollBarWidth - 1) }, {}, kBlackDownArrowString); + } + + /** + * + * rct2: 0x006EB951 + */ + static void WidgetDrawImage(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) + { + // Get the widget + const auto& widget = w.widgets[widgetIndex]; + + // Get the image + if (static_cast(widget.image.ToUInt32()) == SPR_NONE) + return; + auto image = widget.image; + + // Resolve the absolute ltrb + auto screenCoords = w.windowPos + ScreenCoordsXY{ widget.left, widget.top }; + + if (widget.type == WindowWidgetType::ColourBtn || widget.type == WindowWidgetType::TrnBtn + || widget.type == WindowWidgetType::Tab) + if (WidgetIsPressed(w, widgetIndex) || WidgetIsActiveTool(w, widgetIndex)) + image = image.WithIndexOffset(1); + + const auto colour = w.colours[widget.colour].colour; + if (WidgetIsDisabled(w, widgetIndex)) + { + // Draw greyed out (light border bottom right shadow) + auto mappedColour = ColourMapA[colour].lighter; + GfxDrawSpriteSolid(dpi, image, screenCoords + ScreenCoordsXY{ 1, 1 }, mappedColour); + + // Draw greyed out (dark) + mappedColour = ColourMapA[colour].mid_light; + GfxDrawSpriteSolid(dpi, image, screenCoords, mappedColour); + } + else + { + if (image.HasSecondary()) + { + // ? + } + + if (image.IsBlended()) + image = image.WithBlended(false); + else + image = image.WithPrimary(colour); + + GfxDrawSprite(dpi, image, screenCoords); + } + } + + bool WidgetIsDisabled(const WindowBase& w, WidgetIndex widgetIndex) + { + if (w.classification == WindowClass::Custom) + return w.widgets[widgetIndex].flags & WIDGET_FLAGS::IS_DISABLED; + return (w.disabled_widgets & (1LL << widgetIndex)) != 0; + } + + bool WidgetIsHoldable(const WindowBase& w, WidgetIndex widgetIndex) + { + if (w.classification == WindowClass::Custom) + return w.widgets[widgetIndex].flags & WIDGET_FLAGS::IS_HOLDABLE; + return (w.hold_down_widgets & (1LL << widgetIndex)) != 0; + } + + bool WidgetIsVisible(const WindowBase& w, WidgetIndex widgetIndex) + { + return w.widgets[widgetIndex].IsVisible(); + } + + bool WidgetIsPressed(const WindowBase& w, WidgetIndex widgetIndex) + { + if (w.classification == WindowClass::Custom) + { + if (w.widgets[widgetIndex].flags & WIDGET_FLAGS::IS_PRESSED) + { + return true; + } + } + else + { + if (w.pressed_widgets & (1LL << widgetIndex)) + { + return true; + } + } + + if (InputGetState() == InputState::WidgetPressed || InputGetState() == InputState::DropdownActive) + { + if (!(InputTestFlag(INPUT_FLAG_WIDGET_PRESSED))) + return false; + if (gPressedWidget.window_classification != w.classification) + return false; + if (gPressedWidget.window_number != w.number) + return false; + if (gPressedWidget.widget_index != widgetIndex) + return false; + return true; + } + return false; + } + + bool WidgetIsHighlighted(const WindowBase& w, WidgetIndex widgetIndex) + { + if (gHoverWidget.window_classification != w.classification) return false; - if (gPressedWidget.window_classification != w.classification) + if (gHoverWidget.window_number != w.number) return false; - if (gPressedWidget.window_number != w.number) - return false; - if (gPressedWidget.widget_index != widgetIndex) + if (gHoverWidget.widget_index != widgetIndex) return false; return true; } - return false; -} -bool WidgetIsHighlighted(const WindowBase& w, WidgetIndex widgetIndex) -{ - if (gHoverWidget.window_classification != w.classification) - return false; - if (gHoverWidget.window_number != w.number) - return false; - if (gHoverWidget.widget_index != widgetIndex) - return false; - return true; -} - -bool WidgetIsActiveTool(const WindowBase& w, WidgetIndex widgetIndex) -{ - if (!(InputTestFlag(INPUT_FLAG_TOOL_ACTIVE))) - return false; - if (gCurrentToolWidget.window_classification != w.classification) - return false; - if (gCurrentToolWidget.window_number != w.number) - return false; - if (gCurrentToolWidget.widget_index != widgetIndex) - return false; - - return true; -} - -/** - * - * rct2: 0x006E9F92 - * eax: x / output_x - * ebx: y / output_y - * ecx: output_scroll_area - * edx: scroll_id - * esi: w - * edi: widget - */ -void WidgetScrollGetPart( - WindowBase& w, const Widget* widget, const ScreenCoordsXY& screenCoords, ScreenCoordsXY& retScreenCoords, - int32_t* output_scroll_area, int32_t* scroll_id) -{ - *scroll_id = 0; - for (Widget* iterator = w.widgets; iterator != widget; iterator++) + bool WidgetIsActiveTool(const WindowBase& w, WidgetIndex widgetIndex) { - if (iterator->type == WindowWidgetType::Scroll) - { - *scroll_id += 1; - } + if (!(InputTestFlag(INPUT_FLAG_TOOL_ACTIVE))) + return false; + if (gCurrentToolWidget.window_classification != w.classification) + return false; + if (gCurrentToolWidget.window_number != w.number) + return false; + if (gCurrentToolWidget.widget_index != widgetIndex) + return false; + + return true; } - const auto& scroll = w.scrolls[*scroll_id]; - if ((scroll.flags & HSCROLLBAR_VISIBLE) && screenCoords.y >= (w.windowPos.y + widget->bottom - (kScrollBarWidth + 1))) + /** + * + * rct2: 0x006E9F92 + * eax: x / output_x + * ebx: y / output_y + * ecx: output_scroll_area + * edx: scroll_id + * esi: w + * edi: widget + */ + void WidgetScrollGetPart( + WindowBase& w, const Widget* widget, const ScreenCoordsXY& screenCoords, ScreenCoordsXY& retScreenCoords, + int32_t* output_scroll_area, int32_t* scroll_id) { - // horizontal scrollbar - int32_t rightOffset = 0; - int32_t iteratorLeft = widget->left + w.windowPos.x + kScrollBarWidth; - int32_t iteratorRight = widget->right + w.windowPos.x - kScrollBarWidth; - if (!(scroll.flags & VSCROLLBAR_VISIBLE)) + *scroll_id = 0; + for (Widget* iterator = w.widgets; iterator != widget; iterator++) { - rightOffset = kScrollBarWidth + 1; + if (iterator->type == WindowWidgetType::Scroll) + { + *scroll_id += 1; + } } - if (screenCoords.x <= iteratorLeft) + const auto& scroll = w.scrolls[*scroll_id]; + if ((scroll.flags & HSCROLLBAR_VISIBLE) && screenCoords.y >= (w.windowPos.y + widget->bottom - (kScrollBarWidth + 1))) { - *output_scroll_area = SCROLL_PART_HSCROLLBAR_LEFT; + // horizontal scrollbar + int32_t rightOffset = 0; + int32_t iteratorLeft = widget->left + w.windowPos.x + kScrollBarWidth; + int32_t iteratorRight = widget->right + w.windowPos.x - kScrollBarWidth; + if (!(scroll.flags & VSCROLLBAR_VISIBLE)) + { + rightOffset = kScrollBarWidth + 1; + } + + if (screenCoords.x <= iteratorLeft) + { + *output_scroll_area = SCROLL_PART_HSCROLLBAR_LEFT; + } + else if (screenCoords.x >= iteratorRight + rightOffset) + { + *output_scroll_area = SCROLL_PART_NONE; + } + else if (screenCoords.x >= iteratorRight + rightOffset - kScrollBarWidth) + { + *output_scroll_area = SCROLL_PART_HSCROLLBAR_RIGHT; + } + else if (screenCoords.x < (widget->left + w.windowPos.x + scroll.h_thumb_left)) + { + *output_scroll_area = SCROLL_PART_HSCROLLBAR_LEFT_TROUGH; + } + else if (screenCoords.x > (widget->left + w.windowPos.x + scroll.h_thumb_right)) + { + *output_scroll_area = SCROLL_PART_HSCROLLBAR_RIGHT_TROUGH; + } + else + { + *output_scroll_area = SCROLL_PART_HSCROLLBAR_THUMB; + } } - else if (screenCoords.x >= iteratorRight + rightOffset) + else if ( + (scroll.flags & VSCROLLBAR_VISIBLE) && (screenCoords.x >= w.windowPos.x + widget->right - (kScrollBarWidth + 1))) { - *output_scroll_area = SCROLL_PART_NONE; - } - else if (screenCoords.x >= iteratorRight + rightOffset - kScrollBarWidth) - { - *output_scroll_area = SCROLL_PART_HSCROLLBAR_RIGHT; - } - else if (screenCoords.x < (widget->left + w.windowPos.x + scroll.h_thumb_left)) - { - *output_scroll_area = SCROLL_PART_HSCROLLBAR_LEFT_TROUGH; - } - else if (screenCoords.x > (widget->left + w.windowPos.x + scroll.h_thumb_right)) - { - *output_scroll_area = SCROLL_PART_HSCROLLBAR_RIGHT_TROUGH; + // vertical scrollbar + int32_t bottomOffset = 0; + int32_t iteratorTop = widget->top + w.windowPos.y + kScrollBarWidth; + int32_t iteratorBottom = widget->bottom + w.windowPos.y; + if (scroll.flags & HSCROLLBAR_VISIBLE) + { + bottomOffset = (kScrollBarWidth + 1); + } + + if (screenCoords.y <= iteratorTop) + { + *output_scroll_area = SCROLL_PART_VSCROLLBAR_TOP; + } + else if (screenCoords.y >= (iteratorBottom - bottomOffset)) + { + *output_scroll_area = SCROLL_PART_NONE; + } + else if (screenCoords.y >= (iteratorBottom - bottomOffset - kScrollBarWidth)) + { + *output_scroll_area = SCROLL_PART_VSCROLLBAR_BOTTOM; + } + else if (screenCoords.y < (widget->top + w.windowPos.y + scroll.v_thumb_top)) + { + *output_scroll_area = SCROLL_PART_VSCROLLBAR_TOP_TROUGH; + } + else if (screenCoords.y > (widget->top + w.windowPos.y + scroll.v_thumb_bottom)) + { + *output_scroll_area = SCROLL_PART_VSCROLLBAR_BOTTOM_TROUGH; + } + else + { + *output_scroll_area = SCROLL_PART_VSCROLLBAR_THUMB; + } } else { - *output_scroll_area = SCROLL_PART_HSCROLLBAR_THUMB; + // view + *output_scroll_area = SCROLL_PART_VIEW; + retScreenCoords.x = screenCoords.x - widget->left; + retScreenCoords.y = screenCoords.y - widget->top; + retScreenCoords -= w.windowPos; + if (retScreenCoords.x <= 0 || retScreenCoords.y <= 0) + { + *output_scroll_area = SCROLL_PART_NONE; + } + else + { + retScreenCoords.x += scroll.h_left - 1; + retScreenCoords.y += scroll.v_top - 1; + } } } - else if ((scroll.flags & VSCROLLBAR_VISIBLE) && (screenCoords.x >= w.windowPos.x + widget->right - (kScrollBarWidth + 1))) + + Widget* GetWidgetByIndex(const WindowBase& w, WidgetIndex widgetIndex) { - // vertical scrollbar - int32_t bottomOffset = 0; - int32_t iteratorTop = widget->top + w.windowPos.y + kScrollBarWidth; - int32_t iteratorBottom = widget->bottom + w.windowPos.y; - if (scroll.flags & HSCROLLBAR_VISIBLE) + // Make sure we don't go out of bounds if we are given a bad widget index + WidgetIndex index = 0; + for (auto* widget = w.widgets; widget->type != WindowWidgetType::Last; widget++) { - bottomOffset = (kScrollBarWidth + 1); + if (index == widgetIndex) + { + return widget; + } + index++; } - if (screenCoords.y <= iteratorTop) + LOG_ERROR("Widget index %i out of bounds for window class %u", widgetIndex, w.classification); + + return nullptr; + } + + static void SafeSetWidgetFlag(WindowBase& w, WidgetIndex widgetIndex, WidgetFlags mask, bool value) + { + Widget* widget = GetWidgetByIndex(w, widgetIndex); + if (widget == nullptr) { - *output_scroll_area = SCROLL_PART_VSCROLLBAR_TOP; - } - else if (screenCoords.y >= (iteratorBottom - bottomOffset)) - { - *output_scroll_area = SCROLL_PART_NONE; - } - else if (screenCoords.y >= (iteratorBottom - bottomOffset - kScrollBarWidth)) - { - *output_scroll_area = SCROLL_PART_VSCROLLBAR_BOTTOM; - } - else if (screenCoords.y < (widget->top + w.windowPos.y + scroll.v_thumb_top)) - { - *output_scroll_area = SCROLL_PART_VSCROLLBAR_TOP_TROUGH; - } - else if (screenCoords.y > (widget->top + w.windowPos.y + scroll.v_thumb_bottom)) - { - *output_scroll_area = SCROLL_PART_VSCROLLBAR_BOTTOM_TROUGH; - } - else - { - *output_scroll_area = SCROLL_PART_VSCROLLBAR_THUMB; - } - } - else - { - // view - *output_scroll_area = SCROLL_PART_VIEW; - retScreenCoords.x = screenCoords.x - widget->left; - retScreenCoords.y = screenCoords.y - widget->top; - retScreenCoords -= w.windowPos; - if (retScreenCoords.x <= 0 || retScreenCoords.y <= 0) - { - *output_scroll_area = SCROLL_PART_NONE; - } - else - { - retScreenCoords.x += scroll.h_left - 1; - retScreenCoords.y += scroll.v_top - 1; - } - } -} - -Widget* GetWidgetByIndex(const WindowBase& w, WidgetIndex widgetIndex) -{ - // Make sure we don't go out of bounds if we are given a bad widget index - WidgetIndex index = 0; - for (auto* widget = w.widgets; widget->type != WindowWidgetType::Last; widget++) - { - if (index == widgetIndex) - { - return widget; - } - index++; - } - - LOG_ERROR("Widget index %i out of bounds for window class %u", widgetIndex, w.classification); - - return nullptr; -} - -static void SafeSetWidgetFlag(WindowBase& w, WidgetIndex widgetIndex, WidgetFlags mask, bool value) -{ - Widget* widget = GetWidgetByIndex(w, widgetIndex); - if (widget == nullptr) - { - return; - } - - if (value) - widget->flags |= mask; - else - widget->flags &= ~mask; -} - -void WidgetSetEnabled(WindowBase& w, WidgetIndex widgetIndex, bool enabled) -{ - WidgetSetDisabled(w, widgetIndex, !enabled); -} - -void WidgetSetDisabled(WindowBase& w, WidgetIndex widgetIndex, bool value) -{ - SafeSetWidgetFlag(w, widgetIndex, WIDGET_FLAGS::IS_DISABLED, value); - if (value) - { - w.disabled_widgets |= (1uLL << widgetIndex); - } - else - { - w.disabled_widgets &= ~(1uLL << widgetIndex); - } -} - -void WidgetSetHoldable(WindowBase& w, WidgetIndex widgetIndex, bool value) -{ - SafeSetWidgetFlag(w, widgetIndex, WIDGET_FLAGS::IS_HOLDABLE, value); - if (value) - { - w.hold_down_widgets |= (1uLL << widgetIndex); - } - else - { - w.hold_down_widgets &= ~(1uLL << widgetIndex); - } -} - -void WidgetSetVisible(WindowBase& w, WidgetIndex widgetIndex, bool value) -{ - SafeSetWidgetFlag(w, widgetIndex, WIDGET_FLAGS::IS_HIDDEN, !value); -} - -void WidgetSetPressed(WindowBase& w, WidgetIndex widgetIndex, bool value) -{ - SafeSetWidgetFlag(w, widgetIndex, WIDGET_FLAGS::IS_PRESSED, value); - if (value) - w.pressed_widgets |= (1uLL << widgetIndex); - else - w.pressed_widgets &= ~(1uLL << widgetIndex); -} - -void WidgetSetCheckboxValue(WindowBase& w, WidgetIndex widgetIndex, bool value) -{ - WidgetSetPressed(w, widgetIndex, value); -} - -static void WidgetTextBoxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) -{ - // Get the widget - const auto& widget = w.widgets[widgetIndex]; - - // Resolve the absolute ltrb - ScreenCoordsXY topLeft{ w.windowPos + ScreenCoordsXY{ widget.left, widget.top } }; - ScreenCoordsXY bottomRight{ w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom } }; - - auto& tbIdent = OpenRCT2::Ui::Windows::GetCurrentTextBox(); - bool active = w.classification == tbIdent.window.classification && w.number == tbIdent.window.number - && widgetIndex == tbIdent.widget_index; - - // GfxFillRectInset(dpi, l, t, r, b, colour, 0x20 | (!active ? 0x40 : 0x00)); - GfxFillRectInset(dpi, { topLeft, bottomRight }, w.colours[widget.colour], INSET_RECT_F_60); - - // Figure out where the text should be positioned vertically. - topLeft.y = w.windowPos.y + widget.textTop(); - - auto* textInput = OpenRCT2::Ui::Windows::GetTextboxSession(); - if (!active || textInput == nullptr) - { - if (widget.text != 0) - { - u8string wrappedString; - GfxWrapString(widget.string, bottomRight.x - topLeft.x - 5, FontStyle::Medium, &wrappedString, nullptr); - DrawText(dpi, { topLeft.x + 2, topLeft.y }, { w.colours[1] }, wrappedString.c_str(), true); - } - return; - } - - // String length needs to add 12 either side of box - // +13 for cursor when max length. - u8string wrappedString; - GfxWrapString(*textInput->Buffer, bottomRight.x - topLeft.x - 5 - 6, FontStyle::Medium, &wrappedString, nullptr); - - DrawText(dpi, { topLeft.x + 2, topLeft.y }, { w.colours[1] }, wrappedString.c_str(), true); - - // Make a trimmed view of the string for measuring the width. - int32_t curX = topLeft.x - + GfxGetStringWidthNoFormatting( - u8string_view{ wrappedString.c_str(), std::min(wrappedString.length(), textInput->SelectionStart) }, - FontStyle::Medium) - + 3; - - int32_t width = 6; - if (static_cast(textInput->SelectionStart) < textInput->Buffer->size()) - { - // Make a new 1 character wide string for measuring the width - // of the character that the cursor is under. (NOTE: this is broken for multi byte utf8 codepoints) - width = std::max( - GfxGetStringWidthNoFormatting(u8string{ (*textInput->Buffer)[textInput->SelectionStart] }, FontStyle::Medium) - 2, - 4); - } - - if (OpenRCT2::Ui::Windows::TextBoxCaretIsFlashed()) - { - auto colour = ColourMapA[w.colours[1].colour].mid_light; - auto y = topLeft.y + (widget.height() - 1); - GfxFillRect(dpi, { { curX, y }, { curX + width, y } }, colour + 5); - } -} - -static void WidgetProgressBarDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) -{ - const auto& widget = w.widgets[widgetIndex]; - - ScreenCoordsXY topLeft{ w.windowPos + ScreenCoordsXY{ widget.left, widget.top } }; - ScreenCoordsXY bottomRight{ w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom } }; - - auto percentage = widget.content & 0xFF; - auto lowerBlinkBounds = (widget.content >> 8) & 0xFF; - auto upperBlinkBounds = (widget.content >> 16) & 0xFF; - - auto isBlinking = (lowerBlinkBounds != upperBlinkBounds) && (percentage >= lowerBlinkBounds) - && (percentage <= upperBlinkBounds); - - GfxFillRectInset(dpi, { topLeft, bottomRight }, w.colours[1], INSET_RECT_F_30); - if (isBlinking) - { - if (GameIsNotPaused() && (gCurrentRealTimeTicks & 8)) return; + } + + if (value) + widget->flags |= mask; + else + widget->flags &= ~mask; } - const auto barWidth = widget.width() - 2; - const int32_t fillSize = (barWidth * percentage) / 100; - if (fillSize > 0) + void WidgetSetEnabled(WindowBase& w, WidgetIndex widgetIndex, bool enabled) { - GfxFillRectInset( - dpi, { topLeft + ScreenCoordsXY{ 1, 1 }, topLeft + ScreenCoordsXY{ fillSize + 1, widget.height() - 1 } }, - { widget.colour }, 0); + WidgetSetDisabled(w, widgetIndex, !enabled); } -} -ImageId GetColourButtonImage(colour_t colour) -{ - if (colour == COLOUR_INVISIBLE) + void WidgetSetDisabled(WindowBase& w, WidgetIndex widgetIndex, bool value) { - return ImageId(SPR_G2_ICON_PALETTE_INVISIBLE, colour).WithBlended(false); + SafeSetWidgetFlag(w, widgetIndex, WIDGET_FLAGS::IS_DISABLED, value); + if (value) + { + w.disabled_widgets |= (1uLL << widgetIndex); + } + else + { + w.disabled_widgets &= ~(1uLL << widgetIndex); + } } - else - { - return ImageId(SPR_PALETTE_BTN, colour).WithBlended(true); - } -} -void WidgetProgressBarSetNewPercentage(Widget& widget, uint8_t newPercentage) -{ - widget.content &= ~0xFF; - widget.content |= newPercentage; -} + void WidgetSetHoldable(WindowBase& w, WidgetIndex widgetIndex, bool value) + { + SafeSetWidgetFlag(w, widgetIndex, WIDGET_FLAGS::IS_HOLDABLE, value); + if (value) + { + w.hold_down_widgets |= (1uLL << widgetIndex); + } + else + { + w.hold_down_widgets &= ~(1uLL << widgetIndex); + } + } + + void WidgetSetVisible(WindowBase& w, WidgetIndex widgetIndex, bool value) + { + SafeSetWidgetFlag(w, widgetIndex, WIDGET_FLAGS::IS_HIDDEN, !value); + } + + void WidgetSetPressed(WindowBase& w, WidgetIndex widgetIndex, bool value) + { + SafeSetWidgetFlag(w, widgetIndex, WIDGET_FLAGS::IS_PRESSED, value); + if (value) + w.pressed_widgets |= (1uLL << widgetIndex); + else + w.pressed_widgets &= ~(1uLL << widgetIndex); + } + + void WidgetSetCheckboxValue(WindowBase& w, WidgetIndex widgetIndex, bool value) + { + WidgetSetPressed(w, widgetIndex, value); + } + + static void WidgetTextBoxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) + { + // Get the widget + const auto& widget = w.widgets[widgetIndex]; + + // Resolve the absolute ltrb + ScreenCoordsXY topLeft{ w.windowPos + ScreenCoordsXY{ widget.left, widget.top } }; + ScreenCoordsXY bottomRight{ w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom } }; + + auto& tbIdent = OpenRCT2::Ui::Windows::GetCurrentTextBox(); + bool active = w.classification == tbIdent.window.classification && w.number == tbIdent.window.number + && widgetIndex == tbIdent.widget_index; + + // GfxFillRectInset(dpi, l, t, r, b, colour, 0x20 | (!active ? 0x40 : 0x00)); + GfxFillRectInset(dpi, { topLeft, bottomRight }, w.colours[widget.colour], INSET_RECT_F_60); + + // Figure out where the text should be positioned vertically. + topLeft.y = w.windowPos.y + widget.textTop(); + + auto* textInput = OpenRCT2::Ui::Windows::GetTextboxSession(); + if (!active || textInput == nullptr) + { + if (widget.text != 0) + { + u8string wrappedString; + GfxWrapString(widget.string, bottomRight.x - topLeft.x - 5, FontStyle::Medium, &wrappedString, nullptr); + DrawText(dpi, { topLeft.x + 2, topLeft.y }, { w.colours[1] }, wrappedString.c_str(), true); + } + return; + } + + // String length needs to add 12 either side of box + // +13 for cursor when max length. + u8string wrappedString; + GfxWrapString(*textInput->Buffer, bottomRight.x - topLeft.x - 5 - 6, FontStyle::Medium, &wrappedString, nullptr); + + DrawText(dpi, { topLeft.x + 2, topLeft.y }, { w.colours[1] }, wrappedString.c_str(), true); + + // Make a trimmed view of the string for measuring the width. + int32_t curX = topLeft.x + + GfxGetStringWidthNoFormatting( + u8string_view{ wrappedString.c_str(), std::min(wrappedString.length(), textInput->SelectionStart) }, + FontStyle::Medium) + + 3; + + int32_t width = 6; + if (static_cast(textInput->SelectionStart) < textInput->Buffer->size()) + { + // Make a new 1 character wide string for measuring the width + // of the character that the cursor is under. (NOTE: this is broken for multi byte utf8 codepoints) + width = std::max( + GfxGetStringWidthNoFormatting(u8string{ (*textInput->Buffer)[textInput->SelectionStart] }, FontStyle::Medium) + - 2, + 4); + } + + if (OpenRCT2::Ui::Windows::TextBoxCaretIsFlashed()) + { + auto colour = ColourMapA[w.colours[1].colour].mid_light; + auto y = topLeft.y + (widget.height() - 1); + GfxFillRect(dpi, { { curX, y }, { curX + width, y } }, colour + 5); + } + } + + static void WidgetProgressBarDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex) + { + const auto& widget = w.widgets[widgetIndex]; + + ScreenCoordsXY topLeft{ w.windowPos + ScreenCoordsXY{ widget.left, widget.top } }; + ScreenCoordsXY bottomRight{ w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom } }; + + auto percentage = widget.content & 0xFF; + auto lowerBlinkBounds = (widget.content >> 8) & 0xFF; + auto upperBlinkBounds = (widget.content >> 16) & 0xFF; + + auto isBlinking = (lowerBlinkBounds != upperBlinkBounds) && (percentage >= lowerBlinkBounds) + && (percentage <= upperBlinkBounds); + + GfxFillRectInset(dpi, { topLeft, bottomRight }, w.colours[1], INSET_RECT_F_30); + if (isBlinking) + { + if (GameIsNotPaused() && (gCurrentRealTimeTicks & 8)) + return; + } + + const auto barWidth = widget.width() - 2; + const int32_t fillSize = (barWidth * percentage) / 100; + if (fillSize > 0) + { + GfxFillRectInset( + dpi, { topLeft + ScreenCoordsXY{ 1, 1 }, topLeft + ScreenCoordsXY{ fillSize + 1, widget.height() - 1 } }, + { widget.colour }, 0); + } + } + + ImageId GetColourButtonImage(colour_t colour) + { + if (colour == COLOUR_INVISIBLE) + { + return ImageId(SPR_G2_ICON_PALETTE_INVISIBLE, colour).WithBlended(false); + } + else + { + return ImageId(SPR_PALETTE_BTN, colour).WithBlended(true); + } + } + + void WidgetProgressBarSetNewPercentage(Widget& widget, uint8_t newPercentage) + { + widget.content &= ~0xFF; + widget.content |= newPercentage; + } +} // namespace OpenRCT2::Ui diff --git a/src/openrct2-ui/interface/Widget.h b/src/openrct2-ui/interface/Widget.h index 701501f96b..ce911bdafd 100644 --- a/src/openrct2-ui/interface/Widget.h +++ b/src/openrct2-ui/interface/Widget.h @@ -15,9 +15,9 @@ #include #include -using namespace OpenRCT2; - -// clang-format off +namespace OpenRCT2::Ui +{ + // clang-format off #define WINDOW_SHIM_RAW(TITLE, WIDTH, HEIGHT, CLOSE_STR) \ { WindowWidgetType::Frame, 0, 0, WIDTH - 1, 0, HEIGHT - 1, 0xFFFFFFFF, STR_NONE }, \ { WindowWidgetType::Caption, 0, 1, WIDTH - 2, 1, 14, TITLE, STR_WINDOW_TITLE_TIP }, \ @@ -25,168 +25,169 @@ using namespace OpenRCT2; #define WINDOW_SHIM(TITLE, WIDTH, HEIGHT) WINDOW_SHIM_RAW(TITLE, WIDTH, HEIGHT, STR_CLOSE_X) #define WINDOW_SHIM_WHITE(TITLE, WIDTH, HEIGHT) WINDOW_SHIM_RAW(TITLE, WIDTH, HEIGHT, STR_CLOSE_X_WHITE) -// clang-format on + // clang-format on -ImageId GetColourButtonImage(colour_t colour); -Widget* GetWidgetByIndex(const WindowBase& w, WidgetIndex widgetIndex); + ImageId GetColourButtonImage(colour_t colour); + Widget* GetWidgetByIndex(const WindowBase& w, WidgetIndex widgetIndex); -constexpr auto kWidgetsEnd = Widget{ WindowWidgetType::Last, 0, 0, 0, 0, 0, 0, 0 }; -constexpr uint32_t kWidgetContentEmpty = 0xFFFFFFFF; -constexpr auto kBarBlink = (1u << 31); -constexpr uint8_t kScrollBarWidth = 10; -constexpr ScreenSize kTabSize = { 31, 27 }; + constexpr auto kWidgetsEnd = Widget{ WindowWidgetType::Last, 0, 0, 0, 0, 0, 0, 0 }; + constexpr uint32_t kWidgetContentEmpty = 0xFFFFFFFF; + constexpr auto kBarBlink = (1u << 31); + constexpr uint8_t kScrollBarWidth = 10; + constexpr ScreenSize kTabSize = { 31, 27 }; -constexpr const char* kBlackUpArrowString = u8"{BLACK}▲"; -constexpr const char* kBlackDownArrowString = u8"{BLACK}▼"; -constexpr const char* kBlackLeftArrowString = u8"{BLACK}◀"; -constexpr const char* kBlackRightArrowString = u8"{BLACK}▶"; -constexpr const char* kCheckMarkString = u8"✓"; -constexpr const char* kEyeString = u8"👁"; + constexpr const char* kBlackUpArrowString = u8"{BLACK}▲"; + constexpr const char* kBlackDownArrowString = u8"{BLACK}▼"; + constexpr const char* kBlackLeftArrowString = u8"{BLACK}◀"; + constexpr const char* kBlackRightArrowString = u8"{BLACK}▶"; + constexpr const char* kCheckMarkString = u8"✓"; + constexpr const char* kEyeString = u8"👁"; -enum class WindowColour : uint8_t -{ - Primary, - Secondary, - Tertiary, - Quaternary, -}; + enum class WindowColour : uint8_t + { + Primary, + Secondary, + Tertiary, + Quaternary, + }; -constexpr Widget MakeWidget( - const ScreenCoordsXY& origin, const ScreenSize& size, WindowWidgetType type, WindowColour colour, - uint32_t content = kWidgetContentEmpty, StringId tooltip = STR_NONE) -{ - Widget out = {}; - out.left = origin.x; - out.right = origin.x + size.width - 1; - out.top = origin.y; - out.bottom = origin.y + size.height - 1; - out.type = type; - out.colour = static_cast(colour); - out.content = content; - out.tooltip = tooltip; + constexpr Widget MakeWidget( + const ScreenCoordsXY& origin, const ScreenSize& size, WindowWidgetType type, WindowColour colour, + uint32_t content = kWidgetContentEmpty, StringId tooltip = STR_NONE) + { + Widget out = {}; + out.left = origin.x; + out.right = origin.x + size.width - 1; + out.top = origin.y; + out.bottom = origin.y + size.height - 1; + out.type = type; + out.colour = static_cast(colour); + out.content = content; + out.tooltip = tooltip; - return out; -} + return out; + } -constexpr Widget MakeWidget( - const ScreenCoordsXY& origin, const ScreenSize& size, WindowWidgetType type, WindowColour colour, ImageId image, - StringId tooltip = STR_NONE) -{ - Widget out = {}; - out.left = origin.x; - out.right = origin.x + size.width - 1; - out.top = origin.y; - out.bottom = origin.y + size.height - 1; - out.type = type; - out.colour = static_cast(colour); - out.image = image; - out.tooltip = tooltip; + constexpr Widget MakeWidget( + const ScreenCoordsXY& origin, const ScreenSize& size, WindowWidgetType type, WindowColour colour, ImageId image, + StringId tooltip = STR_NONE) + { + Widget out = {}; + out.left = origin.x; + out.right = origin.x + size.width - 1; + out.top = origin.y; + out.bottom = origin.y + size.height - 1; + out.type = type; + out.colour = static_cast(colour); + out.image = image; + out.tooltip = tooltip; - return out; -} + return out; + } -constexpr Widget MakeRemapWidget( - const ScreenCoordsXY& origin, const ScreenSize& size, WindowWidgetType type, WindowColour colour, ImageIndex content, - StringId tooltip = STR_NONE) -{ - return MakeWidget(origin, size, type, colour, ImageId(content, FilterPaletteID::PaletteNull), tooltip); -} + constexpr Widget MakeRemapWidget( + const ScreenCoordsXY& origin, const ScreenSize& size, WindowWidgetType type, WindowColour colour, ImageIndex content, + StringId tooltip = STR_NONE) + { + return MakeWidget(origin, size, type, colour, ImageId(content, FilterPaletteID::PaletteNull), tooltip); + } -constexpr Widget MakeTab(const ScreenCoordsXY& origin, StringId tooltip = STR_NONE) -{ - const ScreenSize size = kTabSize; - const WindowWidgetType type = WindowWidgetType::Tab; - const WindowColour colour = WindowColour::Secondary; - const auto content = ImageId(ImageIndexUndefined); + constexpr Widget MakeTab(const ScreenCoordsXY& origin, StringId tooltip = STR_NONE) + { + const ScreenSize size = kTabSize; + const WindowWidgetType type = WindowWidgetType::Tab; + const WindowColour colour = WindowColour::Secondary; + const auto content = ImageId(ImageIndexUndefined); - return MakeWidget(origin, size, type, colour, content, tooltip); -} + return MakeWidget(origin, size, type, colour, content, tooltip); + } -constexpr Widget MakeProgressBar( - const ScreenCoordsXY& origin, const ScreenSize& size, colour_t colour, uint8_t lowerBlinkBound = 0, - uint8_t upperBlinkBound = 0) -{ - Widget out = {}; - out.left = origin.x; - out.right = origin.x + size.width - 1; - out.top = origin.y; - out.bottom = origin.y + size.height - 1; - out.type = WindowWidgetType::ProgressBar; - out.colour = colour; - out.content = 0 | (lowerBlinkBound << 8) | (upperBlinkBound << 16); - out.tooltip = STR_NONE; + constexpr Widget MakeProgressBar( + const ScreenCoordsXY& origin, const ScreenSize& size, colour_t colour, uint8_t lowerBlinkBound = 0, + uint8_t upperBlinkBound = 0) + { + Widget out = {}; + out.left = origin.x; + out.right = origin.x + size.width - 1; + out.top = origin.y; + out.bottom = origin.y + size.height - 1; + out.type = WindowWidgetType::ProgressBar; + out.colour = colour; + out.content = 0 | (lowerBlinkBound << 8) | (upperBlinkBound << 16); + out.tooltip = STR_NONE; - return out; -} + return out; + } // NOLINTBEGIN #define MakeSpinnerWidgets(...) \ MakeWidget(__VA_ARGS__), MakeSpinnerIncreaseWidget(__VA_ARGS__), MakeSpinnerDecreaseWidget(__VA_ARGS__) -// NOLINTEND + // NOLINTEND -constexpr Widget MakeSpinnerDecreaseWidget( - const ScreenCoordsXY& origin, const ScreenSize& size, [[maybe_unused]] WindowWidgetType type, WindowColour colour, - [[maybe_unused]] uint32_t content = kWidgetContentEmpty, StringId tooltip = STR_NONE) -{ - const int16_t xPos = origin.x + size.width - 26; - const int16_t yPos = origin.y + 1; - const uint16_t width = 13; - const uint16_t height = size.height - 2; + constexpr Widget MakeSpinnerDecreaseWidget( + const ScreenCoordsXY& origin, const ScreenSize& size, [[maybe_unused]] WindowWidgetType type, WindowColour colour, + [[maybe_unused]] uint32_t content = kWidgetContentEmpty, StringId tooltip = STR_NONE) + { + const int16_t xPos = origin.x + size.width - 26; + const int16_t yPos = origin.y + 1; + const uint16_t width = 13; + const uint16_t height = size.height - 2; - return MakeWidget({ xPos, yPos }, { width, height }, WindowWidgetType::Button, colour, STR_NUMERIC_DOWN, tooltip); -} + return MakeWidget({ xPos, yPos }, { width, height }, WindowWidgetType::Button, colour, STR_NUMERIC_DOWN, tooltip); + } -constexpr Widget MakeSpinnerIncreaseWidget( - const ScreenCoordsXY& origin, const ScreenSize& size, [[maybe_unused]] WindowWidgetType type, WindowColour colour, - [[maybe_unused]] uint32_t content = kWidgetContentEmpty, StringId tooltip = STR_NONE) -{ - const int16_t xPos = origin.x + size.width - 13; - const int16_t yPos = origin.y + 1; - const uint16_t width = 12; - const uint16_t height = size.height - 2; + constexpr Widget MakeSpinnerIncreaseWidget( + const ScreenCoordsXY& origin, const ScreenSize& size, [[maybe_unused]] WindowWidgetType type, WindowColour colour, + [[maybe_unused]] uint32_t content = kWidgetContentEmpty, StringId tooltip = STR_NONE) + { + const int16_t xPos = origin.x + size.width - 13; + const int16_t yPos = origin.y + 1; + const uint16_t width = 12; + const uint16_t height = size.height - 2; - return MakeWidget({ xPos, yPos }, { width, height }, WindowWidgetType::Button, colour, STR_NUMERIC_UP, tooltip); -} + return MakeWidget({ xPos, yPos }, { width, height }, WindowWidgetType::Button, colour, STR_NUMERIC_UP, tooltip); + } // NOLINTNEXTLINE #define MakeDropdownWidgets(...) MakeDropdownBoxWidget(__VA_ARGS__), MakeDropdownButtonWidget(__VA_ARGS__) -constexpr Widget MakeDropdownBoxWidget( - const ScreenCoordsXY& origin, const ScreenSize& size, [[maybe_unused]] WindowWidgetType type, WindowColour colour, - [[maybe_unused]] uint32_t content = kWidgetContentEmpty, StringId tooltip = STR_NONE) -{ - return MakeWidget(origin, size, type, colour, content); -} + constexpr Widget MakeDropdownBoxWidget( + const ScreenCoordsXY& origin, const ScreenSize& size, [[maybe_unused]] WindowWidgetType type, WindowColour colour, + [[maybe_unused]] uint32_t content = kWidgetContentEmpty, StringId tooltip = STR_NONE) + { + return MakeWidget(origin, size, type, colour, content); + } -constexpr Widget MakeDropdownButtonWidget( - const ScreenCoordsXY& origin, const ScreenSize& size, [[maybe_unused]] WindowWidgetType type, WindowColour colour, - [[maybe_unused]] uint32_t content = kWidgetContentEmpty, StringId tooltip = STR_NONE) -{ - const int16_t xPos = origin.x + size.width - 11; - const int16_t yPos = origin.y + 1; - const uint16_t width = 11; - const uint16_t height = 10; + constexpr Widget MakeDropdownButtonWidget( + const ScreenCoordsXY& origin, const ScreenSize& size, [[maybe_unused]] WindowWidgetType type, WindowColour colour, + [[maybe_unused]] uint32_t content = kWidgetContentEmpty, StringId tooltip = STR_NONE) + { + const int16_t xPos = origin.x + size.width - 11; + const int16_t yPos = origin.y + 1; + const uint16_t width = 11; + const uint16_t height = 10; - return MakeWidget({ xPos, yPos }, { width, height }, WindowWidgetType::Button, colour, STR_DROPDOWN_GLYPH, tooltip); -} + return MakeWidget({ xPos, yPos }, { width, height }, WindowWidgetType::Button, colour, STR_DROPDOWN_GLYPH, tooltip); + } -void WidgetDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); + void WidgetDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex); -bool WidgetIsDisabled(const WindowBase& w, WidgetIndex widgetIndex); -bool WidgetIsHoldable(const WindowBase& w, WidgetIndex widgetIndex); -bool WidgetIsVisible(const WindowBase& w, WidgetIndex widgetIndex); -bool WidgetIsPressed(const WindowBase& w, WidgetIndex widgetIndex); -bool WidgetIsHighlighted(const WindowBase& w, WidgetIndex widgetIndex); -bool WidgetIsActiveTool(const WindowBase& w, WidgetIndex widgetIndex); -void WidgetScrollGetPart( - WindowBase& w, const Widget* widget, const ScreenCoordsXY& screenCoords, ScreenCoordsXY& retScreenCoords, - int32_t* output_scroll_area, int32_t* scroll_id); + bool WidgetIsDisabled(const WindowBase& w, WidgetIndex widgetIndex); + bool WidgetIsHoldable(const WindowBase& w, WidgetIndex widgetIndex); + bool WidgetIsVisible(const WindowBase& w, WidgetIndex widgetIndex); + bool WidgetIsPressed(const WindowBase& w, WidgetIndex widgetIndex); + bool WidgetIsHighlighted(const WindowBase& w, WidgetIndex widgetIndex); + bool WidgetIsActiveTool(const WindowBase& w, WidgetIndex widgetIndex); + void WidgetScrollGetPart( + WindowBase& w, const Widget* widget, const ScreenCoordsXY& screenCoords, ScreenCoordsXY& retScreenCoords, + int32_t* output_scroll_area, int32_t* scroll_id); -void WidgetSetEnabled(WindowBase& w, WidgetIndex widgetIndex, bool enabled); -void WidgetSetDisabled(WindowBase& w, WidgetIndex widgetIndex, bool value); -void WidgetSetHoldable(WindowBase& w, WidgetIndex widgetIndex, bool value); -void WidgetSetVisible(WindowBase& w, WidgetIndex widgetIndex, bool value); -void WidgetSetPressed(WindowBase& w, WidgetIndex widgetIndex, bool value); -void WidgetSetCheckboxValue(WindowBase& w, WidgetIndex widgetIndex, bool value); + void WidgetSetEnabled(WindowBase& w, WidgetIndex widgetIndex, bool enabled); + void WidgetSetDisabled(WindowBase& w, WidgetIndex widgetIndex, bool value); + void WidgetSetHoldable(WindowBase& w, WidgetIndex widgetIndex, bool value); + void WidgetSetVisible(WindowBase& w, WidgetIndex widgetIndex, bool value); + void WidgetSetPressed(WindowBase& w, WidgetIndex widgetIndex, bool value); + void WidgetSetCheckboxValue(WindowBase& w, WidgetIndex widgetIndex, bool value); -void WidgetProgressBarSetNewPercentage(Widget& widget, uint8_t newPercentage); + void WidgetProgressBarSetNewPercentage(Widget& widget, uint8_t newPercentage); +} // namespace OpenRCT2::Ui diff --git a/src/openrct2-ui/interface/Window.cpp b/src/openrct2-ui/interface/Window.cpp index a9df3f57cd..e5c531c68b 100644 --- a/src/openrct2-ui/interface/Window.cpp +++ b/src/openrct2-ui/interface/Window.cpp @@ -30,6 +30,7 @@ #include using namespace OpenRCT2; +using namespace OpenRCT2::Ui; // The amount of pixels to scroll per wheel click constexpr int32_t WindowScrollPixels = 17; diff --git a/src/openrct2-ui/scripting/ScWidget.hpp b/src/openrct2-ui/scripting/ScWidget.hpp index 5e71b5ee8e..37f05a95f9 100644 --- a/src/openrct2-ui/scripting/ScWidget.hpp +++ b/src/openrct2-ui/scripting/ScWidget.hpp @@ -300,7 +300,7 @@ namespace OpenRCT2::Scripting auto w = GetWindow(); if (w != nullptr) { - return WidgetIsDisabled(*w, _widgetIndex); + return Ui::WidgetIsDisabled(*w, _widgetIndex); } return false; } @@ -309,19 +309,19 @@ namespace OpenRCT2::Scripting auto w = GetWindow(); if (w != nullptr) { - WidgetSetDisabled(*w, _widgetIndex, value); + Ui::WidgetSetDisabled(*w, _widgetIndex, value); auto widget = GetWidget(); if (widget != nullptr) { if (widget->type == WindowWidgetType::DropdownMenu) { - WidgetSetDisabled(*w, _widgetIndex + 1, value); + Ui::WidgetSetDisabled(*w, _widgetIndex + 1, value); } else if (widget->type == WindowWidgetType::Spinner) { - WidgetSetDisabled(*w, _widgetIndex + 1, value); - WidgetSetDisabled(*w, _widgetIndex + 2, value); + Ui::WidgetSetDisabled(*w, _widgetIndex + 1, value); + Ui::WidgetSetDisabled(*w, _widgetIndex + 2, value); } } Invalidate(widget); @@ -333,7 +333,7 @@ namespace OpenRCT2::Scripting auto w = GetWindow(); if (w != nullptr) { - return WidgetIsVisible(*w, _widgetIndex); + return Ui::WidgetIsVisible(*w, _widgetIndex); } return false; } @@ -342,19 +342,19 @@ namespace OpenRCT2::Scripting auto w = GetWindow(); if (w != nullptr) { - WidgetSetVisible(*w, _widgetIndex, value); + Ui::WidgetSetVisible(*w, _widgetIndex, value); auto widget = GetWidget(); if (widget != nullptr) { if (widget->type == WindowWidgetType::DropdownMenu) { - WidgetSetVisible(*w, _widgetIndex + 1, value); + Ui::WidgetSetVisible(*w, _widgetIndex + 1, value); } else if (widget->type == WindowWidgetType::Spinner) { - WidgetSetVisible(*w, _widgetIndex + 1, value); - WidgetSetVisible(*w, _widgetIndex + 2, value); + Ui::WidgetSetVisible(*w, _widgetIndex + 1, value); + Ui::WidgetSetVisible(*w, _widgetIndex + 2, value); } } Invalidate(widget); @@ -486,7 +486,7 @@ namespace OpenRCT2::Scripting auto w = GetWindow(); if (w != nullptr) { - return WidgetIsPressed(*w, _widgetIndex); + return Ui::WidgetIsPressed(*w, _widgetIndex); } return false; } @@ -495,7 +495,7 @@ namespace OpenRCT2::Scripting auto w = GetWindow(); if (w != nullptr) { - WidgetSetCheckboxValue(*w, _widgetIndex, value ? 1 : 0); + Ui::WidgetSetCheckboxValue(*w, _widgetIndex, value ? 1 : 0); Invalidate(); } } @@ -548,7 +548,7 @@ namespace OpenRCT2::Scripting auto w = GetWindow(); if (w != nullptr) { - return WidgetIsPressed(*w, _widgetIndex); + return Ui::WidgetIsPressed(*w, _widgetIndex); } return false; } @@ -557,7 +557,7 @@ namespace OpenRCT2::Scripting auto w = GetWindow(); if (w != nullptr) { - WidgetSetCheckboxValue(*w, _widgetIndex, value ? 1 : 0); + Ui::WidgetSetCheckboxValue(*w, _widgetIndex, value ? 1 : 0); Invalidate(); } } diff --git a/src/openrct2-ui/windows/Dropdown.cpp b/src/openrct2-ui/windows/Dropdown.cpp index 8245f18581..5d5aaa0e20 100644 --- a/src/openrct2-ui/windows/Dropdown.cpp +++ b/src/openrct2-ui/windows/Dropdown.cpp @@ -536,6 +536,7 @@ static constexpr colour_t kColoursDropdownOrder[] = { } // namespace OpenRCT2::Ui::Windows using namespace OpenRCT2::Ui::Windows; +using namespace OpenRCT2; bool Dropdown::IsChecked(int32_t index) {