diff --git a/core/src/event/ctx.rs b/core/src/event/ctx.rs index 0997e1f..f5894a9 100644 --- a/core/src/event/ctx.rs +++ b/core/src/event/ctx.rs @@ -13,6 +13,6 @@ pub struct EventIdCtx<'a, Rsc: HasEvents, Data, W: ?Sized> { impl EventIdCtx<'_, Rsc, Data, W> { pub fn widget<'a>(&self, rsc: &'a mut Rsc) -> &'a mut W { - &mut rsc.widgets_mut()[self.widget] + &mut rsc.ui_mut().widgets[self.widget] } } diff --git a/core/src/event/manager.rs b/core/src/event/manager.rs index 6896478..1a315a6 100644 --- a/core/src/event/manager.rs +++ b/core/src/event/manager.rs @@ -1,6 +1,6 @@ use crate::{ ActiveData, Event, EventCtx, EventFn, EventIdCtx, EventLike, HasEvents, IdLike, LayerId, - Widget, WidgetEventFn, WidgetId, WeakWidget, + WeakWidget, Widget, WidgetEventFn, WidgetId, util::{HashMap, HashSet, TypeMap}, }; use std::{any::TypeId, rc::Rc}; @@ -28,7 +28,7 @@ impl EventManager { &mut self, id: WeakWidget, event: E, - f: impl WidgetEventFn::Data, W>, + f: impl for<'a> WidgetEventFn::Data<'a>, W>, ) { self.get_type::().register(id, event, f); self.widget_to_types @@ -74,7 +74,7 @@ pub trait EventManagerLike { fn undraw(&mut self, data: &ActiveData); } -type EventData = (E, Rc::Data>>); +type EventData = (E, Rc EventFn::Data<'a>>>); pub struct TypeEventManager { // TODO: reduce visiblity!! pub active: HashMap>, @@ -116,7 +116,7 @@ impl TypeEventManager { &mut self, widget: WeakWidget, event: impl EventLike, - f: impl WidgetEventFn, + f: impl for<'a> WidgetEventFn, W>, ) { let event = event.into_event(); self.map.entry(widget.id()).or_default().push(( @@ -137,7 +137,7 @@ impl TypeEventManager { pub fn run_fn<'a>( &mut self, id: impl IdLike, - ) -> impl FnOnce(EventCtx<'_, Rsc, E::Data>, &mut Rsc) + 'a { + ) -> impl for<'b> FnOnce(EventCtx<'_, Rsc, E::Data<'b>>, &mut Rsc) + 'a { let fs = self.map.get(&id.id()).cloned().unwrap_or_default(); move |ctx, rsc| { for (e, f) in fs { diff --git a/core/src/event/mod.rs b/core/src/event/mod.rs index 4010969..7b038f4 100644 --- a/core/src/event/mod.rs +++ b/core/src/event/mod.rs @@ -7,10 +7,10 @@ pub use manager::*; pub use rsc::*; pub trait Event: Sized + 'static + Clone { - type Data: Clone = (); + type Data<'a>: Clone = (); type State: Default = (); #[allow(unused_variables)] - fn should_run(&self, data: &Self::Data) -> Option { + fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option> { Some(data.clone()) } } diff --git a/core/src/event/rsc.rs b/core/src/event/rsc.rs index bcd174f..4824a99 100644 --- a/core/src/event/rsc.rs +++ b/core/src/event/rsc.rs @@ -1,5 +1,5 @@ use crate::{ - Event, EventCtx, EventLike, EventManager, IdLike, UiRsc, Widget, WidgetEventFn, WeakWidget, + Event, EventCtx, EventLike, EventManager, IdLike, UiRsc, WeakWidget, Widget, WidgetEventFn, }; pub trait HasState: 'static { @@ -14,7 +14,7 @@ pub trait HasEvents: Sized + UiRsc + HasState { &mut self, id: WeakWidget, event: E, - f: impl WidgetEventFn::Data, W>, + f: impl for<'a> WidgetEventFn::Data<'a>, W>, ) { self.events_mut().register(id, event, f); } @@ -24,7 +24,7 @@ pub trait RunEvents: HasEvents { fn run_event( &mut self, id: impl IdLike, - data: ::Data, + data: ::Data<'_>, state: &mut Self::State, ) { let f = self.events_mut().get_type::().run_fn(id); diff --git a/core/src/primitive/mod.rs b/core/src/primitive/mod.rs index 7d81635..8284a05 100644 --- a/core/src/primitive/mod.rs +++ b/core/src/primitive/mod.rs @@ -7,12 +7,3 @@ pub use color::*; pub use layer::*; pub use text::*; pub use texture::*; - -use crate::{Mask, util::TrackedArena}; - -#[derive(Default)] -pub struct PainterData { - pub textures: Textures, - pub text: TextData, - pub masks: TrackedArena, -} diff --git a/core/src/render/mod.rs b/core/src/render/mod.rs index 65ad0a1..1695efa 100644 --- a/core/src/render/mod.rs +++ b/core/src/render/mod.rs @@ -1,7 +1,7 @@ use std::num::NonZero; use crate::{ - Textures, UiRenderState, + UiData, UiRenderState, render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf}, util::HashMap, }; @@ -63,14 +63,14 @@ impl UiRenderNode { &mut self, device: &Device, queue: &Queue, - ui: &mut UiRenderState, - textures: &mut Textures, + ui: &mut UiData, + ui_render: &mut UiRenderState, ) { self.active.clear(); - for (i, primitives) in ui.layers.iter_mut() { + for (i, primitives) in ui_render.layers.iter_mut() { self.active.push(i); for change in primitives.apply_free() { - if let Some(inst) = ui.active.get_mut(&change.id) { + if let Some(inst) = ui_render.active.get_mut(&change.id) { for h in &mut inst.primitives { if h.layer == i && h.inst_idx == change.old { h.inst_idx = change.new; @@ -107,7 +107,7 @@ impl UiRenderNode { } } let mut changed = false; - changed |= self.textures.update(textures); + changed |= self.textures.update(&mut ui.textures); if ui.masks.changed { ui.masks.changed = false; self.masks.update(device, queue, &ui.masks[..]); diff --git a/core/src/ui/draw_state.rs b/core/src/ui/draw_state.rs index 02b1d19..ffd6bc1 100644 --- a/core/src/ui/draw_state.rs +++ b/core/src/ui/draw_state.rs @@ -1,6 +1,6 @@ use crate::{ - ActiveData, Axis, EventsLike, Painter, PainterData, SizeCtx, StrongWidget, UiRegion, - UiRenderState, UiVec2, WidgetId, Widgets, + ActiveData, Axis, EventsLike, Painter, SizeCtx, StrongWidget, UiRegion, UiRenderState, UiVec2, + WidgetId, Widgets, render::MaskIdx, util::{HashSet, forget_ref}, }; @@ -16,30 +16,6 @@ pub struct Drawer<'a> { } impl<'a> Drawer<'a> { - pub fn new( - widgets: &'a mut Widgets, - data: &'a mut PainterData, - render: &'a mut UiRenderState, - events: &'a mut dyn EventsLike, - root: Option<&'a StrongWidget>, - ) -> Self { - Self { - widgets, - data, - events, - render, - root, - draw_started: Default::default(), - } - } - - pub fn redraw_updates(&mut self) { - while let Some(&id) = self.widgets.needs_redraw.iter().next() { - self.redraw(id); - } - self.ui.free(self.events); - } - /// redraws a widget that's currently active (drawn) pub fn redraw(&mut self, id: WidgetId) { self.widgets.needs_redraw.remove(&id); @@ -94,21 +70,6 @@ impl<'a> Drawer<'a> { } } - pub fn redraw_all(&mut self) { - // free all resources & cache - for (_, active) in self.render.active.drain() { - self.events.undraw(&active); - } - self.render.cache.clear(); - self.ui.free(self.events); - self.render.layers.clear(); - self.widgets.needs_redraw.clear(); - - if let Some(id) = self.root { - self.draw_inner(0, id.id(), UiRegion::FULL, None, MaskIdx::NONE, None); - } - } - pub(super) fn draw_inner( &mut self, layer: usize, diff --git a/core/src/ui/mod.rs b/core/src/ui/mod.rs index f6eaf77..ad07b6d 100644 --- a/core/src/ui/mod.rs +++ b/core/src/ui/mod.rs @@ -1,8 +1,8 @@ -use crate::{WeakWidget, Widget, Widgets}; +use crate::{Mask, TextData, Textures, WeakWidget, WidgetId, Widgets, util::TrackedArena}; mod active; mod cache; -mod draw_state; +// mod draw_state; mod painter; mod render_state; mod size; @@ -13,10 +13,37 @@ pub use painter::Painter; pub use render_state::*; pub use size::*; -pub struct Ui {} - -pub trait UiRsc: Sized { - fn add_widget(&mut self, widget: W) -> WeakWidget; - fn widgets(&self) -> &Widgets; - fn widgets_mut(&mut self) -> &mut Widgets; +#[derive(Default)] +pub struct UiData { + pub widgets: Widgets, + pub textures: Textures, + pub text: TextData, + pub masks: TrackedArena, +} + +pub trait UiRsc { + fn ui(&self) -> &UiData; + fn ui_mut(&mut self) -> &mut UiData; + + #[allow(unused_variables)] + fn on_add(&mut self, id: WeakWidget) {} + #[allow(unused_variables)] + fn on_remove(&mut self, id: WidgetId) {} + #[allow(unused_variables)] + fn on_draw(&mut self, active: &ActiveData) {} + #[allow(unused_variables)] + fn on_undraw(&mut self, active: &ActiveData) {} + + fn widgets(&self) -> &Widgets { + &self.ui().widgets + } + fn widgets_mut(&mut self) -> &mut Widgets { + &mut self.ui_mut().widgets + } + fn free(&mut self) { + while let Some(id) = self.widgets_mut().free_next() { + self.on_remove(id); + } + self.ui_mut().textures.free(); + } } diff --git a/core/src/ui/painter.rs b/core/src/ui/painter.rs index e48c774..f7f4939 100644 --- a/core/src/ui/painter.rs +++ b/core/src/ui/painter.rs @@ -1,14 +1,14 @@ use crate::{ Axis, Len, RenderedText, Size, SizeCtx, StrongWidget, TextAttrs, TextBuffer, TextData, - TextureHandle, UiRegion, Widget, WidgetId, + TextureHandle, UiRegion, UiRenderState, UiRsc, Widget, WidgetId, render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst}, - ui::draw_state::Drawer, util::Vec2, }; /// makes your surfaces look pretty -pub struct Painter<'a, 'b> { - pub(super) drawer: &'a mut Drawer<'b>, +pub struct Painter<'a> { + pub(super) state: &'a mut UiRenderState, + pub(super) rsc: &'a mut dyn UiRsc, pub(super) region: UiRegion, pub(super) mask: MaskIdx, @@ -19,9 +19,9 @@ pub struct Painter<'a, 'b> { pub(super) id: WidgetId, } -impl<'a, 'c> Painter<'a, 'c> { +impl<'a> Painter<'a> { fn primitive_at(&mut self, primitive: P, region: UiRegion) { - let h = self.drawer.layers.write( + let h = self.state.layers.write( self.layer, PrimitiveInst { id: self.id, @@ -32,7 +32,7 @@ impl<'a, 'c> Painter<'a, 'c> { ); if self.mask != MaskIdx::NONE { // TODO: I have no clue if this works at all :joy: - self.drawer.masks.push_ref(self.mask); + self.rsc.ui_mut().masks.push_ref(self.mask); } self.primitives.push(h); } @@ -48,7 +48,7 @@ impl<'a, 'c> Painter<'a, 'c> { pub fn set_mask(&mut self, region: UiRegion) { assert!(self.mask == MaskIdx::NONE); - self.mask = self.drawer.masks.push(Mask { region }); + self.mask = self.rsc.ui_mut().masks.push(Mask { region }); } /// Draws a widget within this widget's region. @@ -64,8 +64,15 @@ impl<'a, 'c> Painter<'a, 'c> { fn widget_at(&mut self, id: &StrongWidget, region: UiRegion) { self.children.push(id.id()); - self.drawer - .draw_inner(self.layer, id.id(), region, Some(self.id), self.mask, None); + self.state.draw_inner( + self.layer, + id.id(), + region, + Some(self.id), + self.mask, + None, + self.rsc, + ); } pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) { @@ -85,10 +92,8 @@ impl<'a, 'c> Painter<'a, 'c> { /// returns (handle, offset from top left) pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText { - self.drawer - .ui - .text - .draw(buffer, attrs, &mut self.drawer.ui.textures) + let ui = self.rsc.ui_mut(); + ui.text.draw(buffer, attrs, &mut ui.textures) } pub fn region(&self) -> UiRegion { @@ -107,27 +112,27 @@ impl<'a, 'c> Painter<'a, 'c> { } pub fn output_size(&self) -> Vec2 { - self.drawer.output_size + self.state.output_size } pub fn px_size(&mut self) -> Vec2 { - self.region.size().to_abs(self.drawer.output_size) + self.region.size().to_abs(self.state.output_size) } pub fn text_data(&mut self) -> &mut TextData { - &mut self.drawer.text + &mut self.rsc.ui_mut().text } pub fn child_layer(&mut self) { - self.layer = self.drawer.layers.child(self.layer); + self.layer = self.state.layers.child(self.layer); } pub fn next_layer(&mut self) { - self.layer = self.drawer.layers.next(self.layer); + self.layer = self.state.layers.next(self.layer); } pub fn label(&self) -> &str { - &self.drawer.widgets.data(self.id).unwrap().label + &self.rsc.widgets().data(self.id).unwrap().label } pub fn id(&self) -> &WidgetId { @@ -135,6 +140,6 @@ impl<'a, 'c> Painter<'a, 'c> { } pub fn size_ctx(&mut self) -> SizeCtx<'_> { - self.drawer.size_ctx(self.id, self.region.size()) + self.state.size_ctx(self.id, self.region.size(), self.rsc) } } diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index aad10dd..57e79a6 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -1,7 +1,8 @@ use crate::{ - ActiveData, EventsLike, IdLike, PixelRegion, PrimitiveLayers, StrongWidget, WidgetId, Widgets, - ui::{cache::Cache, draw_state::Drawer}, - util::{HashMap, Vec2}, + ActiveData, Axis, IdLike, MaskIdx, Painter, PixelRegion, PrimitiveLayers, SizeCtx, + StrongWidget, UiRegion, UiRsc, UiVec2, WidgetId, Widgets, + ui::cache::Cache, + util::{HashMap, HashSet, Vec2, forget_ref}, }; pub struct UiRenderState { @@ -12,6 +13,7 @@ pub struct UiRenderState { old_root: Option, resized: bool, + draw_started: HashSet, } impl UiRenderState { @@ -23,6 +25,7 @@ impl UiRenderState { output_size: Vec2::ZERO, old_root: None, resized: false, + draw_started: Default::default(), } } @@ -31,15 +34,11 @@ impl UiRenderState { self.resized = true; } - pub fn update<'a>( - &mut self, - root: impl Into>, - widgets: &mut Widgets, - events: &mut dyn EventsLike, - ) { + pub fn update<'a>(&mut self, root: impl Into>, rsc: &mut dyn UiRsc) { // safety mechanism for memory leaks; might wanna return a result instead so user can // decide whether to panic or not - if !widgets.waiting.is_empty() { + if !rsc.widgets().waiting.is_empty() { + let widgets = rsc.widgets(); let len = widgets.waiting.len(); let all: Vec<_> = widgets .waiting @@ -52,18 +51,169 @@ impl UiRenderState { weak widgets: {all:#?}" ); } - if self.root_changed(root) { - Drawer::new(self, events).redraw_all(); - self.old_root = root.into().map(|r| r.id()); - } else if widgets.has_updates() { - Drawer::new(self, events).redraw_updates(); - } - if self.resized { + let root = root.into(); + if self.root_changed(root) || self.resized { + self.redraw_all(root, rsc); + self.old_root = root.map(|r| r.id()); self.resized = false; - Drawer::new(self, events).redraw_all(); + } else if rsc.widgets().has_updates() { + self.redraw_updates(rsc); } } + fn redraw_all(&mut self, root: Option<&StrongWidget>, rsc: &mut dyn UiRsc) { + self.clear(rsc); + // free all resources & cache + if let Some(id) = root { + self.draw_inner(0, id.id(), UiRegion::FULL, None, MaskIdx::NONE, None, rsc); + } + } + + // TODO: should prolly make a DrawInfo struct or smth for everything other than rsc + #[allow(clippy::too_many_arguments)] + pub(super) fn draw_inner( + &mut self, + layer: usize, + id: WidgetId, + region: UiRegion, + parent: Option, + mask: MaskIdx, + old_children: Option>, + rsc: &mut dyn UiRsc, + ) { + let mut old_children = old_children.unwrap_or_default(); + if let Some(active) = self.active.get_mut(&id) + && !rsc.widgets().needs_redraw.contains(&id) + { + // check to see if we can skip drawing first + if active.region == region { + return; + } else if active.region.size() == region.size() { + // TODO: epsilon? + let from = active.region; + self.mov(id, from, region); + return; + } + // if not, then maintain resize and track old children to remove unneeded + let active = self.remove(id, false, rsc).unwrap(); + old_children = active.children; + } + + // draw widget + self.draw_started.insert(id); + + let mut painter = Painter { + state: self, + region, + mask, + layer, + id, + textures: Vec::new(), + primitives: Vec::new(), + children: Vec::new(), + rsc, + }; + + let mut widget = painter.rsc.widgets().get_dyn_dynamic(id); + widget.draw(&mut painter); + drop(widget); + + let Painter { + state: _, + rsc: _, + region, + mask, + textures, + primitives, + children, + layer, + id, + } = painter; + + // add to active + let active = ActiveData { + id, + region, + parent, + textures, + primitives, + children, + mask, + layer, + }; + + // remove old children that weren't kept + for c in &old_children { + if !active.children.contains(c) { + self.remove_rec(*c, rsc); + } + } + + rsc.on_draw(&active); + self.active.insert(id, active); + } + + fn mov(&mut self, id: WidgetId, from: UiRegion, to: UiRegion) { + let active = self.active.get_mut(&id).unwrap(); + for h in &active.primitives { + let region = self.layers[h.layer].region_mut(h); + *region = region.outside(&from).within(&to); + } + active.region = active.region.outside(&from).within(&to); + // SAFETY: children cannot be recursive + let children = unsafe { forget_ref(&active.children) }; + for child in children { + self.mov(*child, from, to); + } + } + + /// NOTE: instance textures are cleared and self.textures freed + fn remove(&mut self, id: WidgetId, undraw: bool, rsc: &mut dyn UiRsc) -> Option { + let mut active = self.active.remove(&id); + if let Some(active) = &mut active { + for h in &active.primitives { + let mask = self.layers.free(h); + if mask != MaskIdx::NONE { + rsc.ui_mut().masks.remove(mask); + } + } + active.textures.clear(); + rsc.ui_mut().textures.free(); + if undraw { + rsc.on_undraw(active); + } + } + active + } + + fn remove_rec(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) -> Option { + self.cache.remove(id); + let inst = self.remove(id, true, rsc); + if let Some(inst) = &inst { + for c in &inst.children { + self.remove_rec(*c, rsc); + } + } + inst + } + + fn clear(&mut self, rsc: &mut dyn UiRsc) { + for (_, active) in self.active.drain() { + rsc.on_undraw(&active); + } + self.cache.clear(); + self.layers.clear(); + rsc.widgets_mut().needs_redraw.clear(); + rsc.free(); + } + + pub fn redraw_updates(&mut self, rsc: &mut dyn UiRsc) { + while let Some(&id) = rsc.widgets().needs_redraw.iter().next() { + self.redraw(id, rsc); + } + rsc.free(); + } + pub fn root_changed<'a>(&self, root: impl Into>) -> bool { root.into().map(|r| r.id()) != self.old_root } @@ -103,10 +253,63 @@ impl UiRenderState { let region = self.active.get(&id.id())?.region; Some(region.to_px(self.output_size)) } -} -pub trait HasRoot { - fn set_root(&mut self, root: StrongWidget); + /// redraws a widget that's currently active (drawn) + pub fn redraw(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) { + rsc.widgets_mut().needs_redraw.remove(&id); + self.draw_started.remove(&id); + // check if parent depends on the desired size of this, if so then redraw it first + for axis in [Axis::X, Axis::Y] { + if let Some(&(outer, old)) = self.cache.size.axis_dyn(axis).get(&id) + && let Some(current) = self.active.get(&id) + && let Some(pid) = current.parent + { + self.cache.size.axis_dyn(axis).remove(&id); + let new = self.size_ctx(id, outer, rsc).len_axis(id, axis); + self.cache.size.axis_dyn(axis).insert(id, (outer, new)); + if new != old { + self.redraw(pid, rsc); + } + } + } + + if self.draw_started.contains(&id) { + return; + } + + let Some(active) = self.remove(id, false, rsc) else { + return; + }; + + self.draw_inner( + active.layer, + id, + active.region, + active.parent, + active.mask, + Some(active.children), + rsc, + ); + } + + pub(super) fn size_ctx<'b>( + &'b mut self, + source: WidgetId, + outer: UiVec2, + rsc: &'b mut dyn UiRsc, + ) -> SizeCtx<'b> { + let ui = rsc.ui_mut(); + SizeCtx { + source, + cache: &mut self.cache, + text: &mut ui.text, + textures: &mut ui.textures, + widgets: &ui.widgets, + outer, + output_size: self.output_size, + id: source, + } + } } impl Default for UiRenderState { diff --git a/core/src/widget/like.rs b/core/src/widget/like.rs index fe4b0ea..45512ae 100644 --- a/core/src/widget/like.rs +++ b/core/src/widget/like.rs @@ -1,4 +1,4 @@ -use crate::{HasRoot, UiRsc}; +use crate::UiRsc; use super::*; use std::marker::Unsize; @@ -22,15 +22,16 @@ pub trait WidgetLike: Sized { } } - fn set_root(self, rsc: &mut Rsc) - where - Rsc: HasRoot, - { + fn set_root(self, rsc: &mut Rsc, root: &mut impl HasRoot) { let id = self.add_strong(rsc); - rsc.set_root(id); + root.set_root(id); } } +pub trait HasRoot { + fn set_root(&mut self, root: StrongWidget); +} + pub trait WidgetArrLike { #[track_caller] fn add(self, state: &mut Rsc) -> WidgetArr; diff --git a/core/src/widget/tag.rs b/core/src/widget/tag.rs index dc51221..1356dac 100644 --- a/core/src/widget/tag.rs +++ b/core/src/widget/tag.rs @@ -6,7 +6,9 @@ pub struct WidgetTag; impl WidgetLike for W { type Widget = W; fn add(self, rsc: &mut Rsc) -> WeakWidget { - rsc.add_widget(self) + let w = rsc.ui_mut().widgets.add_weak(self); + rsc.on_add(w); + w } } diff --git a/core/src/widget/widgets.rs b/core/src/widget/widgets.rs index 19ecbeb..6098aa6 100644 --- a/core/src/widget/widgets.rs +++ b/core/src/widget/widgets.rs @@ -104,10 +104,10 @@ impl Widgets { self.vec.get_mut(id.id()) } - pub fn free(&mut self) { - for id in self.recv.try_iter() { - self.vec.free(id.id()); - } + pub fn free_next(&mut self) -> Option { + let next = self.recv.try_recv().ok()?; + self.vec.free(next); + Some(next) } #[allow(clippy::len_without_is_empty)] diff --git a/examples/minimal.rs b/examples/minimal.rs index bfdf031..e98ae5b 100644 --- a/examples/minimal.rs +++ b/examples/minimal.rs @@ -10,8 +10,12 @@ struct State { } impl DefaultAppState for State { - fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc, _: Proxy) -> Self { - rect(Color::RED).set_root(rsc); + fn new( + mut ui_state: DefaultUiState, + rsc: &mut DefaultRsc, + _: Proxy, + ) -> Self { + rect(Color::RED).set_root(rsc, &mut ui_state); Self { ui_state } } } diff --git a/examples/tabs/main.rs b/examples/tabs/main.rs index fa90797..d9789f1 100644 --- a/examples/tabs/main.rs +++ b/examples/tabs/main.rs @@ -16,7 +16,11 @@ pub struct Client { } impl DefaultAppState for Client { - fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc, _: Proxy) -> Self { + fn new( + mut ui_state: DefaultUiState, + rsc: &mut DefaultRsc, + _: Proxy, + ) -> Self { let rrect = rect(Color::WHITE).radius(20); let pad_test = ( rrect.color(Color::BLUE), @@ -197,20 +201,25 @@ impl DefaultAppState for Client { ((tabs.height(40), main.pad(10)).span(Dir::DOWN), info_sect) .stack() - .set_root(rsc); + .set_root(rsc, &mut ui_state); Self { ui_state, info } } - fn window_event(&mut self, _: WindowEvent, rsc: &mut DefaultRsc) { + fn window_event( + &mut self, + _: WindowEvent, + rsc: &mut DefaultRsc, + render: &mut UiRenderState, + ) { let new = format!( "widgets: {}\nactive: {}\nviews: {}", - rsc.ui.num_widgets(), - rsc.ui.active_widgets(), - self.ui_state.renderer.ui.view_count() + rsc.widgets().len(), + render.active_widgets(), + self.ui_state.renderer.ui.view_count(), ); - if new != *rsc.ui[self.info].content { - *rsc.ui[self.info].content = new; + if new != *rsc.widgets()[self.info].content { + *rsc.widgets_mut()[self.info].content = new; } } } diff --git a/examples/task.rs b/examples/task.rs index 2dafebb..63e4d43 100644 --- a/examples/task.rs +++ b/examples/task.rs @@ -11,11 +11,15 @@ struct State { } impl DefaultAppState for State { - fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc, _: Proxy) -> Self { + fn new( + mut ui_state: DefaultUiState, + rsc: &mut DefaultRsc, + _: Proxy, + ) -> Self { let rect = rect(Color::RED).add(rsc); rect.task_on(CursorSense::click(), async move |mut ctx| { tokio::time::sleep(Duration::from_secs(1)).await; - ctx.task.update(move |_, rsc| { + ctx.update(move |_, rsc| { let rect = rect(rsc); if rect.color == Color::RED { rect.color = Color::BLUE; @@ -24,7 +28,7 @@ impl DefaultAppState for State { } }); }) - .set_root(rsc); + .set_root(rsc, &mut ui_state); Self { ui_state } } } diff --git a/examples/view.rs b/examples/view.rs index 5557b72..ead842f 100644 --- a/examples/view.rs +++ b/examples/view.rs @@ -33,13 +33,17 @@ impl Test { } impl DefaultAppState for State { - fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc, _: Proxy) -> Self { + fn new( + mut ui_state: DefaultUiState, + rsc: &mut DefaultRsc, + _: Proxy, + ) -> Self { let test = Test::new(rsc); test.on(CursorSense::click(), move |_, rsc| { test.toggle(rsc); }) - .set_root(rsc); + .set_root(rsc, &mut ui_state); Self { ui_state } } diff --git a/src/default/attr.rs b/src/default/attr.rs index f387d56..ff4e313 100644 --- a/src/default/attr.rs +++ b/src/default/attr.rs @@ -12,12 +12,20 @@ where fn run(rsc: &mut Rsc, container: WeakWidget, id: Self::Input) { rsc.register_event(container, CursorSense::click_or_drag(), move |ctx, rsc| { - let region = rsc.ui().window_region(&id).unwrap(); + let region = ctx.data.render.window_region(&id).unwrap(); let id_pos = region.top_left; - let container_pos = rsc.ui().window_region(&container).unwrap().top_left; + let container_pos = ctx.data.render.window_region(&container).unwrap().top_left; let pos = ctx.data.pos + container_pos - id_pos; let size = region.size(); - select(rsc, ctx.state, id, pos, size, ctx.data.sense.is_dragging()); + select( + rsc, + ctx.data.render, + ctx.state, + id, + pos, + size, + ctx.data.sense.is_dragging(), + ); }); } } @@ -34,6 +42,7 @@ where rsc.register_event(id, CursorSense::click_or_drag(), move |ctx, rsc| { select( rsc, + ctx.data.render, ctx.state, id, ctx.data.pos, @@ -45,7 +54,8 @@ where } fn select( - rsc: &mut impl HasUi, + rsc: &mut impl UiRsc, + render: &UiRenderState, state: &mut impl HasDefaultUiState, id: WeakWidget, pos: Vec2, @@ -57,7 +67,7 @@ fn select( let recent = (now - state.last_click) < Duration::from_millis(300); state.last_click = now; id.edit(rsc).select(pos, size, dragging, recent); - if let Some(region) = rsc.ui().window_region(&id) { + if let Some(region) = render.window_region(&id) { state.window.set_ime_allowed(true); state.window.set_ime_cursor_area( LogicalPosition::::from(region.top_left.tuple()), diff --git a/src/default/mod.rs b/src/default/mod.rs index 106e5b7..dd3e253 100644 --- a/src/default/mod.rs +++ b/src/default/mod.rs @@ -30,6 +30,7 @@ pub use task::*; pub type Proxy = EventLoopProxy; pub struct DefaultUiState { + pub root: Option, pub renderer: UiRenderer, pub input: Input, pub focus: Option>, @@ -39,10 +40,17 @@ pub struct DefaultUiState { pub last_click: Instant, } +impl HasRoot for DefaultUiState { + fn set_root(&mut self, root: StrongWidget) { + self.root = Some(root); + } +} + impl DefaultUiState { pub fn new(window: impl Into>) -> Self { let window = window.into(); Self { + root: None, renderer: UiRenderer::new(window.clone()), window, input: Input::default(), @@ -64,18 +72,30 @@ pub trait DefaultAppState: HasDefaultUiState { fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc, proxy: Proxy) -> Self; #[allow(unused_variables)] - fn event(&mut self, event: Self::Event, rsc: &mut DefaultRsc) {} + fn event( + &mut self, + event: Self::Event, + rsc: &mut DefaultRsc, + render: &mut UiRenderState, + ) { + } #[allow(unused_variables)] - fn exit(&mut self, rsc: &mut DefaultRsc) {} + fn exit(&mut self, rsc: &mut DefaultRsc, render: &mut UiRenderState) {} #[allow(unused_variables)] - fn window_event(&mut self, event: WindowEvent, rsc: &mut DefaultRsc) {} + fn window_event( + &mut self, + event: WindowEvent, + rsc: &mut DefaultRsc, + render: &mut UiRenderState, + ) { + } fn window_attributes() -> WindowAttributes { Default::default() } } pub struct DefaultRsc { - pub ui: Ui, + pub ui: UiData, pub events: EventManager, pub tasks: Tasks, _state: PhantomData, @@ -96,14 +116,26 @@ impl DefaultRsc { } } -impl HasUi for DefaultRsc { - fn ui(&self) -> &Ui { +impl UiRsc for DefaultRsc { + fn ui(&self) -> &UiData { &self.ui } - fn ui_mut(&mut self) -> &mut Ui { + fn ui_mut(&mut self) -> &mut UiData { &mut self.ui } + + fn on_draw(&mut self, active: &ActiveData) { + self.events.draw(active); + } + + fn on_undraw(&mut self, active: &ActiveData) { + self.events.undraw(active); + } + + fn on_remove(&mut self, id: WidgetId) { + self.events.remove(id); + } } impl HasState for DefaultRsc { @@ -128,6 +160,7 @@ impl HasTasks for DefaultRsc { pub struct DefaultApp { rsc: DefaultRsc, + render: UiRenderState, state: State, task_recv: TaskMsgReceiver>, } @@ -142,23 +175,32 @@ impl AppState for DefaultApp { let default_state = DefaultUiState::new(window); let (mut rsc, task_recv) = DefaultRsc::init(default_state.window.clone()); let state = State::new(default_state, &mut rsc, proxy); + let render = UiRenderState::new(); Self { rsc, state, + render, task_recv, } } fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) { - self.state.event(event, &mut self.rsc); + self.state.event(event, &mut self.rsc, &mut self.render); } fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) { - for update in self.task_recv.try_iter() { - update(&mut self.state, &mut self.rsc); + let Self { + rsc, + render, + state, + task_recv, + } = self; + + for update in task_recv.try_iter() { + update(state, rsc); } - let ui_state = self.state.default_state_mut(); + let ui_state = state.default_state_mut(); let input_changed = ui_state.input.event(&event); let cursor_state = ui_state.cursor_state().clone(); let old = ui_state.focus; @@ -167,45 +209,43 @@ impl AppState for DefaultApp { } if input_changed { let window_size = ui_state.window_size(); - self.rsc - .run_sensors(&mut self.state, cursor_state, window_size); + render.run_sensors(rsc, state, cursor_state, window_size); } - let ui = &mut self.rsc.ui; - let ui_state = self.state.default_state_mut(); + let ui_state = state.default_state_mut(); if old != ui_state.focus && let Some(old) = old { - old.edit(ui).deselect(); + old.edit(rsc).deselect(); } match &event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::RedrawRequested => { - ui.update(&mut self.rsc.events); - ui_state.renderer.update(ui); + render.update(&ui_state.root, rsc); + ui_state.renderer.update(&mut rsc.ui, render); ui_state.renderer.draw(); } WindowEvent::Resized(size) => { - ui.resize((size.width, size.height)); + render.resize((size.width, size.height)); ui_state.renderer.resize(size) } WindowEvent::KeyboardInput { event, .. } => { if let Some(sel) = ui_state.focus && event.state.is_pressed() { - let mut text = sel.edit(ui); + let mut text = sel.edit(rsc); match text.apply_event(event, &ui_state.input.modifiers) { TextInputResult::Unfocus => { ui_state.focus = None; ui_state.window.set_ime_allowed(false); } TextInputResult::Submit => { - self.rsc.run_event::(sel, (), &mut self.state); + rsc.run_event::(sel, (), state); } TextInputResult::Paste => { if let Ok(t) = ui_state.clipboard.get_text() { text.insert(&t); } - self.rsc.run_event::(sel, (), &mut self.state); + rsc.run_event::(sel, (), state); } TextInputResult::Copy(text) => { if let Err(err) = ui_state.clipboard.set_text(text) { @@ -213,7 +253,7 @@ impl AppState for DefaultApp { } } TextInputResult::Used => { - self.rsc.run_event::(sel, (), &mut self.state); + rsc.run_event::(sel, (), state); } TextInputResult::Unused => {} } @@ -221,7 +261,7 @@ impl AppState for DefaultApp { } WindowEvent::Ime(ime) => { if let Some(sel) = ui_state.focus { - let mut text = sel.edit(ui); + let mut text = sel.edit(rsc); match ime { Ime::Enabled | Ime::Disabled => (), Ime::Preedit(content, _pos) => { @@ -237,15 +277,15 @@ impl AppState for DefaultApp { } _ => (), } - self.state.window_event(event, &mut self.rsc); + state.window_event(event, rsc, render); let ui_state = self.state.default_state_mut(); - if self.rsc.ui.needs_redraw() { + if render.needs_redraw(&ui_state.root, rsc.widgets()) { ui_state.renderer.window().request_redraw(); } ui_state.input.end_frame(); } fn exit(&mut self) { - self.state.exit(&mut self.rsc); + self.state.exit(&mut self.rsc, &mut self.render); } } diff --git a/src/default/render.rs b/src/default/render.rs index 09eeadd..625a9fa 100644 --- a/src/default/render.rs +++ b/src/default/render.rs @@ -1,4 +1,4 @@ -use iris_core::{Ui, UiLimits, UiRenderNode}; +use iris_core::{UiData, UiLimits, UiRenderNode, UiRenderState}; use pollster::FutureExt; use std::sync::Arc; use wgpu::*; @@ -17,8 +17,8 @@ pub struct UiRenderer { } impl UiRenderer { - pub fn update(&mut self, ui: &mut Ui) { - self.ui.update(&self.device, &self.queue, ui); + pub fn update(&mut self, ui: &mut UiData, render: &mut UiRenderState) { + self.ui.update(&self.device, &self.queue, ui, render); } pub fn draw(&mut self) { diff --git a/src/default/sense.rs b/src/default/sense.rs index db88abd..ee73ee9 100644 --- a/src/default/sense.rs +++ b/src/default/sense.rs @@ -26,9 +26,9 @@ pub enum CursorSense { pub struct CursorSenses(Vec); impl Event for CursorSenses { - type Data = CursorData; + type Data<'a> = CursorData<'a>; type State = SensorState; - fn should_run(&self, data: &Self::Data) -> Option { + fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option> { if let Some(sense) = should_run(self, &data.cursor, data.hover) { let mut data = data.clone(); data.sense = sense; @@ -129,7 +129,7 @@ pub struct SensorState { } #[derive(Clone)] -pub struct CursorData { +pub struct CursorData<'a> { /// where this widget was hit pub pos: Vec2, pub size: Vec2, @@ -138,20 +138,36 @@ pub struct CursorData { pub cursor: CursorState, /// the first sense that triggered this pub sense: CursorSense, + pub render: &'a UiRenderState, } -pub trait SensorUi { - fn run_sensors(&mut self, state: &mut Rsc::State, cursor: CursorState, window_size: Vec2); +pub trait SensorUi { + fn run_sensors( + &self, + rsc: &mut Rsc, + state: &mut Rsc::State, + cursor: CursorState, + window_size: Vec2, + ); } -impl SensorUi for Rsc { - fn run_sensors(&mut self, state: &mut Rsc::State, cursor: CursorState, window_size: Vec2) { - let layers = std::mem::take(&mut self.ui_mut().layers); - let mut active = std::mem::take(&mut self.events_mut().get_type::().active); - for layer in layers.indices().rev() { +impl SensorUi for UiRenderState { + fn run_sensors( + &self, + rsc: &mut Rsc, + state: &mut Rsc::State, + cursor: CursorState, + window_size: Vec2, + ) { + // in order to remove this take, need to store active list in UiRenderState somehow + // this would probably be done through a generic parameter that adds yet another rsc / + // state like thing, but local to render state, and is passed to UiRsc events so you can + // update it there? + let mut active = std::mem::take(&mut rsc.events_mut().get_type::().active); + for layer in self.layers.indices().rev() { let mut sensed = false; for (id, sensor) in active.get_mut(&layer).into_flat_iter() { - let shape = self.ui().active.get(id).unwrap().region; + let shape = self.active.get(id).unwrap().region; let region = shape.to_px(window_size); let in_shape = cursor.exists && region.contains(cursor.pos); sensor.hover.update(in_shape); @@ -171,15 +187,15 @@ impl SensorUi for Rsc { // this does not have any meaning; // might wanna set up Event to have a prepare stage sense: CursorSense::Hovering, + render: self, }; - self.run_event::(*id, data, state); + rsc.run_event::(*id, data, state); } if sensed { break; } } - self.events_mut().get_type::().active = active; - self.ui_mut().layers = layers; + rsc.events_mut().get_type::().active = active; } } diff --git a/src/event.rs b/src/event.rs index dfb3422..03cd18a 100644 --- a/src/event.rs +++ b/src/event.rs @@ -2,13 +2,13 @@ use iris_core::*; use iris_macro::*; use std::sync::Arc; -use crate::default::{TaskCtx, Tasks}; +use crate::default::{TaskCtx, TaskUpdate, Tasks}; pub trait Eventable: WidgetLike { fn on( self, event: E, - f: impl WidgetEventFn::Data, Self::Widget>, + f: impl for<'a> WidgetEventFn::Data<'a>, Self::Widget>, ) -> impl WidgetIdFn { move |rsc| { let id = self.add(rsc); @@ -30,24 +30,22 @@ impl, Rsc: HasEvents, Tag> Eventable for WL { widget_trait! { pub trait TaskEventable; - fn task_on::Data, WL::Widget>>( + fn task_on<'a, E: EventLike, F: AsyncWidgetEventFn>( self, event: E, f: F, ) -> impl WidgetIdFn - where ::Data: Send, - for<'a> F::CallRefFuture<'a>: Send, + where ::Data<'a>: Send, + for<'b> F::CallRefFuture<'b>: Send, { let f = Arc::new(f); move |rsc| { let id = self.add(rsc); - rsc.register_event(id, event.into_event(), move |ctx, rsc| { - let data = ctx.data; + rsc.register_event(id, event.into_event(), move |_, rsc| { let f = f.clone(); rsc.tasks_mut().spawn(async move |task| { f(AsyncEventIdCtx { widget: id, - data, task, }).await; }); @@ -61,21 +59,22 @@ pub trait HasTasks: Sized + HasState + HasEvents { fn tasks_mut(&mut self) -> &mut Tasks; } -pub trait AsyncWidgetEventFn: - AsyncFn(AsyncEventIdCtx) + Send + Sync + 'static +pub trait AsyncWidgetEventFn: + AsyncFn(AsyncEventIdCtx) + Send + Sync + 'static { } -impl< - Rsc: HasEvents, - F: AsyncFn(AsyncEventIdCtx) + Send + Sync + 'static, - Data, - W: ?Sized, -> AsyncWidgetEventFn for F +impl) + Send + Sync + 'static, W: ?Sized> + AsyncWidgetEventFn for F { } -pub struct AsyncEventIdCtx { +pub struct AsyncEventIdCtx { pub widget: WeakWidget, - pub data: Data, - pub task: TaskCtx, + task: TaskCtx, +} + +impl AsyncEventIdCtx { + pub fn update(&mut self, f: impl TaskUpdate + 'static) { + self.task.update(f); + } } diff --git a/src/widget/image.rs b/src/widget/image.rs index d90b743..244bbb8 100644 --- a/src/widget/image.rs +++ b/src/widget/image.rs @@ -19,10 +19,10 @@ impl Widget for Image { } } -pub fn image(image: impl LoadableImage) -> impl WidgetFn { +pub fn image(image: impl LoadableImage) -> impl WidgetFn { let image = image.get_image().expect("Failed to load image"); move |state| Image { - handle: state.ui_mut().add_texture(image), + handle: state.ui_mut().textures.add(image), } } diff --git a/src/widget/text/build.rs b/src/widget/text/build.rs index 7f8f255..a2e8134 100644 --- a/src/widget/text/build.rs +++ b/src/widget/text/build.rs @@ -51,7 +51,7 @@ impl> TextBuilder { } } -impl TextBuilder { +impl TextBuilder { pub fn hint, Tag>( self, hint: W, @@ -75,7 +75,7 @@ pub trait TextBuilderOutput: Sized { } pub struct TextOutput; -impl TextBuilderOutput for TextOutput { +impl TextBuilderOutput for TextOutput { type Output = Text; fn run>( @@ -103,7 +103,7 @@ pub struct TextEditOutput { mode: EditMode, } -impl TextBuilderOutput for TextEditOutput { +impl TextBuilderOutput for TextEditOutput { type Output = TextEdit; fn run>( diff --git a/src/widget/text/edit.rs b/src/widget/text/edit.rs index d5cc1bb..846d16e 100644 --- a/src/widget/text/edit.rs +++ b/src/widget/text/edit.rs @@ -617,11 +617,11 @@ impl DerefMut for TextEdit { } pub trait TextEditable { - fn edit<'a>(&self, ui: &'a mut impl HasUi) -> TextEditCtx<'a>; + fn edit<'a>(&self, ui: &'a mut impl UiRsc) -> TextEditCtx<'a>; } impl> TextEditable for I { - fn edit<'a>(&self, ui: &'a mut impl HasUi) -> TextEditCtx<'a> { + fn edit<'a>(&self, ui: &'a mut impl UiRsc) -> TextEditCtx<'a> { let ui = ui.ui_mut(); TextEditCtx { text: ui.widgets.get_mut(self).unwrap(), diff --git a/src/widget/trait_fns.rs b/src/widget/trait_fns.rs index 87af7fe..effebc8 100644 --- a/src/widget/trait_fns.rs +++ b/src/widget/trait_fns.rs @@ -3,7 +3,7 @@ use crate::prelude::*; // these methods should "not require any context" (require unit) because they're in core widget_trait! { - pub trait CoreWidget; + pub trait CoreWidget; fn pad(self, padding: impl Into) -> impl WidgetFn { |state| Pad { @@ -26,7 +26,7 @@ widget_trait! { fn label(self, label: impl Into) -> impl WidgetIdFn { |state| { let id = self.add(state); - state.ui_mut().set_label(id, label.into()); + state.ui_mut().widgets.set_label(id, label.into()); id } } @@ -127,7 +127,7 @@ widget_trait! { fn set_ptr(self, ptr: WeakWidget, state: &mut Rsc) { let id = self.add_strong(state); - state.ui_mut()[ptr].inner = Some(id); + state.ui_mut().widgets[ptr].inner = Some(id); } }