diff --git a/core/src/attr.rs b/core/src/attr.rs index df64a0f..c9112ac 100644 --- a/core/src/attr.rs +++ b/core/src/attr.rs @@ -18,7 +18,7 @@ impl, WL: WidgetLike, Tag> ) -> impl WidgetIdFn { |state| { let id = self.add(state); - A::run(state, id.weak(), input); + A::run(state, id, input); id } } diff --git a/core/src/ui/mod.rs b/core/src/ui/mod.rs index 1d119df..bb5ecca 100644 --- a/core/src/ui/mod.rs +++ b/core/src/ui/mod.rs @@ -7,7 +7,7 @@ use crate::{ use image::DynamicImage; use std::{ ops::{Index, IndexMut}, - sync::mpsc::{Receiver, Sender, channel}, + sync::mpsc::{Receiver, channel}, }; mod active; @@ -38,7 +38,6 @@ pub struct Ui { pub root: Option, old_root: Option, recv: Receiver, - send: Sender, resized: bool, } @@ -73,24 +72,20 @@ impl Ui { &self.widgets.data(id.id()).unwrap().label } - pub fn add_widget(&mut self, w: W) -> WidgetHandle { - WidgetHandle::new(self.widgets.add(w), self.send.clone()) - } - pub fn new() -> Self { Self::default() } pub fn get(&self, id: &I) -> Option<&I::Widget> where - I::Widget: Sized, + I::Widget: Sized + Widget, { self.widgets.get(id) } pub fn get_mut(&mut self, id: &I) -> Option<&mut I::Widget> where - I::Widget: Sized, + I::Widget: Sized + Widget, { self.widgets.get_mut(id) } @@ -105,6 +100,20 @@ impl Ui { } pub fn update(&mut self, events: &mut dyn EventsLike) { + if !self.widgets.waiting.is_empty() { + let len = self.widgets.waiting.len(); + let all: Vec<_> = self + .widgets + .waiting + .iter() + .map(|&w| format!("'{}' ({w:?})", self.label(w))) + .collect(); + panic!( + "{len} widget(s) were never upgraded\n\ + this is likely a memory leak; consider upgrading to strong if you plan on using it later\n\ + weak widgets: {all:#?}" + ); + } if self.root_changed() { DrawState::new(self, events).redraw_all(); self.old_root = self.root.as_ref().map(|r| r.id()); @@ -169,7 +178,7 @@ impl Ui { impl Index for Ui where - I::Widget: Sized, + I::Widget: Sized + Widget, { type Output = I::Widget; @@ -180,7 +189,7 @@ where impl IndexMut for Ui where - I::Widget: Sized, + I::Widget: Sized + Widget, { fn index_mut(&mut self, id: I) -> &mut Self::Output { self.get_mut(&id).unwrap() @@ -191,7 +200,7 @@ impl Default for Ui { fn default() -> Self { let (send, recv) = channel(); Self { - widgets: Default::default(), + widgets: Widgets::new(send), active: Default::default(), layers: Default::default(), masks: Default::default(), @@ -201,7 +210,6 @@ impl Default for Ui { output_size: Vec2::ZERO, root: None, old_root: None, - send, recv, resized: false, } diff --git a/core/src/widget/handle.rs b/core/src/widget/handle.rs index e23fded..e82c83c 100644 --- a/core/src/widget/handle.rs +++ b/core/src/widget/handle.rs @@ -68,9 +68,21 @@ impl WidgetHandle { } impl WidgetRef { + pub(crate) fn new(id: WidgetId) -> Self { + Self { + id, + ty: unsafe { MaybeUninit::zeroed().assume_init() }, + } + } + pub fn id(&self) -> WidgetId { self.id } + + #[track_caller] + pub fn upgrade(self, ui: &mut impl HasUi) -> WidgetHandle { + ui.ui_mut().widgets.upgrade(self) + } } impl Drop for WidgetHandle { @@ -81,29 +93,29 @@ impl Drop for WidgetHandle { } } -pub trait WidgetIdFn: FnOnce(&mut State) -> WidgetHandle {} -impl WidgetHandle> WidgetIdFn for F {} +pub trait WidgetIdFn: FnOnce(&mut State) -> WidgetRef {} +impl WidgetRef> WidgetIdFn for F {} pub trait IdLike { - type Widget: Widget + ?Sized + 'static; + type Widget: ?Sized; fn id(&self) -> WidgetId; } -impl IdLike for &WidgetHandle { +impl IdLike for &WidgetHandle { type Widget = W; fn id(&self) -> WidgetId { self.id } } -impl IdLike for WidgetHandle { +impl IdLike for WidgetHandle { type Widget = W; fn id(&self) -> WidgetId { self.id } } -impl IdLike for WidgetRef { +impl IdLike for WidgetRef { type Widget = W; fn id(&self) -> WidgetId { self.id diff --git a/core/src/widget/like.rs b/core/src/widget/like.rs index dac20f6..64e3061 100644 --- a/core/src/widget/like.rs +++ b/core/src/widget/like.rs @@ -16,11 +16,15 @@ impl StateLike for Ui { pub trait WidgetLike, Tag>: Sized { type Widget: Widget + ?Sized + Unsize; - fn add(self, state: &mut impl StateLike) -> WidgetHandle; + fn add(self, state: &mut impl StateLike) -> WidgetRef; + + fn add_strong(self, state: &mut impl StateLike) -> WidgetHandle { + self.add(state).upgrade(state.as_state().ui_mut()) + } fn with_id( self, - f: impl FnOnce(&mut State, WidgetHandle) -> WidgetHandle, + f: impl FnOnce(&mut State, WidgetRef) -> WidgetRef, ) -> impl WidgetIdFn { move |state| { let id = self.add(state); @@ -29,15 +33,18 @@ pub trait WidgetLike, Tag>: Sized { } fn set_root(self, state: &mut impl StateLike) { - state.as_state().get_mut().root = Some(self.add(state)); + let id = self.add(state); + let ui = state.as_state().ui_mut(); + ui.root = Some(id.upgrade(ui)); } fn handles(self, state: &mut impl StateLike) -> WidgetHandles { - self.add(state).handles() + self.add(state).upgrade(state.as_state().ui_mut()).handles() } } pub trait WidgetArrLike { + #[track_caller] fn add(self, state: &mut impl StateLike) -> WidgetArr; } @@ -58,7 +65,7 @@ macro_rules! impl_widget_arr { #[allow(non_snake_case)] let ($($W,)*) = self; WidgetArr::new( - [$($W.add(state),)*], + [$($W.add(state).upgrade(state.as_state().ui_mut()),)*], ) } } diff --git a/core/src/widget/tag.rs b/core/src/widget/tag.rs index 899dea0..9774949 100644 --- a/core/src/widget/tag.rs +++ b/core/src/widget/tag.rs @@ -5,8 +5,8 @@ use std::marker::Unsize; pub struct WidgetTag; impl, W: Widget> WidgetLike for W { type Widget = W; - fn add(self, state: &mut impl StateLike) -> WidgetHandle { - state.as_state().get_mut().add_widget(self) + fn add(self, state: &mut impl StateLike) -> WidgetRef { + state.as_state().get_mut().widgets.add_weak(self) } } @@ -15,17 +15,30 @@ impl, W: Widget, F: FnOnce(&mut State) -> W> WidgetLike for F { type Widget = W; - fn add(self, state: &mut impl StateLike) -> WidgetHandle { + fn add(self, state: &mut impl StateLike) -> WidgetRef { self(state.as_state()).add(state) } } +pub trait WidgetFnTrait { + type Widget: Widget; + fn run(self, state: &mut State) -> Self::Widget; +} +pub struct FnTraitTag; +impl, T: WidgetFnTrait> WidgetLike for T { + type Widget = T::Widget; + #[track_caller] + fn add(self, state: &mut impl StateLike) -> WidgetRef { + self.run(state.as_state()).add(state) + } +} + pub struct IdTag; impl, W: ?Sized + Widget + Unsize> - WidgetLike for WidgetHandle + WidgetLike for WidgetRef { type Widget = W; - fn add(self, _: &mut impl StateLike) -> WidgetHandle { + fn add(self, _: &mut impl StateLike) -> WidgetRef { self } } @@ -34,11 +47,11 @@ pub struct IdFnTag; impl< State: HasUi + StateLike, W: ?Sized + Widget + Unsize, - F: FnOnce(&mut State) -> WidgetHandle, + F: FnOnce(&mut State) -> WidgetRef, > WidgetLike for F { type Widget = W; - fn add(self, state: &mut impl StateLike) -> WidgetHandle { + fn add(self, state: &mut impl StateLike) -> WidgetRef { self(state.as_state()) } } diff --git a/core/src/widget/widgets.rs b/core/src/widget/widgets.rs index c98b511..751ddbf 100644 --- a/core/src/widget/widgets.rs +++ b/core/src/widget/widgets.rs @@ -1,15 +1,27 @@ +use std::sync::mpsc::Sender; + use crate::{ - IdLike, Widget, WidgetData, WidgetId, + IdLike, Widget, WidgetData, WidgetHandle, WidgetId, WidgetRef, util::{DynBorrower, HashSet, SlotVec, forget_mut, to_mut}, }; -#[derive(Default)] pub struct Widgets { pub needs_redraw: HashSet, vec: SlotVec, + send: Sender, + pub(crate) waiting: HashSet, } impl Widgets { + pub fn new(send: Sender) -> Self { + Self { + needs_redraw: Default::default(), + vec: Default::default(), + waiting: Default::default(), + send, + } + } + pub fn has_updates(&self) -> bool { !self.needs_redraw.is_empty() } @@ -37,20 +49,37 @@ impl Widgets { pub fn get(&self, id: &I) -> Option<&I::Widget> where - I::Widget: Sized, + I::Widget: Sized + Widget, { self.get_dyn(id.id())?.as_any().downcast_ref() } pub fn get_mut(&mut self, id: &I) -> Option<&mut I::Widget> where - I::Widget: Sized, + I::Widget: Sized + Widget, { self.get_dyn_mut(id.id())?.as_any_mut().downcast_mut() } - pub fn add(&mut self, widget: W) -> WidgetId { - self.vec.add(WidgetData::new(widget)) + pub fn add_strong(&mut self, widget: W) -> WidgetHandle { + let id = self.vec.add(WidgetData::new(widget)); + WidgetHandle::new(id, self.send.clone()) + } + + pub fn add_weak(&mut self, widget: W) -> WidgetRef { + let id = self.vec.add(WidgetData::new(widget)); + self.waiting.insert(id); + WidgetRef::new(id) + } + + #[track_caller] + pub fn upgrade(&mut self, rf: WidgetRef) -> WidgetHandle { + if !self.waiting.remove(&rf.id()) { + let label = self.label(rf); + let id = rf.id(); + panic!("widget '{label}' ({id:?}) was already added\ncannot add a widget twice; consider creating two") + } + WidgetHandle::new(rf.id(), self.send.clone()) } pub fn data(&self, id: impl IdLike) -> Option<&WidgetData> { diff --git a/src/bin/test/main.rs b/src/bin/test/main.rs index e252901..911e88d 100644 --- a/src/bin/test/main.rs +++ b/src/bin/test/main.rs @@ -21,7 +21,6 @@ impl DefaultAppState for Client { events: EventManager::default(), }; - let info; let rrect = rect(Color::WHITE).radius(20); let pad_test = ( rrect.color(Color::BLUE), @@ -44,7 +43,7 @@ impl DefaultAppState for Client { .width(rest(3)), ) .span(Dir::RIGHT) - .handles(&mut rsc); + .add(&mut rsc); let span_test = ( rrect.color(Color::GREEN).width(100), @@ -57,15 +56,15 @@ impl DefaultAppState for Client { .span(Dir::LEFT) .add(&mut rsc); - let span_add = Span::empty(Dir::RIGHT).handles(&mut rsc); + let span_add = Span::empty(Dir::RIGHT).add(&mut rsc); let add_button = rect(Color::LIME) .radius(30) .on(CursorSense::click(), move |ctx| { let child = image(include_bytes!("assets/sungals.png")) .center() - .add(ctx); - (span_add.r)(ctx).children.push(child); + .add_strong(ctx); + span_add(ctx).children.push(child); }) .sized((150, 150)) .align(Align::BOT_RIGHT); @@ -73,12 +72,12 @@ impl DefaultAppState for Client { let del_button = rect(Color::RED) .radius(30) .on(CursorSense::click(), move |ctx| { - (span_add.r)(ctx).children.pop(); + span_add(ctx).children.pop(); }) .sized((150, 150)) .align(Align::BOT_LEFT); - let span_add_test = (span_add.h, add_button, del_button).stack().add(&mut rsc); + let span_add_test = (span_add, add_button, del_button).stack().add(&mut rsc); let btext = |content| wtext(content).size(30); @@ -103,8 +102,8 @@ impl DefaultAppState for Client { .span(Dir::DOWN) .add(&mut rsc); - let texts = Span::empty(Dir::DOWN).gap(10).handles(&mut rsc); - let msg_area = texts.h.scrollable().masked().background(rect(Color::SKY)); + let texts = Span::empty(Dir::DOWN).gap(10).add(&mut rsc); + let msg_area = texts.scrollable().masked().background(rect(Color::SKY)); let add_text = wtext("add") .editable(EditMode::MultiLine) .text_align(Align::LEFT) @@ -119,19 +118,21 @@ impl DefaultAppState for Client { .text_align(Align::LEFT) .wrap(true) .attr::(()); - let msg_box = text.background(rect(Color::WHITE.darker(0.5))).add(ctx); - (texts.r)(ctx).children.push(msg_box); + let msg_box = text + .background(rect(Color::WHITE.darker(0.5))) + .add_strong(ctx); + texts(ctx).children.push(msg_box); }) - .handles(&mut rsc); + .add(&mut rsc); let text_edit_scroll = ( msg_area.height(rest(1)), ( Rect::new(Color::WHITE.darker(0.9)), ( - add_text.h.width(rest(1)), + add_text.width(rest(1)), Rect::new(Color::GREEN) .on(CursorSense::click(), move |ctx| { - ctx.state.run_event::(add_text.r, &mut ()); + ctx.state.run_event::(add_text, &mut ()); }) .sized((40, 40)), ) @@ -146,15 +147,16 @@ impl DefaultAppState for Client { .span(Dir::DOWN) .add(&mut rsc); - let main = WidgetPtr::new().handles(&mut rsc); + let main = WidgetPtr::new().add(&mut rsc); let vals = Rc::new(RefCell::new((0, Vec::new()))); - let mut switch_button = |color, to: WidgetHandle, label| { + let mut switch_button = |color, to: WidgetRef, label| { + let to = to.upgrade(&mut rsc); let vec = &mut vals.borrow_mut().1; let i = vec.len(); if vec.is_empty() { vec.push(None); - rsc.ui[main.r].set(to); + rsc.ui[main].set(to); } else { vec.push(Some(to)); } @@ -163,7 +165,7 @@ impl DefaultAppState for Client { .on(CursorSense::click(), move |ctx| { let (prev, vec) = &mut *vals.borrow_mut(); if let Some(h) = vec[i].take() { - vec[*prev] = (main.r)(ctx).replace(h); + vec[*prev] = main(ctx).replace(h); *prev = i; } ctx.widget().color = color.darker(0.3); @@ -181,7 +183,7 @@ impl DefaultAppState for Client { }; let tabs = ( - switch_button(Color::RED, pad_test.h, "pad"), + switch_button(Color::RED, pad_test, "pad"), switch_button(Color::GREEN, span_test, "span"), switch_button(Color::BLUE, span_add_test, "image span"), switch_button(Color::MAGENTA, text_test, "text layout"), @@ -193,10 +195,10 @@ impl DefaultAppState for Client { ) .span(Dir::RIGHT); - info = wtext("").handles(&mut rsc); - let info_sect = info.h.pad(10).align(Align::RIGHT); + let info = wtext("").add(&mut rsc); + let info_sect = info.pad(10).align(Align::RIGHT); - ((tabs.height(40), main.h.pad(10)).span(Dir::DOWN), info_sect) + ((tabs.height(40), main.pad(10)).span(Dir::DOWN), info_sect) .stack() .set_root(&mut rsc); @@ -204,7 +206,7 @@ impl DefaultAppState for Client { ui: rsc.ui, ui_state: rsc.ui_state, events: rsc.events, - info: info.r, + info, } } diff --git a/src/event.rs b/src/event.rs index 3b17a2d..6f92a7d 100644 --- a/src/event.rs +++ b/src/event.rs @@ -10,15 +10,15 @@ pub mod eventable { f: impl for<'a> WidgetEventFn::Data<'a>, WL::Widget>, ) -> impl WidgetIdFn { move |state| { - let id = self.handles(state); - state.register_event(id.r, event.into_event(), move |ctx| { + let id = self.add(state); + state.register_event(id, event.into_event(), move |ctx| { f(&mut EventIdCtx { - widget: id.r, + widget: id, state: ctx.state, data: ctx.data, }); }); - id.h + id } } } diff --git a/src/widget/position/span.rs b/src/widget/position/span.rs index eef6c6f..7607ce0 100644 --- a/src/widget/position/span.rs +++ b/src/widget/position/span.rs @@ -151,14 +151,15 @@ pub struct SpanBuilder, } -impl, const LEN: usize, Wa: WidgetArrLike, Tag> FnOnce<(&mut State,)> - for SpanBuilder +impl, const LEN: usize, Wa: WidgetArrLike, Tag> + WidgetFnTrait for SpanBuilder { - type Output = Span; + type Widget = Span; - extern "rust-call" fn call_once(self, args: (&mut State,)) -> Self::Output { + #[track_caller] + fn run(self, state: &mut State) -> Self::Widget { Span { - children: self.children.add(args.0).arr.into_iter().collect(), + children: self.children.add(state).arr.into_iter().collect(), dir: self.dir, gap: self.gap, } diff --git a/src/widget/text/build.rs b/src/widget/text/build.rs index b91b43d..9bc225f 100644 --- a/src/widget/text/build.rs +++ b/src/widget/text/build.rs @@ -59,7 +59,7 @@ impl, O> TextBuilder { TextBuilder { content: self.content, attrs: self.attrs, - hint: move |ui: &mut State| Some(hint.add(ui).any()), + hint: move |ui: &mut State| Some(hint.add_strong(ui).any()), output: self.output, state: PhantomData, } diff --git a/src/widget/trait_fns.rs b/src/widget/trait_fns.rs index 9663158..a4bd893 100644 --- a/src/widget/trait_fns.rs +++ b/src/widget/trait_fns.rs @@ -8,13 +8,13 @@ widget_trait! { fn pad(self, padding: impl Into) -> impl WidgetFn { |state| Pad { padding: padding.into(), - inner: self.add(state), + inner: self.add_strong(state), } } fn align(self, align: impl Into) -> impl WidgetFn { move |state| Aligned { - inner: self.add(state), + inner: self.add_strong(state), align: align.into(), } } @@ -26,7 +26,7 @@ widget_trait! { fn label(self, label: impl Into) -> impl WidgetIdFn { |state| { let id = self.add(state); - state.get_mut().set_label(&id, label.into()); + state.get_mut().set_label(id, label.into()); id } } @@ -34,7 +34,7 @@ widget_trait! { fn sized(self, size: impl Into) -> impl WidgetFn { let size = size.into(); move |state| Sized { - inner: self.add(state), + inner: self.add_strong(state), x: Some(size.x), y: Some(size.y), } @@ -43,7 +43,7 @@ widget_trait! { fn max_width(self, len: impl Into) -> impl WidgetFn { let len = len.into(); move |state| MaxSize { - inner: self.add(state), + inner: self.add_strong(state), x: Some(len), y: None, } @@ -52,7 +52,7 @@ widget_trait! { fn max_height(self, len: impl Into) -> impl WidgetFn { let len = len.into(); move |state| MaxSize { - inner: self.add(state), + inner: self.add_strong(state), x: None, y: Some(len), } @@ -61,7 +61,7 @@ widget_trait! { fn width(self, len: impl Into) -> impl WidgetFn { let len = len.into(); move |state| Sized { - inner: self.add(state), + inner: self.add_strong(state), x: Some(len), y: None, } @@ -70,7 +70,7 @@ widget_trait! { fn height(self, len: impl Into) -> impl WidgetFn { let len = len.into(); move |state| Sized { - inner: self.add(state), + inner: self.add_strong(state), x: None, y: Some(len), } @@ -78,7 +78,7 @@ widget_trait! { fn offset(self, amt: impl Into) -> impl WidgetFn { move |state| Offset { - inner: self.add(state), + inner: self.add_strong(state), amt: amt.into(), } } @@ -86,7 +86,7 @@ widget_trait! { fn scrollable(self) -> impl WidgetIdFn where State: HasEvents { use eventable::*; move |state| { - Scroll::new(self.add(state), Axis::Y) + Scroll::new(self.add_strong(state), Axis::Y) .on(CursorSense::Scroll, |ctx: &mut EventIdCtx<'_, State::State, CursorData<'_>, Scroll>| { let delta = ctx.data.scroll_delta.y * 50.0; ctx.widget().scroll(delta); @@ -97,27 +97,27 @@ widget_trait! { fn masked(self) -> impl WidgetFn { move |state| Masked { - inner: self.add(state), + inner: self.add_strong(state), } } fn background(self, w: impl WidgetLike) -> impl WidgetFn { move |state| Stack { - children: vec![w.add(state), self.add(state)], + children: vec![w.add_strong(state), self.add_strong(state)], size: StackSize::Child(1), } } fn foreground(self, w: impl WidgetLike) -> impl WidgetFn { move |state| Stack { - children: vec![self.add(state), w.add(state)], + children: vec![self.add_strong(state), w.add_strong(state)], size: StackSize::Child(0), } } fn layer_offset(self, offset: usize) -> impl WidgetFn { move |state| LayerOffset { - inner: self.add(state), + inner: self.add_strong(state), offset, } } @@ -127,7 +127,7 @@ widget_trait! { } fn set_ptr(self, ptr: WidgetRef, state: &mut State) { - let id = self.add(state); + let id = self.add_strong(state); state.get_mut()[ptr].inner = Some(id); } }