diff --git a/CMakeLists.txt b/CMakeLists.txt
index d2c2da6..1398588 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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
diff --git a/assets/config_menu.rml b/assets/config_menu.rml
index d18d254..1059216 100644
--- a/assets/config_menu.rml
+++ b/assets/config_menu.rml
@@ -20,14 +20,14 @@
}
.col {
flex: 1;
- text-align: center;
+ text-align: center;
}
-
-
+
+
@@ -104,6 +104,7 @@
+
diff --git a/lib/RmlUi b/lib/RmlUi
index b5bf23d..a893ea6 160000
--- a/lib/RmlUi
+++ b/lib/RmlUi
@@ -1 +1 @@
-Subproject commit b5bf23ddde92dc5bdbf3ab949cbab512fe866b31
+Subproject commit a893ea6386e0c842f90a726a53c9b9e888797519
diff --git a/src/ui/elements/ui_clickable.cpp b/src/ui/elements/ui_clickable.cpp
new file mode 100644
index 0000000..a1facc6
--- /dev/null
+++ b/src/ui/elements/ui_clickable.cpp
@@ -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 callback) {
+ pressed_callbacks.emplace_back(callback);
+ }
+
+ void Clickable::add_dragged_callback(std::function callback) {
+ dragged_callbacks.emplace_back(callback);
+ }
+
+};
\ No newline at end of file
diff --git a/src/ui/elements/ui_clickable.h b/src/ui/elements/ui_clickable.h
new file mode 100644
index 0000000..2c8eb04
--- /dev/null
+++ b/src/ui/elements/ui_clickable.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "ui_element.h"
+
+namespace recompui {
+
+ class Clickable : public Element {
+ protected:
+ std::vector> pressed_callbacks;
+ std::vector> 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 callback);
+ void add_dragged_callback(std::function callback);
+ };
+
+} // namespace recompui
\ No newline at end of file
diff --git a/src/ui/elements/ui_element.cpp b/src/ui/elements/ui_element.cpp
index bd8ffc8..3d7fc97 100644
--- a/src/ui/elements/ui_element.cpp
+++ b/src/ui/elements/ui_element.cpp
@@ -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();
}
-};
\ No newline at end of file
+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();
+}
+
+}
\ No newline at end of file
diff --git a/src/ui/elements/ui_element.h b/src/ui/elements/ui_element.h
index 396b350..07eef78 100644
--- a/src/ui/elements/ui_element.h
+++ b/src/ui/elements/ui_element.h
@@ -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 &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
\ No newline at end of file
diff --git a/src/ui/elements/ui_radio.cpp b/src/ui/elements/ui_radio.cpp
new file mode 100644
index 0000000..dae8f37
--- /dev/null
+++ b/src/ui/elements/ui_radio.cpp
@@ -0,0 +1,5 @@
+#include "ui_radio.h"
+
+namespace recompui {
+
+};
\ No newline at end of file
diff --git a/src/ui/elements/ui_radio.h b/src/ui/elements/ui_radio.h
new file mode 100644
index 0000000..f8a50cb
--- /dev/null
+++ b/src/ui/elements/ui_radio.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "ui_element.h"
+
+namespace recompui {
+
+} // namespace recompui
\ No newline at end of file
diff --git a/src/ui/elements/ui_slider.cpp b/src/ui/elements/ui_slider.cpp
new file mode 100644
index 0000000..9915f20
--- /dev/null
+++ b/src/ui/elements/ui_slider.cpp
@@ -0,0 +1,142 @@
+#include "ui_slider.h"
+
+#include
+
+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 callback) {
+ value_changed_callbacks.emplace_back(callback);
+ }
+
+} // namespace recompui
\ No newline at end of file
diff --git a/src/ui/elements/ui_slider.h b/src/ui/elements/ui_slider.h
new file mode 100644
index 0000000..74fb3b7
--- /dev/null
+++ b/src/ui/elements/ui_slider.h
@@ -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> 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 callback);
+ };
+
+} // namespace recompui
diff --git a/src/ui/elements/ui_style.cpp b/src/ui/elements/ui_style.cpp
index 1736501..57e5566 100644
--- a/src/ui/elements/ui_style.cpp
+++ b/src/ui/elements/ui_style.cpp
@@ -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
\ No newline at end of file
diff --git a/src/ui/elements/ui_style.h b/src/ui/elements/ui_style.h
index 69f71a8..163a539 100644
--- a/src/ui/elements/ui_style.h
+++ b/src/ui/elements/ui_style.h
@@ -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; }
};
diff --git a/src/ui/elements/ui_toggle.cpp b/src/ui/elements/ui_toggle.cpp
index 5a4bcec..af26844 100644
--- a/src/ui/elements/ui_toggle.cpp
+++ b/src/ui/elements/ui_toggle.cpp
@@ -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 {
diff --git a/src/ui/elements/ui_toggle.h b/src/ui/elements/ui_toggle.h
index 2afcd9b..0f3eab5 100644
--- a/src/ui/elements/ui_toggle.h
+++ b/src/ui/elements/ui_toggle.h
@@ -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.
diff --git a/src/ui/elements/ui_types.h b/src/ui/elements/ui_types.h
index 36813fa..2726fe8 100644
--- a/src/ui/elements/ui_types.h
+++ b/src/ui/elements/ui_types.h
@@ -22,9 +22,17 @@ namespace recompui {
Focus,
Hover,
Enable,
+ Drag,
Count
};
+ enum class DragPhase {
+ None,
+ Start,
+ Move,
+ End
+ };
+
template >>
constexpr uint32_t Events(Enum first) {
return 1u << static_cast(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;
diff --git a/src/ui/ui_config_sub_menu.cpp b/src/ui/ui_config_sub_menu.cpp
new file mode 100644
index 0000000..735e569
--- /dev/null
+++ b/src/ui/ui_config_sub_menu.cpp
@@ -0,0 +1,200 @@
+#include "ui_config_sub_menu.h"
+
+#include
+
+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 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 callback) {
+ enter_sub_menu_callback = callback;
+}
+
+void ConfigSubMenu::set_quit_sub_menu_callback(std::function 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(&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 callback) {
+ config_sub_menu->set_enter_sub_menu_callback(callback);
+}
+
+void ElementConfigSubMenu::set_quit_sub_menu_callback(std::function callback) {
+ config_sub_menu->set_quit_sub_menu_callback(callback);
+}
+
+ConfigSubMenu *ElementConfigSubMenu::get_config_sub_menu_element() const {
+ return config_sub_menu.get();
+}
+
+}
\ No newline at end of file
diff --git a/src/ui/ui_config_sub_menu.h b/src/ui/ui_config_sub_menu.h
new file mode 100644
index 0000000..627b915
--- /dev/null
+++ b/src/ui/ui_config_sub_menu.h
@@ -0,0 +1,85 @@
+#ifndef RECOMPUI_CONFIG_SUB_MENU_H
+#define RECOMPUI_CONFIG_SUB_MENU_H
+
+#include
+
+#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 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 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 enter_sub_menu_callback = nullptr;
+ std::function quit_sub_menu_callback = nullptr;
+ std::vector config_option_elements;
+ std::unordered_set 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 callback);
+ void set_quit_sub_menu_callback(std::function 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 callback);
+ void set_quit_sub_menu_callback(std::function callback);
+ ConfigSubMenu *get_config_sub_menu_element() const;
+private:
+ std::unique_ptr config_sub_menu;
+};
+
+}
+#endif
\ No newline at end of file
diff --git a/src/ui/ui_elements.cpp b/src/ui/ui_elements.cpp
index da37998..1201e30 100644
--- a/src/ui/ui_elements.cpp
+++ b/src/ui/ui_elements.cpp
@@ -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() {
diff --git a/src/ui/ui_elements.h b/src/ui/ui_elements.h
index 854a0ff..6a9bfb4 100644
--- a/src/ui/ui_elements.h
+++ b/src/ui/ui_elements.h
@@ -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();
diff --git a/src/ui/ui_mod_details_panel.cpp b/src/ui/ui_mod_details_panel.cpp
index 308da7a..87a9d68 100644
--- a/src/ui/ui_mod_details_panel.cpp
+++ b/src/ui/ui_mod_details_panel.cpp
@@ -59,6 +59,7 @@ ModDetailsPanel::ModDetailsPanel(Element *parent) : Element(parent) {
enable_toggle = context.create_element(buttons_container);
enable_toggle->add_checked_callback(std::bind(&ModDetailsPanel::enable_toggle_checked, this, std::placeholders::_1));
configure_button = context.create_element