mirror of https://github.com/wayvr-org/wayvr.git
wgui: Editbox basics, component focus, minor refactoring
This commit is contained in:
parent
c902f0cd44
commit
841241a7e6
|
|
@ -5,6 +5,7 @@ use wgui::{
|
||||||
widget::label::{WidgetLabel, WidgetLabelParams},
|
widget::label::{WidgetLabel, WidgetLabelParams},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn create_label(layout: &mut Layout, parent: WidgetID, content: Translation) -> anyhow::Result<()> {
|
pub fn create_label(layout: &mut Layout, parent: WidgetID, content: Translation) -> anyhow::Result<()> {
|
||||||
let label = WidgetLabel::create(
|
let label = WidgetLabel::create(
|
||||||
&mut layout.state.globals.get(),
|
&mut layout.state.globals.get(),
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,17 @@
|
||||||
</div>
|
</div>
|
||||||
</rectangle>
|
</rectangle>
|
||||||
|
|
||||||
<rectangle macro="rect">
|
<div>
|
||||||
<label text="Context menu test" />
|
<rectangle macro="rect">
|
||||||
<Button id="button_context_menu" text="Show context menu" />
|
<label text="Context menu test" />
|
||||||
</rectangle>
|
<Button id="button_context_menu" text="Show context menu" />
|
||||||
|
</rectangle>
|
||||||
|
<rectangle macro="rect">
|
||||||
|
<label text="Editbox test" />
|
||||||
|
<EditBox text="Initial text. Test, abcdef." />
|
||||||
|
<EditBox />
|
||||||
|
</rectangle>
|
||||||
|
</div>
|
||||||
|
|
||||||
<rectangle macro="rect">
|
<rectangle macro="rect">
|
||||||
<label text="visibility test" weight="bold" />
|
<label text="visibility test" weight="bold" />
|
||||||
|
|
|
||||||
|
|
@ -349,6 +349,7 @@ fn register_event_mouse_press(state: Rc<RefCell<State>>, listeners: &mut EventLi
|
||||||
common.alterables.trigger_haptics();
|
common.alterables.trigger_haptics();
|
||||||
common.alterables.play_sound(WguiSoundType::ButtonPress);
|
common.alterables.play_sound(WguiSoundType::ButtonPress);
|
||||||
common.alterables.mark_redraw();
|
common.alterables.mark_redraw();
|
||||||
|
common.alterables.unfocus();
|
||||||
|
|
||||||
if state.hovered {
|
if state.hovered {
|
||||||
state.down = true;
|
state.down = true;
|
||||||
|
|
@ -556,25 +557,19 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||||
let base = ComponentBase {
|
let base = ComponentBase {
|
||||||
id: root.id,
|
id: root.id,
|
||||||
lhandles: {
|
lhandles: {
|
||||||
let mut widget = ess.layout.state.widgets.get(id_rect).unwrap().state();
|
let listeners = &mut root.widget.state().event_listeners;
|
||||||
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
|
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
|
||||||
vec![
|
vec![
|
||||||
register_event_mouse_enter(
|
register_event_mouse_enter(data.clone(), state.clone(), listeners, params.tooltip, anim_mult),
|
||||||
data.clone(),
|
register_event_mouse_leave(state.clone(), listeners, anim_mult),
|
||||||
state.clone(),
|
register_event_mouse_press(state.clone(), listeners),
|
||||||
&mut widget.event_listeners,
|
register_event_mouse_release(data.clone(), state.clone(), listeners),
|
||||||
params.tooltip,
|
|
||||||
anim_mult,
|
|
||||||
),
|
|
||||||
register_event_mouse_leave(state.clone(), &mut widget.event_listeners, anim_mult),
|
|
||||||
register_event_mouse_press(state.clone(), &mut widget.event_listeners),
|
|
||||||
register_event_mouse_release(data.clone(), state.clone(), &mut widget.event_listeners),
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let button = Rc::new(ComponentButton { base, data, state });
|
let button = Rc::new(ComponentButton { base, data, state });
|
||||||
|
|
||||||
ess.layout.register_component_refresh(Component(button.clone()));
|
ess.layout.register_component_refresh(&Component(button.clone()));
|
||||||
Ok((root, button))
|
Ok((root, button))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,7 @@ fn register_event_mouse_press(state: Rc<RefCell<State>>, listeners: &mut EventLi
|
||||||
|
|
||||||
common.alterables.trigger_haptics();
|
common.alterables.trigger_haptics();
|
||||||
common.alterables.mark_redraw();
|
common.alterables.mark_redraw();
|
||||||
|
common.alterables.unfocus();
|
||||||
|
|
||||||
if state.hovered {
|
if state.hovered {
|
||||||
state.down = true;
|
state.down = true;
|
||||||
|
|
@ -431,13 +432,13 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||||
let base = ComponentBase {
|
let base = ComponentBase {
|
||||||
id: root.id,
|
id: root.id,
|
||||||
lhandles: {
|
lhandles: {
|
||||||
let mut widget = ess.layout.state.widgets.get(id_container).unwrap().state();
|
let listeners = &mut root.widget.state().event_listeners;
|
||||||
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
|
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
|
||||||
vec![
|
vec![
|
||||||
register_event_mouse_enter(state.clone(), &mut widget.event_listeners, params.tooltip, anim_mult),
|
register_event_mouse_enter(state.clone(), listeners, params.tooltip, anim_mult),
|
||||||
register_event_mouse_leave(state.clone(), &mut widget.event_listeners, anim_mult),
|
register_event_mouse_leave(state.clone(), listeners, anim_mult),
|
||||||
register_event_mouse_press(state.clone(), &mut widget.event_listeners),
|
register_event_mouse_press(state.clone(), listeners),
|
||||||
register_event_mouse_release(data.clone(), state.clone(), &mut widget.event_listeners),
|
register_event_mouse_release(data.clone(), state.clone(), listeners),
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,334 @@
|
||||||
|
use std::{
|
||||||
|
cell::{Ref, RefCell},
|
||||||
|
rc::{Rc, Weak},
|
||||||
|
};
|
||||||
|
|
||||||
|
use glam::FloatExt;
|
||||||
|
use taffy::prelude::{auto, length, percent};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
animation::{Animation, AnimationEasing},
|
||||||
|
components::{Component, ComponentBase, ComponentTrait, FocusChangeData, RefreshData},
|
||||||
|
drawing::{self, Color},
|
||||||
|
event::{self, CallbackDataCommon, EventListenerCollection, EventListenerKind, StyleSetRequest},
|
||||||
|
i18n::Translation,
|
||||||
|
layout::{WidgetID, WidgetPair},
|
||||||
|
renderer_vk::text::{TextShadow, TextStyle},
|
||||||
|
widget::{
|
||||||
|
ConstructEssentials, EventResult,
|
||||||
|
div::WidgetDiv,
|
||||||
|
label::{WidgetLabel, WidgetLabelParams},
|
||||||
|
rectangle::{WidgetRectangle, WidgetRectangleParams},
|
||||||
|
util::WLength,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Params {
|
||||||
|
pub style: taffy::Style,
|
||||||
|
pub initial_text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
text: String,
|
||||||
|
hovered: bool,
|
||||||
|
focused: bool,
|
||||||
|
focused_prev: bool,
|
||||||
|
first_refresh: bool,
|
||||||
|
self_ref: Weak<ComponentEditBox>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::struct_field_names)]
|
||||||
|
struct Data {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
id_rect_container: WidgetID, // Rectangle
|
||||||
|
id_rect_bottom: WidgetID, // Rectangle
|
||||||
|
id_label: WidgetID,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ComponentEditBox {
|
||||||
|
base: ComponentBase,
|
||||||
|
data: Rc<Data>,
|
||||||
|
state: Rc<RefCell<State>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn anim_bottom_rect(
|
||||||
|
common: &mut CallbackDataCommon,
|
||||||
|
accent_color: drawing::Color,
|
||||||
|
id_rect: WidgetID,
|
||||||
|
anim_mult: f32,
|
||||||
|
focused: bool,
|
||||||
|
) {
|
||||||
|
common.alterables.animate(Animation::new(
|
||||||
|
id_rect,
|
||||||
|
(10.0 * anim_mult) as _,
|
||||||
|
AnimationEasing::OutQuad,
|
||||||
|
{
|
||||||
|
Box::new(move |common, data| {
|
||||||
|
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
|
||||||
|
let pos_bidir = if focused { data.pos } else { 1.0 - data.pos };
|
||||||
|
|
||||||
|
rect.set_color(
|
||||||
|
common,
|
||||||
|
accent_color.lerp(&drawing::Color::new(1.0, 1.0, 1.0, 1.0), pos_bidir),
|
||||||
|
);
|
||||||
|
|
||||||
|
common.alterables.set_style(
|
||||||
|
data.widget_id,
|
||||||
|
StyleSetRequest::Size(taffy::Size {
|
||||||
|
width: percent(0.9.lerp(1.0, pos_bidir)),
|
||||||
|
height: length(2.0 + pos_bidir * 2.0),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
common.alterables.set_style(
|
||||||
|
data.widget_id,
|
||||||
|
StyleSetRequest::Margin(taffy::Rect {
|
||||||
|
bottom: length(0.0),
|
||||||
|
left: auto(),
|
||||||
|
right: auto(),
|
||||||
|
top: auto(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
common.alterables.mark_redraw();
|
||||||
|
})
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn refresh_all(common: &mut CallbackDataCommon, data: &Data, state: &mut State) -> Option<()> {
|
||||||
|
let defaults = common.defaults();
|
||||||
|
let editbox_color = defaults.editbox_color;
|
||||||
|
let anim_mult = defaults.animation_mult;
|
||||||
|
let accent_color = defaults.accent_color;
|
||||||
|
drop(defaults);
|
||||||
|
|
||||||
|
let (rect_color, border_color) = if state.focused {
|
||||||
|
(editbox_color.add_rgb(0.1), editbox_color.add_rgb(0.75))
|
||||||
|
} else if state.hovered {
|
||||||
|
(editbox_color.add_rgb(0.1), editbox_color.add_rgb(0.1 + 0.15))
|
||||||
|
} else {
|
||||||
|
(editbox_color, editbox_color.add_rgb(0.15))
|
||||||
|
};
|
||||||
|
|
||||||
|
// update background color
|
||||||
|
let mut rect = common.state.widgets.get_as::<WidgetRectangle>(data.id_rect_container)?;
|
||||||
|
rect.params.border_color = border_color;
|
||||||
|
rect.set_color(common, rect_color);
|
||||||
|
|
||||||
|
if state.focused_prev != state.focused || state.first_refresh {
|
||||||
|
anim_bottom_rect(common, accent_color, data.id_rect_bottom, anim_mult, state.focused);
|
||||||
|
state.focused_prev = state.focused;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.first_refresh = false;
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentTrait for ComponentEditBox {
|
||||||
|
fn base(&self) -> &ComponentBase {
|
||||||
|
&self.base
|
||||||
|
}
|
||||||
|
|
||||||
|
fn base_mut(&mut self) -> &mut ComponentBase {
|
||||||
|
&mut self.base
|
||||||
|
}
|
||||||
|
|
||||||
|
fn refresh(&self, data: &mut RefreshData) {
|
||||||
|
let mut state = self.state.borrow_mut();
|
||||||
|
let res = refresh_all(data.common, &self.data, &mut state);
|
||||||
|
debug_assert!(res.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_focus_change(&self, data: &mut FocusChangeData) {
|
||||||
|
let mut state = self.state.borrow_mut();
|
||||||
|
state.focused = data.focused;
|
||||||
|
data.common.alterables.refresh_component_once(&state.self_ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentEditBox {
|
||||||
|
pub fn set_text(&self, common: &mut CallbackDataCommon, text: Translation) {
|
||||||
|
let Some(mut label) = common.state.widgets.get_as::<WidgetLabel>(self.data.id_label) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
label.set_text(common, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_text(&self) -> Ref<'_, String> {
|
||||||
|
Ref::map(self.state.borrow(), |x| &x.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_event_mouse_enter(
|
||||||
|
state: Rc<RefCell<State>>,
|
||||||
|
listeners: &mut EventListenerCollection,
|
||||||
|
) -> event::EventListenerID {
|
||||||
|
listeners.register(
|
||||||
|
EventListenerKind::MouseEnter,
|
||||||
|
Box::new(move |common, _evt, (), ()| {
|
||||||
|
let mut state = state.borrow_mut();
|
||||||
|
state.hovered = true;
|
||||||
|
common.alterables.trigger_haptics();
|
||||||
|
common.alterables.refresh_component_once(&state.self_ref);
|
||||||
|
Ok(EventResult::Pass)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_event_mouse_press(
|
||||||
|
state: Rc<RefCell<State>>,
|
||||||
|
listeners: &mut EventListenerCollection,
|
||||||
|
) -> event::EventListenerID {
|
||||||
|
listeners.register(
|
||||||
|
EventListenerKind::MousePress,
|
||||||
|
Box::new(move |common, _evt, (), ()| {
|
||||||
|
let state = state.borrow_mut();
|
||||||
|
common.alterables.focus(&state.self_ref);
|
||||||
|
common.alterables.trigger_haptics();
|
||||||
|
common.alterables.refresh_component_once(&state.self_ref);
|
||||||
|
Ok(EventResult::Pass)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_event_mouse_leave(
|
||||||
|
state: Rc<RefCell<State>>,
|
||||||
|
listeners: &mut EventListenerCollection,
|
||||||
|
) -> event::EventListenerID {
|
||||||
|
listeners.register(
|
||||||
|
EventListenerKind::MouseLeave,
|
||||||
|
Box::new(move |common, _evt, (), ()| {
|
||||||
|
let mut state = state.borrow_mut();
|
||||||
|
state.hovered = false;
|
||||||
|
common.alterables.trigger_haptics();
|
||||||
|
common.alterables.refresh_component_once(&state.self_ref);
|
||||||
|
Ok(EventResult::Pass)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn construct(
|
||||||
|
ess: &mut ConstructEssentials,
|
||||||
|
mut params: Params,
|
||||||
|
) -> anyhow::Result<(WidgetPair, Rc<ComponentEditBox>)> {
|
||||||
|
let globals = ess.layout.state.globals.clone();
|
||||||
|
let defaults = globals.defaults();
|
||||||
|
drop(defaults);
|
||||||
|
|
||||||
|
if params.style.size.width.is_auto() {
|
||||||
|
params.style.size.width = length(128.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.style.size.height.is_auto() {
|
||||||
|
params.style.size.height = length(32.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// override style
|
||||||
|
params.style.align_items = Some(taffy::AlignItems::Center);
|
||||||
|
params.style.position = taffy::Position::Relative;
|
||||||
|
params.style.overflow = taffy::Point {
|
||||||
|
x: taffy::Overflow::Scroll,
|
||||||
|
y: taffy::Overflow::Scroll,
|
||||||
|
};
|
||||||
|
params.style.min_size = params.style.max_size;
|
||||||
|
|
||||||
|
let (root, _) = ess.layout.add_child(
|
||||||
|
ess.parent,
|
||||||
|
WidgetRectangle::create(WidgetRectangleParams {
|
||||||
|
border: 2.0,
|
||||||
|
round: WLength::Units(3.0),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
params.style,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// for centering
|
||||||
|
let (rect_bottom_parent, _) = ess.layout.add_child(
|
||||||
|
root.id,
|
||||||
|
WidgetDiv::create(),
|
||||||
|
taffy::Style {
|
||||||
|
position: taffy::Position::Absolute,
|
||||||
|
flex_direction: taffy::FlexDirection::Column,
|
||||||
|
align_content: Some(taffy::AlignContent::Center),
|
||||||
|
align_items: Some(taffy::AlignItems::Center),
|
||||||
|
size: taffy::Size {
|
||||||
|
width: percent(1.0),
|
||||||
|
height: percent(1.0),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let (rect_bottom, _) = ess.layout.add_child(
|
||||||
|
rect_bottom_parent.id,
|
||||||
|
WidgetRectangle::create(Default::default()),
|
||||||
|
Default::default(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let id_container = root.id;
|
||||||
|
|
||||||
|
let (label_parent, _) = ess.layout.add_child(
|
||||||
|
root.id,
|
||||||
|
WidgetDiv::create(),
|
||||||
|
taffy::Style {
|
||||||
|
padding: taffy::Rect::length(8.0),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let (label, _node_label) = ess.layout.add_child(
|
||||||
|
label_parent.id,
|
||||||
|
WidgetLabel::create(
|
||||||
|
&mut globals.get(),
|
||||||
|
WidgetLabelParams {
|
||||||
|
content: Translation::from_raw_text(¶ms.initial_text),
|
||||||
|
style: TextStyle {
|
||||||
|
shadow: Some(TextShadow {
|
||||||
|
x: 1.0,
|
||||||
|
y: 1.0,
|
||||||
|
color: Color::new(0.0, 0.0, 0.0, 1.0),
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Default::default(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let data = Rc::new(Data {
|
||||||
|
id_rect_container: id_container,
|
||||||
|
id_label: label.id,
|
||||||
|
id_rect_bottom: rect_bottom.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
let state = Rc::new(RefCell::new(State {
|
||||||
|
self_ref: Weak::new(),
|
||||||
|
text: params.initial_text,
|
||||||
|
hovered: false,
|
||||||
|
focused: false,
|
||||||
|
focused_prev: false,
|
||||||
|
first_refresh: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let base = ComponentBase {
|
||||||
|
id: root.id,
|
||||||
|
lhandles: {
|
||||||
|
let mut root_state = root.widget.state();
|
||||||
|
vec![
|
||||||
|
register_event_mouse_enter(state.clone(), &mut root_state.event_listeners),
|
||||||
|
register_event_mouse_leave(state.clone(), &mut root_state.event_listeners),
|
||||||
|
register_event_mouse_press(state.clone(), &mut root_state.event_listeners),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let editbox = Rc::new(ComponentEditBox { base, data, state });
|
||||||
|
editbox.state.borrow_mut().self_ref = Rc::downgrade(&editbox);
|
||||||
|
|
||||||
|
ess.layout.defer_component_refresh(Component(editbox.clone()));
|
||||||
|
Ok((root, editbox))
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ use crate::{
|
||||||
|
|
||||||
pub mod button;
|
pub mod button;
|
||||||
pub mod checkbox;
|
pub mod checkbox;
|
||||||
|
pub mod editbox;
|
||||||
pub mod radio_group;
|
pub mod radio_group;
|
||||||
pub mod slider;
|
pub mod slider;
|
||||||
pub mod tabs;
|
pub mod tabs;
|
||||||
|
|
@ -18,6 +19,11 @@ pub struct RefreshData<'a> {
|
||||||
pub common: &'a mut CallbackDataCommon<'a>,
|
pub common: &'a mut CallbackDataCommon<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct FocusChangeData<'a> {
|
||||||
|
pub common: &'a mut CallbackDataCommon<'a>,
|
||||||
|
pub focused: bool,
|
||||||
|
}
|
||||||
|
|
||||||
// common component data
|
// common component data
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ComponentBase {
|
pub struct ComponentBase {
|
||||||
|
|
@ -36,6 +42,7 @@ pub trait ComponentTrait: AnyTrait {
|
||||||
fn base(&self) -> &ComponentBase;
|
fn base(&self) -> &ComponentBase;
|
||||||
fn base_mut(&mut self) -> &mut ComponentBase;
|
fn base_mut(&mut self) -> &mut ComponentBase;
|
||||||
fn refresh(&self, data: &mut RefreshData);
|
fn refresh(&self, data: &mut RefreshData);
|
||||||
|
fn on_focus_change(&self, _data: &mut FocusChangeData) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
|
||||||
|
|
@ -324,7 +324,6 @@ fn register_event_mouse_enter(
|
||||||
common.alterables.trigger_haptics();
|
common.alterables.trigger_haptics();
|
||||||
state.borrow_mut().hovered = true;
|
state.borrow_mut().hovered = true;
|
||||||
on_enter_anim(common, data.slider_handle_rect_id, anim_mult);
|
on_enter_anim(common, data.slider_handle_rect_id, anim_mult);
|
||||||
|
|
||||||
ComponentTooltip::register_hover_in(common, &tooltip_info, event_data.widget_id, state.clone());
|
ComponentTooltip::register_hover_in(common, &tooltip_info, event_data.widget_id, state.clone());
|
||||||
|
|
||||||
Ok(EventResult::Pass)
|
Ok(EventResult::Pass)
|
||||||
|
|
@ -388,6 +387,7 @@ fn register_event_mouse_press(
|
||||||
EventListenerKind::MousePress,
|
EventListenerKind::MousePress,
|
||||||
Box::new(move |common, event_data, (), ()| {
|
Box::new(move |common, event_data, (), ()| {
|
||||||
common.alterables.trigger_haptics();
|
common.alterables.trigger_haptics();
|
||||||
|
common.alterables.unfocus();
|
||||||
let mut state = state.borrow_mut();
|
let mut state = state.borrow_mut();
|
||||||
|
|
||||||
let CallbackMetadata::MouseButton(btn) = event_data.metadata else {
|
let CallbackMetadata::MouseButton(btn) = event_data.metadata else {
|
||||||
|
|
@ -534,26 +534,20 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||||
let base = ComponentBase {
|
let base = ComponentBase {
|
||||||
id: root.id,
|
id: root.id,
|
||||||
lhandles: {
|
lhandles: {
|
||||||
let mut widget = ess.layout.state.widgets.get(body_id).unwrap().state();
|
let listeners = &mut root.widget.state().event_listeners;
|
||||||
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
|
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
|
||||||
vec![
|
vec![
|
||||||
register_event_mouse_enter(
|
register_event_mouse_enter(data.clone(), state.clone(), listeners, params.tooltip, anim_mult),
|
||||||
data.clone(),
|
register_event_mouse_leave(data.clone(), state.clone(), listeners, anim_mult),
|
||||||
state.clone(),
|
register_event_mouse_motion(data.clone(), state.clone(), listeners),
|
||||||
&mut widget.event_listeners,
|
register_event_mouse_press(data.clone(), state.clone(), listeners),
|
||||||
params.tooltip,
|
register_event_mouse_release(state.clone(), listeners),
|
||||||
anim_mult,
|
|
||||||
),
|
|
||||||
register_event_mouse_leave(data.clone(), state.clone(), &mut widget.event_listeners, anim_mult),
|
|
||||||
register_event_mouse_motion(data.clone(), state.clone(), &mut widget.event_listeners),
|
|
||||||
register_event_mouse_press(data.clone(), state.clone(), &mut widget.event_listeners),
|
|
||||||
register_event_mouse_release(state.clone(), &mut widget.event_listeners),
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let slider = Rc::new(ComponentSlider { base, data, state });
|
let slider = Rc::new(ComponentSlider { base, data, state });
|
||||||
|
|
||||||
ess.layout.register_component_refresh(Component(slider.clone()));
|
ess.layout.register_component_refresh(&Component(slider.clone()));
|
||||||
Ok((root, slider))
|
Ok((root, slider))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
cell::{Ref, RefMut},
|
cell::{Ref, RefMut},
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
|
rc::Weak,
|
||||||
};
|
};
|
||||||
|
|
||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
|
|
@ -9,9 +10,10 @@ use slotmap::{DenseSlotMap, new_key_type};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
animation::{self, Animation},
|
animation::{self, Animation},
|
||||||
|
components::{Component, ComponentTrait, ComponentWeak},
|
||||||
globals,
|
globals,
|
||||||
i18n::I18n,
|
i18n::I18n,
|
||||||
layout::{LayoutState, LayoutTask, WidgetID},
|
layout::{LayoutDispatchFunc, LayoutState, LayoutTask, WidgetID},
|
||||||
sound::WguiSoundType,
|
sound::WguiSoundType,
|
||||||
stack::{ScissorStack, Transform, TransformStack},
|
stack::{ScissorStack, Transform, TransformStack},
|
||||||
widget::{EventResult, WidgetData, WidgetObj},
|
widget::{EventResult, WidgetData, WidgetObj},
|
||||||
|
|
@ -101,12 +103,14 @@ pub enum StyleSetRequest {
|
||||||
Margin(taffy::Rect<taffy::LengthPercentageAuto>),
|
Margin(taffy::Rect<taffy::LengthPercentageAuto>),
|
||||||
Width(taffy::Dimension),
|
Width(taffy::Dimension),
|
||||||
Height(taffy::Dimension),
|
Height(taffy::Dimension),
|
||||||
|
Size(taffy::Size<taffy::Dimension>),
|
||||||
}
|
}
|
||||||
|
|
||||||
// alterables which will be dispatched in the next loop iteration phase
|
// alterables which will be dispatched in the next loop iteration phase
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct EventAlterables {
|
pub struct EventAlterables {
|
||||||
pub dirty_widgets: Vec<WidgetID>,
|
pub dirty_widgets: Vec<WidgetID>,
|
||||||
|
pub components_to_refresh_once: Vec<ComponentWeak>,
|
||||||
pub style_set_requests: Vec<(WidgetID, StyleSetRequest)>,
|
pub style_set_requests: Vec<(WidgetID, StyleSetRequest)>,
|
||||||
pub animations: Vec<animation::Animation>,
|
pub animations: Vec<animation::Animation>,
|
||||||
pub widgets_to_tick: HashSet<WidgetID>, // widgets which needs to be ticked in the next `Layout::update()` fn
|
pub widgets_to_tick: HashSet<WidgetID>, // widgets which needs to be ticked in the next `Layout::update()` fn
|
||||||
|
|
@ -147,8 +151,20 @@ impl EventAlterables {
|
||||||
self.tasks.push(LayoutTask::PlaySound(sound_type));
|
self.tasks.push(LayoutTask::PlaySound(sound_type));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dispatch(&mut self, func: Box<dyn FnOnce(&mut CallbackDataCommon) -> anyhow::Result<()>>) {
|
pub fn dispatch(&mut self, func: LayoutDispatchFunc) {
|
||||||
self.tasks.push(LayoutTask::Dispatch(func))
|
self.tasks.push(LayoutTask::Dispatch(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus<ComponentType: ComponentTrait>(&mut self, component: &Weak<ComponentType>) {
|
||||||
|
self.tasks.push(LayoutTask::SetFocus(component.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unfocus(&mut self) {
|
||||||
|
self.tasks.push(LayoutTask::Unfocus);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh_component_once<ComponentType: ComponentTrait>(&mut self, component: &Weak<ComponentType>) {
|
||||||
|
self.components_to_refresh_once.push(component.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ pub struct Defaults {
|
||||||
pub danger_color: drawing::Color,
|
pub danger_color: drawing::Color,
|
||||||
pub faded_color: drawing::Color,
|
pub faded_color: drawing::Color,
|
||||||
pub bg_color: drawing::Color,
|
pub bg_color: drawing::Color,
|
||||||
|
pub editbox_color: drawing::Color,
|
||||||
pub translucent_alpha: f32,
|
pub translucent_alpha: f32,
|
||||||
pub animation_mult: f32,
|
pub animation_mult: f32,
|
||||||
pub rounding_mult: f32,
|
pub rounding_mult: f32,
|
||||||
|
|
@ -42,6 +43,7 @@ impl Default for Defaults {
|
||||||
danger_color: drawing::Color::new(0.9, 0.0, 0.0, 1.0),
|
danger_color: drawing::Color::new(0.9, 0.0, 0.0, 1.0),
|
||||||
faded_color: drawing::Color::new(0.67, 0.74, 0.80, 1.0),
|
faded_color: drawing::Color::new(0.67, 0.74, 0.80, 1.0),
|
||||||
bg_color: drawing::Color::new(0.0, 0.07, 0.1, 0.75),
|
bg_color: drawing::Color::new(0.0, 0.07, 0.1, 0.75),
|
||||||
|
editbox_color: drawing::Color::new(0.15, 0.25, 0.35, 0.95),
|
||||||
translucent_alpha: 0.5,
|
translucent_alpha: 0.5,
|
||||||
animation_mult: 1.0,
|
animation_mult: 1.0,
|
||||||
rounding_mult: 1.0,
|
rounding_mult: 1.0,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use std::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
animation::Animations,
|
animation::Animations,
|
||||||
components::{Component, RefreshData},
|
components::{self, Component, ComponentWeak, FocusChangeData, RefreshData},
|
||||||
drawing::{self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, push_scissor_stack, push_transform_stack},
|
drawing::{self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, push_scissor_stack, push_transform_stack},
|
||||||
event::{self, CallbackDataCommon, EventAlterables},
|
event::{self, CallbackDataCommon, EventAlterables},
|
||||||
globals::WguiGlobals,
|
globals::WguiGlobals,
|
||||||
|
|
@ -134,14 +134,17 @@ pub struct LayoutUpdateResult {
|
||||||
pub sounds_to_play: Vec<WguiSoundType>,
|
pub sounds_to_play: Vec<WguiSoundType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ModifyLayoutStateFunc = Box<dyn FnOnce(ModifyLayoutStateData) -> anyhow::Result<()>>;
|
pub type LayoutModifyStateFunc = Box<dyn FnOnce(ModifyLayoutStateData) -> anyhow::Result<()>>;
|
||||||
|
pub type LayoutDispatchFunc = Box<dyn FnOnce(&mut CallbackDataCommon) -> anyhow::Result<()>>;
|
||||||
|
|
||||||
pub enum LayoutTask {
|
pub enum LayoutTask {
|
||||||
RemoveWidget(WidgetID),
|
RemoveWidget(WidgetID),
|
||||||
SetWidgetStyle(WidgetID, event::StyleSetRequest),
|
SetWidgetStyle(WidgetID, event::StyleSetRequest),
|
||||||
ModifyLayoutState(ModifyLayoutStateFunc),
|
ModifyLayoutState(LayoutModifyStateFunc),
|
||||||
PlaySound(WguiSoundType),
|
PlaySound(WguiSoundType),
|
||||||
Dispatch(Box<dyn FnOnce(&mut CallbackDataCommon) -> anyhow::Result<()>>),
|
Dispatch(LayoutDispatchFunc),
|
||||||
|
SetFocus(ComponentWeak),
|
||||||
|
Unfocus,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type LayoutTasks = Tasks<LayoutTask>;
|
pub type LayoutTasks = Tasks<LayoutTask>;
|
||||||
|
|
@ -152,8 +155,9 @@ pub struct Layout {
|
||||||
pub tasks: LayoutTasks,
|
pub tasks: LayoutTasks,
|
||||||
|
|
||||||
components_to_refresh_once: HashSet<Component>,
|
components_to_refresh_once: HashSet<Component>,
|
||||||
registered_components_to_refresh: HashMap<taffy::NodeId, Component>,
|
registered_components_to_refresh: HashMap<taffy::NodeId, ComponentWeak>,
|
||||||
sounds_to_play_once: Vec<WguiSoundType>,
|
sounds_to_play_once: Vec<WguiSoundType>,
|
||||||
|
focused_component: Option<ComponentWeak>,
|
||||||
|
|
||||||
pub widgets_to_tick: Vec<WidgetID>,
|
pub widgets_to_tick: Vec<WidgetID>,
|
||||||
|
|
||||||
|
|
@ -358,14 +362,14 @@ impl Layout {
|
||||||
}
|
}
|
||||||
|
|
||||||
// call ComponentTrait::refresh() *every time time* the layout is dirty
|
// call ComponentTrait::refresh() *every time time* the layout is dirty
|
||||||
pub fn register_component_refresh(&mut self, component: Component) {
|
pub fn register_component_refresh(&mut self, component: &Component) {
|
||||||
let widget_id = component.0.base().get_id();
|
let widget_id = component.0.base().get_id();
|
||||||
let Some(node_id) = self.state.nodes.get(widget_id) else {
|
let Some(node_id) = self.state.nodes.get(widget_id) else {
|
||||||
debug_assert!(false);
|
debug_assert!(false);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.registered_components_to_refresh.insert(*node_id, component);
|
self.registered_components_to_refresh.insert(*node_id, component.weak());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function to avoid repeated `WidgetID` → `WidgetState` lookups.
|
/// Convenience function to avoid repeated `WidgetID` → `WidgetState` lookups.
|
||||||
|
|
@ -581,6 +585,7 @@ impl Layout {
|
||||||
widgets_to_tick: Vec::new(),
|
widgets_to_tick: Vec::new(),
|
||||||
tasks: LayoutTasks::new(),
|
tasks: LayoutTasks::new(),
|
||||||
sounds_to_play_once: Vec::new(),
|
sounds_to_play_once: Vec::new(),
|
||||||
|
focused_component: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -590,8 +595,10 @@ impl Layout {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(component) = self.registered_components_to_refresh.get(&node_id) {
|
if let Some(component) = self.registered_components_to_refresh.get(&node_id)
|
||||||
to_refresh.push(component.clone());
|
&& let Some(c) = component.upgrade()
|
||||||
|
{
|
||||||
|
to_refresh.push(Component(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
for child_id in self.state.tree.child_ids(node_id) {
|
for child_id in self.state.tree.child_ids(node_id) {
|
||||||
|
|
@ -708,15 +715,48 @@ impl Layout {
|
||||||
c.finish()?;
|
c.finish()?;
|
||||||
}
|
}
|
||||||
LayoutTask::SetWidgetStyle(widget_id, style_request) => {
|
LayoutTask::SetWidgetStyle(widget_id, style_request) => {
|
||||||
self.set_style_request(widget_id, style_request);
|
self.set_style_request(widget_id, &style_request);
|
||||||
}
|
}
|
||||||
|
LayoutTask::SetFocus(weak) => {
|
||||||
|
if let Some(c) = weak.upgrade() {
|
||||||
|
self.set_focus(Some(&components::Component(c)))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LayoutTask::Unfocus => self.set_focus(None)?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_style_request(&mut self, widget_id: WidgetID, style_request: event::StyleSetRequest) {
|
pub fn set_focus(&mut self, to_focus: Option<&Component>) -> anyhow::Result<()> {
|
||||||
|
let mut c = self.start_common();
|
||||||
|
|
||||||
|
if let Some(focused) = &c.layout.focused_component
|
||||||
|
&& let Some(focused) = focused.upgrade()
|
||||||
|
{
|
||||||
|
// Unfocus
|
||||||
|
focused.on_focus_change(&mut FocusChangeData {
|
||||||
|
common: &mut c.common(),
|
||||||
|
focused: false,
|
||||||
|
});
|
||||||
|
c.layout.focused_component = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(to_focus) = to_focus {
|
||||||
|
to_focus.0.on_focus_change(&mut FocusChangeData {
|
||||||
|
common: &mut c.common(),
|
||||||
|
focused: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
c.layout.focused_component = Some(to_focus.weak());
|
||||||
|
}
|
||||||
|
|
||||||
|
c.finish()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_style_request(&mut self, widget_id: WidgetID, style_request: &event::StyleSetRequest) {
|
||||||
let Some(node_id) = self.state.nodes.get(widget_id) else {
|
let Some(node_id) = self.state.nodes.get(widget_id) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -728,22 +768,26 @@ impl Layout {
|
||||||
match style_request {
|
match style_request {
|
||||||
event::StyleSetRequest::Display(display) => {
|
event::StyleSetRequest::Display(display) => {
|
||||||
// refresh the component in case if visibility/display mode has changed
|
// refresh the component in case if visibility/display mode has changed
|
||||||
if cur_style.display != display
|
if cur_style.display != *display
|
||||||
&& let Some(component) = self.registered_components_to_refresh.get(node_id)
|
&& let Some(component) = self.registered_components_to_refresh.get(node_id)
|
||||||
|
&& let Some(c) = component.upgrade()
|
||||||
{
|
{
|
||||||
self.components_to_refresh_once.insert(component.clone());
|
self.components_to_refresh_once.insert(Component(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
cur_style.display = display;
|
cur_style.display = *display;
|
||||||
}
|
}
|
||||||
event::StyleSetRequest::Margin(margin) => {
|
event::StyleSetRequest::Margin(margin) => {
|
||||||
cur_style.margin = margin;
|
cur_style.margin = *margin;
|
||||||
}
|
}
|
||||||
event::StyleSetRequest::Width(val) => {
|
event::StyleSetRequest::Width(val) => {
|
||||||
cur_style.size.width = val;
|
cur_style.size.width = *val;
|
||||||
}
|
}
|
||||||
event::StyleSetRequest::Height(val) => {
|
event::StyleSetRequest::Height(val) => {
|
||||||
cur_style.size.height = val;
|
cur_style.size.height = *val;
|
||||||
|
}
|
||||||
|
event::StyleSetRequest::Size(size) => {
|
||||||
|
cur_style.size = *size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -786,8 +830,14 @@ impl Layout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (widget_id, style_request) in alterables.style_set_requests {
|
for c in alterables.components_to_refresh_once {
|
||||||
self.set_style_request(widget_id, style_request);
|
if let Some(c) = c.upgrade() {
|
||||||
|
self.defer_component_refresh(components::Component(c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (widget_id, style_request) in &alterables.style_set_requests {
|
||||||
|
self.set_style_request(*widget_id, style_request);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@
|
||||||
clippy::needless_pass_by_ref_mut,
|
clippy::needless_pass_by_ref_mut,
|
||||||
clippy::use_self,
|
clippy::use_self,
|
||||||
clippy::match_same_arms,
|
clippy::match_same_arms,
|
||||||
clippy::too_many_lines
|
clippy::too_many_lines,
|
||||||
|
clippy::struct_excessive_bools
|
||||||
)]
|
)]
|
||||||
|
|
||||||
pub mod animation;
|
pub mod animation;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
use crate::{
|
||||||
|
components::{Component, editbox},
|
||||||
|
layout::WidgetID,
|
||||||
|
parser::{AttribPair, ParserContext, process_component, style::parse_style},
|
||||||
|
widget::ConstructEssentials,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn parse_component_editbox(
|
||||||
|
ctx: &mut ParserContext,
|
||||||
|
parent_id: WidgetID,
|
||||||
|
attribs: &[AttribPair],
|
||||||
|
tag_name: &str,
|
||||||
|
) -> anyhow::Result<WidgetID> {
|
||||||
|
let mut initial_text = String::new();
|
||||||
|
|
||||||
|
let style = parse_style(ctx, attribs, tag_name);
|
||||||
|
|
||||||
|
for pair in attribs {
|
||||||
|
let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
|
||||||
|
#[allow(clippy::single_match)]
|
||||||
|
match key {
|
||||||
|
"text" => {
|
||||||
|
initial_text = String::from(value);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (widget, component) = editbox::construct(
|
||||||
|
&mut ConstructEssentials {
|
||||||
|
layout: ctx.layout,
|
||||||
|
parent: parent_id,
|
||||||
|
},
|
||||||
|
editbox::Params { style, initial_text },
|
||||||
|
)?;
|
||||||
|
|
||||||
|
process_component(ctx, Component(component), widget.id, attribs);
|
||||||
|
|
||||||
|
Ok(widget.id)
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
mod component_button;
|
mod component_button;
|
||||||
mod component_checkbox;
|
mod component_checkbox;
|
||||||
|
mod component_editbox;
|
||||||
mod component_radio_group;
|
mod component_radio_group;
|
||||||
mod component_slider;
|
mod component_slider;
|
||||||
mod component_tabs;
|
mod component_tabs;
|
||||||
|
|
@ -22,6 +23,7 @@ use crate::{
|
||||||
parser::{
|
parser::{
|
||||||
component_button::parse_component_button,
|
component_button::parse_component_button,
|
||||||
component_checkbox::{CheckboxKind, parse_component_checkbox},
|
component_checkbox::{CheckboxKind, parse_component_checkbox},
|
||||||
|
component_editbox::parse_component_editbox,
|
||||||
component_radio_group::parse_component_radio_group,
|
component_radio_group::parse_component_radio_group,
|
||||||
component_slider::parse_component_slider,
|
component_slider::parse_component_slider,
|
||||||
component_tabs::parse_component_tabs,
|
component_tabs::parse_component_tabs,
|
||||||
|
|
@ -1053,6 +1055,7 @@ fn parse_child<'a>(
|
||||||
file, ctx, child_node, parent_id, &attribs, tag_name,
|
file, ctx, child_node, parent_id, &attribs, tag_name,
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
|
"EditBox" => new_widget_id = Some(parse_component_editbox(ctx, parent_id, &attribs, tag_name)?),
|
||||||
"Tabs" => {
|
"Tabs" => {
|
||||||
new_widget_id = Some(parse_component_tabs(ctx, child_node, parent_id, &attribs, tag_name)?);
|
new_widget_id = Some(parse_component_tabs(ctx, child_node, parent_id, &attribs, tag_name)?);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue