From 08e0645ef8c800236b784259c0672aa22bd209d5 Mon Sep 17 00:00:00 2001 From: gymnast86 Date: Fri, 19 Jun 2026 03:08:12 -0700 Subject: [PATCH] add functionality for saving and loading presets --- res/rml/window.rcss | 1 + .../randomizer/generator/seedgen/config.cpp | 3 + src/dusk/ui/modal.cpp | 33 ++++ src/dusk/ui/modal.hpp | 18 ++- src/dusk/ui/rando_config.cpp | 147 +++++++++++++++++- src/dusk/ui/rando_config.hpp | 3 + 6 files changed, 199 insertions(+), 6 deletions(-) diff --git a/res/rml/window.rcss b/res/rml/window.rcss index 4118bd319b..c9357cc687 100644 --- a/res/rml/window.rcss +++ b/res/rml/window.rcss @@ -490,6 +490,7 @@ window.modal.danger .modal-header icon { font-size: 20dp; color: #FFFFFF; font-weight: normal; + word-break: break-word; } .modal-body span.tip { diff --git a/src/dusk/randomizer/generator/seedgen/config.cpp b/src/dusk/randomizer/generator/seedgen/config.cpp index 6c4414c778..d972cfc2db 100644 --- a/src/dusk/randomizer/generator/seedgen/config.cpp +++ b/src/dusk/randomizer/generator/seedgen/config.cpp @@ -86,6 +86,9 @@ namespace randomizer::seedgen::config } auto& settings = this->_settingsList.front(); + settings.GetModifiableExcludedLocations().clear(); + settings.GetModifiableMixedEntrancePools().clear(); + settings.GetModifiableStartingInventory().clear(); // Load settings info auto settingInfoMap = settings::GetAllSettingsInfo(); diff --git a/src/dusk/ui/modal.cpp b/src/dusk/ui/modal.cpp index 31cc098bf1..0a50960a90 100644 --- a/src/dusk/ui/modal.cpp +++ b/src/dusk/ui/modal.cpp @@ -1,5 +1,7 @@ #include "modal.hpp" +#include "string_button.hpp" + namespace dusk::ui { Modal::Modal(Props props) : WindowSmall("modal", "modal-dialog"), mProps(std::move(props)) { @@ -100,4 +102,35 @@ bool Modal::handle_nav_command(Rml::Event& event, NavCommand cmd) { return false; } +TextInputModal::TextInputModal(Props props) : Modal(props) { + auto modalBody = mDialog->QuerySelector(".modal-body"); + + mInput = std::make_unique(modalBody, StringButton::Props{ + .getValue = [this]{return mInputText;}, + .setValue = [this](Rml::String value) { mInputText = value; }, + }); + + mInput->start_editing(); +} + +bool TextInputModal::handle_nav_command(Rml::Event& event, NavCommand cmd) { + auto retVal = Modal::handle_nav_command(event, cmd); + if (!retVal) { + if (mInput->root()->IsPseudoClassSet("focus") && cmd == NavCommand::Down) { + mButtons[0]->focus(); + retVal = true; + } else if (!mInput->root()->IsPseudoClassSet("focus") && cmd == NavCommand::Up) { + mInput->focus(); + retVal = true; + } + } + + return retVal; +} + +void TextInputModal::update() { + mInput->update(); + Document::update(); +} + } // namespace dusk::ui diff --git a/src/dusk/ui/modal.hpp b/src/dusk/ui/modal.hpp index d48e3b5dae..2a9f31e6ac 100644 --- a/src/dusk/ui/modal.hpp +++ b/src/dusk/ui/modal.hpp @@ -31,12 +31,26 @@ public: protected: bool handle_nav_command(Rml::Event& event, NavCommand cmd) override; - -private: void dismiss(); Props mProps; std::vector > mButtons; }; +class StringButton; + +class TextInputModal : public Modal { +public: + explicit TextInputModal(Props props); + + Rml::String get_input_text() {return mInputText;} + void update() override; + +protected: + bool handle_nav_command(Rml::Event& event, NavCommand cmd) override; +private: + std::unique_ptr mInput{}; + Rml::String mInputText{}; +}; + } // namespace dusk::ui diff --git a/src/dusk/ui/rando_config.cpp b/src/dusk/ui/rando_config.cpp index 7b4928cac9..81d241991c 100644 --- a/src/dusk/ui/rando_config.cpp +++ b/src/dusk/ui/rando_config.cpp @@ -652,14 +652,11 @@ void RandomizerWindow::rando_excluded_locations_update_left_pane(Pane& innerLeft // Focus the closest child in the next Pane. Returns true if a child was found to focus bool focus_closest_child_on_next_pane(Pane& currentPane, Pane& nextPane) { - auto childToFocusY = currentPane.get_focused_child_y(); return nextPane.focus_closest_child(childToFocusY); - } void delete_seed_callback(Pane& pane) { - // Get the Y position to focus the child nearest to after we rebuild this pane auto childToFocusY = pane.get_focused_child_y(); @@ -688,7 +685,6 @@ void delete_seed_callback(Pane& pane) { } } - // TODO: our ui lib doesnt have an easy way to either refresh or remove values from the right pane pane.add_button( { .text = hash, @@ -713,6 +709,9 @@ RandomizerWindow::RandomizerWindow(dFile_select_c* fileSelect /*= nullptr*/) : m if (!std::filesystem::exists(GetRandomizerSeedsPath())) { std::filesystem::create_directories(GetRandomizerSeedsPath()); } + if (!std::filesystem::exists(GetRandomizerPresetsPath())) { + std::filesystem::create_directories(GetRandomizerPresetsPath()); + } // If we're bringing this menu up during file selection if (mFileSelectMenu) { @@ -823,6 +822,58 @@ RandomizerWindow::RandomizerWindow(dFile_select_c* fileSelect /*= nullptr*/) : m leftPane.add_button("Delete Seeds"), rightPane, delete_seed_callback ); + + leftPane.add_section("Presets"); + leftPane.register_control( + leftPane.add_button("Save Current Settings as Preset") + .on_pressed([] { + push_document(std::make_unique(Modal::Props{ + .title = "Preset Name", + .bodyRml = "", + .actions = { + ModalAction{ + .label = "Save", + .onPressed = [](Modal& modal) { + auto textModal = dynamic_cast(&modal); + if (!textModal->get_input_text().empty()) { + modal.pop(); + SaveNewRandomizerPreset(textModal->get_input_text()); + } + }, + }, + ModalAction{ + .label = "Cancel", + .onPressed = [](Modal& modal) { + modal.pop(); + }, + }, + }, + .icon = "information" + })); + + }), + rightPane, [](Pane& pane) { + pane.clear(); + pane.add_text("Save the current settings to your list of presets."); + }); + + leftPane.register_control( + leftPane.add_button("Load Preset"), + rightPane, [](Pane& pane) { + pane.clear(); + pane.add_text("Choose an existing preset to load from."); + + for (const auto& file : std::filesystem::directory_iterator{GetRandomizerPresetsPath()}) { + const auto& filepath = file.path(); + auto presetName = file.path().stem().generic_string(); + pane.add_button(ControlledButton::Props{ + .text = presetName, + }) + .on_pressed([filepath] { + ApplyExistingRandomizerPreset(filepath); + }); + } + }); }); add_tab("Seed Options", [this](Rml::Element* content) { @@ -1168,6 +1219,90 @@ bool FileSelectRandomizerWindow::consume_close_request() { return true; } +void SaveNewRandomizerPreset(const std::string& presetName, bool overwriteExisting /*= false*/) { + auto presetFilepath = GetRandomizerPresetsPath() / (presetName + ".yaml"); + + // If the preset exists, ask the user if they want to overwrite it. If so, call this function + // again but force overwrite the existing preset + if (std::filesystem::exists(presetFilepath) && !overwriteExisting) { + push_document(std::make_unique(Modal::Props{ + .title = "Overwrite Existing Preset", + .bodyRml = "A preset with the name " + presetName + " already exists. Do you wish to overwrite it?", + .actions = { + ModalAction{ + .label = "Yes", + .onPressed = [presetName](Modal& modal) { + modal.pop(); + SaveNewRandomizerPreset(presetName, true); + } + }, + ModalAction{ + .label = "No", + .onPressed = [](Modal& modal) { + modal.pop(); + } + } + } + })); + } + + // If there was an error trying to save, let the user know + try { + GetRandomizerConfig().WriteSettingsToFile(presetFilepath); + } catch (std::exception& e) { + auto modal = dynamic_cast(&push_document(std::make_unique(Modal::Props{ + .title = "Error Saving Preset", + .bodyRml = fmt::format("Error: {}", e.what()), + .actions = { + ModalAction{ + .label = "Okay", + .onPressed = [](Modal& modal) { + modal.pop(); + } + } + }, + .icon = "error" + }))); + return; + } + + push_toast(Toast{ + .title = "", + .content = fmt::format("Saved preset {}", presetName), + .duration = std::chrono::seconds(3) + }); +} + +void ApplyExistingRandomizerPreset(const std::filesystem::path& presetFilePath) { + // Don't overwrite the seed with the one from the preset + auto seed = GetRandomizerConfig().GetSeed(); + try { + GetRandomizerConfig().LoadFromFile(presetFilePath, GetRandomizerPreferencesPath(), false, false); + GetRandomizerConfig().SetSeed(seed); + } catch (std::exception& e) { + auto modal = dynamic_cast(&push_document(std::make_unique(Modal::Props{ + .title = "Error Loading Preset", + .bodyRml = fmt::format("Error: {}", e.what()), + .actions = { + ModalAction{ + .label = "Okay", + .onPressed = [](Modal& modal) { + modal.pop(); + } + } + }, + .icon = "error" + }))); + return; + } + + push_toast(Toast{ + .title = "", + .content = fmt::format("Loaded preset {}", presetFilePath.stem().generic_string()), + .duration = std::chrono::seconds(3) + }); +} + std::filesystem::path GetRandomizerPath() { return data::configured_data_path() / "randomizer"; } @@ -1180,6 +1315,10 @@ std::filesystem::path GetRandomizerPreferencesPath() { return GetRandomizerPath() / "preferences.yaml"; } +std::filesystem::path GetRandomizerPresetsPath() { + return GetRandomizerPath() / "presets"; +} + std::filesystem::path GetRandomizerSeedsPath() { return GetRandomizerPath() / "seeds"; } diff --git a/src/dusk/ui/rando_config.hpp b/src/dusk/ui/rando_config.hpp index 2cf59082fb..817b22553e 100644 --- a/src/dusk/ui/rando_config.hpp +++ b/src/dusk/ui/rando_config.hpp @@ -10,10 +10,13 @@ class dFile_select_c; namespace dusk::ui { class Pane; + void SaveNewRandomizerPreset(const std::string& presetName, bool overwriteExisting = false); + void ApplyExistingRandomizerPreset(const std::filesystem::path& presetFilePath); std::filesystem::path GetRandomizerPath(); std::filesystem::path GetRandomizerSettingsPath(); std::filesystem::path GetRandomizerPreferencesPath(); std::filesystem::path GetRandomizerSeedsPath(); + std::filesystem::path GetRandomizerPresetsPath(); randomizer::seedgen::config::Config& GetRandomizerConfig();