From 0fdc0e3828f51e165207425d4b26dd69312a1b9d Mon Sep 17 00:00:00 2001 From: Aleksander Date: Tue, 16 Sep 2025 20:09:13 +0200 Subject: [PATCH] dash-frontend: clock, wgui: refactoring, non-panicking casts --- Cargo.lock | 34 +++++----- dash-frontend/Cargo.toml | 1 + dash-frontend/assets/gui/dashboard.xml | 2 +- dash-frontend/src/lib.rs | 64 +++++++++++++++---- uidev/src/testbed/testbed_dashboard.rs | 4 +- uidev/src/testbed/testbed_generic.rs | 31 ++++----- wgui/src/components/button.rs | 37 ++++++----- wgui/src/components/checkbox.rs | 39 ++++++----- wgui/src/components/mod.rs | 12 +++- wgui/src/components/slider.rs | 37 +++++------ wgui/src/event.rs | 25 ++++---- wgui/src/layout.rs | 40 +++++++----- wgui/src/parser/mod.rs | 2 +- wgui/src/widget/div.rs | 18 +++++- wgui/src/widget/label.rs | 29 ++++++++- wgui/src/widget/mod.rs | 16 ++--- wgui/src/widget/rectangle.rs | 21 ++++-- wgui/src/widget/sprite.rs | 20 ++++-- .../src/overlays/keyboard/builder.rs | 8 +-- wlx-overlay-s/src/overlays/watch.rs | 12 ++-- 20 files changed, 287 insertions(+), 165 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e0d05f..c5fff4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,12 +125,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -882,16 +876,15 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-link", + "windows-link 0.2.0", ] [[package]] @@ -1366,6 +1359,7 @@ name = "dash-frontend" version = "0.1.0" dependencies = [ "anyhow", + "chrono", "glam", "log", "rust-embed", @@ -5737,7 +5731,7 @@ dependencies = [ "windows-collections", "windows-core 0.61.2", "windows-future", - "windows-link", + "windows-link 0.1.3", "windows-numerics", ] @@ -5768,7 +5762,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.1.3", "windows-result 0.3.4", "windows-strings", ] @@ -5780,7 +5774,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core 0.61.2", - "windows-link", + "windows-link 0.1.3", "windows-threading", ] @@ -5812,6 +5806,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + [[package]] name = "windows-numerics" version = "0.2.0" @@ -5819,7 +5819,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ "windows-core 0.61.2", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -5837,7 +5837,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -5846,7 +5846,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -5944,7 +5944,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] diff --git a/dash-frontend/Cargo.toml b/dash-frontend/Cargo.toml index 63934c2..a6569bf 100644 --- a/dash-frontend/Cargo.toml +++ b/dash-frontend/Cargo.toml @@ -9,3 +9,4 @@ wgui = { path = "../wgui/" } glam = { workspace = true } log = { workspace = true } rust-embed = "8.7.2" +chrono = "0.4.42" diff --git a/dash-frontend/assets/gui/dashboard.xml b/dash-frontend/assets/gui/dashboard.xml index 21e6da7..60d380a 100644 --- a/dash-frontend/assets/gui/dashboard.xml +++ b/dash-frontend/assets/gui/dashboard.xml @@ -101,7 +101,7 @@
-
diff --git a/dash-frontend/src/lib.rs b/dash-frontend/src/lib.rs index cb2873f..f829d2b 100644 --- a/dash-frontend/src/lib.rs +++ b/dash-frontend/src/lib.rs @@ -1,13 +1,16 @@ use std::{cell::RefCell, collections::VecDeque, rc::Rc}; +use chrono::Timelike; use glam::Vec2; use wgui::{ components::button::ComponentButton, drawing, - event::EventListenerCollection, + event::{CallbackDataCommon, EventAlterables, EventListenerCollection}, globals::WguiGlobals, - layout::{LayoutParams, RcLayout}, + i18n::Translation, + layout::{LayoutParams, RcLayout, WidgetID}, parser::{ParseDocumentParams, ParserState}, + widget::label::WidgetLabel, }; use crate::tab::{ @@ -28,6 +31,10 @@ pub struct Frontend { current_tab: Option>, tasks: VecDeque, + + ticks: u32, + + label_time_id: WidgetID, } pub type RcFrontend = Rc>; @@ -64,40 +71,73 @@ impl Frontend { let mut tasks = VecDeque::::new(); tasks.push_back(FrontendTask::SetTab(TabType::Home)); + let label_time_id = state.get_widget_id("label_time")?; + let res = Rc::new(RefCell::new(Self { layout: rc_layout.clone(), state, current_tab: None, globals, tasks, + ticks: 0, + label_time_id, })); - Frontend::register_buttons(&res)?; + Frontend::register_widgets(&res)?; Ok((res, rc_layout)) } pub fn update( + &mut self, rc_this: &RcFrontend, listeners: &mut EventListenerCollection<(), ()>, width: f32, height: f32, timestep_alpha: f32, ) -> anyhow::Result<()> { - let mut this = rc_this.borrow_mut(); - - while let Some(task) = this.tasks.pop_front() { - this.process_task(rc_this, task, listeners)?; + while let Some(task) = self.tasks.pop_front() { + self.process_task(rc_this, task, listeners)?; } - this - .layout - .borrow_mut() - .update(Vec2::new(width, height), timestep_alpha)?; + self.tick(width, height, timestep_alpha)?; + self.ticks += 1; Ok(()) } + fn tick(&mut self, width: f32, height: f32, timestep_alpha: f32) -> anyhow::Result<()> { + let mut layout = self.layout.borrow_mut(); + + let mut alterables = EventAlterables::default(); + let mut common = CallbackDataCommon { + alterables: &mut alterables, + state: &layout.state, + }; + + // fixme: timer events instead of this thing + if self.ticks % 1000 == 0 { + self.update_time(&mut common); + } + + layout.update(Vec2::new(width, height), timestep_alpha)?; + layout.process_alterables(alterables)?; + + Ok(()) + } + + fn update_time(&self, common: &mut CallbackDataCommon) { + let Some(mut label) = common.state.widgets.get_as::(self.label_time_id) else { + return; + }; + + let now = chrono::Local::now(); + let hours = now.hour(); + let minutes = now.minute(); + + label.set_text(common, Translation::from_raw_text(&format!("{hours:02}:{minutes:02}"))); + } + pub fn get_layout(&self) -> &RcLayout { &self.layout } @@ -151,7 +191,7 @@ impl Frontend { Ok(()) } - fn register_buttons(rc_this: &RcFrontend) -> anyhow::Result<()> { + fn register_widgets(rc_this: &RcFrontend) -> anyhow::Result<()> { let this = rc_this.borrow_mut(); let btn_home = this.state.fetch_component_as::("btn_side_home")?; let btn_apps = this.state.fetch_component_as::("btn_side_apps")?; diff --git a/uidev/src/testbed/testbed_dashboard.rs b/uidev/src/testbed/testbed_dashboard.rs index 050496c..39661c4 100644 --- a/uidev/src/testbed/testbed_dashboard.rs +++ b/uidev/src/testbed/testbed_dashboard.rs @@ -1,5 +1,4 @@ use crate::testbed::{Testbed, TestbedUpdateParams}; -use dash_frontend::Frontend; use wgui::{event::EventListenerCollection, layout::RcLayout}; pub struct TestbedDashboard { @@ -17,7 +16,8 @@ impl TestbedDashboard { impl Testbed for TestbedDashboard { fn update(&mut self, params: TestbedUpdateParams) -> anyhow::Result<()> { - Frontend::update( + let mut frontend = self.frontend.borrow_mut(); + frontend.update( &self.frontend, params.listeners, params.width, diff --git a/uidev/src/testbed/testbed_generic.rs b/uidev/src/testbed/testbed_generic.rs index 5151e77..bc9211b 100644 --- a/uidev/src/testbed/testbed_generic.rs +++ b/uidev/src/testbed/testbed_generic.rs @@ -32,15 +32,14 @@ fn button_click_callback( label: Widget, text: &'static str, ) -> ButtonClickCallback { - Box::new(move |e| { - label.get_as_mut::().set_text( - &mut e.state.globals.i18n(), - Translation::from_raw_text(text), - ); + Box::new(move |mut e| { + label + .get_as_mut::() + .unwrap() + .set_text(&mut e.as_common(), Translation::from_raw_text(text)); button.try_cast::()?.set_text( - e.state, - e.alterables, + &mut e.as_common(), Translation::from_raw_text("this button has been clicked"), ); @@ -93,12 +92,8 @@ impl TestbedGeneric { let button_click_me = state.fetch_component_as::("button_click_me")?; let button = button_click_me.clone(); - button_click_me.on_click(Box::new(move |e| { - button.set_text( - e.state, - e.alterables, - Translation::from_raw_text("congrats!"), - ); + button_click_me.on_click(Box::new(move |mut e| { + button.set_text(&mut e.as_common(), Translation::from_raw_text("congrats!")); Ok(()) })); @@ -116,12 +111,10 @@ impl TestbedGeneric { let cb_first = state.fetch_component_as::("cb_first")?; let label = label_cur_option.widget.clone(); - cb_first.on_toggle(Box::new(move |e| { - let mut widget = label.get_as_mut::(); - widget.set_text( - &mut e.state.globals.i18n(), - Translation::from_raw_text(&format!("checkbox toggle: {}", e.checked)), - ); + cb_first.on_toggle(Box::new(move |mut e| { + let mut widget = label.get_as_mut::().unwrap(); + let text = format!("checkbox toggle: {}", e.checked); + widget.set_text(&mut e.as_common(), Translation::from_raw_text(&text)); Ok(()) })); diff --git a/wgui/src/components/button.rs b/wgui/src/components/button.rs index 3baf187..0cf12fc 100644 --- a/wgui/src/components/button.rs +++ b/wgui/src/components/button.rs @@ -5,7 +5,7 @@ use crate::{ animation::{Animation, AnimationEasing}, components::{Component, ComponentBase, ComponentTrait, InitData}, drawing::{self, Color}, - event::{EventAlterables, EventListenerCollection, EventListenerKind, ListenerHandleVec}, + event::{CallbackDataCommon, EventAlterables, EventListenerCollection, EventListenerKind, ListenerHandleVec}, i18n::Translation, layout::{Layout, LayoutState, WidgetID}, renderer_vk::text::{FontWeight, TextStyle}, @@ -46,6 +46,16 @@ pub struct ButtonClickEvent<'a> { pub state: &'a LayoutState, pub alterables: &'a mut EventAlterables, } + +impl ButtonClickEvent<'_> { + pub const fn as_common(&mut self) -> CallbackDataCommon { + CallbackDataCommon { + alterables: self.alterables, + state: self.state, + } + } +} + pub type ButtonClickCallback = Box anyhow::Result<()>>; struct State { @@ -61,7 +71,6 @@ struct Data { initial_hover_border_color: drawing::Color, id_label: WidgetID, // Label id_rect: WidgetID, // Rectangle - node_label: taffy::NodeId, } pub struct ComponentButton { @@ -79,15 +88,12 @@ impl ComponentTrait for ComponentButton { } impl ComponentButton { - pub fn set_text(&self, state: &LayoutState, alterables: &mut EventAlterables, text: Translation) { - let globals = state.globals.clone(); + 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; + }; - state.widgets.call(self.data.id_label, |label: &mut WidgetLabel| { - label.set_text(&mut globals.i18n(), text); - }); - - alterables.mark_redraw(); - alterables.mark_dirty(self.data.node_label); + label.set_text(common, text); } pub fn on_click(&self, func: ButtonClickCallback) { @@ -115,7 +121,7 @@ fn anim_hover_in(data: Rc, state: Rc>, widget_id: WidgetID) 2, AnimationEasing::OutQuad, Box::new(move |common, anim_data| { - let rect = anim_data.obj.get_as_mut::(); + let rect = anim_data.obj.get_as_mut::().unwrap(); anim_hover(rect, &data, anim_data.pos, state.borrow().down); common.alterables.mark_redraw(); }), @@ -128,7 +134,7 @@ fn anim_hover_out(data: Rc, state: Rc>, widget_id: WidgetID 8, AnimationEasing::OutQuad, Box::new(move |common, anim_data| { - let rect = anim_data.obj.get_as_mut::(); + let rect = anim_data.obj.get_as_mut::().unwrap(); anim_hover(rect, &data, 1.0 - anim_data.pos, state.borrow().down); common.alterables.mark_redraw(); }), @@ -190,7 +196,7 @@ fn register_event_mouse_press( Box::new(move |common, event_data, _, _| { let mut state = state.borrow_mut(); - let rect = event_data.obj.get_as_mut::(); + let rect = event_data.obj.get_as_mut::().unwrap(); anim_hover(rect, &data, 1.0, true); if state.hovered { @@ -216,7 +222,7 @@ fn register_event_mouse_release( data.id_rect, EventListenerKind::MouseRelease, Box::new(move |common, event_data, _, _| { - let rect = event_data.obj.get_as_mut::(); + let rect = event_data.obj.get_as_mut::().unwrap(); anim_hover(rect, &data, 1.0, false); let mut state = state.borrow_mut(); @@ -272,7 +278,7 @@ pub fn construct( let light_text = (params.color.r + params.color.g + params.color.b) < 1.5; - let (id_label, node_label) = layout.add_child( + let (id_label, _node_label) = layout.add_child( id_rect, WidgetLabel::create( &mut globals.get(), @@ -295,7 +301,6 @@ pub fn construct( let data = Rc::new(Data { id_label, id_rect, - node_label, initial_color: params.color, initial_border_color: params.border_color, initial_hover_color: params.hover_color, diff --git a/wgui/src/components/checkbox.rs b/wgui/src/components/checkbox.rs index 54bd22f..d73a7b8 100644 --- a/wgui/src/components/checkbox.rs +++ b/wgui/src/components/checkbox.rs @@ -8,7 +8,7 @@ use crate::{ animation::{Animation, AnimationEasing}, components::{Component, ComponentBase, ComponentTrait, InitData}, drawing::Color, - event::{EventAlterables, EventListenerCollection, EventListenerKind, ListenerHandleVec}, + event::{CallbackDataCommon, EventAlterables, EventListenerCollection, EventListenerKind, ListenerHandleVec}, i18n::Translation, layout::{self, Layout, LayoutState, WidgetID}, renderer_vk::text::{FontWeight, TextStyle}, @@ -42,6 +42,16 @@ pub struct CheckboxToggleEvent<'a> { pub alterables: &'a mut EventAlterables, pub checked: bool, } + +impl CheckboxToggleEvent<'_> { + pub const fn as_common(&mut self) -> CallbackDataCommon { + CallbackDataCommon { + alterables: self.alterables, + state: self.state, + } + } +} + pub type CheckboxToggleCallback = Box anyhow::Result<()>>; struct State { @@ -51,14 +61,13 @@ struct State { on_toggle: Option, } +#[allow(clippy::struct_field_names)] struct Data { id_container: WidgetID, // Rectangle, transparent if not hovered //id_outer_box: WidgetID, // Rectangle, parent of container id_inner_box: WidgetID, // Rectangle, parent of outer_box id_label: WidgetID, // Label, parent of container - - node_label: taffy::NodeId, } pub struct ComponentCheckbox { @@ -85,15 +94,12 @@ fn set_box_checked(widgets: &layout::WidgetMap, data: &Data, checked: bool) { } impl ComponentCheckbox { - pub fn set_text(&self, state: &LayoutState, alterables: &mut EventAlterables, text: Translation) { - let globals = state.globals.clone(); + pub fn set_text(&self, state: &LayoutState, common: &mut CallbackDataCommon, text: Translation) { + let Some(mut label) = state.widgets.get_as::(self.data.id_label) else { + return; + }; - state.widgets.call(self.data.id_label, |label: &mut WidgetLabel| { - label.set_text(&mut globals.i18n(), text); - }); - - alterables.mark_redraw(); - alterables.mark_dirty(self.data.node_label); + label.set_text(common, text); } pub fn set_checked(&self, state: &LayoutState, alterables: &mut EventAlterables, checked: bool) { @@ -123,7 +129,7 @@ fn anim_hover_in(state: Rc>, widget_id: WidgetID) -> Animation { 5, AnimationEasing::OutQuad, Box::new(move |common, anim_data| { - let rect = anim_data.obj.get_as_mut::(); + let rect = anim_data.obj.get_as_mut::().unwrap(); anim_hover(rect, anim_data.pos, state.borrow().down); common.alterables.mark_redraw(); }), @@ -136,7 +142,7 @@ fn anim_hover_out(state: Rc>, widget_id: WidgetID) -> Animation { 8, AnimationEasing::OutQuad, Box::new(move |common, anim_data| { - let rect = anim_data.obj.get_as_mut::(); + let rect = anim_data.obj.get_as_mut::().unwrap(); anim_hover(rect, 1.0 - anim_data.pos, state.borrow().down); common.alterables.mark_redraw(); }), @@ -198,7 +204,7 @@ fn register_event_mouse_press( Box::new(move |common, event_data, _, _| { let mut state = state.borrow_mut(); - let rect = event_data.obj.get_as_mut::(); + let rect = event_data.obj.get_as_mut::().unwrap(); anim_hover(rect, 1.0, true); if state.hovered { @@ -224,7 +230,7 @@ fn register_event_mouse_release( data.id_container, EventListenerKind::MouseRelease, Box::new(move |common, event_data, _, _| { - let rect = event_data.obj.get_as_mut::(); + let rect = event_data.obj.get_as_mut::().unwrap(); anim_hover(rect, 1.0, false); let mut state = state.borrow_mut(); @@ -327,7 +333,7 @@ pub fn construct( }, )?; - let (id_label, node_label) = layout.add_child( + let (id_label, _node_label) = layout.add_child( id_container, WidgetLabel::create( &mut globals.get(), @@ -346,7 +352,6 @@ pub fn construct( id_container, id_inner_box, id_label, - node_label, }); let state = Rc::new(RefCell::new(State { diff --git a/wgui/src/components/mod.rs b/wgui/src/components/mod.rs index c877661..df45fb1 100644 --- a/wgui/src/components/mod.rs +++ b/wgui/src/components/mod.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use crate::{ any::AnyTrait, - event::{self, EventAlterables}, + event::{self, CallbackDataCommon, EventAlterables}, layout::LayoutState, }; @@ -15,6 +15,15 @@ pub struct InitData<'a> { pub alterables: &'a mut EventAlterables, } +impl InitData<'_> { + const fn as_common(&mut self) -> CallbackDataCommon { + CallbackDataCommon { + alterables: self.alterables, + state: self.state, + } + } +} + // common component data #[derive(Default)] pub struct ComponentBase { @@ -32,7 +41,6 @@ pub struct Component(pub Rc); pub type ComponentWeak = std::rc::Weak; impl Component { - pub fn weak(&self) -> ComponentWeak { Rc::downgrade(&self.0) } diff --git a/wgui/src/components/slider.rs b/wgui/src/components/slider.rs index c42d345..103e279 100644 --- a/wgui/src/components/slider.rs +++ b/wgui/src/components/slider.rs @@ -7,9 +7,9 @@ use crate::{ animation::{Animation, AnimationEasing}, components::{Component, ComponentBase, ComponentTrait, InitData}, drawing::{self}, - event::{self, CallbackDataCommon, EventAlterables, EventListenerCollection, EventListenerKind, ListenerHandleVec}, - i18n::{I18n, Translation}, - layout::{Layout, LayoutState, WidgetID}, + event::{self, CallbackDataCommon, EventListenerCollection, EventListenerKind, ListenerHandleVec}, + i18n::Translation, + layout::{Layout, WidgetID}, renderer_vk::{ text::{FontWeight, HorizontalAlign, TextStyle}, util, @@ -69,7 +69,7 @@ impl ComponentTrait for ComponentSlider { fn init(&self, init_data: &mut InitData) { let mut state = self.state.borrow_mut(); let value = state.values.value; - state.set_value(init_data.state, &self.data, init_data.alterables, value); + state.set_value(&mut init_data.as_common(), &self.data, value); } fn base(&mut self) -> &mut ComponentBase { @@ -125,25 +125,26 @@ impl State { let target_value = self.values.get_from_normalized(norm); let val = target_value; - self.set_value(common.state, data, common.alterables, val); + self.set_value(common, data, val); } - fn update_text(i18n: &mut I18n, text: &mut WidgetLabel, value: f32) { + fn update_text(common: &mut CallbackDataCommon, text: &mut WidgetLabel, value: f32) { // round displayed value, should be sufficient for now - text.set_text(i18n, Translation::from_raw_text(&format!("{}", value.round()))); + text.set_text(common, Translation::from_raw_text(&format!("{}", value.round()))); } - fn set_value(&mut self, state: &LayoutState, data: &Data, alterables: &mut EventAlterables, value: f32) { + fn set_value(&mut self, common: &mut CallbackDataCommon, data: &Data, value: f32) { //common.call_on_widget(data.slider_handle_id, |_div: &mut Div| {}); self.values.value = value; - let mut style = state.tree.style(data.slider_handle_node).unwrap().clone(); - conf_handle_style(&self.values, data.slider_body_node, &mut style, &state.tree); - alterables.mark_dirty(data.slider_handle_node); - alterables.mark_redraw(); - alterables.set_style(data.slider_handle_node, style); - state.widgets.call(data.slider_text_id, |label: &mut WidgetLabel| { - Self::update_text(&mut state.globals.i18n(), label, value); - }); + let mut style = common.state.tree.style(data.slider_handle_node).unwrap().clone(); + conf_handle_style(&self.values, data.slider_body_node, &mut style, &common.state.tree); + common.alterables.mark_dirty(data.slider_handle_node); + common.alterables.mark_redraw(); + common.alterables.set_style(data.slider_handle_node, style); + + if let Some(mut label) = common.state.widgets.get_as::(data.slider_text_id) { + Self::update_text(common, &mut label, value); + } } } @@ -173,7 +174,7 @@ fn on_enter_anim(common: &mut event::CallbackDataCommon, handle_id: WidgetID) { 20, AnimationEasing::OutBack, Box::new(move |common, data| { - let rect = data.obj.get_as_mut::(); + let rect = data.obj.get_as_mut::().unwrap(); data.data.transform = get_anim_transform(data.pos, data.widget_size); anim_rect(rect, data.pos); common.alterables.mark_redraw(); @@ -187,7 +188,7 @@ fn on_leave_anim(common: &mut event::CallbackDataCommon, handle_id: WidgetID) { 10, AnimationEasing::OutQuad, Box::new(move |common, data| { - let rect = data.obj.get_as_mut::(); + let rect = data.obj.get_as_mut::().unwrap(); data.data.transform = get_anim_transform(1.0 - data.pos, data.widget_size); anim_rect(rect, 1.0 - data.pos); common.alterables.mark_redraw(); diff --git a/wgui/src/event.rs b/wgui/src/event.rs index ca02e31..ea99de9 100644 --- a/wgui/src/event.rs +++ b/wgui/src/event.rs @@ -80,7 +80,6 @@ impl Event { && pos.y < transform.pos.y + transform.dim.y } - pub fn test_mouse_within_transform(&self, transform: &Transform) -> bool { match self { Self::MouseDown(evt) => Self::test_transform_pos(transform, evt.pos), @@ -130,10 +129,17 @@ pub struct CallbackDataCommon<'a> { } impl CallbackDataCommon<'_> { - pub fn i18n(&self) -> RefMut { self.state.globals.i18n() } + + // helper function + pub fn mark_widget_dirty(&mut self, id: WidgetID) { + if let Some(node_id) = self.state.nodes.get(id) { + self.alterables.mark_dirty(*node_id); + } + self.alterables.mark_redraw(); + } } pub struct CallbackData<'a> { @@ -153,7 +159,7 @@ pub enum CallbackMetadata { impl CallbackMetadata { // helper function - + pub const fn get_mouse_pos_absolute(&self) -> Option { match *self { Self::MouseButton(b) => Some(b.pos), @@ -162,7 +168,6 @@ impl CallbackMetadata { } } - pub fn get_mouse_pos_relative(&self, transform_stack: &TransformStack) -> Option { let mouse_pos_abs = self.get_mouse_pos_absolute()?; Some(mouse_pos_abs - transform_stack.get_pos()) @@ -209,18 +214,11 @@ pub struct EventListener { } impl EventListener { - pub fn callback_for_kind( &self, kind: EventListenerKind, - ) -> Option< - &impl Fn(&mut CallbackDataCommon, &mut CallbackData, &mut U1, &mut U2) -> anyhow::Result<()>, - > { - if self.kind == kind { - Some(&self.callback) - } else { - None - } + ) -> Option<&impl Fn(&mut CallbackDataCommon, &mut CallbackData, &mut U1, &mut U2) -> anyhow::Result<()>> { + if self.kind == kind { Some(&self.callback) } else { None } } } @@ -311,7 +309,6 @@ impl EventListenerCollection { log::debug!("EventListenerCollection: cleaned-up {count} expired events"); } - pub fn get(&self, widget_id: WidgetID) -> Option<&EventListenerVec> { self.map.get(widget_id) } diff --git a/wgui/src/layout.rs b/wgui/src/layout.rs index 35d92db..eaeb891 100644 --- a/wgui/src/layout.rs +++ b/wgui/src/layout.rs @@ -29,10 +29,8 @@ impl Widget { Self(Rc::new(RefCell::new(widget_state))) } - // panics on failure - // TODO: panic-less alternative - pub fn get_as_mut(&self) -> RefMut { - RefMut::map(self.0.borrow_mut(), |w| w.obj.get_as_mut::()) + pub fn get_as_mut(&self) -> Option> { + RefMut::filter_map(self.0.borrow_mut(), |w| w.obj.get_as_mut::()).ok() } pub fn state(&self) -> RefMut { @@ -54,7 +52,7 @@ impl WidgetMap { } pub fn get_as(&self, handle: WidgetID) -> Option> { - Some(self.0.get(handle)?.get_as_mut::()) + self.0.get(handle)?.get_as_mut::() } pub fn get(&self, handle: WidgetID) -> Option<&Widget> { @@ -62,16 +60,20 @@ impl WidgetMap { } pub fn insert(&mut self, obj: Widget) -> WidgetID { - self.0.insert(obj) + self + .0 + .try_insert_with_key::<_, ()>(|widget_id| { + obj.state().obj.set_id(widget_id); + Ok(obj) + }) + .unwrap() } pub fn remove_single(&mut self, handle: WidgetID) { self.0.remove(handle); } - // cast to specific widget type, does nothing if widget ID is expired - // panics in case if the widget type is wrong - // TODO: panic-less alternative + // cast to specific widget type, does nothing if widget ID is expired or the type is wrong pub fn call(&self, widget_id: WidgetID, func: FUNC) where WIDGET: WidgetObj, @@ -82,7 +84,9 @@ impl WidgetMap { return; }; - func(&mut widget.get_as_mut::()); + if let Some(mut casted) = widget.get_as_mut::() { + func(&mut casted); + } } } @@ -151,7 +155,7 @@ impl Layout { ) -> anyhow::Result<(WidgetID, taffy::NodeId)> { let parent_node = *self.state.nodes.get(parent_widget_id).unwrap(); - self.needs_redraw = true; + self.mark_redraw(); add_child_internal( &mut self.state.tree, @@ -181,7 +185,7 @@ impl Layout { self.collect_children_ids_recursive(widget_id, &mut ids); if !ids.is_empty() { - self.needs_redraw = true; + self.mark_redraw(); } for (widget_id, node_id) in ids { @@ -191,6 +195,10 @@ impl Layout { } } + pub const fn mark_redraw(&mut self) { + self.needs_redraw = true; + } + fn process_pending_components(&mut self) -> anyhow::Result<()> { let mut alterables = EventAlterables::default(); @@ -365,7 +373,7 @@ impl Layout { return Ok(()); } - self.needs_redraw = true; + self.mark_redraw(); log::debug!("re-computing layout, size {}x{}", size.x, size.y); self.prev_size = size; @@ -429,13 +437,13 @@ impl Layout { Ok(()) } - fn process_alterables(&mut self, alterables: EventAlterables) -> anyhow::Result<()> { + pub fn process_alterables(&mut self, alterables: EventAlterables) -> anyhow::Result<()> { for node in alterables.dirty_nodes { self.state.tree.mark_dirty(node)?; } if alterables.needs_redraw { - self.needs_redraw = true; + self.mark_redraw(); } if alterables.trigger_haptics { @@ -443,7 +451,7 @@ impl Layout { } if !alterables.animations.is_empty() { - self.needs_redraw = true; + self.mark_redraw(); for anim in alterables.animations { self.animations.add(anim); } diff --git a/wgui/src/parser/mod.rs b/wgui/src/parser/mod.rs index 48cd95a..3676747 100644 --- a/wgui/src/parser/mod.rs +++ b/wgui/src/parser/mod.rs @@ -709,7 +709,7 @@ impl CustomAttribInfo<'_> { } pub fn get_widget_as(&self) -> Option> { - Some(self.widgets.get(self.widget_id)?.get_as_mut::()) + self.widgets.get(self.widget_id)?.get_as_mut::() } } diff --git a/wgui/src/widget/div.rs b/wgui/src/widget/div.rs index 6a7ac6c..8a146ff 100644 --- a/wgui/src/widget/div.rs +++ b/wgui/src/widget/div.rs @@ -1,10 +1,16 @@ +use slotmap::Key; + +use crate::layout::WidgetID; + use super::{WidgetObj, WidgetState}; -pub struct WidgetDiv {} +pub struct WidgetDiv { + id: WidgetID, +} impl WidgetDiv { pub fn create() -> WidgetState { - WidgetState::new(Box::new(Self {})) + WidgetState::new(Box::new(Self { id: WidgetID::null() })) } } @@ -12,4 +18,12 @@ impl WidgetObj for WidgetDiv { fn draw(&mut self, _state: &mut super::DrawState, _params: &super::DrawParams) { // no-op } + + fn get_id(&self) -> WidgetID { + self.id + } + + fn set_id(&mut self, id: WidgetID) { + self.id = id; + } } diff --git a/wgui/src/widget/label.rs b/wgui/src/widget/label.rs index 323b513..709313a 100644 --- a/wgui/src/widget/label.rs +++ b/wgui/src/widget/label.rs @@ -1,12 +1,15 @@ use std::{cell::RefCell, rc::Rc}; use cosmic_text::{Attrs, Buffer, Metrics, Shaping, Wrap}; +use slotmap::Key; use taffy::AvailableSpace; use crate::{ drawing::{self, Boundary}, + event::CallbackDataCommon, globals::Globals, i18n::{I18n, Translation}, + layout::WidgetID, renderer_vk::text::{FONT_SYSTEM, TextStyle}, }; @@ -19,6 +22,8 @@ pub struct WidgetLabelParams { } pub struct WidgetLabel { + id: WidgetID, + params: WidgetLabelParams, buffer: Rc>, last_boundary: Boundary, @@ -52,12 +57,15 @@ impl WidgetLabel { params, buffer: Rc::new(RefCell::new(buffer)), last_boundary: Boundary::default(), + id: WidgetID::null(), })) } - pub fn set_text(&mut self, i18n: &mut I18n, translation: Translation) { + // set text without layout/re-render update. + // Not recommended unless the widget wasn't rendered yet (first init). + pub fn set_text_simple(&mut self, i18n: &mut I18n, translation: Translation) -> bool { if self.params.content == translation { - return; + return false; } self.params.content = translation; @@ -72,6 +80,15 @@ impl WidgetLabel { Shaping::Advanced, self.params.style.align.map(Into::into), ); + + true + } + + // set text and check if it needs to be re-rendered/re-layouted + pub fn set_text(&mut self, common: &mut CallbackDataCommon, translation: Translation) { + if self.set_text_simple(&mut common.i18n(), translation) { + common.mark_widget_dirty(self.id); + } } } @@ -118,4 +135,12 @@ impl WidgetObj for WidgetLabel { let height = total_lines as f32 * buffer.metrics().line_height; taffy::Size { width, height } } + + fn get_id(&self) -> WidgetID { + self.id + } + + fn set_id(&mut self, id: WidgetID) { + self.id = id; + } } diff --git a/wgui/src/widget/mod.rs b/wgui/src/widget/mod.rs index fd070a7..74a6a78 100644 --- a/wgui/src/widget/mod.rs +++ b/wgui/src/widget/mod.rs @@ -111,6 +111,10 @@ pub struct DrawParams<'a> { } pub trait WidgetObj: AnyTrait { + // every widget stores their of id for convenience reasons + fn get_id(&self) -> WidgetID; + fn set_id(&mut self, id: WidgetID); // always set at insertion + fn draw(&mut self, state: &mut DrawState, params: &DrawParams); fn measure( &mut self, @@ -173,18 +177,14 @@ pub fn get_scrollbar_info(l: &taffy::Layout) -> Option { } impl dyn WidgetObj { - // panics on failure - // TODO: panic-less alternative - pub fn get_as(&self) -> &T { + pub fn get_as(&self) -> Option<&T> { let any = self.as_any(); - any.downcast_ref::().unwrap() + any.downcast_ref::() } - // panics on failure - // TODO: panic-less alternative - pub fn get_as_mut(&mut self) -> &mut T { + pub fn get_as_mut(&mut self) -> Option<&mut T> { let any = self.as_any_mut(); - any.downcast_mut::().unwrap() + any.downcast_mut::() } } diff --git a/wgui/src/widget/rectangle.rs b/wgui/src/widget/rectangle.rs index 87fd337..e5a258d 100644 --- a/wgui/src/widget/rectangle.rs +++ b/wgui/src/widget/rectangle.rs @@ -1,5 +1,8 @@ +use slotmap::Key; + use crate::{ drawing::{self, GradientMode}, + layout::WidgetID, widget::util::WLength, }; @@ -19,11 +22,15 @@ pub struct WidgetRectangleParams { pub struct WidgetRectangle { pub params: WidgetRectangleParams, + id: WidgetID, } impl WidgetRectangle { pub fn create(params: WidgetRectangleParams) -> WidgetState { - WidgetState::new(Box::new(Self { params })) + WidgetState::new(Box::new(Self { + params, + id: WidgetID::null(), + })) } } @@ -33,9 +40,7 @@ impl WidgetObj for WidgetRectangle { let round_units = match self.params.round { WLength::Units(units) => units as u8, - WLength::Percent(percent) => { - (f32::min(boundary.size.x, boundary.size.y) * percent / 2.0) as u8 - } + WLength::Percent(percent) => (f32::min(boundary.size.x, boundary.size.y) * percent / 2.0) as u8, }; state.primitives.push(drawing::RenderPrimitive { @@ -52,4 +57,12 @@ impl WidgetObj for WidgetRectangle { }), }); } + + fn get_id(&self) -> WidgetID { + self.id + } + + fn set_id(&mut self, id: WidgetID) { + self.id = id; + } } diff --git a/wgui/src/widget/sprite.rs b/wgui/src/widget/sprite.rs index 87cab75..a30dea7 100644 --- a/wgui/src/widget/sprite.rs +++ b/wgui/src/widget/sprite.rs @@ -1,9 +1,11 @@ use std::{cell::RefCell, rc::Rc}; use cosmic_text::{Attrs, Buffer, Color, Shaping, Weight}; +use slotmap::Key; use crate::{ drawing::{self}, + layout::WidgetID, renderer_vk::text::{ DEFAULT_METRICS, FONT_SYSTEM, custom_glyph::{CustomGlyph, CustomGlyphData}, @@ -21,11 +23,15 @@ pub struct WidgetSpriteParams { #[derive(Debug, Default)] pub struct WidgetSprite { params: WidgetSpriteParams, + id: WidgetID, } impl WidgetSprite { pub fn create(params: WidgetSpriteParams) -> WidgetState { - WidgetState::new(Box::new(Self { params })) + WidgetState::new(Box::new(Self { + params, + id: WidgetID::null(), + })) } } @@ -62,9 +68,7 @@ impl WidgetObj for WidgetSprite { { let mut font_system = FONT_SYSTEM.lock(); let mut buffer = buffer.borrow_with(&mut font_system); - let attrs = Attrs::new() - .color(Color::rgb(255, 0, 255)) - .weight(Weight::BOLD); + let attrs = Attrs::new().color(Color::rgb(255, 0, 255)).weight(Weight::BOLD); // set text last in order to avoid expensive re-shaping buffer.set_text("Error", &attrs, Shaping::Basic); @@ -85,4 +89,12 @@ impl WidgetObj for WidgetSprite { ) -> taffy::Size { taffy::Size::ZERO } + + fn get_id(&self) -> WidgetID { + self.id + } + + fn set_id(&mut self, id: WidgetID) { + self.id = id; + } } diff --git a/wlx-overlay-s/src/overlays/keyboard/builder.rs b/wlx-overlay-s/src/overlays/keyboard/builder.rs index 61ba0e9..6f9795b 100644 --- a/wlx-overlay-s/src/overlays/keyboard/builder.rs +++ b/wlx-overlay-s/src/overlays/keyboard/builder.rs @@ -324,7 +324,7 @@ fn on_enter_anim( 10, AnimationEasing::OutBack, Box::new(move |common, data| { - let rect = data.obj.get_as_mut::(); + let rect = data.obj.get_as_mut::().unwrap(); set_anim_color(&key_state, rect, data.pos); data.data.transform = get_anim_transform(data.pos, data.widget_size); common.alterables.mark_redraw(); @@ -342,7 +342,7 @@ fn on_leave_anim( 15, AnimationEasing::OutQuad, Box::new(move |common, data| { - let rect = data.obj.get_as_mut::(); + let rect = data.obj.get_as_mut::().unwrap(); set_anim_color(&key_state, rect, 1.0 - data.pos); data.data.transform = get_anim_transform(1.0 - data.pos, data.widget_size); common.alterables.mark_redraw(); @@ -358,7 +358,7 @@ fn on_press_anim( if key_state.drawn_state.get() { return; } - let rect = data.obj.get_as_mut::(); + let rect = data.obj.get_as_mut::().unwrap(); rect.params.border_color = Color::new(1.0, 1.0, 1.0, 1.0); common.alterables.mark_redraw(); key_state.drawn_state.set(true); @@ -372,7 +372,7 @@ fn on_release_anim( if !key_state.drawn_state.get() { return; } - let rect = data.obj.get_as_mut::(); + let rect = data.obj.get_as_mut::().unwrap(); rect.params.border_color = key_state.border_color; common.alterables.mark_redraw(); key_state.drawn_state.set(false); diff --git a/wlx-overlay-s/src/overlays/watch.rs b/wlx-overlay-s/src/overlays/watch.rs index 27d4d60..dc163e6 100644 --- a/wlx-overlay-s/src/overlays/watch.rs +++ b/wlx-overlay-s/src/overlays/watch.rs @@ -55,9 +55,9 @@ where if let Some(s) = tz_str.and_then(|tz| tz.split('/').next_back().map(|x| x.replace('_', " "))) { - label.set_text(&mut i18n, Translation::from_raw_text(&s)); + label.set_text_simple(&mut i18n, Translation::from_raw_text(&s)); } else { - label.set_text(&mut i18n, Translation::from_raw_text("Local")); + label.set_text_simple(&mut i18n, Translation::from_raw_text("Local")); } continue; @@ -73,7 +73,7 @@ where } _ => { let mut i18n = panel.layout.state.globals.i18n(); - label.set_text(&mut i18n, Translation::from_raw_text("ERR")); + label.set_text_simple(&mut i18n, Translation::from_raw_text("ERR")); continue; } }; @@ -158,7 +158,7 @@ struct ClockState { fn clock_on_tick( clock: &ClockState, - common: &event::CallbackDataCommon, + common: &mut event::CallbackDataCommon, data: &mut event::CallbackData, ) { let date_time = clock.timezone.as_ref().map_or_else( @@ -166,6 +166,6 @@ fn clock_on_tick( |tz| format!("{}", Local::now().with_timezone(tz).format(&clock.format)), ); - let label = data.obj.get_as_mut::(); - label.set_text(&mut common.i18n(), Translation::from_raw_text(&date_time)); + let label = data.obj.get_as_mut::().unwrap(); + label.set_text(common, Translation::from_raw_text(&date_time)); }