From 554ba645361be5f7221f8424c6b739c906303c8f Mon Sep 17 00:00:00 2001 From: Dario Date: Sat, 18 Jan 2025 13:02:19 -0300 Subject: [PATCH] Config submenu. --- CMakeLists.txt | 4 + assets/config_menu.rml | 7 +- assets/config_menu/mods.rml | 2 +- lib/RmlUi | 2 +- src/ui/elements/ui_clickable.cpp | 45 +++++++ src/ui/elements/ui_clickable.h | 20 ++++ src/ui/elements/ui_element.cpp | 46 ++++++- src/ui/elements/ui_element.h | 11 +- src/ui/elements/ui_radio.cpp | 5 + src/ui/elements/ui_radio.h | 7 ++ src/ui/elements/ui_slider.cpp | 142 ++++++++++++++++++++++ src/ui/elements/ui_slider.h | 50 ++++++++ src/ui/elements/ui_style.cpp | 22 ++++ src/ui/elements/ui_style.h | 2 +- src/ui/elements/ui_toggle.cpp | 10 +- src/ui/elements/ui_toggle.h | 2 +- src/ui/elements/ui_types.h | 32 +++++ src/ui/ui_config_sub_menu.cpp | 200 +++++++++++++++++++++++++++++++ src/ui/ui_config_sub_menu.h | 85 +++++++++++++ src/ui/ui_elements.cpp | 1 + src/ui/ui_elements.h | 1 + src/ui/ui_mod_details_panel.cpp | 11 ++ src/ui/ui_mod_details_panel.h | 5 +- src/ui/ui_mod_menu.cpp | 27 +++++ src/ui/ui_mod_menu.h | 5 + src/ui/ui_renderer.cpp | 87 ++++++++++++-- 26 files changed, 799 insertions(+), 32 deletions(-) create mode 100644 src/ui/elements/ui_clickable.cpp create mode 100644 src/ui/elements/ui_clickable.h create mode 100644 src/ui/elements/ui_radio.cpp create mode 100644 src/ui/elements/ui_radio.h create mode 100644 src/ui/elements/ui_slider.cpp create mode 100644 src/ui/elements/ui_slider.h create mode 100644 src/ui/ui_config_sub_menu.cpp create mode 100644 src/ui/ui_config_sub_menu.h 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