mirror of
https://github.com/Zelda64Recomp/Zelda64Recomp
synced 2026-09-07 19:50:34 -04:00
Modding menu and UI and additional mod exports (#535)
* init config opt system w/ 3 types and description support * Move config registry/option to librecomp + added Color conf opt type * Updated color option type styling * Added dropdown option type * Added TextField option type * Button config type + callback wip * init mod menu + bem class + button presets * WIP mod menu, fix some warnings * Rewrite mod details under new UI system. * Refactored mods menu entirely. * Remove ModMenu.scss. * Take ownership of created pointers on Element class. * Add styles. * Multi-style state and disabled state propagation. * Switch to string views. * Convert to spaces, hook up mod enabled to toggle. * Mod menu progress. * Layout for mod details panel, add gap property setters * Update RmlUi for gap property in flexbox * Add slot_map and begin ui context * Implement context and resource storage slotmaps * Config submenu. * Refactored to account for context changes. * Turn off tab searching when submenu is open. * Revert accidental RmlUi downgrade * Upgrade RmlUi to 6.0 release * Text input. * Radio option. * Cleanup. * Refactor Rml document handling to use new ContextId system (prompts currently unimplemented) * Add support for config schema. * Split config sub menu into separate context and fix configure button, prevent infinite loop when looking for autofocus element * Reimplement mechanism to open the config menu to a specific tab * Begin implementing mod UI API * Link storage to mod menu. * Proper enum parsing. * Enable mod reordering. * Draggable improvements to mod menu and runtime update. * Adjust styling of submenu. * Mods folder button. * Linux build fixes. * Hook up new manifest fields to mod UI * Add basic thumbnail parsing functionality. * More style changes. * Implement update event for elements * Use RT64's texture laoding instead. * Restore spacer animations. * Animation API begone. * Auto-enabled mods. * Update runtime submodule and N64Recomp commit in CI for mod config API, remove unnecessary extern C * Sub menu display name, assert on text input. * Clamp delta time to fix UI disappearing on OS with timestamps that don't always increase. * Add a state for when no mods are installed. * Unify API function naming scheme and export relevant API functions * Add actor update/init events and save init event (#536) * Expose remaining property setters to mod UI API * Implemented mod UI callbacks * Implement actor extension data and use it for transform tagging * Zero the memory allocated to hold extended actor data * Implement label and textinput in mod UI API * Patch virtual address translation to support entire extended RAM address space (#533) * Download full target build of llvm in CI Windows runners to fix missing MIPS support and update N64Recomp CI commit * Enable triple buffering in RT64 (#546) * Implement controlling input capturing for mod UI contexts * Created mod UI API functions for setting visibility, setting text, and destroying elements * Fix errant RML tag in mod menu and insert breaks for newlines when setting element text * Fix compilation after rebase * Fixes for macOS * Set the blender description manually for the UI renderer * Created mod UI API functions for imageview elements * Switch to designated initializers to work around missing aggregate initialization compiler support * Update RT64 for driver bug workarounds and misc fixes * Update RT64 to fix native sampler issues with tile copies * Update RT64 for depth clear optimization and more native sampler changes * Update RT64 and allow it to choose the graphics API when set to Auto * Update runtime to allow renderers to choose the graphics API * Update RT64 to enable early Z test shader optimization * Implement data structure mod APIs * Update lunasvg to increase its minimum cmake version * Switch to runtime concatenation of function name in data API error reporting to fix Linux compilation issue * Add missing typename to fix compilation on some compilers * Update RT64 to fix failed assert with MSAA off * Reimplement prompts as a separate UI context and make it so the quit game prompt doesn't bring up the config menu * DnD prototype. * Fix to dynamic lib path and runtime commit. * Finish drag and drop mod installation, disable mod refresh button and code mod toggle when game starts * Remove std::format usage and add missing <list> includes to fix Linux/MacOS compilation * Switch to aggregate initialization for Version to work around missing implicit constructor on some compilers * Replace use of std::bind with lambdas * Add mod install button, put mod description in scroll container, minor mod menu tweaks * Update runtime to fix renderer shutdown race condition * Implement texture pack reordering * Add mod UI API exports for slider, password input, and label radio and expose RmlUi debugger on F8 * Update runtime for mod version export * Update runtime for save swapping mod API * Apply recomp.rcss to mod UI contexts (fixes scrolls) * Updated mod list styling (#561) * Updated mod list styling * mod entry max height * Update RT64 for v5 texture hash * Update runtime for mod API to get save file path * Add special config option id to control texture pack state for code mods * Update runtime for mod default enabled state * Add exports for stars' display lists (#563) * Update runtime to fix default value of enabled_by_default * Update runtime to allow NULL recomp_free * Implement navigation and focus styling for new UI framework (no manual overrides yet) * Fix the previous scissor state bleeding when drawing the RmlUi output onto the swapchain buffer * Use a multiple file select dialog for mod install button * Add mod export for loading UI image from bytes (png/dds) * Manual navigation in UI framework and WIP mod menu navigation * Repeat key events when holding down controller inputs for UI navigation * Patch AnimationContext_SetLoadFrame to allow custom animations (#564) * Close context when showing or hiding a context and reopen afterwards to prevent deadlocks * Add quotes around xdg-open and open commands to support paths with spaces * Update RT64 for high precision texture coordinates when using texture replacements * Add support for built-in mods and convert D-Pad to a built-in mod (#567) * Add embedded mod (using mm_recomp_draw_distance as an example). * Update runtime after merge * Experiment with removing the D-Pad. * Add event needed for dpad as mod, revert remaining changes in built-in patches for dpad * Add built-in dpad mod, add remaining event calls to input.c * Add built-in mods readme --------- Co-authored-by: Dario <dariosamo@gmail.com> * Fixing navigation of mods menu. * Focused state for mod entry. * Prevent hover styling and focus on input elements when disabled * Fix up/down navigation on text input elements * Set mod tab to navigate down to first mod, fix redundant mod scanning * Remove more redundant mod scanning and fix mods being scanned during gameplay * Update runtime for mod folder export * Improve radio navigation and setup mod config submenu navigation setup * Restore fd anywhere export functionality (#570) * fix fd * add comment back in * Make config tabset navigate down to first mod entry when mod menu is open, make mod configure screen focus on configure button after closing * Add navigation exports to mod UI API * Fix opening the config menu via keyboard/controller causing a double animation warning in RmlUi --------- Co-authored-by: Dario <dariosamo@gmail.com> Co-authored-by: thecozies <79979276+thecozies@users.noreply.github.com> Co-authored-by: Garrett Cox <garrettjcox@gmail.com> Co-authored-by: David Chavez <david@dcvz.io> Co-authored-by: danielryb <59661841+danielryb@users.noreply.github.com> Co-authored-by: Reonu <danileon95@gmail.com> Co-authored-by: LittleCube <littlecubehax@gmail.com>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
#include "ui_button.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Button::Button(Element *parent, const std::string &text, ButtonStyle style) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "button", true) {
|
||||
this->style = style;
|
||||
|
||||
enable_focus();
|
||||
|
||||
set_text(text);
|
||||
set_display(Display::Block);
|
||||
set_padding(23.0f);
|
||||
set_border_width(1.1f);
|
||||
set_border_radius(12.0f);
|
||||
set_font_size(28.0f);
|
||||
set_letter_spacing(3.08f);
|
||||
set_line_height(28.0f);
|
||||
set_font_style(FontStyle::Normal);
|
||||
set_font_weight(700);
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_color(Color{ 204, 204, 204, 255 });
|
||||
set_tab_index(TabIndex::Auto);
|
||||
hover_style.set_color(Color{ 242, 242, 242, 255 });
|
||||
focus_style.set_color(Color{ 242, 242, 242, 255 });
|
||||
disabled_style.set_color(Color{ 204, 204, 204, 128 });
|
||||
hover_disabled_style.set_color(Color{ 242, 242, 242, 128 });
|
||||
|
||||
const uint8_t border_opacity = 204;
|
||||
const uint8_t background_opacity = 13;
|
||||
const uint8_t border_hover_opacity = 255;
|
||||
const uint8_t background_hover_opacity = 76;
|
||||
switch (style) {
|
||||
case ButtonStyle::Primary: {
|
||||
set_border_color({ 185, 125, 242, border_opacity });
|
||||
set_background_color({ 185, 125, 242, background_opacity });
|
||||
hover_style.set_border_color({ 185, 125, 242, border_hover_opacity });
|
||||
hover_style.set_background_color({ 185, 125, 242, background_hover_opacity });
|
||||
focus_style.set_border_color({ 185, 125, 242, border_hover_opacity });
|
||||
focus_style.set_background_color({ 185, 125, 242, background_hover_opacity });
|
||||
disabled_style.set_border_color({ 185, 125, 242, border_opacity / 4 });
|
||||
disabled_style.set_background_color({ 185, 125, 242, background_opacity / 4 });
|
||||
hover_disabled_style.set_border_color({ 185, 125, 242, border_hover_opacity / 4 });
|
||||
hover_disabled_style.set_background_color({ 185, 125, 242, background_hover_opacity / 4 });
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Secondary: {
|
||||
set_border_color({ 23, 214, 232, border_opacity });
|
||||
set_background_color({ 23, 214, 232, background_opacity });
|
||||
hover_style.set_border_color({ 23, 214, 232, border_hover_opacity });
|
||||
hover_style.set_background_color({ 23, 214, 232, background_hover_opacity });
|
||||
focus_style.set_border_color({ 23, 214, 232, border_hover_opacity });
|
||||
focus_style.set_background_color({ 23, 214, 232, background_hover_opacity });
|
||||
disabled_style.set_border_color({ 23, 214, 232, border_opacity / 4 });
|
||||
disabled_style.set_background_color({ 23, 214, 232, background_opacity / 4 });
|
||||
hover_disabled_style.set_border_color({ 23, 214, 232, border_hover_opacity / 4 });
|
||||
hover_disabled_style.set_background_color({ 23, 214, 232, background_hover_opacity / 4 });
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false && "Unknown button style.");
|
||||
break;
|
||||
}
|
||||
|
||||
add_style(&hover_style, hover_state);
|
||||
add_style(&focus_style, focus_state);
|
||||
add_style(&disabled_style, disabled_state);
|
||||
add_style(&hover_disabled_style, { hover_state, disabled_state });
|
||||
|
||||
// transition: color 0.05s linear-in-out, background-color 0.05s linear-in-out;
|
||||
}
|
||||
|
||||
void Button::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
if (is_enabled()) {
|
||||
for (const auto &function : pressed_callbacks) {
|
||||
function();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Hover:
|
||||
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active && is_enabled());
|
||||
break;
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Focus:
|
||||
set_style_enabled(focus_state, std::get<EventFocus>(e.variant).active);
|
||||
break;
|
||||
case EventType::Update:
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Button::add_pressed_callback(std::function<void()> callback) {
|
||||
pressed_callbacks.emplace_back(callback);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
enum class ButtonStyle {
|
||||
Primary,
|
||||
Secondary
|
||||
};
|
||||
|
||||
class Button : public Element {
|
||||
protected:
|
||||
ButtonStyle style = ButtonStyle::Primary;
|
||||
Style hover_style;
|
||||
Style focus_style;
|
||||
Style disabled_style;
|
||||
Style hover_disabled_style;
|
||||
std::list<std::function<void()>> pressed_callbacks;
|
||||
|
||||
// Element overrides.
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "Button"; }
|
||||
public:
|
||||
Button(Element *parent, const std::string &text, ButtonStyle style);
|
||||
void add_pressed_callback(std::function<void()> callback);
|
||||
Style* get_hover_style() { return &hover_style; }
|
||||
Style* get_focus_style() { return &focus_style; }
|
||||
Style* get_disabled_style() { return &disabled_style; }
|
||||
Style* get_hover_disabled_style() { return &hover_disabled_style; }
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,62 @@
|
||||
#include "ui_clickable.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Clickable::Clickable(Element *parent, bool draggable) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, draggable ? EventType::Drag : EventType::None)) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
if (draggable) {
|
||||
set_drag(Drag::Drag);
|
||||
}
|
||||
}
|
||||
|
||||
void Clickable::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click: {
|
||||
if (is_enabled()) {
|
||||
const EventClick &click = std::get<EventClick>(e.variant);
|
||||
for (const auto &function : pressed_callbacks) {
|
||||
function(click.x, click.y);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
case EventType::Hover:
|
||||
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active && is_enabled());
|
||||
break;
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Drag: {
|
||||
if (is_enabled()) {
|
||||
const EventDrag &drag = std::get<EventDrag>(e.variant);
|
||||
for (const auto &function : dragged_callbacks) {
|
||||
function(drag.x, drag.y, 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);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
#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;
|
||||
std::string_view get_type_name() override { return "Clickable"; }
|
||||
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
|
||||
@@ -0,0 +1,13 @@
|
||||
#include "ui_container.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Container::Container(Element *parent, FlexDirection direction, JustifyContent justify_content, uint32_t events_enabled) : Element(parent, events_enabled) {
|
||||
set_display(Display::Flex);
|
||||
set_flex_direction(direction);
|
||||
set_justify_content(justify_content);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class Container : public Element {
|
||||
protected:
|
||||
std::string_view get_type_name() override { return "Container"; }
|
||||
public:
|
||||
Container(Element* parent, FlexDirection direction, JustifyContent justify_content, uint32_t events_enabled = 0);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,478 @@
|
||||
#include "RmlUi/Core/StringUtilities.h"
|
||||
|
||||
#include "overloaded.h"
|
||||
#include "recomp_ui.h"
|
||||
#include "ui_element.h"
|
||||
#include "../core/ui_context.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Element::Element(Rml::Element *base) {
|
||||
assert(base != nullptr);
|
||||
|
||||
this->base = base;
|
||||
this->base_owning = {};
|
||||
this->shim = true;
|
||||
}
|
||||
|
||||
Element::Element(Element* parent, uint32_t events_enabled, Rml::String base_class, bool can_set_text) : can_set_text(can_set_text) {
|
||||
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);
|
||||
}
|
||||
else {
|
||||
base = base_owning.get();
|
||||
}
|
||||
|
||||
set_display(Display::Block);
|
||||
set_property(Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox);
|
||||
|
||||
register_event_listeners(events_enabled);
|
||||
}
|
||||
|
||||
Element::~Element() {
|
||||
if (!shim) {
|
||||
clear_children();
|
||||
if (!base_owning) {
|
||||
base->GetParentNode()->RemoveChild(base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Element::add_child(Element *child) {
|
||||
assert(child != nullptr);
|
||||
if (can_set_text) {
|
||||
assert(false && "Elements with settable text cannot have children");
|
||||
return;
|
||||
}
|
||||
|
||||
children.emplace_back(child);
|
||||
|
||||
if (shim) {
|
||||
ContextId context = get_current_context();
|
||||
context.add_loose_element(child);
|
||||
}
|
||||
}
|
||||
|
||||
void Element::set_property(Rml::PropertyId property_id, const Rml::Property &property) {
|
||||
assert(base != nullptr);
|
||||
|
||||
base->SetProperty(property_id, property);
|
||||
Style::set_property(property_id, property);
|
||||
}
|
||||
|
||||
void Element::register_event_listeners(uint32_t events_enabled) {
|
||||
assert(base != nullptr);
|
||||
|
||||
this->events_enabled = events_enabled;
|
||||
|
||||
if (events_enabled & Events(EventType::Click)) {
|
||||
base->AddEventListener(Rml::EventId::Click, this);
|
||||
}
|
||||
|
||||
if (events_enabled & Events(EventType::Focus)) {
|
||||
base->AddEventListener(Rml::EventId::Focus, this);
|
||||
base->AddEventListener(Rml::EventId::Blur, this);
|
||||
}
|
||||
|
||||
if (events_enabled & Events(EventType::Hover)) {
|
||||
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);
|
||||
}
|
||||
|
||||
if (events_enabled & Events(EventType::Text)) {
|
||||
base->AddEventListener(Rml::EventId::Change, this);
|
||||
}
|
||||
|
||||
if (events_enabled & Events(EventType::Navigate)) {
|
||||
base->AddEventListener(Rml::EventId::Keydown, this);
|
||||
}
|
||||
}
|
||||
|
||||
void Element::apply_style(Style *style) {
|
||||
for (auto it : style->property_map) {
|
||||
// Skip redundant SetProperty calls to prevent dirtying unnecessary state.
|
||||
// This avoids expensive layout operations when a simple color-only style is applied.
|
||||
const Rml::Property* cur_value = base->GetLocalProperty(it.first);
|
||||
if (*cur_value != it.second) {
|
||||
base->SetProperty(it.first, it.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Element::apply_styles() {
|
||||
apply_style(this);
|
||||
|
||||
for (size_t i = 0; i < styles_counter.size(); i++) {
|
||||
if (styles_counter[i] == 0) {
|
||||
apply_style(styles[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Element::propagate_disabled(bool disabled) {
|
||||
disabled_from_parent = disabled;
|
||||
|
||||
bool attribute_state = disabled_from_parent || !enabled;
|
||||
if (disabled_attribute != attribute_state) {
|
||||
disabled_attribute = attribute_state;
|
||||
base->SetAttribute("disabled", attribute_state);
|
||||
|
||||
if (events_enabled & Events(EventType::Enable)) {
|
||||
handle_event(Event::enable_event(!attribute_state));
|
||||
}
|
||||
|
||||
for (auto &child : children) {
|
||||
child->propagate_disabled(attribute_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Element::handle_event(const Event& event) {
|
||||
for (const auto& callback : callbacks) {
|
||||
recompui::queue_ui_callback(resource_id, event, callback);
|
||||
}
|
||||
|
||||
process_event(event);
|
||||
}
|
||||
|
||||
void Element::set_id(const std::string& new_id) {
|
||||
id = new_id;
|
||||
base->SetId(new_id);
|
||||
}
|
||||
|
||||
void Element::ProcessEvent(Rml::Event &event) {
|
||||
ContextId prev_context = recompui::try_close_current_context();
|
||||
ContextId context = ContextId::null();
|
||||
Rml::ElementDocument* doc = event.GetTargetElement()->GetOwnerDocument();
|
||||
if (doc != nullptr) {
|
||||
context = get_context_from_document(doc);
|
||||
}
|
||||
|
||||
bool did_open = false;
|
||||
|
||||
// TODO disallow null contexts once the entire UI system has been migrated.
|
||||
if (context != ContextId::null()) {
|
||||
did_open = context.open_if_not_already();
|
||||
}
|
||||
|
||||
// Events that are processed during any phase.
|
||||
switch (event.GetId()) {
|
||||
case Rml::EventId::Click:
|
||||
handle_event(Event::click_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f)));
|
||||
break;
|
||||
case Rml::EventId::Keydown:
|
||||
switch ((Rml::Input::KeyIdentifier)event.GetParameter<int>("key_identifier", 0)) {
|
||||
case Rml::Input::KeyIdentifier::KI_LEFT:
|
||||
handle_event(Event::navigate_event(NavDirection::Left));
|
||||
break;
|
||||
case Rml::Input::KeyIdentifier::KI_UP:
|
||||
handle_event(Event::navigate_event(NavDirection::Up));
|
||||
break;
|
||||
case Rml::Input::KeyIdentifier::KI_RIGHT:
|
||||
handle_event(Event::navigate_event(NavDirection::Right));
|
||||
break;
|
||||
case Rml::Input::KeyIdentifier::KI_DOWN:
|
||||
handle_event(Event::navigate_event(NavDirection::Down));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Rml::EventId::Drag:
|
||||
handle_event(Event::drag_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), DragPhase::Move));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Events that are only processed during the Target phase.
|
||||
if (event.GetPhase() == Rml::EventPhase::Target) {
|
||||
switch (event.GetId()) {
|
||||
case Rml::EventId::Mouseover:
|
||||
handle_event(Event::hover_event(true));
|
||||
break;
|
||||
case Rml::EventId::Mouseout:
|
||||
handle_event(Event::hover_event(false));
|
||||
break;
|
||||
case Rml::EventId::Focus:
|
||||
handle_event(Event::focus_event(true));
|
||||
break;
|
||||
case Rml::EventId::Blur:
|
||||
handle_event(Event::focus_event(false));
|
||||
break;
|
||||
case Rml::EventId::Dragstart:
|
||||
handle_event(Event::drag_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), DragPhase::Start));
|
||||
break;
|
||||
case Rml::EventId::Dragend:
|
||||
handle_event(Event::drag_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), DragPhase::End));
|
||||
break;
|
||||
case Rml::EventId::Change: {
|
||||
if (events_enabled & Events(EventType::Text)) {
|
||||
Rml::Variant *value_variant = base->GetAttribute("value");
|
||||
if (value_variant != nullptr) {
|
||||
handle_event(Event::text_event(value_variant->Get<std::string>()));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (context != ContextId::null() && did_open) {
|
||||
context.close();
|
||||
}
|
||||
|
||||
if (prev_context != ContextId::null()) {
|
||||
prev_context.open();
|
||||
}
|
||||
}
|
||||
|
||||
void Element::set_attribute(const Rml::String &attribute_key, const Rml::String &attribute_value) {
|
||||
base->SetAttribute(attribute_key, attribute_value);
|
||||
}
|
||||
|
||||
void Element::process_event(const Event &) {
|
||||
// Does nothing by default.
|
||||
}
|
||||
|
||||
void Element::enable_focus() {
|
||||
set_tab_index_auto();
|
||||
set_focusable(true);
|
||||
set_nav_auto(NavDirection::Up);
|
||||
set_nav_auto(NavDirection::Down);
|
||||
set_nav_auto(NavDirection::Left);
|
||||
set_nav_auto(NavDirection::Right);
|
||||
}
|
||||
|
||||
void Element::clear_children() {
|
||||
if (children.empty()) {
|
||||
return;
|
||||
}
|
||||
ContextId context = get_current_context();
|
||||
|
||||
// Remove the children from the context.
|
||||
for (Element* child : children) {
|
||||
context.destroy_resource(child);
|
||||
}
|
||||
|
||||
// Clear the child list.
|
||||
children.clear();
|
||||
}
|
||||
|
||||
bool Element::remove_child(ResourceId child) {
|
||||
bool found = false;
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
for (auto it = children.begin(); it != children.end(); ++it) {
|
||||
Element* cur_child = *it;
|
||||
if (cur_child->get_resource_id() == child) {
|
||||
children.erase(it);
|
||||
context.destroy_resource(cur_child);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
void Element::add_style(Style *style, const std::string_view style_name) {
|
||||
add_style(style, { style_name });
|
||||
}
|
||||
|
||||
void Element::add_style(Style *style, const std::initializer_list<std::string_view> &style_names) {
|
||||
for (const std::string_view &style_name : style_names) {
|
||||
style_name_index_map.emplace(style_name, styles.size());
|
||||
}
|
||||
|
||||
styles.emplace_back(style);
|
||||
|
||||
uint32_t initial_style_counter = style_names.size();
|
||||
for (const std::string_view &style_name : style_names) {
|
||||
if (style_active_set.find(style_name) != style_active_set.end()) {
|
||||
initial_style_counter--;
|
||||
}
|
||||
}
|
||||
|
||||
styles_counter.push_back(initial_style_counter);
|
||||
}
|
||||
|
||||
void Element::set_enabled(bool enabled) {
|
||||
this->enabled = enabled;
|
||||
|
||||
propagate_disabled(disabled_from_parent);
|
||||
}
|
||||
|
||||
bool Element::is_enabled() const {
|
||||
return enabled && !disabled_from_parent;
|
||||
}
|
||||
|
||||
// Adapted from RmlUi's `EncodeRml`.
|
||||
std::string escape_rml(std::string_view string)
|
||||
{
|
||||
std::string result;
|
||||
result.reserve(string.size());
|
||||
for (char c : string)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '<': result += "<"; break;
|
||||
case '>': result += ">"; break;
|
||||
case '&': result += "&"; break;
|
||||
case '"': result += """; break;
|
||||
case '\n': result += "<br/>"; break;
|
||||
default: result += c; break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Element::set_text(std::string_view text) {
|
||||
if (can_set_text) {
|
||||
// Escape the string into Rml to prevent element injection.
|
||||
base->SetInnerRML(escape_rml(text));
|
||||
}
|
||||
else {
|
||||
assert(false && "Attempted to set text of an element that cannot have its text set.");
|
||||
}
|
||||
}
|
||||
|
||||
std::string Element::get_input_text() {
|
||||
return base->GetAttribute("value", std::string{});
|
||||
}
|
||||
|
||||
void Element::set_input_text(std::string_view val) {
|
||||
base->SetAttribute("value", std::string{ val });
|
||||
}
|
||||
|
||||
void Element::set_src(std::string_view src) {
|
||||
base->SetAttribute("src", std::string(src));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
else if (!enable && style_active_set.find(style_name) != style_active_set.end()) {
|
||||
// Style was enabled and will be disabled.
|
||||
style_active_set.erase(style_name);
|
||||
}
|
||||
else {
|
||||
// Do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
auto range = style_name_index_map.equal_range(style_name);
|
||||
for (auto it = range.first; it != range.second; it++) {
|
||||
if (enable) {
|
||||
styles_counter[it->second]--;
|
||||
}
|
||||
else {
|
||||
styles_counter[it->second]++;
|
||||
}
|
||||
}
|
||||
|
||||
apply_styles();
|
||||
}
|
||||
|
||||
bool Element::is_style_enabled(std::string_view style_name) {
|
||||
return style_active_set.contains(style_name);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
uint32_t Element::get_input_value_u32() {
|
||||
ElementValue value = get_element_value();
|
||||
|
||||
return std::visit(overloaded {
|
||||
[](double d) { return (uint32_t)d; },
|
||||
[](float f) { return (uint32_t)f; },
|
||||
[](uint32_t u) { return u; },
|
||||
[](std::monostate) { return 0U; }
|
||||
}, value);
|
||||
}
|
||||
|
||||
float Element::get_input_value_float() {
|
||||
ElementValue value = get_element_value();
|
||||
|
||||
return std::visit(overloaded {
|
||||
[](double d) { return (float)d; },
|
||||
[](float f) { return f; },
|
||||
[](uint32_t u) { return (float)u; },
|
||||
[](std::monostate) { return 0.0f; }
|
||||
}, value);
|
||||
}
|
||||
|
||||
double Element::get_input_value_double() {
|
||||
ElementValue value = get_element_value();
|
||||
|
||||
return std::visit(overloaded {
|
||||
[](double d) { return d; },
|
||||
[](float f) { return (double)f; },
|
||||
[](uint32_t u) { return (double)u; },
|
||||
[](std::monostate) { return 0.0; }
|
||||
}, value);
|
||||
}
|
||||
|
||||
void Element::focus() {
|
||||
base->Focus();
|
||||
}
|
||||
|
||||
void Element::blur() {
|
||||
base->Blur();
|
||||
}
|
||||
|
||||
void Element::queue_update() {
|
||||
ContextId cur_context = get_current_context();
|
||||
|
||||
// TODO disallow null contexts once the entire UI system has been migrated.
|
||||
if (cur_context == ContextId::null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
cur_context.queue_element_update(resource_id);
|
||||
}
|
||||
|
||||
void Element::register_callback(ContextId context, PTR(void) callback, PTR(void) userdata) {
|
||||
callbacks.emplace_back(UICallback{.context = context, .callback = callback, .userdata = userdata});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_style.h"
|
||||
#include "../core/ui_context.h"
|
||||
|
||||
#include "recomp.h"
|
||||
#include <ultramodern/ultra64.h>
|
||||
|
||||
#include <unordered_set>
|
||||
#include <variant>
|
||||
|
||||
namespace recompui {
|
||||
struct UICallback {
|
||||
ContextId context;
|
||||
PTR(void) callback;
|
||||
PTR(void) userdata;
|
||||
};
|
||||
|
||||
using ElementValue = std::variant<uint32_t, float, double, std::monostate>;
|
||||
|
||||
class ContextId;
|
||||
class Element : public Style, public Rml::EventListener {
|
||||
friend ContextId create_context(const std::filesystem::path& path);
|
||||
friend ContextId create_context();
|
||||
friend class ContextId; // To allow ContextId to call the handle_event method directly.
|
||||
private:
|
||||
Rml::Element *base = nullptr;
|
||||
Rml::ElementPtr base_owning = {};
|
||||
uint32_t events_enabled = 0;
|
||||
std::vector<Style *> styles;
|
||||
std::vector<uint32_t> styles_counter;
|
||||
std::unordered_set<std::string_view> style_active_set;
|
||||
std::unordered_multimap<std::string_view, uint32_t> style_name_index_map;
|
||||
std::vector<UICallback> callbacks;
|
||||
std::vector<Element *> children;
|
||||
std::string id;
|
||||
bool shim = false;
|
||||
bool enabled = true;
|
||||
bool disabled_attribute = false;
|
||||
bool disabled_from_parent = false;
|
||||
bool can_set_text = false;
|
||||
|
||||
void add_child(Element *child);
|
||||
void register_event_listeners(uint32_t events_enabled);
|
||||
void apply_style(Style *style);
|
||||
void propagate_disabled(bool disabled);
|
||||
void handle_event(const Event &e);
|
||||
void set_id(const std::string& new_id);
|
||||
|
||||
// Style overrides.
|
||||
virtual void set_property(Rml::PropertyId property_id, const Rml::Property &property) override;
|
||||
|
||||
// Rml::EventListener overrides.
|
||||
void ProcessEvent(Rml::Event &event) override final;
|
||||
protected:
|
||||
// Use of this method in inherited classes is discouraged unless it's necessary.
|
||||
void set_attribute(const Rml::String &attribute_key, const Rml::String &attribute_value);
|
||||
virtual void process_event(const Event &e);
|
||||
virtual ElementValue get_element_value() { return std::monostate{}; }
|
||||
virtual void set_input_value(const ElementValue&) {}
|
||||
virtual std::string_view get_type_name() { return "Element"; }
|
||||
public:
|
||||
// Used for backwards compatibility with legacy UI elements.
|
||||
Element(Rml::Element *base);
|
||||
|
||||
// Used to actually construct elements.
|
||||
Element(Element* parent, uint32_t events_enabled = 0, Rml::String base_class = "div", bool can_set_text = false);
|
||||
virtual ~Element();
|
||||
void clear_children();
|
||||
bool remove_child(ResourceId child);
|
||||
bool remove_child(Element *child) { return remove_child(child->get_resource_id()); }
|
||||
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(std::string_view text);
|
||||
std::string get_input_text();
|
||||
void set_input_text(std::string_view text);
|
||||
void set_src(std::string_view src);
|
||||
void set_style_enabled(std::string_view style_name, bool enabled);
|
||||
bool is_style_enabled(std::string_view style_name);
|
||||
void apply_styles();
|
||||
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();
|
||||
void enable_focus();
|
||||
void focus();
|
||||
void blur();
|
||||
void queue_update();
|
||||
void register_callback(ContextId context, PTR(void) callback, PTR(void) userdata);
|
||||
uint32_t get_input_value_u32();
|
||||
float get_input_value_float();
|
||||
double get_input_value_double();
|
||||
void set_input_value_u32(uint32_t val) { set_input_value(val); }
|
||||
void set_input_value_float(float val) { set_input_value(val); }
|
||||
void set_input_value_double(double val) { set_input_value(val); }
|
||||
const std::string& get_id() { return id; }
|
||||
};
|
||||
|
||||
void queue_ui_callback(recompui::ResourceId resource, const Event& e, const UICallback& callback);
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "ui_image.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Image::Image(Element *parent, std::string_view src) : Element(parent, 0, "img") {
|
||||
set_src(src);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class Image : public Element {
|
||||
protected:
|
||||
std::string_view get_type_name() override { return "ImageView"; }
|
||||
public:
|
||||
Image(Element *parent, std::string_view src);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "ui_label.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Label::Label(Element *parent, LabelStyle label_style) : Element(parent, 0U, "div", true) {
|
||||
switch (label_style) {
|
||||
case LabelStyle::Annotation:
|
||||
set_color(Color{ 185, 125, 242, 255 });
|
||||
set_font_size(18.0f);
|
||||
set_letter_spacing(2.52f);
|
||||
set_line_height(18.0f);
|
||||
set_font_weight(400);
|
||||
break;
|
||||
case LabelStyle::Small:
|
||||
set_font_size(20.0f);
|
||||
set_letter_spacing(0.0f);
|
||||
set_line_height(20.0f);
|
||||
set_font_weight(400);
|
||||
break;
|
||||
case LabelStyle::Normal:
|
||||
set_font_size(28.0f);
|
||||
set_letter_spacing(3.08f);
|
||||
set_line_height(28.0f);
|
||||
set_font_weight(700);
|
||||
break;
|
||||
case LabelStyle::Large:
|
||||
set_font_size(36.0f);
|
||||
set_letter_spacing(2.52f);
|
||||
set_line_height(36.0f);
|
||||
set_font_weight(700);
|
||||
break;
|
||||
}
|
||||
|
||||
set_font_style(FontStyle::Normal);
|
||||
}
|
||||
|
||||
Label::Label(Element *parent, const std::string &text, LabelStyle label_style) : Label(parent, label_style) {
|
||||
set_text(text);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
enum class LabelStyle {
|
||||
Annotation,
|
||||
Small,
|
||||
Normal,
|
||||
Large
|
||||
};
|
||||
|
||||
class Label : public Element {
|
||||
protected:
|
||||
std::string_view get_type_name() override { return "Label"; }
|
||||
public:
|
||||
Label(Element *parent, LabelStyle label_style);
|
||||
Label(Element *parent, const std::string &text, LabelStyle label_style);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,240 @@
|
||||
#include "overloaded.h"
|
||||
#include "ui_radio.h"
|
||||
#include "../ui_utils.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
// RadioOption
|
||||
|
||||
RadioOption::RadioOption(Element *parent, std::string_view name, uint32_t index) : Element(parent, Events(EventType::Click, EventType::Focus, EventType::Hover, EventType::Enable, EventType::Update), "label", true) {
|
||||
this->index = index;
|
||||
|
||||
enable_focus();
|
||||
set_text(name);
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_font_size(20.0f);
|
||||
set_letter_spacing(2.8f);
|
||||
set_line_height(20.0f);
|
||||
set_font_weight(400);
|
||||
set_font_style(FontStyle::Normal);
|
||||
set_border_color(Color{ 242, 242, 242, 0 });
|
||||
set_border_bottom_width(1.0f);
|
||||
set_color(Color{ 255, 255, 255, 153 });
|
||||
set_padding_bottom(8.0f);
|
||||
set_text_transform(TextTransform::Uppercase);
|
||||
set_height_auto();
|
||||
hover_style.set_color(Color{ 255, 255, 255, 204 });
|
||||
checked_style.set_color(Color{ 255, 255, 255, 255 });
|
||||
checked_style.set_border_color(Color{ 242, 242, 242, 255 });
|
||||
pulsing_style.set_border_color(Color{ 23, 214, 232, 244 });
|
||||
|
||||
add_style(&hover_style, { hover_state });
|
||||
add_style(&checked_style, { checked_state });
|
||||
add_style(&pulsing_style, { focus_state });
|
||||
}
|
||||
|
||||
void RadioOption::set_pressed_callback(std::function<void(uint32_t)> callback) {
|
||||
pressed_callback = callback;
|
||||
}
|
||||
|
||||
void RadioOption::set_selected_state(bool enable) {
|
||||
set_style_enabled(checked_state, enable);
|
||||
}
|
||||
|
||||
void RadioOption::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
pressed_callback(index);
|
||||
break;
|
||||
case EventType::Hover:
|
||||
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active);
|
||||
break;
|
||||
case EventType::Enable:
|
||||
set_style_enabled(disabled_state, !std::get<EventEnable>(e.variant).active);
|
||||
break;
|
||||
case EventType::Focus:
|
||||
{
|
||||
bool active = std::get<EventFocus>(e.variant).active;
|
||||
set_style_enabled(focus_state, active);
|
||||
if (active) {
|
||||
queue_update();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Update:
|
||||
if (is_style_enabled(focus_state)) {
|
||||
pulsing_style.set_color(recompui::get_pulse_color(750));
|
||||
apply_styles();
|
||||
queue_update();
|
||||
}
|
||||
if (focus_queued) {
|
||||
focus_queued = false;
|
||||
focus();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Radio
|
||||
|
||||
void Radio::set_index_internal(uint32_t index, bool setup, bool trigger_callbacks) {
|
||||
if (this->index != index || setup) {
|
||||
options[this->index]->set_selected_state(false);
|
||||
this->index = index;
|
||||
options[index]->set_selected_state(true);
|
||||
|
||||
if (trigger_callbacks) {
|
||||
for (const auto &function : index_changed_callbacks) {
|
||||
function(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Radio::option_selected(uint32_t index) {
|
||||
set_index_internal(index, false, true);
|
||||
}
|
||||
|
||||
void Radio::set_input_value(const ElementValue& val) {
|
||||
std::visit(overloaded {
|
||||
[this](uint32_t u) { set_index(u); },
|
||||
[this](float f) { set_index(f); },
|
||||
[this](double d) { set_index(d); },
|
||||
[](std::monostate) {}
|
||||
}, val);
|
||||
}
|
||||
|
||||
Radio::Radio(Element *parent) : Container(parent, FlexDirection::Row, JustifyContent::FlexStart, Events(EventType::Focus)) {
|
||||
set_gap(24.0f);
|
||||
set_align_items(AlignItems::FlexStart);
|
||||
enable_focus();
|
||||
}
|
||||
|
||||
void Radio::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Focus:
|
||||
if (!options.empty()) {
|
||||
if (std::get<EventFocus>(e.variant).active) {
|
||||
blur();
|
||||
options[index]->queue_focus();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Radio::~Radio() {
|
||||
|
||||
}
|
||||
|
||||
void Radio::add_option(std::string_view name) {
|
||||
RadioOption *option = get_current_context().create_element<RadioOption>(this, name, uint32_t(options.size()));
|
||||
option->set_pressed_callback([this](uint32_t index){ option_selected(index); });
|
||||
options.emplace_back(option);
|
||||
|
||||
// The first option was added, select it.
|
||||
if (options.size() == 1) {
|
||||
set_index_internal(0, true, false);
|
||||
}
|
||||
// At least one other option already existed, so set up navigation.
|
||||
else {
|
||||
options[options.size() - 2]->set_nav(NavDirection::Right, options[options.size() - 1]);
|
||||
options[options.size() - 1]->set_nav(NavDirection::Left, options[options.size() - 2]);
|
||||
}
|
||||
}
|
||||
|
||||
void Radio::set_index(uint32_t index) {
|
||||
set_index_internal(index, false, false);
|
||||
}
|
||||
|
||||
uint32_t Radio::get_index() const {
|
||||
return index;
|
||||
}
|
||||
|
||||
void Radio::add_index_changed_callback(std::function<void(uint32_t)> callback) {
|
||||
index_changed_callbacks.emplace_back(callback);
|
||||
}
|
||||
|
||||
void Radio::set_nav_auto(NavDirection dir) {
|
||||
Element::set_nav_auto(dir);
|
||||
if (!options.empty()) {
|
||||
switch (dir) {
|
||||
case NavDirection::Up:
|
||||
case NavDirection::Down:
|
||||
for (Element* e : options) {
|
||||
e->set_nav_auto(dir);
|
||||
}
|
||||
break;
|
||||
case NavDirection::Left:
|
||||
options.front()->set_nav_auto(dir);
|
||||
break;
|
||||
case NavDirection::Right:
|
||||
options.back()->set_nav_auto(dir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Radio::set_nav_none(NavDirection dir) {
|
||||
Element::set_nav_none(dir);
|
||||
if (!options.empty()) {
|
||||
switch (dir) {
|
||||
case NavDirection::Up:
|
||||
case NavDirection::Down:
|
||||
for (Element* e : options) {
|
||||
e->set_nav_none(dir);
|
||||
}
|
||||
break;
|
||||
case NavDirection::Left:
|
||||
options.front()->set_nav_none(dir);
|
||||
break;
|
||||
case NavDirection::Right:
|
||||
options.back()->set_nav_none(dir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Radio::set_nav(NavDirection dir, Element* element) {
|
||||
Element::set_nav(dir, element);
|
||||
if (!options.empty()) {
|
||||
switch (dir) {
|
||||
case NavDirection::Up:
|
||||
case NavDirection::Down:
|
||||
for (Element* e : options) {
|
||||
e->set_nav(dir, element);
|
||||
}
|
||||
break;
|
||||
case NavDirection::Left:
|
||||
options.front()->set_nav(dir, element);
|
||||
break;
|
||||
case NavDirection::Right:
|
||||
options.back()->set_nav(dir, element);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Radio::set_nav_manual(NavDirection dir, const std::string& target) {
|
||||
Element::set_nav_manual(dir, target);
|
||||
if (!options.empty()) {
|
||||
switch (dir) {
|
||||
case NavDirection::Up:
|
||||
case NavDirection::Down:
|
||||
for (Element* e : options) {
|
||||
e->set_nav_manual(dir, target);
|
||||
}
|
||||
break;
|
||||
case NavDirection::Left:
|
||||
options.front()->set_nav_manual(dir, target);
|
||||
break;
|
||||
case NavDirection::Right:
|
||||
options.back()->set_nav_manual(dir, target);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_container.h"
|
||||
#include "ui_label.h"
|
||||
|
||||
namespace recompui {
|
||||
class RadioOption : public Element {
|
||||
private:
|
||||
Style hover_style;
|
||||
Style checked_style;
|
||||
Style pulsing_style;
|
||||
std::function<void(uint32_t)> pressed_callback = nullptr;
|
||||
uint32_t index = 0;
|
||||
bool focus_queued = false;
|
||||
protected:
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "LabelRadioOption"; }
|
||||
public:
|
||||
RadioOption(Element *parent, std::string_view name, uint32_t index);
|
||||
void set_pressed_callback(std::function<void(uint32_t)> callback);
|
||||
void set_selected_state(bool enable);
|
||||
void queue_focus() { focus_queued = true; queue_update(); }
|
||||
};
|
||||
|
||||
class Radio : public Container {
|
||||
private:
|
||||
std::vector<RadioOption *> options;
|
||||
uint32_t index = 0;
|
||||
std::vector<std::function<void(uint32_t)>> index_changed_callbacks;
|
||||
|
||||
void set_index_internal(uint32_t index, bool setup, bool trigger_callbacks);
|
||||
void option_selected(uint32_t index);
|
||||
void set_input_value(const ElementValue& val) override;
|
||||
ElementValue get_element_value() override { return get_index(); }
|
||||
protected:
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "LabelRadio"; }
|
||||
public:
|
||||
Radio(Element *parent);
|
||||
virtual ~Radio();
|
||||
void add_option(std::string_view name);
|
||||
void set_index(uint32_t index);
|
||||
uint32_t get_index() const;
|
||||
void add_index_changed_callback(std::function<void(uint32_t)> callback);
|
||||
size_t num_options() const { return options.size(); }
|
||||
RadioOption* get_option_element(size_t option_index) { return options[option_index]; }
|
||||
RadioOption* get_current_option_element() { return options.empty() ? nullptr : options[index]; }
|
||||
void set_nav_auto(NavDirection dir) override;
|
||||
void set_nav_none(NavDirection dir) override;
|
||||
void set_nav(NavDirection dir, Element* element) override;
|
||||
void set_nav_manual(NavDirection dir, const std::string& target) override;
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "ui_scroll_container.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
ScrollContainer::ScrollContainer(Element *parent, ScrollDirection direction) : Element(parent) {
|
||||
set_flex(1.0f, 1.0f, 100.0f);
|
||||
set_width(100.0f, Unit::Percent);
|
||||
set_height(100.0f, Unit::Percent);
|
||||
|
||||
switch (direction) {
|
||||
case ScrollDirection::Horizontal:
|
||||
set_max_width(100.0f, Unit::Percent);
|
||||
set_overflow_x(Overflow::Auto);
|
||||
break;
|
||||
case ScrollDirection::Vertical:
|
||||
set_max_height(100.0f, Unit::Percent);
|
||||
set_overflow_y(Overflow::Auto);
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown scroll direction.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
enum class ScrollDirection {
|
||||
Horizontal,
|
||||
Vertical
|
||||
};
|
||||
|
||||
class ScrollContainer : public Element {
|
||||
protected:
|
||||
std::string_view get_type_name() override { return "ScrollContainer"; }
|
||||
public:
|
||||
ScrollContainer(Element *parent, ScrollDirection direction);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,236 @@
|
||||
#include "overloaded.h"
|
||||
#include "ui_slider.h"
|
||||
#include "../ui_utils.h"
|
||||
|
||||
#include <cmath>
|
||||
#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(ratio * 100.0, Unit::Percent);
|
||||
}
|
||||
|
||||
void Slider::update_label_text() {
|
||||
char text_buffer[32];
|
||||
|
||||
if (type == SliderType::Double) {
|
||||
std::snprintf(text_buffer, sizeof(text_buffer), "%.1f", value);
|
||||
} else if (type == SliderType::Percent) {
|
||||
std::snprintf(text_buffer, sizeof(text_buffer), "%d%%", static_cast<int>(value));
|
||||
} else {
|
||||
std::snprintf(text_buffer, sizeof(text_buffer), "%d", static_cast<int>(value));
|
||||
}
|
||||
|
||||
value_label->set_text(text_buffer);
|
||||
}
|
||||
|
||||
void Slider::set_input_value(const ElementValue& val) {
|
||||
std::visit(overloaded {
|
||||
[this](uint32_t u) { set_value(u); },
|
||||
[this](float f) { set_value(f); },
|
||||
[this](double d) { set_value(d); },
|
||||
[](std::monostate) {}
|
||||
}, val);
|
||||
}
|
||||
|
||||
void Slider::process_event(const Event& e) {
|
||||
switch (e.type) {
|
||||
case EventType::Focus:
|
||||
{
|
||||
bool active = std::get<EventFocus>(e.variant).active;
|
||||
circle_element->set_style_enabled(focus_state, active);
|
||||
if (active) {
|
||||
queue_update();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Update:
|
||||
if (is_enabled()) {
|
||||
if (circle_element->is_style_enabled(focus_state)) {
|
||||
circle_element->set_background_color(recompui::get_pulse_color(750));
|
||||
queue_update();
|
||||
}
|
||||
else {
|
||||
circle_element->set_background_color(Color{ 204, 204, 204, 255 });
|
||||
}
|
||||
}
|
||||
else {
|
||||
circle_element->set_background_color(Color{ 102, 102, 102, 255 });
|
||||
}
|
||||
break;
|
||||
case EventType::Navigate:
|
||||
{
|
||||
NavDirection dir = std::get<EventNavigate>(e.variant).direction;
|
||||
if (dir == NavDirection::Left) {
|
||||
do_step(false);
|
||||
}
|
||||
else if (dir == NavDirection::Right) {
|
||||
do_step(true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
circle_element->set_enabled(enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
circle_element->set_background_color(Color{ 204, 204, 204, 255 });
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
circle_element->set_background_color(Color{ 102, 102, 102, 255 });
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Slider::Slider(Element *parent, SliderType type) : Element(parent, Events(EventType::Focus, EventType::Update, EventType::Navigate, EventType::Enable)) {
|
||||
this->type = type;
|
||||
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_display(Display::Flex);
|
||||
set_flex_direction(FlexDirection::Row);
|
||||
set_text_align(TextAlign::Left);
|
||||
set_min_width(120.0f);
|
||||
|
||||
enable_focus();
|
||||
set_nav_none(NavDirection::Left);
|
||||
set_nav_none(NavDirection::Right);
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
value_label = context.create_element<Label>(this, "0", LabelStyle::Small);
|
||||
value_label->set_margin_right(20.0f);
|
||||
value_label->set_min_width(60.0f);
|
||||
value_label->set_max_width(60.0f);
|
||||
|
||||
slider_element = context.create_element<Clickable>(this, true);
|
||||
slider_element->set_flex(1.0f, 0.0f);
|
||||
slider_element->add_pressed_callback([this](float x, float y){ bar_clicked(x, y); focus(); });
|
||||
slider_element->add_dragged_callback([this](float x, float y, recompui::DragPhase phase){ bar_dragged(x, y, phase); focus(); });
|
||||
|
||||
{
|
||||
bar_element = context.create_element<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([this](float x, float y){ bar_clicked(x, y); focus(); });
|
||||
bar_element->add_dragged_callback([this](float x, float y, recompui::DragPhase phase){ bar_dragged(x, y, phase); focus(); });
|
||||
|
||||
circle_element = context.create_element<Clickable>(bar_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(-7.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_pressed_callback([this](float, float){ focus(); });
|
||||
circle_element->add_dragged_callback([this](float x, float y, recompui::DragPhase phase){ circle_dragged(x, y, phase); focus(); });
|
||||
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);
|
||||
}
|
||||
|
||||
void Slider::do_step(bool increment) {
|
||||
double new_value = value;
|
||||
if (increment) {
|
||||
new_value += step_value;
|
||||
}
|
||||
else {
|
||||
new_value -= step_value;
|
||||
}
|
||||
new_value = std::clamp(new_value, min_value, max_value);
|
||||
if (new_value != value) {
|
||||
set_value_internal(new_value, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,56 @@
|
||||
#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;
|
||||
Clickable *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 = 0.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();
|
||||
void set_input_value(const ElementValue& val) override;
|
||||
ElementValue get_element_value() override { return get_value(); }
|
||||
|
||||
protected:
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "Slider"; }
|
||||
|
||||
public:
|
||||
Slider(Element *parent, SliderType type);
|
||||
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);
|
||||
void do_step(bool increment);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "ui_span.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Span::Span(Element *parent) : Element(parent, 0, "span", true) {
|
||||
set_font_style(FontStyle::Normal);
|
||||
}
|
||||
|
||||
Span::Span(Element *parent, const std::string &text) : Span(parent) {
|
||||
set_text(text);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
#include "ui_label.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class Span : public Element {
|
||||
protected:
|
||||
std::string_view get_type_name() override { return "Span"; }
|
||||
public:
|
||||
Span(Element *parent);
|
||||
Span(Element *parent, const std::string &text);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,612 @@
|
||||
#include "ui_style.h"
|
||||
#include "ui_element.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
static Rml::Unit to_rml(Unit unit) {
|
||||
switch (unit) {
|
||||
case Unit::Px:
|
||||
return Rml::Unit::PX;
|
||||
case Unit::Dp:
|
||||
return Rml::Unit::DP;
|
||||
case Unit::Percent:
|
||||
return Rml::Unit::PERCENT;
|
||||
default:
|
||||
return Rml::Unit::UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::Style::AlignItems to_rml(AlignItems align_items) {
|
||||
switch (align_items) {
|
||||
case AlignItems::FlexStart:
|
||||
return Rml::Style::AlignItems::FlexStart;
|
||||
case AlignItems::FlexEnd:
|
||||
return Rml::Style::AlignItems::FlexEnd;
|
||||
case AlignItems::Center:
|
||||
return Rml::Style::AlignItems::Center;
|
||||
case AlignItems::Baseline:
|
||||
return Rml::Style::AlignItems::Baseline;
|
||||
case AlignItems::Stretch:
|
||||
return Rml::Style::AlignItems::Stretch;
|
||||
default:
|
||||
assert(false && "Unknown align items.");
|
||||
return Rml::Style::AlignItems::FlexStart;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::Style::Overflow to_rml(Overflow overflow) {
|
||||
switch (overflow) {
|
||||
case Overflow::Visible:
|
||||
return Rml::Style::Overflow::Visible;
|
||||
case Overflow::Hidden:
|
||||
return Rml::Style::Overflow::Hidden;
|
||||
case Overflow::Auto:
|
||||
return Rml::Style::Overflow::Auto;
|
||||
case Overflow::Scroll:
|
||||
return Rml::Style::Overflow::Scroll;
|
||||
default:
|
||||
assert(false && "Unknown overflow.");
|
||||
return Rml::Style::Overflow::Visible;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::Style::TextAlign to_rml(TextAlign text_align) {
|
||||
switch (text_align) {
|
||||
case TextAlign::Left:
|
||||
return Rml::Style::TextAlign::Left;
|
||||
case TextAlign::Right:
|
||||
return Rml::Style::TextAlign::Right;
|
||||
case TextAlign::Center:
|
||||
return Rml::Style::TextAlign::Center;
|
||||
case TextAlign::Justify:
|
||||
return Rml::Style::TextAlign::Justify;
|
||||
default:
|
||||
assert(false && "Unknown text align.");
|
||||
return Rml::Style::TextAlign::Left;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::Style::TextTransform to_rml(TextTransform text_transform) {
|
||||
switch (text_transform) {
|
||||
case TextTransform::None:
|
||||
return Rml::Style::TextTransform::None;
|
||||
case TextTransform::Capitalize:
|
||||
return Rml::Style::TextTransform::Capitalize;
|
||||
case TextTransform::Uppercase:
|
||||
return Rml::Style::TextTransform::Uppercase;
|
||||
case TextTransform::Lowercase:
|
||||
return Rml::Style::TextTransform::Lowercase;
|
||||
default:
|
||||
assert(false && "Unknown text transform.");
|
||||
return Rml::Style::TextTransform::None;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::Style::TabIndex to_rml(TabIndex tab_index) {
|
||||
switch (tab_index) {
|
||||
case TabIndex::None:
|
||||
return Rml::Style::TabIndex::None;
|
||||
case TabIndex::Auto:
|
||||
return Rml::Style::TabIndex::Auto;
|
||||
default:
|
||||
assert(false && "Unknown tab index.");
|
||||
return Rml::Style::TabIndex::None;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::Style::Display to_rml(Display display) {
|
||||
switch (display) {
|
||||
case Display::None:
|
||||
return Rml::Style::Display::None;
|
||||
case Display::Block:
|
||||
return Rml::Style::Display::Block;
|
||||
case Display::Inline:
|
||||
return Rml::Style::Display::Inline;
|
||||
case Display::InlineBlock:
|
||||
return Rml::Style::Display::InlineBlock;
|
||||
case Display::FlowRoot:
|
||||
return Rml::Style::Display::FlowRoot;
|
||||
case Display::Flex:
|
||||
return Rml::Style::Display::Flex;
|
||||
case Display::InlineFlex:
|
||||
return Rml::Style::Display::InlineFlex;
|
||||
case Display::Table:
|
||||
return Rml::Style::Display::Table;
|
||||
case Display::InlineTable:
|
||||
return Rml::Style::Display::InlineTable;
|
||||
case Display::TableRow:
|
||||
return Rml::Style::Display::TableRow;
|
||||
case Display::TableRowGroup:
|
||||
return Rml::Style::Display::TableRowGroup;
|
||||
case Display::TableColumn:
|
||||
return Rml::Style::Display::TableColumn;
|
||||
case Display::TableColumnGroup:
|
||||
return Rml::Style::Display::TableColumnGroup;
|
||||
case Display::TableCell:
|
||||
return Rml::Style::Display::TableCell;
|
||||
default:
|
||||
assert(false && "Unknown display.");
|
||||
return Rml::Style::Display::Block;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::Style::JustifyContent to_rml(JustifyContent justify_content) {
|
||||
switch (justify_content) {
|
||||
case JustifyContent::FlexStart:
|
||||
return Rml::Style::JustifyContent::FlexStart;
|
||||
case JustifyContent::FlexEnd:
|
||||
return Rml::Style::JustifyContent::FlexEnd;
|
||||
case JustifyContent::Center:
|
||||
return Rml::Style::JustifyContent::Center;
|
||||
case JustifyContent::SpaceBetween:
|
||||
return Rml::Style::JustifyContent::SpaceBetween;
|
||||
case JustifyContent::SpaceAround:
|
||||
return Rml::Style::JustifyContent::SpaceAround;
|
||||
case JustifyContent::SpaceEvenly:
|
||||
return Rml::Style::JustifyContent::SpaceEvenly;
|
||||
default:
|
||||
assert(false && "Unknown justify content.");
|
||||
return Rml::Style::JustifyContent::FlexStart;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::PropertyId nav_to_property(NavDirection dir) {
|
||||
switch (dir) {
|
||||
case NavDirection::Up:
|
||||
return Rml::PropertyId::NavUp;
|
||||
case NavDirection::Right:
|
||||
return Rml::PropertyId::NavRight;
|
||||
case NavDirection::Down:
|
||||
return Rml::PropertyId::NavDown;
|
||||
case NavDirection::Left:
|
||||
return Rml::PropertyId::NavLeft;
|
||||
default:
|
||||
assert(false && "Unknown nav direction.");
|
||||
return Rml::PropertyId::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
void Style::set_property(Rml::PropertyId property_id, const Rml::Property &property) {
|
||||
property_map[property_id] = property;
|
||||
}
|
||||
|
||||
Style::Style() {
|
||||
|
||||
}
|
||||
|
||||
Style::~Style() {
|
||||
|
||||
}
|
||||
|
||||
void Style::set_visibility(Visibility visibility) {
|
||||
switch (visibility) {
|
||||
case Visibility::Visible:
|
||||
set_property(Rml::PropertyId::Visibility, Rml::Style::Visibility::Visible);
|
||||
break;
|
||||
case Visibility::Hidden:
|
||||
set_property(Rml::PropertyId::Visibility, Rml::Style::Visibility::Hidden);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Style::set_position(Position position) {
|
||||
switch (position) {
|
||||
case Position::Absolute:
|
||||
set_property(Rml::PropertyId::Position, Rml::Style::Position::Absolute);
|
||||
break;
|
||||
case Position::Relative:
|
||||
set_property(Rml::PropertyId::Position, Rml::Style::Position::Relative);
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown position.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Style::set_left(float left, Unit unit) {
|
||||
set_property(Rml::PropertyId::Left, Rml::Property(left, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_top(float top, Unit unit) {
|
||||
set_property(Rml::PropertyId::Top, Rml::Property(top, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_right(float right, Unit unit) {
|
||||
set_property(Rml::PropertyId::Right, Rml::Property(right, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_bottom(float bottom, Unit unit) {
|
||||
set_property(Rml::PropertyId::Bottom, Rml::Property(bottom, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_width(float width, Unit unit) {
|
||||
set_property(Rml::PropertyId::Width, Rml::Property(width, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_width_auto() {
|
||||
set_property(Rml::PropertyId::Width, Rml::Property(Rml::Style::FlexBasis::Type::Auto, Rml::Unit::KEYWORD));
|
||||
}
|
||||
|
||||
void Style::set_height(float height, Unit unit) {
|
||||
set_property(Rml::PropertyId::Height, Rml::Property(height, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_height_auto() {
|
||||
set_property(Rml::PropertyId::Height, Rml::Property(Rml::Style::FlexBasis::Type::Auto, Rml::Unit::KEYWORD));
|
||||
}
|
||||
|
||||
void Style::set_min_width(float width, Unit unit) {
|
||||
set_property(Rml::PropertyId::MinWidth, Rml::Property(width, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_min_height(float height, Unit unit) {
|
||||
set_property(Rml::PropertyId::MinHeight, Rml::Property(height, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_max_width(float width, Unit unit) {
|
||||
set_property(Rml::PropertyId::MaxWidth, Rml::Property(width, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_max_height(float height, Unit unit) {
|
||||
set_property(Rml::PropertyId::MaxHeight, Rml::Property(height, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_padding(float padding, Unit unit) {
|
||||
set_property(Rml::PropertyId::PaddingLeft, Rml::Property(padding, to_rml(unit)));
|
||||
set_property(Rml::PropertyId::PaddingTop, Rml::Property(padding, to_rml(unit)));
|
||||
set_property(Rml::PropertyId::PaddingRight, Rml::Property(padding, to_rml(unit)));
|
||||
set_property(Rml::PropertyId::PaddingBottom, Rml::Property(padding, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_padding_left(float padding, Unit unit) {
|
||||
set_property(Rml::PropertyId::PaddingLeft, Rml::Property(padding, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_padding_top(float padding, Unit unit) {
|
||||
set_property(Rml::PropertyId::PaddingTop, Rml::Property(padding, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_padding_right(float padding, Unit unit) {
|
||||
set_property(Rml::PropertyId::PaddingRight, Rml::Property(padding, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_padding_bottom(float padding, Unit unit) {
|
||||
set_property(Rml::PropertyId::PaddingBottom, Rml::Property(padding, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_margin(float margin, Unit unit) {
|
||||
set_property(Rml::PropertyId::MarginLeft, Rml::Property(margin, to_rml(unit)));
|
||||
set_property(Rml::PropertyId::MarginTop, Rml::Property(margin, to_rml(unit)));
|
||||
set_property(Rml::PropertyId::MarginRight, Rml::Property(margin, to_rml(unit)));
|
||||
set_property(Rml::PropertyId::MarginBottom, Rml::Property(margin, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_margin_left(float margin, Unit unit) {
|
||||
set_property(Rml::PropertyId::MarginLeft, Rml::Property(margin, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_margin_top(float margin, Unit unit) {
|
||||
set_property(Rml::PropertyId::MarginTop, Rml::Property(margin, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_margin_right(float margin, Unit unit) {
|
||||
set_property(Rml::PropertyId::MarginRight, Rml::Property(margin, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_margin_bottom(float margin, Unit unit) {
|
||||
set_property(Rml::PropertyId::MarginBottom, Rml::Property(margin, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_margin_auto() {
|
||||
set_property(Rml::PropertyId::MarginLeft, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||
set_property(Rml::PropertyId::MarginTop, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||
set_property(Rml::PropertyId::MarginRight, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||
set_property(Rml::PropertyId::MarginBottom, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||
}
|
||||
|
||||
void Style::set_margin_left_auto() {
|
||||
set_property(Rml::PropertyId::MarginLeft, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||
}
|
||||
|
||||
void Style::set_margin_top_auto() {
|
||||
set_property(Rml::PropertyId::MarginTop, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||
}
|
||||
|
||||
void Style::set_margin_right_auto() {
|
||||
set_property(Rml::PropertyId::MarginRight, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||
}
|
||||
|
||||
void Style::set_margin_bottom_auto() {
|
||||
set_property(Rml::PropertyId::MarginBottom, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||
}
|
||||
|
||||
void Style::set_border_width(float width, Unit unit) {
|
||||
Rml::Property property(width, to_rml(unit));
|
||||
set_property(Rml::PropertyId::BorderTopWidth, property);
|
||||
set_property(Rml::PropertyId::BorderBottomWidth, property);
|
||||
set_property(Rml::PropertyId::BorderLeftWidth, property);
|
||||
set_property(Rml::PropertyId::BorderRightWidth, property);
|
||||
}
|
||||
|
||||
void Style::set_border_left_width(float width, Unit unit) {
|
||||
set_property(Rml::PropertyId::BorderLeftWidth, Rml::Property(width, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_border_top_width(float width, Unit unit) {
|
||||
set_property(Rml::PropertyId::BorderTopWidth, Rml::Property(width, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_border_right_width(float width, Unit unit) {
|
||||
set_property(Rml::PropertyId::BorderRightWidth, Rml::Property(width, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_border_bottom_width(float width, Unit unit) {
|
||||
set_property(Rml::PropertyId::BorderBottomWidth, Rml::Property(width, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_border_radius(float radius, Unit unit) {
|
||||
Rml::Property property(radius, to_rml(unit));
|
||||
set_property(Rml::PropertyId::BorderTopLeftRadius, property);
|
||||
set_property(Rml::PropertyId::BorderTopRightRadius, property);
|
||||
set_property(Rml::PropertyId::BorderBottomLeftRadius, property);
|
||||
set_property(Rml::PropertyId::BorderBottomRightRadius, property);
|
||||
}
|
||||
|
||||
void Style::set_border_top_left_radius(float radius, Unit unit) {
|
||||
set_property(Rml::PropertyId::BorderTopLeftRadius, Rml::Property(radius, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_border_top_right_radius(float radius, Unit unit) {
|
||||
set_property(Rml::PropertyId::BorderTopRightRadius, Rml::Property(radius, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_border_bottom_left_radius(float radius, Unit unit) {
|
||||
set_property(Rml::PropertyId::BorderBottomLeftRadius, Rml::Property(radius, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_border_bottom_right_radius(float radius, Unit unit) {
|
||||
set_property(Rml::PropertyId::BorderBottomRightRadius, Rml::Property(radius, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_background_color(const Color &color) {
|
||||
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||
set_property(Rml::PropertyId::BackgroundColor, property);
|
||||
}
|
||||
|
||||
void Style::set_border_color(const Color &color) {
|
||||
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||
set_property(Rml::PropertyId::BorderTopColor, property);
|
||||
set_property(Rml::PropertyId::BorderBottomColor, property);
|
||||
set_property(Rml::PropertyId::BorderLeftColor, property);
|
||||
set_property(Rml::PropertyId::BorderRightColor, property);
|
||||
}
|
||||
|
||||
void Style::set_border_left_color(const Color &color) {
|
||||
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||
set_property(Rml::PropertyId::BorderLeftColor, property);
|
||||
}
|
||||
|
||||
void Style::set_border_top_color(const Color &color) {
|
||||
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||
set_property(Rml::PropertyId::BorderTopColor, property);
|
||||
}
|
||||
|
||||
void Style::set_border_right_color(const Color &color) {
|
||||
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||
set_property(Rml::PropertyId::BorderRightColor, property);
|
||||
}
|
||||
|
||||
void Style::set_border_bottom_color(const Color &color) {
|
||||
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||
set_property(Rml::PropertyId::BorderBottomColor, property);
|
||||
}
|
||||
|
||||
void Style::set_color(const Color &color) {
|
||||
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||
set_property(Rml::PropertyId::Color, property);
|
||||
}
|
||||
|
||||
void Style::set_cursor(Cursor cursor) {
|
||||
switch (cursor) {
|
||||
case Cursor::None:
|
||||
set_property(Rml::PropertyId::Cursor, Rml::Property("", Rml::Unit::STRING));
|
||||
break;
|
||||
case Cursor::Pointer:
|
||||
set_property(Rml::PropertyId::Cursor, Rml::Property("pointer", Rml::Unit::STRING));
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown cursor.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Style::set_opacity(float opacity) {
|
||||
set_property(Rml::PropertyId::Opacity, Rml::Property(opacity, Rml::Unit::NUMBER));
|
||||
}
|
||||
|
||||
void Style::set_display(Display display) {
|
||||
set_property(Rml::PropertyId::Display, to_rml(display));
|
||||
}
|
||||
|
||||
void Style::set_justify_content(JustifyContent justify_content) {
|
||||
set_property(Rml::PropertyId::JustifyContent, to_rml(justify_content));
|
||||
}
|
||||
|
||||
void Style::set_flex_grow(float grow) {
|
||||
set_property(Rml::PropertyId::FlexGrow, Rml::Property(grow, Rml::Unit::NUMBER));
|
||||
}
|
||||
|
||||
void Style::set_flex_shrink(float shrink) {
|
||||
set_property(Rml::PropertyId::FlexShrink, Rml::Property(shrink, Rml::Unit::NUMBER));
|
||||
}
|
||||
|
||||
void Style::set_flex_basis_auto() {
|
||||
set_property(Rml::PropertyId::FlexBasis, Rml::Property(Rml::Style::FlexBasis::Type::Auto, Rml::Unit::KEYWORD));
|
||||
}
|
||||
|
||||
void Style::set_flex_basis(float basis, Unit unit) {
|
||||
set_property(Rml::PropertyId::FlexBasis, Rml::Property(basis, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_flex(float grow, float shrink) {
|
||||
set_flex_grow(grow);
|
||||
set_flex_shrink(shrink);
|
||||
set_flex_basis_auto();
|
||||
}
|
||||
|
||||
void Style::set_flex(float grow, float shrink, float basis, Unit basis_unit) {
|
||||
set_flex_grow(grow);
|
||||
set_flex_shrink(shrink);
|
||||
set_flex_basis(basis, basis_unit);
|
||||
}
|
||||
|
||||
void Style::set_flex_direction(FlexDirection flex_direction) {
|
||||
switch (flex_direction) {
|
||||
case FlexDirection::Row:
|
||||
set_property(Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Row);
|
||||
break;
|
||||
case FlexDirection::Column:
|
||||
set_property(Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Column);
|
||||
break;
|
||||
case FlexDirection::RowReverse:
|
||||
set_property(Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::RowReverse);
|
||||
break;
|
||||
case FlexDirection::ColumnReverse:
|
||||
set_property(Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::ColumnReverse);
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown flex direction.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Style::set_align_items(AlignItems align_items) {
|
||||
set_property(Rml::PropertyId::AlignItems, to_rml(align_items));
|
||||
}
|
||||
|
||||
void Style::set_overflow(Overflow overflow) {
|
||||
set_property(Rml::PropertyId::OverflowX, to_rml(overflow));
|
||||
set_property(Rml::PropertyId::OverflowY, to_rml(overflow));
|
||||
}
|
||||
|
||||
void Style::set_overflow_x(Overflow overflow) {
|
||||
set_property(Rml::PropertyId::OverflowX, to_rml(overflow));
|
||||
}
|
||||
|
||||
void Style::set_overflow_y(Overflow overflow) {
|
||||
set_property(Rml::PropertyId::OverflowY, to_rml(overflow));
|
||||
}
|
||||
|
||||
void Style::set_font_size(float size, Unit unit) {
|
||||
set_property(Rml::PropertyId::FontSize, Rml::Property(size, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_letter_spacing(float spacing, Unit unit) {
|
||||
set_property(Rml::PropertyId::LetterSpacing, Rml::Property(spacing, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_line_height(float height, Unit unit) {
|
||||
set_property(Rml::PropertyId::LineHeight, Rml::Property(height, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_font_style(FontStyle style) {
|
||||
switch (style) {
|
||||
case FontStyle::Normal:
|
||||
set_property(Rml::PropertyId::FontStyle, Rml::Style::FontStyle::Normal);
|
||||
break;
|
||||
case FontStyle::Italic:
|
||||
set_property(Rml::PropertyId::FontStyle, Rml::Style::FontStyle::Italic);
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown font style.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Style::set_font_weight(uint32_t weight) {
|
||||
set_property(Rml::PropertyId::FontWeight, Rml::Style::FontWeight(weight));
|
||||
}
|
||||
|
||||
void Style::set_text_align(TextAlign text_align) {
|
||||
set_property(Rml::PropertyId::TextAlign, to_rml(text_align));
|
||||
}
|
||||
|
||||
void Style::set_text_transform(TextTransform text_transform) {
|
||||
set_property(Rml::PropertyId::TextTransform, to_rml(text_transform));
|
||||
}
|
||||
|
||||
void Style::set_gap(float size, Unit unit) {
|
||||
set_row_gap(size, unit);
|
||||
set_column_gap(size, unit);
|
||||
}
|
||||
|
||||
void Style::set_row_gap(float size, Unit unit) {
|
||||
set_property(Rml::PropertyId::RowGap, Rml::Property(size, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_column_gap(float size, Unit unit) {
|
||||
set_property(Rml::PropertyId::ColumnGap, Rml::Property(size, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_drag(Drag drag) {
|
||||
set_property(Rml::PropertyId::Drag, to_rml(drag));
|
||||
}
|
||||
|
||||
void Style::set_tab_index(TabIndex tab_index) {
|
||||
set_property(Rml::PropertyId::TabIndex, to_rml(tab_index));
|
||||
}
|
||||
|
||||
void Style::set_font_family(std::string_view family) {
|
||||
set_property(Rml::PropertyId::FontFamily, Rml::Property(Rml::String{ family }, Rml::Unit::UNKNOWN));
|
||||
}
|
||||
|
||||
void Style::set_nav_auto(NavDirection dir) {
|
||||
set_property(nav_to_property(dir), Rml::Style::Nav::Auto);
|
||||
}
|
||||
|
||||
void Style::set_nav_none(NavDirection dir) {
|
||||
set_property(nav_to_property(dir), Rml::Style::Nav::None);
|
||||
}
|
||||
|
||||
void Style::set_nav(NavDirection dir, Element* element) {
|
||||
set_property(nav_to_property(dir), Rml::Property(Rml::String{ "#" + element->get_id() }, Rml::Unit::STRING));
|
||||
}
|
||||
|
||||
void Style::set_nav_manual(NavDirection dir, const std::string& target) {
|
||||
set_property(nav_to_property(dir), Rml::Property(target, Rml::Unit::STRING));
|
||||
}
|
||||
|
||||
void Style::set_tab_index_auto() {
|
||||
set_property(Rml::PropertyId::TabIndex, Rml::Style::Nav::Auto);
|
||||
}
|
||||
|
||||
void Style::set_tab_index_none() {
|
||||
set_property(Rml::PropertyId::TabIndex, Rml::Style::Nav::None);
|
||||
}
|
||||
|
||||
void Style::set_focusable(bool focusable) {
|
||||
set_property(Rml::PropertyId::Focus, focusable ? Rml::Style::Focus::Auto : Rml::Style::Focus::None);
|
||||
}
|
||||
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "RmlUi/Core.h"
|
||||
|
||||
#include "../core/ui_resource.h"
|
||||
#include "ui_types.h"
|
||||
|
||||
namespace recompui {
|
||||
class ContextId;
|
||||
class Style {
|
||||
friend class Element; // For access to property_map without making it visible to element subclasses.
|
||||
friend class ContextId;
|
||||
private:
|
||||
std::map<Rml::PropertyId, Rml::Property> property_map;
|
||||
protected:
|
||||
virtual void set_property(Rml::PropertyId property_id, const Rml::Property &property);
|
||||
ResourceId resource_id = ResourceId::null();
|
||||
public:
|
||||
Style();
|
||||
virtual ~Style();
|
||||
void set_visibility(Visibility visibility);
|
||||
void set_position(Position position);
|
||||
void set_left(float left, Unit unit = Unit::Dp);
|
||||
void set_top(float top, Unit unit = Unit::Dp);
|
||||
void set_right(float right, Unit unit = Unit::Dp);
|
||||
void set_bottom(float bottom, Unit unit = Unit::Dp);
|
||||
void set_width(float width, Unit unit = Unit::Dp);
|
||||
void set_width_auto();
|
||||
void set_height(float height, Unit unit = Unit::Dp);
|
||||
void set_height_auto();
|
||||
void set_min_width(float width, Unit unit = Unit::Dp);
|
||||
void set_min_height(float height, Unit unit = Unit::Dp);
|
||||
void set_max_width(float width, Unit unit = Unit::Dp);
|
||||
void set_max_height(float height, Unit unit = Unit::Dp);
|
||||
void set_padding(float padding, Unit unit = Unit::Dp);
|
||||
void set_padding_left(float padding, Unit unit = Unit::Dp);
|
||||
void set_padding_top(float padding, Unit unit = Unit::Dp);
|
||||
void set_padding_right(float padding, Unit unit = Unit::Dp);
|
||||
void set_padding_bottom(float padding, Unit unit = Unit::Dp);
|
||||
void set_margin(float margin, Unit unit = Unit::Dp);
|
||||
void set_margin_left(float margin, Unit unit = Unit::Dp);
|
||||
void set_margin_top(float margin, Unit unit = Unit::Dp);
|
||||
void set_margin_right(float margin, Unit unit = Unit::Dp);
|
||||
void set_margin_bottom(float margin, Unit unit = Unit::Dp);
|
||||
void set_margin_auto();
|
||||
void set_margin_left_auto();
|
||||
void set_margin_top_auto();
|
||||
void set_margin_right_auto();
|
||||
void set_margin_bottom_auto();
|
||||
void set_border_width(float width, Unit unit = Unit::Dp);
|
||||
void set_border_left_width(float width, Unit unit = Unit::Dp);
|
||||
void set_border_top_width(float width, Unit unit = Unit::Dp);
|
||||
void set_border_right_width(float width, Unit unit = Unit::Dp);
|
||||
void set_border_bottom_width(float width, Unit unit = Unit::Dp);
|
||||
void set_border_radius(float radius, Unit unit = Unit::Dp);
|
||||
void set_border_top_left_radius(float radius, Unit unit = Unit::Dp);
|
||||
void set_border_top_right_radius(float radius, Unit unit = Unit::Dp);
|
||||
void set_border_bottom_left_radius(float radius, Unit unit = Unit::Dp);
|
||||
void set_border_bottom_right_radius(float radius, Unit unit = Unit::Dp);
|
||||
void set_background_color(const Color &color);
|
||||
void set_border_color(const Color &color);
|
||||
void set_border_left_color(const Color &color);
|
||||
void set_border_top_color(const Color &color);
|
||||
void set_border_right_color(const Color &color);
|
||||
void set_border_bottom_color(const Color &color);
|
||||
void set_color(const Color &color);
|
||||
void set_cursor(Cursor cursor);
|
||||
void set_opacity(float opacity);
|
||||
void set_display(Display display);
|
||||
void set_justify_content(JustifyContent justify_content);
|
||||
void set_flex_grow(float grow);
|
||||
void set_flex_shrink(float shrink);
|
||||
void set_flex_basis_auto();
|
||||
void set_flex_basis(float basis, Unit unit = Unit::Percent);
|
||||
void set_flex(float grow, float shrink);
|
||||
void set_flex(float grow, float shrink, float basis, Unit basis_unit = Unit::Percent);
|
||||
void set_flex_direction(FlexDirection flex_direction);
|
||||
void set_align_items(AlignItems align_items);
|
||||
void set_overflow(Overflow overflow);
|
||||
void set_overflow_x(Overflow overflow);
|
||||
void set_overflow_y(Overflow overflow);
|
||||
void set_font_size(float size, Unit unit = Unit::Dp);
|
||||
void set_letter_spacing(float spacing, Unit unit = Unit::Dp);
|
||||
void set_line_height(float height, Unit unit = Unit::Dp);
|
||||
void set_font_style(FontStyle style);
|
||||
void set_font_weight(uint32_t weight);
|
||||
void set_text_align(TextAlign text_align);
|
||||
void set_text_transform(TextTransform text_transform);
|
||||
void set_gap(float size, Unit unit = Unit::Dp);
|
||||
void set_row_gap(float size, Unit unit = Unit::Dp);
|
||||
void set_column_gap(float size, Unit unit = Unit::Dp);
|
||||
void set_drag(Drag drag);
|
||||
void set_tab_index(TabIndex focus);
|
||||
void set_font_family(std::string_view family);
|
||||
virtual void set_nav_auto(NavDirection dir);
|
||||
virtual void set_nav_none(NavDirection dir);
|
||||
virtual void set_nav(NavDirection dir, Element* element);
|
||||
virtual void set_nav_manual(NavDirection dir, const std::string& target);
|
||||
void set_tab_index_auto();
|
||||
void set_tab_index_none();
|
||||
void set_focusable(bool focusable);
|
||||
virtual bool is_element() { return false; }
|
||||
ResourceId get_resource_id() { return resource_id; }
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "ui_text_input.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
void TextInput::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Text: {
|
||||
const EventText &event = std::get<EventText>(e.variant);
|
||||
text = event.text;
|
||||
|
||||
for (const auto &function : text_changed_callbacks) {
|
||||
function(text);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TextInput::TextInput(Element *parent, bool text_visible) : Element(parent, Events(EventType::Text), "input") {
|
||||
if (!text_visible) {
|
||||
set_attribute("type", "password");
|
||||
}
|
||||
set_min_width(60.0f);
|
||||
set_border_color(Color{ 242, 242, 242, 255 });
|
||||
set_border_bottom_width(1.0f);
|
||||
set_padding_bottom(6.0f);
|
||||
set_focusable(true);
|
||||
set_nav_auto(NavDirection::Up);
|
||||
set_nav_auto(NavDirection::Down);
|
||||
set_tab_index_auto();
|
||||
}
|
||||
|
||||
void TextInput::set_text(std::string_view text) {
|
||||
this->text = std::string(text);
|
||||
set_attribute("value", this->text);
|
||||
}
|
||||
|
||||
const std::string &TextInput::get_text() {
|
||||
return text;
|
||||
}
|
||||
|
||||
void TextInput::add_text_changed_callback(std::function<void(const std::string &)> callback) {
|
||||
text_changed_callbacks.emplace_back(callback);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class TextInput : public Element {
|
||||
private:
|
||||
std::string text;
|
||||
std::vector<std::function<void(const std::string &)>> text_changed_callbacks;
|
||||
protected:
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "TextInput"; }
|
||||
public:
|
||||
TextInput(Element *parent, bool text_visible = true);
|
||||
void set_text(std::string_view text);
|
||||
const std::string &get_text();
|
||||
void add_text_changed_callback(std::function<void(const std::string &)> callback);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,161 @@
|
||||
#include "ui_toggle.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <ultramodern/ultramodern.hpp>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Toggle::Toggle(Element *parent) : Element(parent, Events(EventType::Click, EventType::Focus, EventType::Hover, EventType::Enable), "button") {
|
||||
enable_focus();
|
||||
|
||||
set_width(162.0f);
|
||||
set_height(72.0f);
|
||||
set_border_radius(36.0f);
|
||||
set_opacity(0.9f);
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_border_width(2.0f);
|
||||
set_border_color(Color{ 177, 76, 34, 255 });
|
||||
set_background_color(Color{ 0, 0, 0, 0 });
|
||||
checked_style.set_border_color(Color{ 34, 177, 76, 255 });
|
||||
hover_style.set_border_color(Color{ 177, 76, 34, 255 });
|
||||
hover_style.set_background_color(Color{ 206, 120, 68, 76 });
|
||||
focus_style.set_border_color(Color{ 177, 76, 34, 255 });
|
||||
focus_style.set_background_color(Color{ 206, 120, 68, 76 });
|
||||
checked_hover_style.set_border_color(Color{ 34, 177, 76, 255 });
|
||||
checked_hover_style.set_background_color(Color{ 68, 206, 120, 76 });
|
||||
checked_focus_style.set_border_color(Color{ 34, 177, 76, 255 });
|
||||
checked_focus_style.set_background_color(Color{ 68, 206, 120, 76 });
|
||||
disabled_style.set_border_color(Color{ 177, 76, 34, 128 });
|
||||
checked_disabled_style.set_border_color(Color{ 34, 177, 76, 128 });
|
||||
add_style(&checked_style, checked_state);
|
||||
add_style(&hover_style, hover_state);
|
||||
add_style(&focus_style, focus_state);
|
||||
add_style(&checked_hover_style, { checked_state, hover_state });
|
||||
add_style(&checked_focus_style, { checked_state, focus_state });
|
||||
add_style(&disabled_style, disabled_state);
|
||||
add_style(&checked_disabled_style, { checked_state, disabled_state });
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
floater = context.create_element<Element>(this);
|
||||
floater->set_position(Position::Relative);
|
||||
floater->set_top(2.0f);
|
||||
floater->set_width(80.0f);
|
||||
floater->set_height(64.0f);
|
||||
floater->set_border_radius(32.0f);
|
||||
floater->set_background_color(Color{ 177, 76, 34, 255 });
|
||||
floater_checked_style.set_background_color(Color{ 34, 177, 76, 255 });
|
||||
floater_disabled_style.set_background_color(Color{ 177, 76, 34, 128 });
|
||||
floater_disabled_checked_style.set_background_color(Color{ 34, 177, 76, 128 });
|
||||
floater->add_style(&floater_checked_style, checked_state);
|
||||
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, false);
|
||||
}
|
||||
|
||||
void Toggle::set_checked_internal(bool checked, bool animate, bool setup, bool trigger_callbacks) {
|
||||
if (this->checked != checked || setup) {
|
||||
this->checked = checked;
|
||||
|
||||
if (animate) {
|
||||
last_time = ultramodern::time_since_start();
|
||||
queue_update();
|
||||
}
|
||||
else {
|
||||
floater_left = floater_left_target();
|
||||
}
|
||||
|
||||
floater->set_left(floater_left, Unit::Dp);
|
||||
|
||||
if (trigger_callbacks) {
|
||||
for (const auto &function : checked_callbacks) {
|
||||
function(checked);
|
||||
}
|
||||
}
|
||||
|
||||
set_style_enabled(checked_state, checked);
|
||||
floater->set_style_enabled(checked_state, checked);
|
||||
}
|
||||
}
|
||||
|
||||
float Toggle::floater_left_target() const {
|
||||
return checked ? 78.0f : 4.0f;
|
||||
}
|
||||
|
||||
void Toggle::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
if (is_enabled()) {
|
||||
set_checked_internal(!checked, true, false, true);
|
||||
}
|
||||
|
||||
break;
|
||||
case EventType::Hover: {
|
||||
bool hover_active = std::get<EventHover>(e.variant).active && is_enabled();
|
||||
set_style_enabled(hover_state, hover_active);
|
||||
floater->set_style_enabled(hover_state, hover_active);
|
||||
break;
|
||||
}
|
||||
case EventType::Focus: {
|
||||
bool focus_active = std::get<EventFocus>(e.variant).active;
|
||||
set_style_enabled(focus_state, focus_active);
|
||||
break;
|
||||
}
|
||||
case EventType::Enable: {
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !enable_active);
|
||||
floater->set_style_enabled(disabled_state, !enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventType::Update: {
|
||||
std::chrono::high_resolution_clock::duration now = ultramodern::time_since_start();
|
||||
float delta_time = std::max(std::chrono::duration<float>(now - last_time).count(), 0.0f);
|
||||
last_time = now;
|
||||
|
||||
constexpr float dp_speed = 740.0f;
|
||||
const float target = floater_left_target();
|
||||
if (target < floater_left) {
|
||||
floater_left += std::max(-dp_speed * delta_time, target - floater_left);
|
||||
}
|
||||
else {
|
||||
floater_left += std::min(dp_speed * delta_time, target - floater_left);
|
||||
}
|
||||
|
||||
if (abs(target - floater_left) < 1e-4f) {
|
||||
floater_left = target;
|
||||
}
|
||||
else {
|
||||
queue_update();
|
||||
}
|
||||
|
||||
floater->set_left(floater_left, Unit::Dp);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Toggle::set_checked(bool checked) {
|
||||
set_checked_internal(checked, false, false, false);
|
||||
}
|
||||
|
||||
bool Toggle::is_checked() const {
|
||||
return checked;
|
||||
}
|
||||
|
||||
void Toggle::add_checked_callback(std::function<void(bool)> callback) {
|
||||
checked_callbacks.emplace_back(callback);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class Toggle : public Element {
|
||||
protected:
|
||||
Element *floater;
|
||||
float floater_left = 0.0f;
|
||||
std::chrono::high_resolution_clock::duration last_time;
|
||||
std::list<std::function<void(bool)>> checked_callbacks;
|
||||
Style checked_style;
|
||||
Style hover_style;
|
||||
Style focus_style;
|
||||
Style checked_hover_style;
|
||||
Style checked_focus_style;
|
||||
Style disabled_style;
|
||||
Style checked_disabled_style;
|
||||
Style floater_checked_style;
|
||||
Style floater_disabled_style;
|
||||
Style floater_disabled_checked_style;
|
||||
bool checked = false;
|
||||
|
||||
void set_checked_internal(bool checked, bool animate, bool setup, bool trigger_callbacks);
|
||||
float floater_left_target() const;
|
||||
|
||||
// Element overrides.
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "Toggle"; }
|
||||
public:
|
||||
Toggle(Element *parent);
|
||||
void set_checked(bool checked);
|
||||
bool is_checked() const;
|
||||
void add_checked_callback(std::function<void(bool)> callback);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,260 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <variant>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
constexpr std::string_view checked_state = "checked";
|
||||
constexpr std::string_view hover_state = "hover";
|
||||
constexpr std::string_view focus_state = "focus";
|
||||
constexpr std::string_view disabled_state = "disabled";
|
||||
|
||||
struct Color {
|
||||
uint8_t r = 255;
|
||||
uint8_t g = 255;
|
||||
uint8_t b = 255;
|
||||
uint8_t a = 255;
|
||||
};
|
||||
|
||||
enum class Cursor {
|
||||
None,
|
||||
Pointer
|
||||
};
|
||||
|
||||
// These two enums must be kept in sync with patches/recompui_event_structs.h!
|
||||
enum class EventType {
|
||||
None,
|
||||
Click,
|
||||
Focus,
|
||||
Hover,
|
||||
Enable,
|
||||
Drag,
|
||||
Text,
|
||||
Update,
|
||||
Navigate,
|
||||
Count
|
||||
};
|
||||
|
||||
enum class DragPhase {
|
||||
None,
|
||||
Start,
|
||||
Move,
|
||||
End
|
||||
};
|
||||
|
||||
enum class NavDirection {
|
||||
Up,
|
||||
Right,
|
||||
Down,
|
||||
Left
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
template <typename Enum, typename... Enums, typename = std::enable_if_t<std::is_enum_v<Enum>>>
|
||||
constexpr uint32_t Events(Enum first, Enums... rest) {
|
||||
return Events(first) | Events(rest...);
|
||||
}
|
||||
|
||||
struct EventClick {
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
||||
struct EventFocus {
|
||||
bool active;
|
||||
};
|
||||
|
||||
struct EventHover {
|
||||
bool active;
|
||||
};
|
||||
|
||||
struct EventEnable {
|
||||
bool active;
|
||||
};
|
||||
|
||||
struct EventDrag {
|
||||
float x;
|
||||
float y;
|
||||
DragPhase phase;
|
||||
};
|
||||
|
||||
struct EventText {
|
||||
std::string text;
|
||||
};
|
||||
|
||||
struct EventNavigate {
|
||||
NavDirection direction;
|
||||
};
|
||||
|
||||
using EventVariant = std::variant<EventClick, EventFocus, EventHover, EventEnable, EventDrag, EventText, EventNavigate, std::monostate>;
|
||||
|
||||
struct Event {
|
||||
EventType type;
|
||||
EventVariant variant;
|
||||
|
||||
// Factory methods for creating specific events
|
||||
static Event click_event(float x, float y) {
|
||||
Event e;
|
||||
e.type = EventType::Click;
|
||||
e.variant = EventClick{ x, y };
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event focus_event(bool active) {
|
||||
Event e;
|
||||
e.type = EventType::Focus;
|
||||
e.variant = EventFocus{ active };
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event hover_event(bool active) {
|
||||
Event e;
|
||||
e.type = EventType::Hover;
|
||||
e.variant = EventHover{ active };
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event enable_event(bool enable) {
|
||||
Event e;
|
||||
e.type = EventType::Enable;
|
||||
e.variant = EventEnable{ enable };
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event drag_event(float x, float y, DragPhase phase) {
|
||||
Event e;
|
||||
e.type = EventType::Drag;
|
||||
e.variant = EventDrag{ x, y, phase };
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event text_event(const std::string &text) {
|
||||
Event e;
|
||||
e.type = EventType::Text;
|
||||
e.variant = EventText{ text };
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event update_event() {
|
||||
Event e;
|
||||
e.type = EventType::Update;
|
||||
e.variant = std::monostate{};
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event navigate_event(NavDirection direction) {
|
||||
Event e;
|
||||
e.type = EventType::Navigate;
|
||||
e.variant = EventNavigate{ direction };
|
||||
return e;
|
||||
}
|
||||
};
|
||||
|
||||
enum class Display {
|
||||
None,
|
||||
Block,
|
||||
Inline,
|
||||
InlineBlock,
|
||||
FlowRoot,
|
||||
Flex,
|
||||
InlineFlex,
|
||||
Table,
|
||||
InlineTable,
|
||||
TableRow,
|
||||
TableRowGroup,
|
||||
TableColumn,
|
||||
TableColumnGroup,
|
||||
TableCell
|
||||
};
|
||||
|
||||
enum class Visibility {
|
||||
Visible,
|
||||
Hidden
|
||||
};
|
||||
|
||||
enum class Position {
|
||||
Absolute,
|
||||
Relative
|
||||
};
|
||||
|
||||
enum class JustifyContent {
|
||||
FlexStart,
|
||||
FlexEnd,
|
||||
Center,
|
||||
SpaceBetween,
|
||||
SpaceAround,
|
||||
SpaceEvenly
|
||||
};
|
||||
|
||||
enum class FlexDirection {
|
||||
Row,
|
||||
Column,
|
||||
RowReverse,
|
||||
ColumnReverse
|
||||
};
|
||||
|
||||
enum class AlignItems {
|
||||
FlexStart,
|
||||
FlexEnd,
|
||||
Center,
|
||||
Baseline,
|
||||
Stretch
|
||||
};
|
||||
|
||||
enum class Overflow {
|
||||
Visible,
|
||||
Hidden,
|
||||
Auto,
|
||||
Scroll
|
||||
};
|
||||
|
||||
enum class Unit {
|
||||
Px,
|
||||
Dp,
|
||||
Percent
|
||||
};
|
||||
|
||||
enum class AnimationType : uint32_t {
|
||||
None,
|
||||
Set,
|
||||
Tween
|
||||
};
|
||||
|
||||
enum class FontStyle {
|
||||
Normal,
|
||||
Italic
|
||||
};
|
||||
|
||||
enum class TextAlign {
|
||||
Left,
|
||||
Right,
|
||||
Center,
|
||||
Justify
|
||||
};
|
||||
|
||||
enum class TextTransform {
|
||||
None,
|
||||
Capitalize,
|
||||
Uppercase,
|
||||
Lowercase
|
||||
};
|
||||
|
||||
enum class Drag {
|
||||
None,
|
||||
Drag,
|
||||
DragDrop,
|
||||
Block,
|
||||
Clone
|
||||
};
|
||||
|
||||
enum class TabIndex {
|
||||
None,
|
||||
Auto
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
Reference in New Issue
Block a user