diff --git a/dash-frontend/src/util/wgui_simple.rs b/dash-frontend/src/util/wgui_simple.rs
index f68a467f..b689b194 100644
--- a/dash-frontend/src/util/wgui_simple.rs
+++ b/dash-frontend/src/util/wgui_simple.rs
@@ -5,6 +5,7 @@ use wgui::{
widget::label::{WidgetLabel, WidgetLabelParams},
};
+#[allow(dead_code)]
pub fn create_label(layout: &mut Layout, parent: WidgetID, content: Translation) -> anyhow::Result<()> {
let label = WidgetLabel::create(
&mut layout.state.globals.get(),
diff --git a/uidev/assets/gui/various_widgets.xml b/uidev/assets/gui/various_widgets.xml
index 5efb8e8b..653f66ca 100644
--- a/uidev/assets/gui/various_widgets.xml
+++ b/uidev/assets/gui/various_widgets.xml
@@ -59,10 +59,17 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wgui/src/components/button.rs b/wgui/src/components/button.rs
index e2e96a81..0243c7e1 100644
--- a/wgui/src/components/button.rs
+++ b/wgui/src/components/button.rs
@@ -349,6 +349,7 @@ fn register_event_mouse_press(state: Rc>, listeners: &mut EventLi
common.alterables.trigger_haptics();
common.alterables.play_sound(WguiSoundType::ButtonPress);
common.alterables.mark_redraw();
+ common.alterables.unfocus();
if state.hovered {
state.down = true;
@@ -556,25 +557,19 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
let base = ComponentBase {
id: root.id,
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;
vec![
- register_event_mouse_enter(
- data.clone(),
- state.clone(),
- &mut widget.event_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),
+ register_event_mouse_enter(data.clone(), state.clone(), listeners, params.tooltip, anim_mult),
+ register_event_mouse_leave(state.clone(), listeners, anim_mult),
+ register_event_mouse_press(state.clone(), listeners),
+ register_event_mouse_release(data.clone(), state.clone(), listeners),
]
},
};
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))
}
diff --git a/wgui/src/components/checkbox.rs b/wgui/src/components/checkbox.rs
index 1aa52fd1..1d48167f 100644
--- a/wgui/src/components/checkbox.rs
+++ b/wgui/src/components/checkbox.rs
@@ -248,6 +248,7 @@ fn register_event_mouse_press(state: Rc>, listeners: &mut EventLi
common.alterables.trigger_haptics();
common.alterables.mark_redraw();
+ common.alterables.unfocus();
if state.hovered {
state.down = true;
@@ -431,13 +432,13 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
let base = ComponentBase {
id: root.id,
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;
vec![
- register_event_mouse_enter(state.clone(), &mut widget.event_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),
+ register_event_mouse_enter(state.clone(), listeners, params.tooltip, anim_mult),
+ register_event_mouse_leave(state.clone(), listeners, anim_mult),
+ register_event_mouse_press(state.clone(), listeners),
+ register_event_mouse_release(data.clone(), state.clone(), listeners),
]
},
};
diff --git a/wgui/src/components/editbox.rs b/wgui/src/components/editbox.rs
new file mode 100644
index 00000000..237b14b5
--- /dev/null
+++ b/wgui/src/components/editbox.rs
@@ -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,
+}
+
+#[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,
+ state: Rc>,
+}
+
+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::().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::(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::(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>,
+ 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>,
+ 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>,
+ 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)> {
+ 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))
+}
diff --git a/wgui/src/components/mod.rs b/wgui/src/components/mod.rs
index 3ef88650..57ef7398 100644
--- a/wgui/src/components/mod.rs
+++ b/wgui/src/components/mod.rs
@@ -9,6 +9,7 @@ use crate::{
pub mod button;
pub mod checkbox;
+pub mod editbox;
pub mod radio_group;
pub mod slider;
pub mod tabs;
@@ -18,6 +19,11 @@ pub struct RefreshData<'a> {
pub common: &'a mut CallbackDataCommon<'a>,
}
+pub struct FocusChangeData<'a> {
+ pub common: &'a mut CallbackDataCommon<'a>,
+ pub focused: bool,
+}
+
// common component data
#[derive(Default)]
pub struct ComponentBase {
@@ -36,6 +42,7 @@ pub trait ComponentTrait: AnyTrait {
fn base(&self) -> &ComponentBase;
fn base_mut(&mut self) -> &mut ComponentBase;
fn refresh(&self, data: &mut RefreshData);
+ fn on_focus_change(&self, _data: &mut FocusChangeData) {}
}
#[derive(Clone)]
diff --git a/wgui/src/components/slider.rs b/wgui/src/components/slider.rs
index 433c8cf6..28d74e58 100644
--- a/wgui/src/components/slider.rs
+++ b/wgui/src/components/slider.rs
@@ -324,7 +324,6 @@ fn register_event_mouse_enter(
common.alterables.trigger_haptics();
state.borrow_mut().hovered = true;
on_enter_anim(common, data.slider_handle_rect_id, anim_mult);
-
ComponentTooltip::register_hover_in(common, &tooltip_info, event_data.widget_id, state.clone());
Ok(EventResult::Pass)
@@ -388,6 +387,7 @@ fn register_event_mouse_press(
EventListenerKind::MousePress,
Box::new(move |common, event_data, (), ()| {
common.alterables.trigger_haptics();
+ common.alterables.unfocus();
let mut state = state.borrow_mut();
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 {
id: root.id,
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;
vec![
- register_event_mouse_enter(
- data.clone(),
- state.clone(),
- &mut widget.event_listeners,
- params.tooltip,
- 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),
+ register_event_mouse_enter(data.clone(), state.clone(), listeners, params.tooltip, anim_mult),
+ register_event_mouse_leave(data.clone(), state.clone(), listeners, anim_mult),
+ register_event_mouse_motion(data.clone(), state.clone(), listeners),
+ register_event_mouse_press(data.clone(), state.clone(), listeners),
+ register_event_mouse_release(state.clone(), listeners),
]
},
};
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))
}
diff --git a/wgui/src/event.rs b/wgui/src/event.rs
index b0aec6cd..4b035d64 100644
--- a/wgui/src/event.rs
+++ b/wgui/src/event.rs
@@ -2,6 +2,7 @@ use std::{
any::{Any, TypeId},
cell::{Ref, RefMut},
collections::HashSet,
+ rc::Weak,
};
use glam::Vec2;
@@ -9,9 +10,10 @@ use slotmap::{DenseSlotMap, new_key_type};
use crate::{
animation::{self, Animation},
+ components::{Component, ComponentTrait, ComponentWeak},
globals,
i18n::I18n,
- layout::{LayoutState, LayoutTask, WidgetID},
+ layout::{LayoutDispatchFunc, LayoutState, LayoutTask, WidgetID},
sound::WguiSoundType,
stack::{ScissorStack, Transform, TransformStack},
widget::{EventResult, WidgetData, WidgetObj},
@@ -101,12 +103,14 @@ pub enum StyleSetRequest {
Margin(taffy::Rect),
Width(taffy::Dimension),
Height(taffy::Dimension),
+ Size(taffy::Size),
}
// alterables which will be dispatched in the next loop iteration phase
#[derive(Default)]
pub struct EventAlterables {
pub dirty_widgets: Vec,
+ pub components_to_refresh_once: Vec,
pub style_set_requests: Vec<(WidgetID, StyleSetRequest)>,
pub animations: Vec,
pub widgets_to_tick: HashSet, // 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));
}
- pub fn dispatch(&mut self, func: Box anyhow::Result<()>>) {
- self.tasks.push(LayoutTask::Dispatch(func))
+ pub fn dispatch(&mut self, func: LayoutDispatchFunc) {
+ self.tasks.push(LayoutTask::Dispatch(func));
+ }
+
+ pub fn focus(&mut self, component: &Weak) {
+ self.tasks.push(LayoutTask::SetFocus(component.clone()));
+ }
+
+ pub fn unfocus(&mut self) {
+ self.tasks.push(LayoutTask::Unfocus);
+ }
+
+ pub fn refresh_component_once(&mut self, component: &Weak) {
+ self.components_to_refresh_once.push(component.clone());
}
}
diff --git a/wgui/src/globals.rs b/wgui/src/globals.rs
index 11c7fc56..47cb4325 100644
--- a/wgui/src/globals.rs
+++ b/wgui/src/globals.rs
@@ -26,6 +26,7 @@ pub struct Defaults {
pub danger_color: drawing::Color,
pub faded_color: drawing::Color,
pub bg_color: drawing::Color,
+ pub editbox_color: drawing::Color,
pub translucent_alpha: f32,
pub animation_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),
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),
+ editbox_color: drawing::Color::new(0.15, 0.25, 0.35, 0.95),
translucent_alpha: 0.5,
animation_mult: 1.0,
rounding_mult: 1.0,
diff --git a/wgui/src/layout.rs b/wgui/src/layout.rs
index 5a19cdec..4ff0e653 100644
--- a/wgui/src/layout.rs
+++ b/wgui/src/layout.rs
@@ -7,7 +7,7 @@ use std::{
use crate::{
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},
event::{self, CallbackDataCommon, EventAlterables},
globals::WguiGlobals,
@@ -134,14 +134,17 @@ pub struct LayoutUpdateResult {
pub sounds_to_play: Vec,
}
-pub type ModifyLayoutStateFunc = Box anyhow::Result<()>>;
+pub type LayoutModifyStateFunc = Box anyhow::Result<()>>;
+pub type LayoutDispatchFunc = Box anyhow::Result<()>>;
pub enum LayoutTask {
RemoveWidget(WidgetID),
SetWidgetStyle(WidgetID, event::StyleSetRequest),
- ModifyLayoutState(ModifyLayoutStateFunc),
+ ModifyLayoutState(LayoutModifyStateFunc),
PlaySound(WguiSoundType),
- Dispatch(Box anyhow::Result<()>>),
+ Dispatch(LayoutDispatchFunc),
+ SetFocus(ComponentWeak),
+ Unfocus,
}
pub type LayoutTasks = Tasks;
@@ -152,8 +155,9 @@ pub struct Layout {
pub tasks: LayoutTasks,
components_to_refresh_once: HashSet,
- registered_components_to_refresh: HashMap,
+ registered_components_to_refresh: HashMap,
sounds_to_play_once: Vec,
+ focused_component: Option,
pub widgets_to_tick: Vec,
@@ -358,14 +362,14 @@ impl Layout {
}
// 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 Some(node_id) = self.state.nodes.get(widget_id) else {
debug_assert!(false);
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.
@@ -581,6 +585,7 @@ impl Layout {
widgets_to_tick: Vec::new(),
tasks: LayoutTasks::new(),
sounds_to_play_once: Vec::new(),
+ focused_component: None,
})
}
@@ -590,8 +595,10 @@ impl Layout {
return;
}
- if let Some(component) = self.registered_components_to_refresh.get(&node_id) {
- to_refresh.push(component.clone());
+ if let Some(component) = self.registered_components_to_refresh.get(&node_id)
+ && let Some(c) = component.upgrade()
+ {
+ to_refresh.push(Component(c));
}
for child_id in self.state.tree.child_ids(node_id) {
@@ -708,15 +715,48 @@ impl Layout {
c.finish()?;
}
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(())
}
- 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 {
return;
};
@@ -728,22 +768,26 @@ impl Layout {
match style_request {
event::StyleSetRequest::Display(display) => {
// 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(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) => {
- cur_style.margin = margin;
+ cur_style.margin = *margin;
}
event::StyleSetRequest::Width(val) => {
- cur_style.size.width = val;
+ cur_style.size.width = *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 {
- self.set_style_request(widget_id, style_request);
+ for c in alterables.components_to_refresh_once {
+ 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(())
diff --git a/wgui/src/lib.rs b/wgui/src/lib.rs
index 8a1e39ea..f81e501b 100644
--- a/wgui/src/lib.rs
+++ b/wgui/src/lib.rs
@@ -18,7 +18,8 @@
clippy::needless_pass_by_ref_mut,
clippy::use_self,
clippy::match_same_arms,
- clippy::too_many_lines
+ clippy::too_many_lines,
+ clippy::struct_excessive_bools
)]
pub mod animation;
diff --git a/wgui/src/parser/component_editbox.rs b/wgui/src/parser/component_editbox.rs
new file mode 100644
index 00000000..e05122ce
--- /dev/null
+++ b/wgui/src/parser/component_editbox.rs
@@ -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 {
+ 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)
+}
diff --git a/wgui/src/parser/mod.rs b/wgui/src/parser/mod.rs
index 2f5fb276..a97c80d4 100644
--- a/wgui/src/parser/mod.rs
+++ b/wgui/src/parser/mod.rs
@@ -1,5 +1,6 @@
mod component_button;
mod component_checkbox;
+mod component_editbox;
mod component_radio_group;
mod component_slider;
mod component_tabs;
@@ -22,6 +23,7 @@ use crate::{
parser::{
component_button::parse_component_button,
component_checkbox::{CheckboxKind, parse_component_checkbox},
+ component_editbox::parse_component_editbox,
component_radio_group::parse_component_radio_group,
component_slider::parse_component_slider,
component_tabs::parse_component_tabs,
@@ -1053,6 +1055,7 @@ fn parse_child<'a>(
file, ctx, child_node, parent_id, &attribs, tag_name,
)?);
}
+ "EditBox" => new_widget_id = Some(parse_component_editbox(ctx, parent_id, &attribs, tag_name)?),
"Tabs" => {
new_widget_id = Some(parse_component_tabs(ctx, child_node, parent_id, &attribs, tag_name)?);
}