Config submenu.

This commit is contained in:
Dario 2025-01-18 13:02:19 -03:00 committed by Mr-Wiseguy
parent 5c11adda89
commit f153fd1b8f
26 changed files with 799 additions and 32 deletions

View File

@ -156,6 +156,7 @@ set (SOURCES
${CMAKE_SOURCE_DIR}/src/ui/ui_renderer.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_config_sub_menu.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_color_hack.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_rml_hacks.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_elements.cpp
@ -176,11 +177,14 @@ set (SOURCES
${CMAKE_SOURCE_DIR}/src/ui/elements/ElementOptionTypeTextField.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/presets.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_button.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_clickable.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_container.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_element.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_image.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_label.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_radio.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_scroll_container.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_slider.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_style.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_toggle.cpp

View File

@ -20,14 +20,14 @@
}
.col {
flex: 1;
text-align: center;
text-align: center;
}
</style>
<link type="text/template" href="config_menu/general.rml" />
<link type="text/template" href="config_menu/controls.rml" />
<link type="text/template" href="config_menu/graphics.rml" />
<link type="text/template" href="config_menu/sound.rml" />
<link type="text/template" href="config_menu/mods.rml" />
<link type="text/template" href="config_menu/sound.rml" />
<link type="text/template" href="config_menu/mods.rml" />
<link type="text/template" href="config_menu/debug.rml" />
<link type="text/template" href="config_menu/cheats.rml" />
<link type="text/template" href="components/prompt.rml" />
@ -104,6 +104,7 @@
<svg src="icons/X.svg" />
</button>
</div>
<recomp-config-sub-menu id="config_sub_menu" />
</div>
<div
class="centered-page__controls"

View File

@ -3,7 +3,7 @@
</head>
<body>
<form class="config__form">
<recomp-mod-menu />
<recomp-mod-menu id="menu_mods" />
</form>
</body>
</template>

@ -1 +1 @@
Subproject commit b5bf23ddde92dc5bdbf3ab949cbab512fe866b31
Subproject commit a893ea6386e0c842f90a726a53c9b9e888797519

View File

@ -0,0 +1,45 @@
#include "ui_clickable.h"
namespace recompui {
static const std::string_view hover_state = "hover";
static const std::string_view disabled_state = "disabled";
Clickable::Clickable(Element *parent, bool draggable) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, draggable ? EventType::Drag : EventType::None)) {
if (draggable) {
set_drag(Drag::Drag);
}
}
void Clickable::process_event(const Event &e) {
switch (e.type) {
case EventType::Click:
for (const auto &function : pressed_callbacks) {
function(e.click.mouse.x, e.click.mouse.y);
}
break;
case EventType::Hover:
set_style_enabled(hover_state, e.hover.active);
break;
case EventType::Enable:
set_style_enabled(disabled_state, !e.enable.enable);
break;
case EventType::Drag:
for (const auto &function : dragged_callbacks) {
function(e.drag.mouse.x, e.drag.mouse.y, e.drag.phase);
}
break;
default:
break;
}
}
void Clickable::add_pressed_callback(std::function<void(float, float)> callback) {
pressed_callbacks.emplace_back(callback);
}
void Clickable::add_dragged_callback(std::function<void(float, float, DragPhase)> callback) {
dragged_callbacks.emplace_back(callback);
}
};

View File

@ -0,0 +1,20 @@
#pragma once
#include "ui_element.h"
namespace recompui {
class Clickable : public Element {
protected:
std::vector<std::function<void(float, float)>> pressed_callbacks;
std::vector<std::function<void(float, float, DragPhase)>> dragged_callbacks;
// Element overrides.
virtual void process_event(const Event &e) override;
public:
Clickable(Element *parent, bool draggable = false);
void add_pressed_callback(std::function<void(float, float)> callback);
void add_dragged_callback(std::function<void(float, float, DragPhase)> callback);
};
} // namespace recompui

View File

@ -16,6 +16,7 @@ Element::Element(Rml::Element *base) {
Element::Element(Element* parent, uint32_t events_enabled, Rml::String base_class) {
ContextId context = get_current_context();
base_owning = context.get_document()->CreateElement(base_class);
if (parent != nullptr) {
base = parent->base->AppendChild(std::move(base_owning));
parent->add_child(this);
@ -82,6 +83,12 @@ void Element::register_event_listeners(uint32_t events_enabled) {
base->AddEventListener(Rml::EventId::Mouseover, this);
base->AddEventListener(Rml::EventId::Mouseout, this);
}
if (events_enabled & Events(EventType::Drag)) {
base->AddEventListener(Rml::EventId::Drag, this);
base->AddEventListener(Rml::EventId::Dragstart, this);
base->AddEventListener(Rml::EventId::Dragend, this);
}
}
void Element::apply_style(Style *style) {
@ -135,6 +142,9 @@ void Element::ProcessEvent(Rml::Event &event) {
case Rml::EventId::Mousedown:
process_event(Event::click_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f)));
break;
case Rml::EventId::Drag:
process_event(Event::drag_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), DragPhase::Move));
break;
default:
break;
}
@ -154,6 +164,12 @@ void Element::ProcessEvent(Rml::Event &event) {
case Rml::EventId::Blur:
process_event(Event::focus_event(false));
break;
case Rml::EventId::Dragstart:
process_event(Event::drag_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), DragPhase::Start));
break;
case Rml::EventId::Dragend:
process_event(Event::drag_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), DragPhase::End));
break;
default:
break;
}
@ -212,10 +228,10 @@ bool Element::is_enabled() const {
}
void Element::set_text(const std::string &text) {
base->SetInnerRML(text);
base->SetInnerRML(std::string(text));
}
void Element::set_style_enabled(const std::string_view &style_name, bool enable) {
void Element::set_style_enabled(std::string_view style_name, bool enable) {
if (enable && style_active_set.find(style_name) == style_active_set.end()) {
// Style was disabled and will be enabled.
style_active_set.emplace(style_name);
@ -243,4 +259,28 @@ void Element::set_style_enabled(const std::string_view &style_name, bool enable)
apply_styles();
}
};
float Element::get_absolute_left() {
return base->GetAbsoluteLeft();
}
float Element::get_absolute_top() {
return base->GetAbsoluteTop();
}
float Element::get_client_left() {
return base->GetClientLeft();
}
float Element::get_client_top() {
return base->GetClientTop();
}
float Element::get_client_width() {
return base->GetClientWidth();
}
float Element::get_client_height() {
return base->GetClientHeight();
}
}

View File

@ -43,14 +43,19 @@ public:
Element(Element* parent, uint32_t events_enabled = 0, Rml::String base_class = "div");
virtual ~Element();
void clear_children();
void add_style(Style *style, const std::string_view style_name);
void add_style(Style *style, std::string_view style_name);
void add_style(Style *style, const std::initializer_list<std::string_view> &style_names);
void set_enabled(bool enabled);
bool is_enabled() const;
void set_text(const std::string &text);
void set_style_enabled(const std::string_view &style_name, bool enabled);
void set_style_enabled(std::string_view style_name, bool enabled);
bool is_element() override { return true; }
float get_absolute_left();
float get_absolute_top();
float get_client_left();
float get_client_top();
float get_client_width();
float get_client_height();
};
} // namespace recompui

View File

@ -0,0 +1,5 @@
#include "ui_radio.h"
namespace recompui {
};

View File

@ -0,0 +1,7 @@
#pragma once
#include "ui_element.h"
namespace recompui {
} // namespace recompui

View File

@ -0,0 +1,142 @@
#include "ui_slider.h"
#include <charconv>
namespace recompui {
void Slider::set_value_internal(double v, bool setup, bool trigger_callbacks) {
if (step_value != 0.0) {
v = std::lround(v / step_value) * step_value;
}
if (value != v || setup) {
value = v;
update_circle_position();
update_label_text();
if (trigger_callbacks) {
for (auto callback : value_changed_callbacks) {
callback(v);
}
}
}
}
void Slider::bar_clicked(float x, float) {
update_value_from_mouse(x);
}
void Slider::bar_dragged(float x, float, DragPhase) {
update_value_from_mouse(x);
}
void Slider::circle_dragged(float x, float, DragPhase) {
update_value_from_mouse(x);
}
void Slider::update_value_from_mouse(float x) {
double left = slider_element->get_absolute_left();
double width = slider_element->get_client_width();
double ratio = std::clamp((x - left) / width, 0.0, 1.0);
set_value_internal(min_value + ratio * (max_value - min_value), false, true);
}
void Slider::update_circle_position() {
double ratio = std::clamp((value - min_value) / (max_value - min_value), 0.0, 1.0);
circle_element->set_left(slider_width_dp * ratio);
}
void Slider::update_label_text() {
char text_buffer[32];
int precision = type == SliderType::Double ? 1 : 0;
auto result = std::to_chars(text_buffer, text_buffer + sizeof(text_buffer) - 1, value, std::chars_format::fixed, precision);
if (result.ec == std::errc()) {
if (type == SliderType::Percent) {
*result.ptr = '%';
result.ptr++;
}
value_label->set_text(std::string(text_buffer, result.ptr));
}
}
Slider::Slider(SliderType type, Element *parent) : Element(parent) {
this->type = type;
set_display(Display::Flex);
set_flex(1.0f, 1.0f, 100.0f, Unit::Percent);
set_flex_direction(FlexDirection::Row);
value_label = new Label("0", LabelStyle::Small, this);
value_label->set_margin_right(20.0f);
value_label->set_min_width(60.0f);
value_label->set_max_width(60.0f);
slider_element = new Element(this);
slider_element->set_width(slider_width_dp);
{
bar_element = new Clickable(slider_element, true);
bar_element->set_width(100.0f, Unit::Percent);
bar_element->set_height(2.0f);
bar_element->set_margin_top(8.0f);
bar_element->set_background_color(Color{ 255, 255, 255, 50 });
bar_element->add_pressed_callback(std::bind(&Slider::bar_clicked, this, std::placeholders::_1, std::placeholders::_2));
bar_element->add_dragged_callback(std::bind(&Slider::bar_dragged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
circle_element = new Clickable(slider_element, true);
circle_element->set_position(Position::Relative);
circle_element->set_width(16.0f);
circle_element->set_height(16.0f);
circle_element->set_margin_top(-8.0f);
circle_element->set_margin_right(-8.0f);
circle_element->set_margin_left(-8.0f);
circle_element->set_background_color(Color{ 204, 204, 204, 255 });
circle_element->set_border_radius(8.0f);
circle_element->add_dragged_callback(std::bind(&Slider::circle_dragged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
circle_element->set_cursor(Cursor::Pointer);
}
set_value_internal(value, true, false);
}
Slider::~Slider() {
}
void Slider::set_value(double v) {
set_value_internal(v, false, false);
}
double Slider::get_value() const {
return value;
}
void Slider::set_min_value(double v) {
min_value = v;
}
double Slider::get_min_value() const {
return min_value;
}
void Slider::set_max_value(double v) {
max_value = v;
}
double Slider::get_max_value() const {
return max_value;
}
void Slider::set_step_value(double v) {
step_value = v;
}
double Slider::get_step_value() const {
return step_value;
}
void Slider::add_value_changed_callback(std::function<void(double)> callback) {
value_changed_callbacks.emplace_back(callback);
}
} // namespace recompui

View File

@ -0,0 +1,50 @@
#pragma once
#include "ui_clickable.h"
#include "ui_label.h"
namespace recompui {
enum SliderType {
Double,
Percent,
Integer
};
class Slider : public Element {
private:
SliderType type = SliderType::Percent;
Label *value_label = nullptr;
Element *slider_element = nullptr;
Clickable *bar_element = nullptr;
Clickable *circle_element = nullptr;
double value = 50.0;
double min_value = 0.0;
double max_value = 100.0;
double step_value = 1.0;
float slider_width_dp = 300.0;
std::vector<std::function<void(double)>> value_changed_callbacks;
void set_value_internal(double v, bool setup, bool trigger_callbacks);
void bar_clicked(float x, float y);
void bar_dragged(float x, float y, DragPhase phase);
void circle_dragged(float x, float y, DragPhase phase);
void update_value_from_mouse(float x);
void update_circle_position();
void update_label_text();
public:
Slider(SliderType type, Element *parent);
virtual ~Slider();
void set_value(double v);
double get_value() const;
void set_min_value(double v);
double get_min_value() const;
void set_max_value(double v);
double get_max_value() const;
void set_step_value(double v);
double get_step_value() const;
void add_value_changed_callback(std::function<void(double)> callback);
};
} // namespace recompui

View File

@ -67,6 +67,24 @@ namespace recompui {
}
}
static Rml::Style::Drag to_rml(Drag drag) {
switch (drag) {
case Drag::None:
return Rml::Style::Drag::None;
case Drag::Drag:
return Rml::Style::Drag::Drag;
case Drag::DragDrop:
return Rml::Style::Drag::DragDrop;
case Drag::Block:
return Rml::Style::Drag::Block;
case Drag::Clone:
return Rml::Style::Drag::Clone;
default:
assert(false && "Unknown drag.");
return Rml::Style::Drag::None;
}
}
void Style::set_property(Rml::PropertyId property_id, const Rml::Property &property, Animation) {
property_map[property_id] = property;
}
@ -437,4 +455,8 @@ namespace recompui {
set_property(Rml::PropertyId::ColumnGap, Rml::Property(size, to_rml(unit)), animation);
}
void Style::set_drag(Drag drag) {
set_property(Rml::PropertyId::Drag, to_rml(drag), Animation());
}
} // namespace recompui

View File

@ -82,7 +82,7 @@ namespace recompui {
void set_gap(float size, Unit unit = Unit::Dp, Animation animation = Animation());
void set_row_gap(float size, Unit unit = Unit::Dp, Animation animation = Animation());
void set_column_gap(float size, Unit unit = Unit::Dp, Animation animation = Animation());
void set_drag(Drag drag);
virtual bool is_element() { return false; }
ResourceId get_resource_id() { return resource_id; }
};

View File

@ -46,16 +46,16 @@ namespace recompui {
floater->add_style(&floater_disabled_style, disabled_state);
floater->add_style(&floater_disabled_checked_style, { checked_state, disabled_state });
set_checked_internal(false, false, true);
set_checked_internal(false, false, true, false);
}
void Toggle::set_checked_internal(bool checked, bool animate, bool setup) {
void Toggle::set_checked_internal(bool checked, bool animate, bool setup, bool trigger_callbacks) {
if (this->checked != checked || setup) {
this->checked = checked;
floater->set_left(floater_left_target(), Unit::Dp, animate ? Animation::tween(0.1f) : Animation::set());
if (!setup) {
if (trigger_callbacks) {
for (const auto &function : checked_callbacks) {
function(checked);
}
@ -74,7 +74,7 @@ namespace recompui {
switch (e.type) {
case EventType::Click:
if (is_enabled()) {
set_checked_internal(!checked, true, false);
set_checked_internal(!checked, true, false, true);
}
break;
@ -93,7 +93,7 @@ namespace recompui {
}
void Toggle::set_checked(bool checked) {
set_checked_internal(checked, false, false);
set_checked_internal(checked, false, false, false);
}
bool Toggle::is_checked() const {

View File

@ -18,7 +18,7 @@ namespace recompui {
Style floater_disabled_checked_style;
bool checked = false;
void set_checked_internal(bool checked, bool animate, bool setup);
void set_checked_internal(bool checked, bool animate, bool setup, bool trigger_callbacks);
float floater_left_target() const;
// Element overrides.

View File

@ -22,9 +22,17 @@ namespace recompui {
Focus,
Hover,
Enable,
Drag,
Count
};
enum class DragPhase {
None,
Start,
Move,
End
};
template <typename Enum, typename = std::enable_if_t<std::is_enum_v<Enum>>>
constexpr uint32_t Events(Enum first) {
return 1u << static_cast<uint32_t>(first);
@ -44,6 +52,8 @@ namespace recompui {
EventType type;
union {
uint64_t raw;
struct {
Mouse mouse;
} click;
@ -59,6 +69,11 @@ namespace recompui {
struct {
bool enable;
} enable;
struct {
Mouse mouse;
DragPhase phase;
} drag;
};
static Event click_event(float x, float y) {
@ -89,6 +104,15 @@ namespace recompui {
e.enable.enable = enable;
return e;
}
static Event drag_event(float x, float y, DragPhase phase) {
Event e = {};
e.type = EventType::Drag;
e.drag.mouse.x = x;
e.drag.mouse.y = y;
e.drag.phase = phase;
return e;
}
};
enum class Display {
@ -154,6 +178,14 @@ namespace recompui {
Justify
};
enum class Drag {
None,
Drag,
DragDrop,
Block,
Clone
};
struct Animation {
AnimationType type = AnimationType::None;
float duration = 0.0f;

View File

@ -0,0 +1,200 @@
#include "ui_config_sub_menu.h"
#include <cassert>
namespace recompui {
// ConfigOptionElement
void ConfigOptionElement::process_event(const Event &e) {
switch (e.type) {
case EventType::Hover:
hover_callback(this, e.hover.active);
break;
default:
assert(false && "Unknown event type.");
break;
}
}
ConfigOptionElement::ConfigOptionElement(Element *parent) : Element(parent, Events(EventType::Hover)) {
set_min_height(100.0f);
name_label = new Label(LabelStyle::Normal, this);
}
ConfigOptionElement::~ConfigOptionElement() {
}
void ConfigOptionElement::set_name(std::string_view name) {
this->name = name;
name_label->set_text(name);
}
void ConfigOptionElement::set_description(std::string_view description) {
this->description = description;
}
void ConfigOptionElement::set_hover_callback(std::function<void(ConfigOptionElement *, bool)> callback) {
hover_callback = callback;
}
const std::string &ConfigOptionElement::get_description() const {
return description;
}
// ConfigOptionSlider
void ConfigOptionSlider::slider_value_changed(double v) {
// TODO: Hook up to whatever API Recomp exposes to set the value of the persisent configuration in mods.
printf("%s changed to %f.\n", name.c_str(), v);
}
ConfigOptionSlider::ConfigOptionSlider(Element *parent) : ConfigOptionElement(parent) {
slider = new Slider(SliderType::Percent, this);
slider->add_value_changed_callback(std::bind(&ConfigOptionSlider::slider_value_changed, this, std::placeholders::_1));
}
ConfigOptionSlider::~ConfigOptionSlider() {
}
void ConfigOptionSlider::set_value(double v) {
slider->set_value(v);
}
void ConfigOptionSlider::set_min_value(double v) {
slider->set_min_value(v);
}
void ConfigOptionSlider::set_max_value(double v) {
slider->set_max_value(v);
}
// ConfigSubMenu
void ConfigSubMenu::back_button_pressed() {
if (quit_sub_menu_callback != nullptr) {
quit_sub_menu_callback();
}
}
void ConfigSubMenu::option_hovered(ConfigOptionElement *option, bool active) {
if (active) {
hover_option_elements.emplace(option);
}
else {
hover_option_elements.erase(option);
}
if (hover_option_elements.empty()) {
description_label->set_text("");
}
else {
description_label->set_text((*hover_option_elements.begin())->get_description());
}
}
ConfigSubMenu::ConfigSubMenu(Element *parent) : Element(parent) {
set_display(Display::Flex);
set_flex(1, 1, 100.0f, Unit::Percent);
set_flex_direction(FlexDirection::Column);
set_height(100.0f, Unit::Percent);
header_container = new Container(FlexDirection::Row, JustifyContent::FlexStart, this);
{
back_button = new Button("Back", ButtonStyle::Secondary, header_container);
back_button->add_pressed_callback(std::bind(&ConfigSubMenu::back_button_pressed, this));
title_label = new Label("Title", LabelStyle::Large, header_container);
}
body_container = new Container(FlexDirection::Row, JustifyContent::SpaceEvenly, this);
{
config_container = new Container(FlexDirection::Column, JustifyContent::Center, body_container);
config_container->set_display(Display::Block);
config_container->set_flex_basis(100.0f);
config_container->set_align_items(AlignItems::Center);
{
config_scroll_container = new ScrollContainer(ScrollDirection::Vertical, config_container);
}
description_label = new Label("Description", LabelStyle::Small, body_container);
description_label->set_min_width(800.0f);
}
}
ConfigSubMenu::~ConfigSubMenu() {
}
void ConfigSubMenu::enter(std::string_view title) {
title_label->set_text(title);
if (enter_sub_menu_callback != nullptr) {
enter_sub_menu_callback();
}
}
void ConfigSubMenu::clear_options() {
config_scroll_container->clear_children();
config_option_elements.clear();
hover_option_elements.clear();
}
void ConfigSubMenu::add_option(ConfigOptionElement *option, std::string_view name, std::string_view description) {
option->set_name(name);
option->set_description(description);
option->set_hover_callback(std::bind(&ConfigSubMenu::option_hovered, this, std::placeholders::_1, std::placeholders::_2));
config_option_elements.emplace_back(option);
}
void ConfigSubMenu::add_slider_option(std::string_view name, std::string_view description, double min, double max) {
ConfigOptionSlider *option_slider = new ConfigOptionSlider(config_scroll_container);
option_slider->set_min_value(min);
option_slider->set_max_value(max);
add_option(option_slider, name, description);
}
void ConfigSubMenu::set_enter_sub_menu_callback(std::function<void()> callback) {
enter_sub_menu_callback = callback;
}
void ConfigSubMenu::set_quit_sub_menu_callback(std::function<void()> callback) {
quit_sub_menu_callback = callback;
}
// ElementConfigSubMenu
ElementConfigSubMenu::ElementConfigSubMenu(const Rml::String &tag) : Rml::Element(tag) {
SetProperty(Rml::PropertyId::Display, Rml::Style::Display::None);
SetProperty("width", "100%");
SetProperty("height", "100%");
recompui::Element this_compat(this);
config_sub_menu = std::make_unique<ConfigSubMenu>(&this_compat);
}
ElementConfigSubMenu::~ElementConfigSubMenu() {
}
void ElementConfigSubMenu::set_display(bool display) {
SetProperty(Rml::PropertyId::Display, display ? Rml::Style::Display::Block : Rml::Style::Display::None);
}
void ElementConfigSubMenu::set_enter_sub_menu_callback(std::function<void()> callback) {
config_sub_menu->set_enter_sub_menu_callback(callback);
}
void ElementConfigSubMenu::set_quit_sub_menu_callback(std::function<void()> callback) {
config_sub_menu->set_quit_sub_menu_callback(callback);
}
ConfigSubMenu *ElementConfigSubMenu::get_config_sub_menu_element() const {
return config_sub_menu.get();
}
}

View File

@ -0,0 +1,85 @@
#ifndef RECOMPUI_CONFIG_SUB_MENU_H
#define RECOMPUI_CONFIG_SUB_MENU_H
#include <span>
#include "elements/ui_button.h"
#include "elements/ui_container.h"
#include "elements/ui_label.h"
#include "elements/ui_scroll_container.h"
#include "elements/ui_slider.h"
namespace recompui {
class ConfigOptionElement : public Element {
protected:
Label *name_label = nullptr;
std::string name;
std::string description;
std::function<void(ConfigOptionElement *, bool)> hover_callback = nullptr;
virtual void process_event(const Event &e) override;
public:
ConfigOptionElement(Element *parent);
virtual ~ConfigOptionElement();
void set_name(std::string_view name);
void set_description(std::string_view description);
void set_hover_callback(std::function<void(ConfigOptionElement *, bool)> callback);
const std::string &get_description() const;
};
class ConfigOptionSlider : public ConfigOptionElement {
protected:
Slider *slider = nullptr;
void slider_value_changed(double v);
public:
ConfigOptionSlider(Element *parent);
virtual ~ConfigOptionSlider();
void set_value(double v);
void set_min_value(double v);
void set_max_value(double v);
};
class ConfigSubMenu : public Element {
private:
Container *header_container = nullptr;
Button *back_button = nullptr;
Label *title_label = nullptr;
Container *body_container = nullptr;
Label *description_label = nullptr;
Container *config_container = nullptr;
ScrollContainer *config_scroll_container = nullptr;
std::function<void()> enter_sub_menu_callback = nullptr;
std::function<void()> quit_sub_menu_callback = nullptr;
std::vector<ConfigOptionElement *> config_option_elements;
std::unordered_set<ConfigOptionElement *> hover_option_elements;
void back_button_pressed();
void option_hovered(ConfigOptionElement *option, bool active);
void add_option(ConfigOptionElement *option, std::string_view name, std::string_view description);
public:
ConfigSubMenu(Element *parent);
virtual ~ConfigSubMenu();
void enter(std::string_view title);
void clear_options();
void add_slider_option(std::string_view name, std::string_view description, double min, double max);
void set_enter_sub_menu_callback(std::function<void()> callback);
void set_quit_sub_menu_callback(std::function<void()> callback);
};
class ElementConfigSubMenu : public Rml::Element {
public:
ElementConfigSubMenu(const Rml::String &tag);
virtual ~ElementConfigSubMenu();
void set_display(bool display);
void set_enter_sub_menu_callback(std::function<void()> callback);
void set_quit_sub_menu_callback(std::function<void()> callback);
ConfigSubMenu *get_config_sub_menu_element() const;
private:
std::unique_ptr<ConfigSubMenu> config_sub_menu;
};
}
#endif

View File

@ -20,6 +20,7 @@ static RecompElementConfig custom_elements[] = {
CUSTOM_ELEMENT("recomp-option-type-radio-tabs", recompui::ElementOptionTypeRadioTabs),
CUSTOM_ELEMENT("recomp-option-type-range", recompui::ElementOptionTypeRange),
CUSTOM_ELEMENT("recomp-mod-menu", recompui::ElementModMenu),
CUSTOM_ELEMENT("recomp-config-sub-menu", recompui::ElementConfigSubMenu),
};
void recompui::register_custom_elements() {

View File

@ -15,6 +15,7 @@
#include "elements/ElementOptionTypeTextField.h"
#include "elements/ElementDescription.h"
#include "ui_mod_menu.h"
#include "ui_config_sub_menu.h"
namespace recompui {
void register_custom_elements();

View File

@ -59,6 +59,7 @@ ModDetailsPanel::ModDetailsPanel(Element *parent) : Element(parent) {
enable_toggle = context.create_element<Toggle>(buttons_container);
enable_toggle->add_checked_callback(std::bind(&ModDetailsPanel::enable_toggle_checked, this, std::placeholders::_1));
configure_button = context.create_element<Button>("Configure", recompui::ButtonStyle::Secondary, buttons_container);
configure_button->add_pressed_callback(std::bind(&ModDetailsPanel::configure_button_pressed, this));
erase_button = context.create_element<Button>("Erase", recompui::ButtonStyle::Secondary, buttons_container);
}
}
@ -89,10 +90,20 @@ void ModDetailsPanel::set_mod_toggled_callback(std::function<void(bool)> callbac
mod_toggled_callback = callback;
}
void ModDetailsPanel::set_mod_configure_pressed_callback(std::function<void()> callback) {
mod_configure_pressed_callback = callback;
}
void ModDetailsPanel::enable_toggle_checked(bool checked) {
if (mod_toggled_callback != nullptr) {
mod_toggled_callback(checked);
}
}
void ModDetailsPanel::configure_button_pressed() {
if (mod_configure_pressed_callback != nullptr) {
mod_configure_pressed_callback();
}
}
} // namespace recompui

View File

@ -16,6 +16,7 @@ public:
virtual ~ModDetailsPanel();
void set_mod_details(const recomp::mods::ModDetails& details, bool mod_enabled, bool toggle_enabled);
void set_mod_toggled_callback(std::function<void(bool)> callback);
void set_mod_configure_pressed_callback(std::function<void()> callback);
private:
recomp::mods::ModDetails cur_details;
Container *thumbnail_container = nullptr;
@ -32,9 +33,11 @@ private:
Toggle *enable_toggle = nullptr;
Button *configure_button = nullptr;
Button *erase_button = nullptr;
std::function<void(bool)> mod_toggled_callback = {};
std::function<void(bool)> mod_toggled_callback = nullptr;
std::function<void()> mod_configure_pressed_callback = nullptr;
void enable_toggle_checked(bool checked);
void configure_button_pressed();
};
} // namespace recompui

View File

@ -81,6 +81,10 @@ void ModMenu::set_active_mod(int32_t mod_index) {
}
}
void ModMenu::set_config_sub_menu(ConfigSubMenu *config_sub_menu) {
ext_config_sub_menu = config_sub_menu;
}
void ModMenu::refresh_mods() {
recomp::mods::scan_mods();
mod_details = recomp::mods::get_mod_details(game_mod_id);
@ -93,6 +97,24 @@ void ModMenu::mod_toggled(bool enabled) {
}
}
void ModMenu::mod_configure_requested() {
if (active_mod_index >= 0) {
ext_config_sub_menu->clear_options();
ext_config_sub_menu->add_slider_option("Simple Option", "Description for simple option.", 0.0, 100.0);
ext_config_sub_menu->add_slider_option("Slider Option", "Description for slider option.", 0.0, 100.0);
ext_config_sub_menu->add_slider_option("Option B", "Description for option B.", 0.0, 100.0);
ext_config_sub_menu->add_slider_option("Option C", "Description for option C.", 0.0, 100.0);
ext_config_sub_menu->add_slider_option("Option D", "Description for option D.", 0.0, 100.0);
ext_config_sub_menu->add_slider_option("Option E", "Description for option E.", 0.0, 100.0);
ext_config_sub_menu->add_slider_option("Option F", "Description for option F.", 0.0, 100.0);
ext_config_sub_menu->add_slider_option("Option G", "Description for option G.", 0.0, 100.0);
ext_config_sub_menu->add_slider_option("Option H", "Description for option H.", 0.0, 100.0);
ext_config_sub_menu->add_slider_option("Option J", "Description for option J.", 0.0, 100.0);
ext_config_sub_menu->add_slider_option("Option K", "Description for option K.", 0.0, 100.0);
ext_config_sub_menu->enter(mod_details[active_mod_index].mod_id);
}
}
void ModMenu::create_mod_list() {
ContextId context = get_current_context();
@ -140,6 +162,7 @@ ModMenu::ModMenu(Element *parent) : Element(parent) {
mod_details_panel = context.create_element<ModDetailsPanel>(body_container);
mod_details_panel->set_mod_toggled_callback(std::bind(&ModMenu::mod_toggled, this, std::placeholders::_1));
mod_details_panel->set_mod_configure_pressed_callback(std::bind(&ModMenu::mod_configure_requested, this));
} // body_container
@ -179,4 +202,8 @@ ElementModMenu::~ElementModMenu() {
}
void ElementModMenu::set_config_sub_menu(ConfigSubMenu *config_sub_menu) {
mod_menu->set_config_sub_menu(config_sub_menu);
}
} // namespace recompui

View File

@ -3,6 +3,7 @@
#include "librecomp/mods.hpp"
#include "elements/ui_scroll_container.h"
#include "ui_config_sub_menu.h"
#include "ui_mod_details_panel.h"
namespace recompui {
@ -29,9 +30,11 @@ public:
ModMenu(Element *parent);
virtual ~ModMenu();
void set_active_mod(int32_t mod_index);
void set_config_sub_menu(ConfigSubMenu *config_sub_menu);
private:
void refresh_mods();
void mod_toggled(bool enabled);
void mod_configure_requested();
void create_mod_list();
Container *body_container = nullptr;
@ -40,6 +43,7 @@ private:
ModDetailsPanel *mod_details_panel = nullptr;
Container *footer_container = nullptr;
Button *refresh_button = nullptr;
ConfigSubMenu *ext_config_sub_menu = nullptr;
int32_t active_mod_index = -1;
std::vector<ModEntry *> mod_entries;
std::vector<recomp::mods::ModDetails> mod_details{};
@ -50,6 +54,7 @@ class ElementModMenu : public Rml::Element {
public:
ElementModMenu(const Rml::String& tag);
virtual ~ElementModMenu();
void set_config_sub_menu(ConfigSubMenu *config_sub_menu);
private:
std::unique_ptr<ModMenu> mod_menu;
};

View File

@ -29,6 +29,7 @@
#include "RmlUi_Platform_SDL.h"
#include "ui_elements.h"
#include "ui_mod_menu.h"
#include "librecomp/config.hpp"
#include "InterfaceVS.hlsl.spirv.h"
@ -753,7 +754,7 @@ Rml::Element* find_autofocus_element(Rml::Element* start) {
struct UIContext {
struct UIRenderContext render;
class {
class Context {
std::unordered_map<recompui::Menu, std::unique_ptr<recompui::MenuController>> menus;
std::unordered_map<recompui::Menu, Rml::ElementDocument*> documents;
Rml::ElementDocument* current_document;
@ -776,6 +777,10 @@ struct UIContext {
}
void swap_document(recompui::Menu menu) {
if (menu != recompui::Menu::Config) {
quit_sub_menu();
}
if (current_document != nullptr) {
Rml::Element* window_el = current_document->GetElementById("window");
if (window_el != nullptr) {
@ -804,21 +809,77 @@ struct UIContext {
mouse_is_active = false;
mouse_is_active_changed = false;
mouse_is_active_initialized = false;
if (menu == recompui::Menu::Config) {
recompui::ElementModMenu *mods_menu = get_mods_menu();
recompui::ElementConfigSubMenu *config_sub_menu = get_config_sub_menu();
if (mods_menu != nullptr && config_sub_menu != nullptr) {
mods_menu->set_config_sub_menu(config_sub_menu->get_config_sub_menu_element());
config_sub_menu->set_enter_sub_menu_callback(std::bind(&Context::enter_sub_menu, this));
config_sub_menu->set_quit_sub_menu_callback(std::bind(&Context::quit_sub_menu, this));
}
}
}
Rml::ElementTabSet *get_config_tabset() {
if (current_document != nullptr) {
Rml::Element *config_tabset_base = current_document->GetElementById("config_tabset");
if (config_tabset_base != nullptr) {
return rmlui_dynamic_cast<Rml::ElementTabSet *>(config_tabset_base);
}
}
return nullptr;
}
recompui::ElementModMenu *get_mods_menu() {
if (current_document != nullptr) {
Rml::Element *menu_mods_base = current_document->GetElementById("menu_mods");
if (menu_mods_base != nullptr) {
return rmlui_dynamic_cast<recompui::ElementModMenu *>(menu_mods_base);
}
}
return nullptr;
}
recompui::ElementConfigSubMenu *get_config_sub_menu() {
if (current_document != nullptr) {
Rml::Element *config_sub_menu_base = current_document->GetElementById("config_sub_menu");
if (config_sub_menu_base != nullptr) {
return rmlui_dynamic_cast<recompui::ElementConfigSubMenu *>(config_sub_menu_base);
}
}
return nullptr;
}
void swap_config_menu(recompui::ConfigSubmenu submenu) {
if (current_document != nullptr) {
Rml::Element* config_tabset_base = current_document->GetElementById("config_tabset");
if (config_tabset_base != nullptr) {
Rml::ElementTabSet* config_tabset = rmlui_dynamic_cast<Rml::ElementTabSet*>(config_tabset_base);
if (config_tabset != nullptr) {
config_tabset->SetActiveTab(static_cast<int>(submenu));
prev_focused = nullptr;
mouse_is_active = false;
mouse_is_active_changed = false;
mouse_is_active_initialized = false;
}
}
Rml::ElementTabSet* config_tabset = get_config_tabset();
if (config_tabset != nullptr) {
config_tabset->SetActiveTab(static_cast<int>(submenu));
prev_focused = nullptr;
mouse_is_active = false;
mouse_is_active_changed = false;
mouse_is_active_initialized = false;
}
}
void enter_sub_menu() {
Rml::ElementTabSet *config_tabset = get_config_tabset();
recompui::ElementConfigSubMenu *config_sub_menu = get_config_sub_menu();
if (config_tabset != nullptr && config_sub_menu != nullptr) {
config_tabset->SetProperty(Rml::PropertyId::Display, Rml::Style::Display::None);
config_sub_menu->set_display(true);
}
}
void quit_sub_menu() {
Rml::ElementTabSet *config_tabset = get_config_tabset();
recompui::ElementConfigSubMenu *config_sub_menu = get_config_sub_menu();
if (config_tabset != nullptr && config_sub_menu != nullptr) {
config_tabset->SetProperty(Rml::PropertyId::Display, Rml::Style::Display::Flex);
config_sub_menu->set_display(false);
}
}