wgui: Editbox basics, component focus, minor refactoring

This commit is contained in:
Aleksander 2026-02-14 14:19:03 +01:00
parent c902f0cd44
commit 841241a7e6
13 changed files with 509 additions and 58 deletions

View File

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

View File

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

View File

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

View File

@ -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),
] ]
}, },
}; };

View File

@ -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(&params.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))
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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