Split out string/number components

This commit is contained in:
Luke Street
2026-04-29 20:19:27 -06:00
parent ecd74a4cbd
commit 1657fe8083
9 changed files with 296 additions and 246 deletions
+15 -230
View File
@@ -1,19 +1,16 @@
#include "editor.hpp"
#include <RmlUi/Core.h>
#include <fmt/format.h>
#include "fmt/format.h"
#include "aurora/lib/dolphin/gd/gd.hpp"
#include "button.hpp"
#include "number_button.hpp"
#include "pane.hpp"
#include "select_button.hpp"
#include <charconv>
#include "string_button.hpp"
namespace dusk::ui {
namespace {
aurora::Module Log{"dusk::ui::editor"};
bool has_save_data() {
return dComIfGs_getSaveData() != nullptr;
@@ -181,218 +178,6 @@ bool handle_editor_action(const Rml::VariantList& arguments) {
} // namespace
class ControlledSelectButton : public SelectButton {
public:
ControlledSelectButton(Rml::Element* parent, Props props)
: SelectButton(parent, std::move(props)) {}
void update() override {
set_disabled(is_disabled());
set_value_label(format_value());
SelectButton::update();
}
protected:
virtual Rml::String format_value() = 0;
virtual bool is_disabled() { return false; }
};
class BaseStringButton : public ControlledSelectButton {
public:
struct Props {
Rml::String key;
Rml::String type = "text";
int maxLength = -1;
};
BaseStringButton(Rml::Element* parent, Props props)
: ControlledSelectButton(parent, {std::move(props.key)}), mType(std::move(props.type)),
mMaxLength(props.maxLength) {
mInputListeners.reserve(3);
}
void update() override {
if (mPendingStopEditing) {
stop_editing(mPendingCommit, mPendingRefocusRoot);
}
ControlledSelectButton::update();
}
void start_editing() {
if (mInputElem != nullptr) {
return;
}
auto* doc = mRoot->GetOwnerDocument();
auto elemPtr = doc->CreateElement("input");
mInputElem = rmlui_dynamic_cast<Rml::ElementFormControlInput*>(elemPtr.get());
if (mInputElem == nullptr) {
return;
}
mInputElem->SetAttribute("type", mType);
mInputElem->SetAttribute("value", format_value());
if (mMaxLength > -1) {
mInputElem->SetAttribute("maxlength", mMaxLength);
}
mRoot->AppendChild(std::move(elemPtr));
mValueElem->SetProperty(Rml::PropertyId::Visibility, Rml::Style::Visibility::Hidden);
mInputElem->Focus(true);
const int end = static_cast<int>(Rml::StringUtilities::LengthUTF8(mInputElem->GetValue()));
mInputElem->SetSelectionRange(0, end);
set_selected(true);
mInputListeners.emplace_back(std::make_unique<ScopedEventListener>(
mInputElem, Rml::EventId::Keydown, [this](Rml::Event& event) {
const auto cmd = map_nav_event(event);
if (cmd == NavCommand::Confirm) {
request_stop_editing(true, true);
event.StopImmediatePropagation();
} else if (cmd == NavCommand::Cancel) {
request_stop_editing(false, true);
event.StopImmediatePropagation();
}
}));
mInputListeners.emplace_back(std::make_unique<ScopedEventListener>(
mInputElem, Rml::EventId::Click, [](Rml::Event& event) { event.StopPropagation(); }));
mInputListeners.emplace_back(std::make_unique<ScopedEventListener>(mInputElem,
Rml::EventId::Blur, [this](Rml::Event&) { request_stop_editing(true, false); }));
}
void request_stop_editing(bool commit, bool refocusRoot) {
mPendingStopEditing = true;
mPendingCommit = commit;
mPendingRefocusRoot = refocusRoot;
}
protected:
bool handle_nav_command(NavCommand cmd) override {
if (cmd == NavCommand::Confirm) {
if (mInputElem == nullptr) {
start_editing();
} else {
request_stop_editing(true, true);
}
return true;
} else if (cmd == NavCommand::Cancel) {
request_stop_editing(false, true);
return true;
}
return false;
}
virtual void set_value(Rml::String value) = 0;
private:
void stop_editing(bool commit = true, bool refocusRoot = false) {
if (mInputElem == nullptr) {
return;
}
mPendingStopEditing = false;
if (commit) {
set_value(mInputElem->GetValue());
}
mInputListeners.clear();
mRoot->RemoveChild(mInputElem);
mInputElem = nullptr;
mValueElem->SetProperty(Rml::PropertyId::Visibility, Rml::Style::Visibility::Visible);
set_selected(false);
if (refocusRoot) {
mRoot->Focus(true);
}
}
Rml::ElementFormControlInput* mInputElem = nullptr;
std::vector<std::unique_ptr<ScopedEventListener> > mInputListeners;
Rml::String mType;
int mMaxLength;
bool mPendingStopEditing = false;
bool mPendingCommit = true;
bool mPendingRefocusRoot = false;
};
class StringButton : public BaseStringButton {
public:
struct Props {
Rml::String key;
std::function<Rml::String()> getValue;
std::function<void(Rml::String)> setValue;
int maxLength = -1;
};
StringButton(Rml::Element* parent, Props props)
: BaseStringButton(parent,
{
.key = std::move(props.key),
.maxLength = props.maxLength,
}),
mGetValue(std::move(props.getValue)), mSetValue(std::move(props.setValue)) {}
protected:
Rml::String format_value() override { return mGetValue(); }
void set_value(Rml::String value) override {
if (mSetValue) {
mSetValue(std::move(value));
}
}
private:
std::function<Rml::String()> mGetValue;
std::function<void(Rml::String)> mSetValue;
};
struct IntSelectProps {
Rml::String key;
std::function<int()> getValue;
std::function<void(int)> setValue;
int min = 0;
int max = INT_MAX;
int step = 1;
};
class IntSelectButton : public BaseStringButton {
public:
using Props = IntSelectProps;
IntSelectButton(Rml::Element* parent, Props props)
: BaseStringButton(parent, {.key = std::move(props.key), .type = "number"}),
mGetValue(std::move(props.getValue)), mSetValue(std::move(props.setValue)),
mMin(props.min), mMax(props.max), mStep(props.step) {}
protected:
Rml::String format_value() override { return fmt::to_string(mGetValue()); }
void set_value(Rml::String value) override {
if (!mSetValue) {
return;
}
int parsedValue = 0;
const char* begin = value.data();
const char* end = begin + value.size();
const auto result = std::from_chars(begin, end, parsedValue);
if (result.ec != std::errc() || result.ptr != end) {
return;
}
mSetValue(std::clamp(parsedValue, mMin, mMax));
}
bool handle_nav_command(NavCommand cmd) override {
if (cmd == NavCommand::Left) {
mSetValue(std::clamp(mGetValue() - mStep, mMin, mMax));
return true;
} else if (cmd == NavCommand::Right) {
mSetValue(std::clamp(mGetValue() + mStep, mMin, mMax));
return true;
}
return BaseStringButton::handle_nav_command(cmd);
}
private:
std::function<int()> mGetValue;
std::function<void(int)> mSetValue;
int mMin;
int mMax;
int mStep;
};
EditorWindow::EditorWindow() {
add_tab("Player Status", [this](Rml::Element* content) {
auto& leftPane = add_child<Pane>(content, Pane::Direction::Vertical);
@@ -411,33 +196,33 @@ EditorWindow::EditorWindow() {
.setValue = set_horse_name,
.maxLength = 16,
});
leftPane.add_child<IntSelectButton>(leftPane.root(),
IntSelectButton::Props{
leftPane.add_child<NumberButton>(leftPane.root(),
NumberButton::Props{
.key = "Max Health",
.getValue = [] { return get_player_status()->getMaxLife(); },
.setValue = [](int value) { return get_player_status()->setMaxLife(value); },
.max = UINT16_MAX, // TODO: actual max
.max = UINT16_MAX, // TODO: actual max
});
leftPane.add_child<IntSelectButton>(leftPane.root(),
IntSelectButton::Props{
leftPane.add_child<NumberButton>(leftPane.root(),
NumberButton::Props{
.key = "Health",
.getValue = [] { return get_player_status()->getLife(); },
.setValue = [](int value) { return get_player_status()->setLife(value); },
.max = UINT16_MAX, // TODO: actual max
.max = UINT16_MAX, // TODO: actual max
});
leftPane.add_child<IntSelectButton>(leftPane.root(),
IntSelectButton::Props{
leftPane.add_child<NumberButton>(leftPane.root(),
NumberButton::Props{
.key = "Max Oil",
.getValue = [] { return get_player_status()->getMaxOil(); },
.setValue = [](int value) { return get_player_status()->setMaxOil(value); },
.max = UINT16_MAX, // TODO: actual max
.max = UINT16_MAX, // TODO: actual max
});
leftPane.add_child<IntSelectButton>(leftPane.root(),
IntSelectButton::Props{
leftPane.add_child<NumberButton>(leftPane.root(),
NumberButton::Props{
.key = "Oil",
.getValue = [] { return get_player_status()->getOil(); },
.setValue = [](int value) { return get_player_status()->setOil(value); },
.max = UINT16_MAX, // TODO: actual max
.max = UINT16_MAX, // TODO: actual max
});
leftPane.add_section("Equipment");