From 65a5945778f07be9ea13b5c9374b10a74669d3a4 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 1 May 2026 12:37:40 -0600 Subject: [PATCH] Location tab in editor --- extern/aurora | 2 +- src/dusk/ui/component.hpp | 14 +-- src/dusk/ui/editor.cpp | 216 +++++++++++++++++++++++++++++++++- src/dusk/ui/pane.cpp | 2 +- src/dusk/ui/select_button.cpp | 16 +-- src/dusk/ui/select_button.hpp | 7 +- 6 files changed, 234 insertions(+), 23 deletions(-) diff --git a/extern/aurora b/extern/aurora index 702583583d..222545580e 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 702583583dba50107261eab7d6f59572101cd549 +Subproject commit 222545580e90d4d18d5fc6011170d33a4a368127 diff --git a/src/dusk/ui/component.hpp b/src/dusk/ui/component.hpp index 80fa6ad968..86e6bbf608 100644 --- a/src/dusk/ui/component.hpp +++ b/src/dusk/ui/component.hpp @@ -65,15 +65,13 @@ public: return static_cast(*this); } - Derived& on_hover(ScopedEventListener::Callback callback) { - listen(Rml::EventId::Mouseover, callback); - listen(Rml::EventId::Focus, std::move(callback)); - return static_cast(*this); - } - Derived& on_focus(ScopedEventListener::Callback callback) { - listen(Rml::EventId::Focus, std::move(callback)); - return static_cast(*this); + return listen( + Rml::EventId::Focus, [this, callback = std::move(callback)](Rml::Event& event) { + if (!disabled()) { + callback(event); + } + }); } }; diff --git a/src/dusk/ui/editor.cpp b/src/dusk/ui/editor.cpp index a0afef3042..c46ded84e8 100644 --- a/src/dusk/ui/editor.cpp +++ b/src/dusk/ui/editor.cpp @@ -7,6 +7,7 @@ #include "d/actor/d_a_player.h" #include "d/d_kankyo.h" #include "d/d_meter2_info.h" +#include "dusk/map_loader_definitions.h" #include "number_button.hpp" #include "pane.hpp" #include "select_button.hpp" @@ -14,6 +15,11 @@ #include #include +#include +#include +#include +#include +#include namespace dusk::ui { namespace { @@ -36,6 +42,108 @@ dSv_player_status_b_c* get_player_status_b() { return &dComIfGs_getSaveData()->getPlayer().getPlayerStatusB(); } +dSv_player_return_place_c* get_player_return_place() { + if (!has_save_data()) { + return nullptr; + } + return &dComIfGs_getSaveData()->getPlayer().getPlayerReturnPlace(); +} + +dSv_horse_place_c* get_horse_place() { + if (!has_save_data()) { + return nullptr; + } + return &dComIfGs_getSaveData()->getPlayer().getHorsePlace(); +} + +template +Rml::String fixed_string(const char (&value)[Size]) { + size_t length = 0; + while (length < Size && value[length] != '\0') { + ++length; + } + return Rml::String(value, length); +} + +template +void set_fixed_string(char (&dest)[Size], const Rml::String& value) { + std::memset(dest, 0, Size); + std::memcpy(dest, value.data(), std::min(value.size(), Size - 1)); +} + +void skip_whitespace(const char*& cursor) { + while (std::isspace(static_cast(*cursor))) { + ++cursor; + } +} + +bool parse_float_token(const char*& cursor, float& parsedValue) { + skip_whitespace(cursor); + char* end = nullptr; + parsedValue = std::strtof(cursor, &end); + if (end == cursor) { + return false; + } + cursor = end; + skip_whitespace(cursor); + return true; +} + +bool consume_comma(const char*& cursor) { + skip_whitespace(cursor); + if (*cursor != ',') { + return false; + } + ++cursor; + return true; +} + +bool parse_vec3(const Rml::String& value, float& x, float& y, float& z) { + const char* cursor = value.c_str(); + if (!parse_float_token(cursor, x) || !consume_comma(cursor) || !parse_float_token(cursor, y) || + !consume_comma(cursor) || !parse_float_token(cursor, z)) + { + return false; + } + skip_whitespace(cursor); + return *cursor == '\0'; +} + +Rml::String stage_option_label(const MapEntry& map) { + // TODO: option to show internal name? + // return fmt::format("{} ({})", map.mapName, map.mapFile); + return map.mapName; +} + +Rml::String stage_label_for_file(const Rml::String& stageFile) { + for (const auto& region : gameRegions) { + for (const auto& map : region.maps) { + if (stageFile == map.mapFile) { + return stage_option_label(map); + } + } + } + return stageFile; +} + +void populate_stage_picker(Pane& pane, std::function getStageFile, + std::function setStageFile) { + pane.clear(); + for (const auto& region : gameRegions) { + pane.add_section(region.regionName); + for (const auto& map : region.maps) { + pane.add_button( + { + .text = stage_option_label(map), + .isSelected = [getStageFile, + stageFile = + map.mapFile] { return getStageFile() == stageFile; }, + }, + [setStageFile, stageFile = map.mapFile] { setStageFile(stageFile); }); + } + } +} + Rml::String get_player_name() { if (!has_save_data()) { return ""; @@ -646,7 +754,113 @@ EditorWindow::EditorWindow() { }); add_tab("Location", [this](Rml::Element* content) { - // TODO + auto& leftPane = add_child(content, Pane::Direction::Vertical); + auto& rightPane = add_child(content, Pane::Direction::Vertical); + + leftPane.add_section("Save Location"); + leftPane + .add_select_button({ + .key = "Stage", + .getValue = + [] { + return stage_label_for_file(fixed_string(get_player_return_place()->mName)); + }, + }) + .on_focus([&rightPane](Rml::Event&) { + populate_stage_picker( + rightPane, [] { return fixed_string(get_player_return_place()->mName); }, + [](const char* stageFile) { + set_fixed_string(get_player_return_place()->mName, Rml::String(stageFile)); + }); + }) + .set_disabled(true); + leftPane + .add_child(NumberButton::Props{ + .key = "Room", + .getValue = [] { return get_player_return_place()->mRoomNo; }, + .setValue = + [](int value) { get_player_return_place()->mRoomNo = static_cast(value); }, + .min = std::numeric_limits::min(), + .max = std::numeric_limits::max(), + }) + .on_focus([&rightPane](Rml::Event&) { rightPane.clear(); }); + leftPane + .add_child(NumberButton::Props{ + .key = "Spawn ID", + .getValue = [] { return get_player_return_place()->mPlayerStatus; }, + .setValue = + [](int value) { + get_player_return_place()->mPlayerStatus = static_cast(value); + }, + .max = std::numeric_limits::max(), + }) + .on_focus([&rightPane](Rml::Event&) { rightPane.clear(); }); + + leftPane.add_section("Horse Location"); + leftPane + .add_child(StringButton::Props{ + .key = "Horse Position", + .getValue = + [] { + const auto* horsePlace = get_horse_place(); + return fmt::format("{}, {}, {}", static_cast(horsePlace->mPos.x), + static_cast(horsePlace->mPos.y), + static_cast(horsePlace->mPos.z)); + }, + .setValue = + [](Rml::String value) { + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + if (parse_vec3(value, x, y, z)) { + auto* horsePlace = get_horse_place(); + horsePlace->mPos.x = x; + horsePlace->mPos.y = y; + horsePlace->mPos.z = z; + } + }, + }) + .on_focus([&rightPane](Rml::Event&) { rightPane.clear(); }); + leftPane + .add_child(NumberButton::Props{ + .key = "Horse Angle", + .getValue = [] { return get_horse_place()->mAngleY; }, + .setValue = [](int value) { get_horse_place()->mAngleY = static_cast(value); }, + .min = std::numeric_limits::min(), + .max = std::numeric_limits::max(), + }) + .on_focus([&rightPane](Rml::Event&) { rightPane.clear(); }); + leftPane + .add_select_button({ + .key = "Horse Stage", + .getValue = + [] { return stage_label_for_file(fixed_string(get_horse_place()->mName)); }, + }) + .on_focus([&rightPane](Rml::Event&) { + populate_stage_picker( + rightPane, [] { return fixed_string(get_horse_place()->mName); }, + [](const char* stageFile) { + set_fixed_string(get_horse_place()->mName, Rml::String(stageFile)); + }); + }) + .set_disabled(true); + leftPane + .add_child(NumberButton::Props{ + .key = "Horse Room", + .getValue = [] { return get_horse_place()->mRoomNo; }, + .setValue = [](int value) { get_horse_place()->mRoomNo = static_cast(value); }, + .min = std::numeric_limits::min(), + .max = std::numeric_limits::max(), + }) + .on_focus([&rightPane](Rml::Event&) { rightPane.clear(); }); + leftPane + .add_child(NumberButton::Props{ + .key = "Horse Spawn ID", + .getValue = [] { return get_horse_place()->mSpawnId; }, + .setValue = [](int value) { get_horse_place()->mSpawnId = static_cast(value); }, + .max = std::numeric_limits::max(), + }) + .on_focus([&rightPane](Rml::Event&) { rightPane.clear(); }); }); add_tab("Inventory", [this](Rml::Element* content) { diff --git a/src/dusk/ui/pane.cpp b/src/dusk/ui/pane.cpp index 614b518c98..de81f4dc2e 100644 --- a/src/dusk/ui/pane.cpp +++ b/src/dusk/ui/pane.cpp @@ -18,7 +18,7 @@ Pane::Pane(Rml::Element* parent, Direction direction) listen(Rml::EventId::Keydown, [this](Rml::Event& event) { const auto cmd = map_nav_event(event); - // If + // If navigating to the next pane, select the focused item if ((mDirection == Direction::Vertical && cmd == NavCommand::Right) || (mDirection == Direction::Horizontal && cmd == NavCommand::Down)) { diff --git a/src/dusk/ui/select_button.cpp b/src/dusk/ui/select_button.cpp index 4aa971bdb6..7e8dd894b3 100644 --- a/src/dusk/ui/select_button.cpp +++ b/src/dusk/ui/select_button.cpp @@ -63,11 +63,18 @@ bool SelectButton::handle_nav_command(NavCommand cmd) { } void BaseControlledSelectButton::update() { - set_disabled(is_disabled()); + set_disabled(disabled()); set_value_label(format_value()); SelectButton::update(); } +bool ControlledSelectButton::disabled() const { + if (mIsDisabled) { + return mIsDisabled(); + } + return BaseControlledSelectButton::disabled(); +} + Rml::String ControlledSelectButton::format_value() { if (!mGetValue) { return ""; @@ -75,11 +82,4 @@ Rml::String ControlledSelectButton::format_value() { return mGetValue(); } -bool ControlledSelectButton::is_disabled() { - if (!mIsDisabled) { - return false; - } - return mIsDisabled(); -} - } // namespace dusk::ui diff --git a/src/dusk/ui/select_button.hpp b/src/dusk/ui/select_button.hpp index 29a41a9af5..b45f1db508 100644 --- a/src/dusk/ui/select_button.hpp +++ b/src/dusk/ui/select_button.hpp @@ -17,10 +17,9 @@ public: void set_value_label(const Rml::String& value); SelectButton& on_selected(std::function callback) { - listen(Rml::EventId::Change, [callback = std::move(callback)](Rml::Event& event) { + return listen(Rml::EventId::Change, [callback = std::move(callback)](Rml::Event& event) { callback(event.GetParameter("selected", false)); }); - return *this; } protected: @@ -42,7 +41,6 @@ public: protected: virtual Rml::String format_value() = 0; - virtual bool is_disabled() { return false; } }; class ControlledSelectButton : public BaseControlledSelectButton { @@ -57,9 +55,10 @@ public: : BaseControlledSelectButton(parent, {std::move(props.key)}), mGetValue(std::move(props.getValue)), mIsDisabled(std::move(props.isDisabled)) {} + bool disabled() const override; + protected: Rml::String format_value() override; - bool is_disabled() override; std::function mGetValue; std::function mIsDisabled;