Implement navigation and focus styling for new UI framework (no manual overrides yet)

This commit is contained in:
Mr-Wiseguy 2025-04-13 22:19:16 -04:00
parent 0b48ab9324
commit 67e1ddb70b
20 changed files with 315 additions and 27 deletions

View file

@ -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

View file

@ -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()) {

View file

@ -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();

View file

@ -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:

View file

@ -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; }
};

View file

@ -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();

View file

@ -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();

View file

@ -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;
}

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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; }
};

View file

@ -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);

View file

@ -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 {

View file

@ -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;

View file

@ -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(

View file

@ -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
View 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
View 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