mirror of
https://github.com/Zelda64Recomp/Zelda64Recomp.git
synced 2025-10-30 08:03:03 +00:00
Implement navigation and focus styling for new UI framework (no manual overrides yet)
This commit is contained in:
parent
0b48ab9324
commit
67e1ddb70b
20 changed files with 315 additions and 27 deletions
|
|
@ -183,6 +183,7 @@ set (SOURCES
|
|||
${CMAKE_SOURCE_DIR}/src/ui/ui_api.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_api_events.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_api_images.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_utils.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/util/hsv.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/core/ui_context.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_button.cpp
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ namespace recompui {
|
|||
resource_slotmap resources;
|
||||
Rml::ElementDocument* document;
|
||||
Element root_element;
|
||||
Element* autofocus_element = nullptr;
|
||||
std::vector<Element*> loose_elements;
|
||||
std::unordered_set<ResourceId> to_update;
|
||||
bool captures_input = true;
|
||||
|
|
@ -72,6 +73,8 @@ enum class ContextErrorType {
|
|||
DestroyResourceInWrongContext,
|
||||
DestroyResourceNotFound,
|
||||
GetDocumentInvalidContext,
|
||||
GetAutofocusInvalidContext,
|
||||
SetAutofocusInvalidContext,
|
||||
InternalError,
|
||||
};
|
||||
|
||||
|
|
@ -134,6 +137,12 @@ void context_error(recompui::ContextId id, ContextErrorType type) {
|
|||
case ContextErrorType::GetDocumentInvalidContext:
|
||||
error_message = "Attempted to get the document of an invalid UI context";
|
||||
break;
|
||||
case ContextErrorType::GetAutofocusInvalidContext:
|
||||
error_message = "Attempted to get the autofocus element of an invalid UI context";
|
||||
break;
|
||||
case ContextErrorType::SetAutofocusInvalidContext:
|
||||
error_message = "Attempted to set the autofocus element of an invalid UI context";
|
||||
break;
|
||||
case ContextErrorType::InternalError:
|
||||
error_message = "Internal error in UI context";
|
||||
break;
|
||||
|
|
@ -572,6 +581,28 @@ recompui::Element* recompui::ContextId::get_root_element() {
|
|||
return &ctx->root_element;
|
||||
}
|
||||
|
||||
recompui::Element* recompui::ContextId::get_autofocus_element() {
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
|
||||
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||
if (ctx == nullptr) {
|
||||
context_error(*this, ContextErrorType::GetAutofocusInvalidContext);
|
||||
}
|
||||
|
||||
return ctx->autofocus_element;
|
||||
}
|
||||
|
||||
void recompui::ContextId::set_autofocus_element(Element* element) {
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
|
||||
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||
if (ctx == nullptr) {
|
||||
context_error(*this, ContextErrorType::SetAutofocusInvalidContext);
|
||||
}
|
||||
|
||||
ctx->autofocus_element = element;
|
||||
}
|
||||
|
||||
recompui::ContextId recompui::get_current_context() {
|
||||
// Ensure a context is currently opened by this thread.
|
||||
if (opened_context_id == ContextId::null()) {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ namespace recompui {
|
|||
|
||||
Rml::ElementDocument* get_document();
|
||||
Element* get_root_element();
|
||||
Element* get_autofocus_element();
|
||||
void set_autofocus_element(Element* element);
|
||||
|
||||
void open();
|
||||
bool open_if_not_already();
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
|
||||
namespace recompui {
|
||||
|
||||
Button::Button(Element *parent, const std::string &text, ButtonStyle style) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable), "button", true) {
|
||||
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);
|
||||
|
|
@ -21,6 +23,7 @@ namespace recompui {
|
|||
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 });
|
||||
|
||||
|
|
@ -34,6 +37,8 @@ namespace recompui {
|
|||
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 });
|
||||
|
|
@ -45,6 +50,8 @@ namespace recompui {
|
|||
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 });
|
||||
|
|
@ -57,6 +64,7 @@ namespace recompui {
|
|||
}
|
||||
|
||||
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 });
|
||||
|
||||
|
|
@ -78,6 +86,9 @@ namespace recompui {
|
|||
case EventType::Enable:
|
||||
set_style_enabled(disabled_state, !std::get<EventEnable>(e.variant).active);
|
||||
break;
|
||||
case EventType::Focus:
|
||||
set_style_enabled(focus_state, std::get<EventFocus>(e.variant).active);
|
||||
break;
|
||||
case EventType::Update:
|
||||
break;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace recompui {
|
|||
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;
|
||||
|
|
@ -23,6 +24,7 @@ namespace recompui {
|
|||
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; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ void Element::register_event_listeners(uint32_t events_enabled) {
|
|||
this->events_enabled = events_enabled;
|
||||
|
||||
if (events_enabled & Events(EventType::Click)) {
|
||||
base->AddEventListener(Rml::EventId::Mousedown, this);
|
||||
base->AddEventListener(Rml::EventId::Click, this);
|
||||
}
|
||||
|
||||
if (events_enabled & Events(EventType::Focus)) {
|
||||
|
|
@ -94,11 +94,20 @@ void Element::register_event_listeners(uint32_t events_enabled) {
|
|||
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) {
|
||||
base->SetProperty(it.first, it.second);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -155,9 +164,25 @@ void Element::ProcessEvent(Rml::Event &event) {
|
|||
|
||||
// Events that are processed during any phase.
|
||||
switch (event.GetId()) {
|
||||
case Rml::EventId::Mousedown:
|
||||
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;
|
||||
|
|
@ -218,6 +243,15 @@ void Element::process_event(const Event &) {
|
|||
// Does nothing by default.
|
||||
}
|
||||
|
||||
void Element::enable_focus() {
|
||||
set_property(Rml::PropertyId::TabIndex, Rml::Style::TabIndex::Auto);
|
||||
set_property(Rml::PropertyId::Focus, Rml::Style::Focus::Auto);
|
||||
set_property(Rml::PropertyId::NavUp, Rml::Style::Nav::Auto);
|
||||
set_property(Rml::PropertyId::NavDown, Rml::Style::Nav::Auto);
|
||||
set_property(Rml::PropertyId::NavLeft, Rml::Style::Nav::Auto);
|
||||
set_property(Rml::PropertyId::NavRight, Rml::Style::Nav::Auto);
|
||||
}
|
||||
|
||||
void Element::clear_children() {
|
||||
if (children.empty()) {
|
||||
return;
|
||||
|
|
@ -352,6 +386,10 @@ void Element::set_style_enabled(std::string_view style_name, bool enable) {
|
|||
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();
|
||||
}
|
||||
|
|
@ -409,6 +447,10 @@ double Element::get_input_value_double() {
|
|||
}, value);
|
||||
}
|
||||
|
||||
void Element::focus() {
|
||||
base->Focus();
|
||||
}
|
||||
|
||||
void Element::queue_update() {
|
||||
ContextId cur_context = get_current_context();
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ private:
|
|||
void add_child(Element *child);
|
||||
void register_event_listeners(uint32_t events_enabled);
|
||||
void apply_style(Style *style);
|
||||
void apply_styles();
|
||||
void propagate_disabled(bool disabled);
|
||||
void handle_event(const Event &e);
|
||||
|
||||
|
|
@ -76,6 +75,8 @@ public:
|
|||
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();
|
||||
|
|
@ -83,6 +84,8 @@ public:
|
|||
float get_client_top();
|
||||
float get_client_width();
|
||||
float get_client_height();
|
||||
void enable_focus();
|
||||
void focus();
|
||||
void queue_update();
|
||||
void register_callback(ContextId context, PTR(void) callback, PTR(void) userdata);
|
||||
uint32_t get_input_value_u32();
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
#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), "label", true) {
|
||||
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);
|
||||
|
|
@ -24,9 +26,11 @@ namespace recompui {
|
|||
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) {
|
||||
|
|
@ -48,6 +52,22 @@ namespace recompui {
|
|||
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();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ namespace recompui {
|
|||
private:
|
||||
Style hover_style;
|
||||
Style checked_style;
|
||||
Style pulsing_style;
|
||||
std::function<void(uint32_t)> pressed_callback = nullptr;
|
||||
uint32_t index = 0;
|
||||
protected:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "overloaded.h"
|
||||
#include "ui_slider.h"
|
||||
#include "../ui_utils.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <charconv>
|
||||
|
|
@ -45,7 +46,6 @@ namespace recompui {
|
|||
|
||||
void Slider::update_circle_position() {
|
||||
double ratio = std::clamp((value - min_value) / (max_value - min_value), 0.0, 1.0);
|
||||
float slider_relative_left = slider_element->get_absolute_left() - get_absolute_left();
|
||||
circle_element->set_left(ratio * 100.0, Unit::Percent);
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +72,42 @@ namespace recompui {
|
|||
}, val);
|
||||
}
|
||||
|
||||
Slider::Slider(Element *parent, SliderType type) : Element(parent) {
|
||||
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 (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 });
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Slider::Slider(Element *parent, SliderType type) : Element(parent, Events(EventType::Focus, EventType::Update, EventType::Navigate)) {
|
||||
this->type = type;
|
||||
|
||||
set_display(Display::Flex);
|
||||
|
|
@ -80,6 +115,10 @@ namespace recompui {
|
|||
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);
|
||||
|
|
@ -87,8 +126,10 @@ namespace recompui {
|
|||
value_label->set_min_width(60.0f);
|
||||
value_label->set_max_width(60.0f);
|
||||
|
||||
slider_element = context.create_element<Element>(this);
|
||||
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);
|
||||
|
|
@ -96,8 +137,8 @@ namespace recompui {
|
|||
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); });
|
||||
bar_element->add_dragged_callback([this](float x, float y, recompui::DragPhase phase){ bar_dragged(x, y, phase); });
|
||||
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);
|
||||
|
|
@ -108,7 +149,8 @@ namespace recompui {
|
|||
circle_element->set_margin_left(-8.0f);
|
||||
circle_element->set_background_color(Color{ 204, 204, 204, 255 });
|
||||
circle_element->set_border_radius(8.0f);
|
||||
circle_element->add_dragged_callback([this](float x, float y, recompui::DragPhase phase){ circle_dragged(x, y, phase); });
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -154,4 +196,18 @@ namespace recompui {
|
|||
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
|
||||
|
|
@ -15,7 +15,7 @@ namespace recompui {
|
|||
private:
|
||||
SliderType type = SliderType::Percent;
|
||||
Label *value_label = nullptr;
|
||||
Element *slider_element = nullptr;
|
||||
Clickable *slider_element = nullptr;
|
||||
Clickable *bar_element = nullptr;
|
||||
Clickable *circle_element = nullptr;
|
||||
double value = 50.0;
|
||||
|
|
@ -34,6 +34,9 @@ namespace recompui {
|
|||
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;
|
||||
|
||||
public:
|
||||
Slider(Element *parent, SliderType type);
|
||||
virtual ~Slider();
|
||||
|
|
@ -46,6 +49,7 @@ namespace recompui {
|
|||
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
|
||||
|
|
|
|||
|
|
@ -169,6 +169,22 @@ namespace recompui {
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -471,6 +487,12 @@ namespace recompui {
|
|||
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;
|
||||
|
|
@ -556,5 +578,22 @@ namespace recompui {
|
|||
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_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);
|
||||
}
|
||||
|
||||
|
||||
} // namespace recompui
|
||||
|
|
@ -94,6 +94,11 @@ namespace recompui {
|
|||
void set_drag(Drag drag);
|
||||
void set_tab_index(TabIndex focus);
|
||||
void set_font_family(std::string_view family);
|
||||
// TODO set_nav with Element*
|
||||
void set_nav_auto(NavDirection dir);
|
||||
void set_nav_none(NavDirection dir);
|
||||
void set_tab_index_auto();
|
||||
void set_tab_index_none();
|
||||
virtual bool is_element() { return false; }
|
||||
ResourceId get_resource_id() { return resource_id; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
namespace recompui {
|
||||
|
||||
Toggle::Toggle(Element *parent) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable), "button") {
|
||||
enable_focus();
|
||||
|
||||
set_width(162.0f);
|
||||
set_height(72.0f);
|
||||
set_border_radius(36.0f);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ 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 {
|
||||
|
|
@ -31,6 +32,7 @@ namespace recompui {
|
|||
Drag,
|
||||
Text,
|
||||
Update,
|
||||
Navigate,
|
||||
Count
|
||||
};
|
||||
|
||||
|
|
@ -41,6 +43,13 @@ namespace recompui {
|
|||
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);
|
||||
|
|
@ -78,7 +87,11 @@ namespace recompui {
|
|||
std::string text;
|
||||
};
|
||||
|
||||
using EventVariant = std::variant<EventClick, EventFocus, EventHover, EventEnable, EventDrag, EventText, std::monostate>;
|
||||
struct EventNavigate {
|
||||
NavDirection direction;
|
||||
};
|
||||
|
||||
using EventVariant = std::variant<EventClick, EventFocus, EventHover, EventEnable, EventDrag, EventText, EventNavigate, std::monostate>;
|
||||
|
||||
struct Event {
|
||||
EventType type;
|
||||
|
|
@ -133,6 +146,13 @@ namespace recompui {
|
|||
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 {
|
||||
|
|
@ -173,7 +193,9 @@ namespace recompui {
|
|||
|
||||
enum class FlexDirection {
|
||||
Row,
|
||||
Column
|
||||
Column,
|
||||
RowReverse,
|
||||
ColumnReverse
|
||||
};
|
||||
|
||||
enum class AlignItems {
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ ModEntryButton::ModEntryButton(Element *parent, uint32_t mod_index) : Element(pa
|
|||
this->mod_index = mod_index;
|
||||
|
||||
set_drag(Drag::Drag);
|
||||
enable_focus();
|
||||
|
||||
ContextId context = get_current_context();
|
||||
view = context.create_element<ModEntryView>(this);
|
||||
|
|
@ -145,13 +146,12 @@ void ModEntryButton::set_selected(bool selected) {
|
|||
void ModEntryButton::process_event(const Event& e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
case EventType::Focus:
|
||||
selected_callback(mod_index);
|
||||
break;
|
||||
case EventType::Hover:
|
||||
view->set_style_enabled(hover_state, std::get<EventHover>(e.variant).active);
|
||||
break;
|
||||
case EventType::Focus:
|
||||
break;
|
||||
case EventType::Drag:
|
||||
drag_callback(mod_index, std::get<EventDrag>(e.variant));
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -124,13 +124,16 @@ void recompui::init_prompt_context() {
|
|||
prompt_state.confirm_button->set_text_align(TextAlign::Center);
|
||||
prompt_state.confirm_button->set_color(Color{ 204, 204, 204, 255 });
|
||||
prompt_state.confirm_button->add_pressed_callback(run_confirm_callback);
|
||||
// TODO nav: autofocus
|
||||
// TODO nav: nav-left: none; nav-right: cancel_button
|
||||
|
||||
Style* confirm_hover_style = prompt_state.confirm_button->get_hover_style();
|
||||
confirm_hover_style->set_border_color(Color{ 69, 208, 67, 255 });
|
||||
confirm_hover_style->set_background_color(Color{ 69, 208, 67, 76 });
|
||||
confirm_hover_style->set_color(Color{ 242, 242, 242, 255 });
|
||||
|
||||
Style* confirm_focus_style = prompt_state.confirm_button->get_focus_style();
|
||||
confirm_focus_style->set_border_color(Color{ 69, 208, 67, 255 });
|
||||
confirm_focus_style->set_background_color(Color{ 69, 208, 67, 76 });
|
||||
confirm_focus_style->set_color(Color{ 242, 242, 242, 255 });
|
||||
|
||||
prompt_state.cancel_button = context.create_element<Button>(prompt_state.prompt_controls, "", ButtonStyle::Primary);
|
||||
prompt_state.cancel_button->set_min_width(185.0f, Unit::Dp);
|
||||
|
|
@ -141,13 +144,18 @@ void recompui::init_prompt_context() {
|
|||
prompt_state.cancel_button->set_text_align(TextAlign::Center);
|
||||
prompt_state.cancel_button->set_color(Color{ 204, 204, 204, 255 });
|
||||
prompt_state.cancel_button->add_pressed_callback(run_cancel_callback);
|
||||
// TODO nav: nav-left: confirm_button; nav-right: none
|
||||
|
||||
Style* cancel_hover_style = prompt_state.cancel_button->get_hover_style();
|
||||
cancel_hover_style->set_border_color(Color{ 248, 96, 57, 255 });
|
||||
cancel_hover_style->set_background_color(Color{ 248, 96, 57, 76 });
|
||||
cancel_hover_style->set_color(Color{ 242, 242, 242, 255 });
|
||||
|
||||
Style* cancel_focus_style = prompt_state.cancel_button->get_focus_style();
|
||||
cancel_focus_style->set_border_color(Color{ 248, 96, 57, 255 });
|
||||
cancel_focus_style->set_background_color(Color{ 248, 96, 57, 76 });
|
||||
cancel_focus_style->set_color(Color{ 242, 242, 242, 255 });
|
||||
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
|
|
@ -192,6 +200,10 @@ void style_button(recompui::Button* button, recompui::ButtonVariant variant) {
|
|||
recompui::Style* hover_style = button->get_hover_style();
|
||||
hover_style->set_border_color(hover_border_color);
|
||||
hover_style->set_background_color(hover_background_color);
|
||||
|
||||
recompui::Style* focus_style = button->get_focus_style();
|
||||
focus_style->set_border_color(hover_border_color);
|
||||
focus_style->set_background_color(hover_background_color);
|
||||
|
||||
recompui::Color disabled_color { 255, 255, 255, 0.6f * 255 };
|
||||
recompui::Style* disabled_style = button->get_disabled_style();
|
||||
|
|
@ -200,6 +212,13 @@ void style_button(recompui::Button* button, recompui::ButtonVariant variant) {
|
|||
|
||||
// Must be called while prompt_state.mutex is locked.
|
||||
void show_prompt(std::function<void()>& prev_cancel_action, bool focus_on_cancel) {
|
||||
if (focus_on_cancel) {
|
||||
prompt_state.ui_context.set_autofocus_element(prompt_state.cancel_button);
|
||||
}
|
||||
else {
|
||||
prompt_state.ui_context.set_autofocus_element(prompt_state.confirm_button);
|
||||
}
|
||||
|
||||
if (!recompui::is_context_shown(prompt_state.ui_context)) {
|
||||
recompui::show_context(prompt_state.ui_context, "");
|
||||
}
|
||||
|
|
@ -209,13 +228,6 @@ void show_prompt(std::function<void()>& prev_cancel_action, bool focus_on_cancel
|
|||
prev_cancel_action();
|
||||
}
|
||||
}
|
||||
|
||||
if (focus_on_cancel) {
|
||||
// TODO nav: focus cancel button
|
||||
}
|
||||
else {
|
||||
// TODO nav: focus confirm button
|
||||
}
|
||||
}
|
||||
|
||||
void recompui::open_choice_prompt(
|
||||
|
|
|
|||
|
|
@ -358,6 +358,10 @@ public:
|
|||
|
||||
document->PullToFront();
|
||||
document->Show();
|
||||
recompui::Element* default_element = context.get_autofocus_element();
|
||||
if (default_element) {
|
||||
default_element->focus();
|
||||
}
|
||||
}
|
||||
|
||||
void hide_context(recompui::ContextId context) {
|
||||
|
|
|
|||
20
src/ui/ui_utils.cpp
Normal file
20
src/ui/ui_utils.cpp
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#include "ultramodern/ultramodern.hpp"
|
||||
|
||||
#include "ui_utils.h"
|
||||
|
||||
recompui::Color recompui::lerp_color(const recompui::Color& a, const recompui::Color& b, float factor) {
|
||||
return recompui::Color{
|
||||
static_cast<uint8_t>(std::lerp(float(a.r), float(b.r), factor)),
|
||||
static_cast<uint8_t>(std::lerp(float(a.g), float(b.g), factor)),
|
||||
static_cast<uint8_t>(std::lerp(float(a.b), float(b.b), factor)),
|
||||
static_cast<uint8_t>(std::lerp(float(a.a), float(b.a), factor)),
|
||||
};
|
||||
}
|
||||
|
||||
recompui::Color recompui::get_pulse_color(uint32_t pulse_milliseconds) {
|
||||
uint64_t millis = std::chrono::duration_cast<std::chrono::milliseconds>(ultramodern::time_since_start()).count();
|
||||
uint32_t anim_offset = millis % pulse_milliseconds;
|
||||
|
||||
float factor = std::abs((2.0f * anim_offset / pulse_milliseconds) - 1.0f);
|
||||
return lerp_color(Color{ 23, 214, 232, 255 }, Color{ 162, 239, 246, 255 }, factor);
|
||||
}
|
||||
11
src/ui/ui_utils.h
Normal file
11
src/ui/ui_utils.h
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#ifndef __UI_UTILS_H__
|
||||
#define __UI_UTILS_H__
|
||||
|
||||
#include "elements/ui_types.h"
|
||||
|
||||
namespace recompui {
|
||||
Color lerp_color(const Color& a, const Color& b, float factor);
|
||||
Color get_pulse_color(uint32_t millisecond_period);
|
||||
}
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Reference in a new issue