mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-06-11 12:39:48 -04:00
More settings/editor components
This commit is contained in:
+21
-4
@@ -120,12 +120,15 @@ button {
|
||||
transition: background-color 0.1s linear-in-out, opacity 0.1s linear-in-out;
|
||||
}
|
||||
|
||||
.button.active, .button:hover, .button:focus-visible {
|
||||
.button:not(:disabled).active,
|
||||
.button:not(:disabled):hover,
|
||||
.button:not(:disabled):focus-visible {
|
||||
background-color: rgba(204, 184, 119, 20%);
|
||||
box-shadow: #C2A42D 0 0 0 2dp;
|
||||
}
|
||||
|
||||
.button.selected, .button:active {
|
||||
.button:not(:disabled).selected,
|
||||
.button:not(:disabled):active {
|
||||
opacity: 1;
|
||||
background-color: rgba(204, 184, 119, 40%);
|
||||
box-shadow: #C2A42D 0 0 0 2dp;
|
||||
@@ -143,25 +146,39 @@ button {
|
||||
transition: background-color 0.1s linear-in-out, opacity 0.1s linear-in-out;
|
||||
}
|
||||
|
||||
.select-button.active, .select-button:hover, .select-button:focus-visible {
|
||||
.select-button:not(:disabled).active,
|
||||
.select-button:not(:disabled):hover,
|
||||
.select-button:not(:disabled):focus-visible {
|
||||
background-color: rgba(204, 184, 119, 20%);
|
||||
box-shadow: #C2A42D 0 0 0 2dp;
|
||||
}
|
||||
|
||||
.select-button.selected, .select-button:active {
|
||||
.select-button:not(:disabled).selected,
|
||||
.select-button:not(:disabled):active {
|
||||
opacity: 1;
|
||||
background-color: rgba(204, 184, 119, 40%);
|
||||
box-shadow: #C2A42D 0 0 0 2dp;
|
||||
}
|
||||
|
||||
.select-button:disabled {
|
||||
opacity: 0.35;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.select-button .key {
|
||||
font-family: "Fira Sans Condensed";
|
||||
font-weight: bold;
|
||||
font-size: 18dp;
|
||||
text-transform: uppercase;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.select-button .value {
|
||||
margin-left: auto;
|
||||
font-size: 20dp;
|
||||
}
|
||||
|
||||
.select-button input {
|
||||
text-align: right;
|
||||
font-size: 20dp;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <RmlUi/Core.h>
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Rml {
|
||||
@@ -27,6 +28,9 @@ public:
|
||||
|
||||
void listen(Rml::Element* element, Rml::EventId event, ScopedEventListener::Callback callback,
|
||||
bool capture = false);
|
||||
void listen(Rml::EventId event, ScopedEventListener::Callback callback, bool capture = false) {
|
||||
listen(mRoot, event, std::move(callback), capture);
|
||||
}
|
||||
bool contains(Rml::Element* element) const;
|
||||
|
||||
template <typename T, typename... Args>
|
||||
|
||||
+267
-25
@@ -9,6 +9,8 @@
|
||||
#include "pane.hpp"
|
||||
#include "select_button.hpp"
|
||||
|
||||
#include <charconv>
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
aurora::Module Log{"dusk::ui::editor"};
|
||||
@@ -26,18 +28,26 @@ dSv_player_status_a_c* get_player_status() {
|
||||
|
||||
Rml::String get_player_name() {
|
||||
if (!has_save_data()) {
|
||||
return "Link";
|
||||
return "";
|
||||
}
|
||||
return dComIfGs_getPlayerName();
|
||||
}
|
||||
|
||||
void set_player_name(Rml::String name) {
|
||||
dComIfGs_setPlayerName(name.c_str());
|
||||
}
|
||||
|
||||
Rml::String get_horse_name() {
|
||||
if (!has_save_data()) {
|
||||
return "Epona";
|
||||
return "";
|
||||
}
|
||||
return dComIfGs_getHorseName();
|
||||
}
|
||||
|
||||
void set_horse_name(Rml::String name) {
|
||||
dComIfGs_setHorseName(name.c_str());
|
||||
}
|
||||
|
||||
Rml::String value_for_player_selection(const Rml::String& selection) {
|
||||
dSv_player_status_a_c* status = get_player_status();
|
||||
if (selection == "get_horse_name") {
|
||||
@@ -171,43 +181,275 @@ 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);
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Direction::Vertical);
|
||||
|
||||
leftPane.add_section("Player");
|
||||
leftPane.add_select_button({
|
||||
.key = "Player Name",
|
||||
.getValue = get_player_name,
|
||||
});
|
||||
leftPane.add_select_button({
|
||||
.key = "Horse Name",
|
||||
.getValue = get_horse_name,
|
||||
});
|
||||
leftPane.add_select_button({
|
||||
.key = "Max Health",
|
||||
.getValue = [] { return value_for_player_selection("max_health"); },
|
||||
});
|
||||
leftPane.add_select_button({
|
||||
.key = "Max Oil",
|
||||
.getValue = [] { return value_for_player_selection("max_oil"); },
|
||||
});
|
||||
leftPane.add_select_button({
|
||||
.key = "Oil",
|
||||
.getValue = [] { return value_for_player_selection("oil"); },
|
||||
});
|
||||
leftPane.add_child<StringButton>(leftPane.root(), StringButton::Props{
|
||||
.key = "Player Name",
|
||||
.getValue = get_player_name,
|
||||
.setValue = set_player_name,
|
||||
.maxLength = 16,
|
||||
});
|
||||
leftPane.add_child<StringButton>(leftPane.root(), StringButton::Props{
|
||||
.key = "Horse Name",
|
||||
.getValue = get_horse_name,
|
||||
.setValue = set_horse_name,
|
||||
.maxLength = 16,
|
||||
});
|
||||
leftPane.add_child<IntSelectButton>(leftPane.root(),
|
||||
IntSelectButton::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
|
||||
});
|
||||
leftPane.add_child<IntSelectButton>(leftPane.root(),
|
||||
IntSelectButton::Props{
|
||||
.key = "Health",
|
||||
.getValue = [] { return get_player_status()->getLife(); },
|
||||
.setValue = [](int value) { return get_player_status()->setLife(value); },
|
||||
.max = UINT16_MAX, // TODO: actual max
|
||||
});
|
||||
leftPane.add_child<IntSelectButton>(leftPane.root(),
|
||||
IntSelectButton::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
|
||||
});
|
||||
leftPane.add_child<IntSelectButton>(leftPane.root(),
|
||||
IntSelectButton::Props{
|
||||
.key = "Oil",
|
||||
.getValue = [] { return get_player_status()->getOil(); },
|
||||
.setValue = [](int value) { return get_player_status()->setOil(value); },
|
||||
.max = UINT16_MAX, // TODO: actual max
|
||||
});
|
||||
|
||||
leftPane.add_section("Equipment");
|
||||
leftPane.add_select_button({
|
||||
.key = "Equip X",
|
||||
.value = "TODO",
|
||||
.selected = true,
|
||||
});
|
||||
leftPane.add_select_button({
|
||||
.key = "Equip Y",
|
||||
.value = "TODO",
|
||||
.selected = false,
|
||||
});
|
||||
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Direction::Vertical);
|
||||
rightPane.add_button({
|
||||
.text = "Hello, world!",
|
||||
});
|
||||
|
||||
@@ -23,17 +23,29 @@ SelectButton::SelectButton(Rml::Element* parent, Props props) : Component(create
|
||||
mValueElem->SetClass("value", true);
|
||||
update_props(std::move(props));
|
||||
listen(mRoot, Rml::EventId::Click, [this](Rml::Event& event) {
|
||||
if (mProps.onPressed) {
|
||||
mProps.onPressed(*this, event);
|
||||
if (mProps.disabled) {
|
||||
return;
|
||||
}
|
||||
if (handle_nav_command(NavCommand::Confirm)) {
|
||||
event.StopPropagation();
|
||||
}
|
||||
});
|
||||
listen(mRoot, Rml::EventId::Keydown, [this](Rml::Event& event) {
|
||||
if (mProps.disabled) {
|
||||
return;
|
||||
}
|
||||
const auto cmd = map_nav_event(event);
|
||||
if (cmd != NavCommand::None && handle_nav_command(cmd)) {
|
||||
event.StopPropagation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SelectButton::update() {
|
||||
if (mProps.getValue) {
|
||||
set_value(mProps.getValue());
|
||||
bool SelectButton::focus() {
|
||||
if (mProps.disabled) {
|
||||
return false;
|
||||
}
|
||||
Component::update();
|
||||
return Component::focus();
|
||||
}
|
||||
|
||||
void SelectButton::set_selected(bool selected) {
|
||||
@@ -43,7 +55,21 @@ void SelectButton::set_selected(bool selected) {
|
||||
}
|
||||
}
|
||||
|
||||
void SelectButton::set_value(const Rml::String& value) {
|
||||
void SelectButton::set_disabled(bool disabled) {
|
||||
if (mProps.disabled != disabled) {
|
||||
if (disabled) {
|
||||
mRoot->SetAttribute("disabled", "");
|
||||
mRoot->SetPseudoClass("disabled", true);
|
||||
mRoot->Blur();
|
||||
} else {
|
||||
mRoot->RemoveAttribute("disabled");
|
||||
mRoot->SetPseudoClass("disabled", false);
|
||||
}
|
||||
mProps.disabled = disabled;
|
||||
}
|
||||
}
|
||||
|
||||
void SelectButton::set_value_label(const Rml::String& value) {
|
||||
if (mProps.value != value) {
|
||||
mValueElem->SetInnerRML(escape(value));
|
||||
mProps.value = value;
|
||||
@@ -54,9 +80,18 @@ void SelectButton::update_props(Props props) {
|
||||
if (mProps.key != props.key) {
|
||||
mKeyElem->SetInnerRML(escape(props.key));
|
||||
}
|
||||
set_value(props.value);
|
||||
set_value_label(props.value);
|
||||
set_selected(props.selected);
|
||||
set_disabled(props.disabled);
|
||||
mProps = std::move(props);
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
bool SelectButton::handle_nav_command(NavCommand cmd) {
|
||||
if (cmd == NavCommand::Confirm) {
|
||||
set_selected(!get_selected());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "component.hpp"
|
||||
#include "ui.hpp"
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
@@ -9,9 +10,8 @@ class SelectButton;
|
||||
struct SelectButtonProps {
|
||||
Rml::String key;
|
||||
Rml::String value;
|
||||
std::function<Rml::String()> getValue;
|
||||
std::function<void(SelectButton& self, Rml::Event&)> onPressed;
|
||||
bool selected = false;
|
||||
bool disabled = false;
|
||||
};
|
||||
|
||||
class SelectButton : public Component {
|
||||
@@ -20,19 +20,22 @@ public:
|
||||
|
||||
SelectButton(Rml::Element* parent, SelectButtonProps props);
|
||||
|
||||
void update() override;
|
||||
bool focus() override;
|
||||
|
||||
void set_selected(bool selected);
|
||||
bool get_selected() const { return mProps.selected; }
|
||||
void set_disabled(bool disabled);
|
||||
bool get_disabled() const { return mProps.disabled; }
|
||||
|
||||
void set_value(const Rml::String& value);
|
||||
void set_value_label(const Rml::String& value);
|
||||
|
||||
private:
|
||||
protected:
|
||||
void update_props(Props props);
|
||||
virtual bool handle_nav_command(NavCommand cmd);
|
||||
|
||||
SelectButtonProps mProps;
|
||||
Rml::Element* mKeyElem = nullptr;
|
||||
Rml::Element* mValueElem = nullptr;
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
} // namespace dusk::ui
|
||||
|
||||
+115
-43
@@ -8,6 +8,8 @@
|
||||
#include "pane.hpp"
|
||||
#include "ui.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
template <typename T>
|
||||
@@ -15,81 +17,109 @@ struct ConfigProps {
|
||||
Rml::String key;
|
||||
ConfigVar<T>* value;
|
||||
std::function<void(T)> onChange;
|
||||
std::function<bool()> isDisabled;
|
||||
Rml::String helpText;
|
||||
Pane* rightPane = nullptr;
|
||||
bool selected = false;
|
||||
};
|
||||
|
||||
class ConfigBoolSelect : public SelectButton {
|
||||
template <typename T>
|
||||
class ConfigSelect : public SelectButton {
|
||||
public:
|
||||
using Props = ConfigProps<bool>;
|
||||
using Props = ConfigProps<T>;
|
||||
|
||||
ConfigBoolSelect(Rml::Element* parent, Props props)
|
||||
: SelectButton(
|
||||
parent, {
|
||||
.key = std::move(props.key),
|
||||
.getValue = [this] { return mValue->getValue() ? "On" : "Off"; },
|
||||
.onPressed = [this](SelectButton&, Rml::Event&) { toggle_value(); },
|
||||
.selected = props.selected,
|
||||
}),
|
||||
mValue(props.value), onChange(std::move(props.onChange)),
|
||||
ConfigSelect(Rml::Element* parent, Props props)
|
||||
: SelectButton(parent, {std::move(props.key)}), mVar(props.value),
|
||||
mOnChange(std::move(props.onChange)), mIsDisabled(std::move(props.isDisabled)),
|
||||
mHelpText(std::move(props.helpText)), mRightPane(props.rightPane) {
|
||||
if (!mHelpText.empty() && mRightPane != nullptr) {
|
||||
listen(nullptr, Rml::EventId::Focus, [this](Rml::Event&) {
|
||||
listen(Rml::EventId::Focus, [this](Rml::Event&) {
|
||||
mRightPane->clear();
|
||||
mRightPane->add_text(mHelpText);
|
||||
});
|
||||
listen(nullptr, Rml::EventId::Mouseover, [this](Rml::Event&) {
|
||||
listen(Rml::EventId::Mouseover, [this](Rml::Event&) {
|
||||
mRightPane->clear();
|
||||
mRightPane->add_text(mHelpText);
|
||||
});
|
||||
}
|
||||
listen(nullptr, Rml::EventId::Keydown, [this](Rml::Event& event) {
|
||||
const auto cmd = map_nav_event(event);
|
||||
if (cmd == NavCommand::Left || cmd == NavCommand::Right || cmd == NavCommand::Confirm) {
|
||||
toggle_value();
|
||||
event.StopPropagation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
void toggle_value() {
|
||||
const bool newValue = !mValue->getValue();
|
||||
mValue->setValue(newValue);
|
||||
if (onChange) {
|
||||
onChange(newValue);
|
||||
void update() override {
|
||||
if (mIsDisabled) {
|
||||
set_disabled(mIsDisabled());
|
||||
}
|
||||
set_value_label(get_value());
|
||||
SelectButton::update();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual Rml::String get_value() = 0;
|
||||
|
||||
void set_value(T newValue) {
|
||||
if (mVar->getValue() == newValue) {
|
||||
return;
|
||||
}
|
||||
mVar->setValue(newValue);
|
||||
if (mOnChange) {
|
||||
mOnChange(newValue);
|
||||
}
|
||||
config::Save();
|
||||
}
|
||||
|
||||
ConfigVar<bool>* mValue;
|
||||
std::function<void(bool)> onChange;
|
||||
ConfigVar<T>* mVar;
|
||||
std::function<void(T)> mOnChange;
|
||||
std::function<bool()> mIsDisabled;
|
||||
Rml::String mHelpText;
|
||||
Pane* mRightPane;
|
||||
};
|
||||
|
||||
class ConfigBoolSelect : public ConfigSelect<bool> {
|
||||
public:
|
||||
ConfigBoolSelect(Rml::Element* parent, Props props) : ConfigSelect(parent, std::move(props)) {}
|
||||
|
||||
protected:
|
||||
Rml::String get_value() override { return mVar->getValue() ? "On" : "Off"; }
|
||||
bool handle_nav_command(NavCommand cmd) override {
|
||||
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left || cmd == NavCommand::Right) {
|
||||
set_value(!mVar->getValue());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class ConfigIntPercentSelect : public ConfigSelect<int> {
|
||||
public:
|
||||
ConfigIntPercentSelect(Rml::Element* parent, Props props)
|
||||
: ConfigSelect(parent, std::move(props)) {}
|
||||
|
||||
protected:
|
||||
Rml::String get_value() override { return fmt::format("{}%", mVar->getValue()); }
|
||||
bool handle_nav_command(NavCommand cmd) override {
|
||||
if (cmd == NavCommand::Left) {
|
||||
set_value(std::max(mVar->getValue() - 1, 0));
|
||||
return true;
|
||||
} else if (cmd == NavCommand::Right) {
|
||||
set_value(std::min(mVar->getValue() + 1, 100));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
SettingsWindow::SettingsWindow() {
|
||||
add_tab("Audio", [this](Rml::Element* content) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Direction::Vertical);
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Direction::Vertical);
|
||||
|
||||
leftPane.add_section("Volume");
|
||||
{
|
||||
auto& btn = leftPane.add_select_button({
|
||||
leftPane.add_child<ConfigIntPercentSelect>(leftPane.root(),
|
||||
ConfigIntPercentSelect::Props{
|
||||
.key = "Master Volume",
|
||||
.getValue =
|
||||
[] { return fmt::format("{}%", getSettings().audio.masterVolume.getValue()); },
|
||||
.value = &getSettings().audio.masterVolume,
|
||||
.onChange = [](int value) { audio::SetMasterVolume(value / 100.f); },
|
||||
.helpText = "Adjusts the volume of all sounds in the game.",
|
||||
.rightPane = &rightPane,
|
||||
});
|
||||
btn.listen(nullptr, Rml::EventId::Focus, [&](Rml::Event&) {
|
||||
rightPane.clear();
|
||||
rightPane.add_text("Adjusts the volume of all sounds in the game.");
|
||||
});
|
||||
btn.listen(nullptr, Rml::EventId::Mouseover, [&](Rml::Event&) {
|
||||
rightPane.clear();
|
||||
rightPane.add_text("Adjusts the volume of all sounds in the game.");
|
||||
});
|
||||
}
|
||||
|
||||
leftPane.add_section("Effects");
|
||||
leftPane.add_child<ConfigBoolSelect>(
|
||||
@@ -119,7 +149,49 @@ SettingsWindow::SettingsWindow() {
|
||||
});
|
||||
|
||||
add_tab("Cheats", [this](Rml::Element* content) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Direction::Vertical);
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Direction::Vertical);
|
||||
|
||||
auto addCheat = [&](const Rml::String& key, ConfigVar<bool>& value,
|
||||
const Rml::String& helpText) {
|
||||
leftPane.add_child<ConfigBoolSelect>(
|
||||
leftPane.root(), ConfigBoolSelect::Props{
|
||||
.key = key,
|
||||
.value = &value,
|
||||
.isDisabled = [] { return !getSettings().game.speedrunMode; },
|
||||
.helpText = helpText,
|
||||
.rightPane = &rightPane,
|
||||
});
|
||||
};
|
||||
|
||||
leftPane.add_section("Resources");
|
||||
addCheat("Infinite Hearts", getSettings().game.infiniteHearts, "Keeps your health full.");
|
||||
addCheat(
|
||||
"Infinite Arrows", getSettings().game.infiniteArrows, "Keeps your arrow count full.");
|
||||
addCheat("Infinite Bombs", getSettings().game.infiniteBombs, "Keeps all bomb bags full.");
|
||||
addCheat("Infinite Oil", getSettings().game.infiniteOil, "Keeps your lantern oil full.");
|
||||
addCheat("Infinite Oxygen", getSettings().game.infiniteOxygen,
|
||||
"Keeps your underwater oxygen meter full.");
|
||||
addCheat(
|
||||
"Infinite Rupees", getSettings().game.infiniteRupees, "Keeps your rupee count full.");
|
||||
addCheat("No Item Timer", getSettings().game.enableIndefiniteItemDrops,
|
||||
"Item drops such as rupees and hearts will never disappear after they drop.");
|
||||
|
||||
leftPane.add_section("Abilities");
|
||||
addCheat(
|
||||
"Moon Jump (R+A)", getSettings().game.moonJump, "Hold R and A to rise into the air.");
|
||||
addCheat("Super Clawshot", getSettings().game.superClawshot,
|
||||
"Extends clawshot behavior beyond the normal game rules.");
|
||||
addCheat("Always Greatspin", getSettings().game.alwaysGreatspin,
|
||||
"Allows the Great Spin attack without requiring full health.");
|
||||
addCheat("Fast Iron Boots", getSettings().game.enableFastIronBoots,
|
||||
"Speeds up movement while wearing the Iron Boots.");
|
||||
addCheat("Can Transform Anywhere", getSettings().game.canTransformAnywhere,
|
||||
"Allows transforming even if NPCs are looking.");
|
||||
addCheat("Fast Spinner", getSettings().game.fastSpinner,
|
||||
"Speeds up Spinner movement while holding R.");
|
||||
addCheat("Free Magic Armor", getSettings().game.freeMagicArmor,
|
||||
"Lets the magic armor work without consuming rupees.");
|
||||
});
|
||||
|
||||
add_tab("Gameplay", [this](Rml::Element* content) {
|
||||
@@ -177,4 +249,4 @@ SettingsWindow::SettingsWindow() {
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
} // namespace dusk::ui
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "ui.hpp"
|
||||
|
||||
namespace dusk::ui {
|
||||
static aurora::Module Log{"dusk::ui::window"};
|
||||
|
||||
Window::Window() {
|
||||
auto* context = aurora::rmlui::get_context();
|
||||
@@ -115,6 +114,7 @@ void Window::add_tab(const Rml::String& title, TabBuilder builder) {
|
||||
});
|
||||
if (index == mSelectedTabIndex && builder) {
|
||||
builder(mDocument->GetElementById("content"));
|
||||
focus_active_tab();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,7 @@ bool Window::handle_tab_bar_nav(Rml::Event& event, NavCommand cmd) noexcept {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Window::handle_content_nav(Rml::Event& event, NavCommand cmd) noexcept {
|
||||
if (cmd == NavCommand::Up || cmd == NavCommand::Cancel) {
|
||||
return focus_active_tab();
|
||||
|
||||
@@ -590,9 +590,9 @@ int game_main(int argc, char* argv[]) {
|
||||
|
||||
// TODO: just for testing
|
||||
auto& editorWindow = dusk::ui::add_window(std::make_unique<dusk::ui::EditorWindow>());
|
||||
// editorWindow.show();
|
||||
editorWindow.show();
|
||||
auto& settingsWindow = dusk::ui::add_window(std::make_unique<dusk::ui::SettingsWindow>());
|
||||
settingsWindow.show();
|
||||
// settingsWindow.show();
|
||||
|
||||
std::string dvd_path;
|
||||
bool dvd_opened = false;
|
||||
|
||||
Reference in New Issue
Block a user