From 36668c82f4a655a100b6425ac970636e392ccd50 Mon Sep 17 00:00:00 2001 From: shadowcat Date: Thu, 11 Dec 2025 07:16:06 -0500 Subject: [PATCH] strong & weak widgets --- core/src/attr.rs | 10 +-- core/src/event.rs | 19 ++--- core/src/painter.rs | 18 ++--- core/src/ui.rs | 50 ++++++------ core/src/widget/handle.rs | 130 ++++++++++++++++++++++++++++++ core/src/widget/id.rs | 152 ----------------------------------- core/src/widget/like.rs | 23 ++---- core/src/widget/mod.rs | 20 ++--- core/src/widget/tag.rs | 9 ++- core/src/widget/widgets.rs | 4 +- src/bin/test/main.rs | 73 ++++++++++------- src/default/attr.rs | 36 ++++----- src/default/mod.rs | 7 +- src/event.rs | 12 +-- src/lib.rs | 1 + src/widget/position/span.rs | 2 +- src/widget/position/stack.rs | 2 +- src/widget/ptr.rs | 17 ++++ src/widget/trait_fns.rs | 2 +- 19 files changed, 293 insertions(+), 294 deletions(-) create mode 100644 core/src/widget/handle.rs delete mode 100644 core/src/widget/id.rs diff --git a/core/src/attr.rs b/core/src/attr.rs index 03063fd..4090896 100644 --- a/core/src/attr.rs +++ b/core/src/attr.rs @@ -1,11 +1,11 @@ -use crate::{Ui, WidgetHandle, WidgetIdFn, WidgetLike}; +use crate::{Ui, WidgetIdFn, WidgetLike, WidgetView}; -pub trait WidgetAttr { +pub trait WidgetAttr { type Input; - fn run(ui: &mut Ui, id: &WidgetHandle, input: Self::Input); + fn run(ui: &mut Ui, id: WidgetView, input: Self::Input); } -pub trait Attrable { +pub trait Attrable { fn attr>(self, input: A::Input) -> impl WidgetIdFn; } @@ -13,7 +13,7 @@ impl, Tag> Attrable for WL { fn attr>(self, input: A::Input) -> impl WidgetIdFn { |ui| { let id = self.add(ui); - A::run(ui, &id, input); + A::run(ui, id.weak(), input); id } } diff --git a/core/src/event.rs b/core/src/event.rs index 2b3cf3f..95f7db5 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -1,9 +1,8 @@ -use std::{hash::Hash, rc::Rc}; - use crate::{ - Ui, UiModule, WidgetHandle, WidgetView, + Ui, UiModule, WidgetView, util::{HashMap, Id}, }; +use std::{hash::Hash, rc::Rc}; pub trait Event: Sized { type Module: EventModule; @@ -16,9 +15,8 @@ pub struct EventCtx<'a, Ctx, Data> { pub data: Data, } -pub type ECtx<'a, Ctx, Data, W> = EventIdCtx<'a, Ctx, Data, W>; -pub struct EventIdCtx<'a, Ctx, Data, W> { - pub id: &'a WidgetView, +pub struct EventIdCtx<'a, Ctx, Data, W: ?Sized> { + pub id: WidgetView, pub ui: &'a mut Ui, pub state: &'a mut Ctx, pub data: Data, @@ -27,8 +25,11 @@ pub struct EventIdCtx<'a, Ctx, Data, W> { pub trait EventFn: Fn(EventCtx) + 'static {} impl) + 'static, Ctx, Data> EventFn for F {} -pub trait WidgetEventFn: Fn(EventIdCtx) + 'static {} -impl) + 'static, Ctx, Data, W> WidgetEventFn for F {} +pub trait WidgetEventFn: Fn(EventIdCtx) + 'static {} +impl) + 'static, Ctx, Data, W: ?Sized> WidgetEventFn + for F +{ +} pub trait DefaultEvent: Hash + Eq + 'static { type Data: Clone = (); @@ -129,7 +130,7 @@ impl Ui { pub fn run_event( &mut self, ctx: &mut Ctx, - id: &WidgetHandle, + id: WidgetView, event: E, data: E::Data, ) { diff --git a/core/src/painter.rs b/core/src/painter.rs index 2396acc..5a7709c 100644 --- a/core/src/painter.rs +++ b/core/src/painter.rs @@ -387,17 +387,17 @@ impl<'a, 'c> Painter<'a, 'c> { } /// Draws a widget within this widget's region. - pub fn widget(&mut self, id: &WidgetHandle) { + pub fn widget(&mut self, id: &WidgetHandle) { self.widget_at(id, self.region); } /// Draws a widget somewhere within this one. /// Useful for drawing child widgets in select areas. - pub fn widget_within(&mut self, id: &WidgetHandle, region: UiRegion) { + pub fn widget_within(&mut self, id: &WidgetHandle, region: UiRegion) { self.widget_at(id, region.within(&self.region)); } - fn widget_at(&mut self, id: &WidgetHandle, region: UiRegion) { + fn widget_at(&mut self, id: &WidgetHandle, region: UiRegion) { self.children.push(id.id()); self.ctx .draw_inner(self.layer, id.id(), region, Some(self.id), self.mask, None); @@ -427,11 +427,11 @@ impl<'a, 'c> Painter<'a, 'c> { self.region } - pub fn size(&mut self, id: &WidgetHandle) -> Size { + pub fn size(&mut self, id: &WidgetHandle) -> Size { self.size_ctx().size(id) } - pub fn len_axis(&mut self, id: &WidgetHandle, axis: Axis) -> Len { + pub fn len_axis(&mut self, id: &WidgetHandle, axis: Axis) -> Len { match axis { Axis::X => self.size_ctx().width(id), Axis::Y => self.size_ctx().height(id), @@ -550,22 +550,22 @@ impl SizeCtx<'_> { len } - pub fn width(&mut self, id: &WidgetHandle) -> Len { + pub fn width(&mut self, id: &WidgetHandle) -> Len { self.width_inner(id.id()) } - pub fn height(&mut self, id: &WidgetHandle) -> Len { + pub fn height(&mut self, id: &WidgetHandle) -> Len { self.height_inner(id.id()) } - pub fn len_axis(&mut self, id: &WidgetHandle, axis: Axis) -> Len { + pub fn len_axis(&mut self, id: &WidgetHandle, axis: Axis) -> Len { match axis { Axis::X => self.width(id), Axis::Y => self.height(id), } } - pub fn size(&mut self, id: &WidgetHandle) -> Size { + pub fn size(&mut self, id: &WidgetHandle) -> Size { Size { x: self.width(id), y: self.height(id), diff --git a/core/src/ui.rs b/core/src/ui.rs index dd30819..f9e1db7 100644 --- a/core/src/ui.rs +++ b/core/src/ui.rs @@ -5,7 +5,7 @@ use crate::{ }; use image::DynamicImage; use std::{ - any::{Any, TypeId}, + any::Any, ops::{Index, IndexMut}, sync::mpsc::{Receiver, Sender, channel}, }; @@ -26,26 +26,22 @@ impl Ui { } /// useful for debugging - pub fn set_label(&mut self, id: &WidgetHandle, label: String) { + pub fn set_label(&mut self, id: &WidgetHandle, label: String) { self.data.widgets.data_mut(&id.id()).unwrap().label = label; } - pub fn label(&self, id: &WidgetHandle) -> &String { + pub fn label(&self, id: &WidgetHandle) -> &String { &self.data.widgets.data(&id.id()).unwrap().label } pub fn add_widget(&mut self, w: W) -> WidgetHandle { - self.push(w) - } - - pub fn push(&mut self, w: W) -> WidgetHandle { let id = self.new_id(); self.data.widgets.insert(id.id(), w); id } pub fn set_root(&mut self, w: impl WidgetLike) { - self.root = Some(w.add(self).any()); + self.root = Some(w.add(self)); self.full_redraw = true; } @@ -53,29 +49,31 @@ impl Ui { Self::default() } - pub fn get(&self, id: &I) -> Option<&I::Widget> { + pub fn get(&self, id: &I) -> Option<&I::Widget> + where + I::Widget: Sized, + { self.data.widgets.get(id) } - pub fn get_mut(&mut self, id: &I) -> Option<&mut I::Widget> { + pub fn get_mut(&mut self, id: &I) -> Option<&mut I::Widget> + where + I::Widget: Sized, + { self.data.widgets.get_mut(id) } fn new_id(&mut self) -> WidgetHandle { - WidgetHandle::new( - self.data.widgets.reserve(), - TypeId::of::(), - self.send.clone(), - ) + WidgetHandle::new(self.data.widgets.reserve(), self.send.clone()) } pub fn add_texture(&mut self, image: DynamicImage) -> TextureHandle { self.data.textures.add(image) } - pub fn register_event( + pub fn register_event( &mut self, - id: &WidgetHandle, + id: &I, event: E, f: impl EventFn, ) { @@ -171,17 +169,23 @@ impl Ui { } } -impl Index<&I> for Ui { +impl Index for Ui +where + I::Widget: Sized, +{ type Output = I::Widget; - fn index(&self, id: &I) -> &Self::Output { - self.get(id).unwrap() + fn index(&self, id: I) -> &Self::Output { + self.get(&id).unwrap() } } -impl IndexMut<&I> for Ui { - fn index_mut(&mut self, id: &I) -> &mut Self::Output { - self.get_mut(id).unwrap() +impl IndexMut for Ui +where + I::Widget: Sized, +{ + fn index_mut(&mut self, id: I) -> &mut Self::Output { + self.get_mut(&id).unwrap() } } diff --git a/core/src/widget/handle.rs b/core/src/widget/handle.rs new file mode 100644 index 0000000..96cb936 --- /dev/null +++ b/core/src/widget/handle.rs @@ -0,0 +1,130 @@ +use std::{marker::Unsize, mem::MaybeUninit, ops::CoerceUnsized, sync::mpsc::Sender}; + +use crate::{ + Ui, Widget, + util::{Id, RefCounter}, +}; + +/// An identifier for a widget that can index a UI to get the associated widget. +/// It should always remain valid; it keeps a ref count and removes the widget from the UI if all +/// references are dropped. +/// +/// W does not need to implement widget so that AnyWidget is valid; +/// Instead, add generic bounds on methods that take an ID if they need specific data. +/// +/// TODO: ergonomic clones when they get put in rust-analyzer & don't cause ICEs? +pub struct WidgetHandle { + pub(super) id: Id, + counter: RefCounter, + send: Sender, + ty: *const W, +} + +pub struct WidgetView { + pub(super) id: Id, + #[allow(unused)] + ty: *const W, +} + +impl> WidgetHandle { + pub fn any(self) -> WidgetHandle { + self + } +} + +impl WidgetHandle { + pub(crate) fn new(id: Id, send: Sender) -> Self { + Self { + id, + counter: RefCounter::new(), + send, + ty: unsafe { MaybeUninit::zeroed().assume_init() }, + } + } + + pub fn as_any(&self) -> &WidgetHandle { + // SAFETY: self is repr(C) and generic only used for phantom data + unsafe { std::mem::transmute(self) } + } + + pub fn id(&self) -> Id { + self.id + } + + pub fn refs(&self) -> u32 { + self.counter.refs() + } + + pub fn weak(&self) -> WidgetView { + let Self { ty, id, .. } = *self; + WidgetView { ty, id } + } + + pub fn handles(self) -> (Self, WidgetView) { + let weak = self.weak(); + (self, weak) + } +} + +impl WidgetView { + pub fn id(&self) -> Id { + self.id + } +} + +impl Drop for WidgetHandle { + fn drop(&mut self) { + if self.counter.drop() { + let _ = self.send.send(self.id); + } + } +} + +pub trait WidgetIdFn: FnOnce(&mut Ui) -> WidgetHandle {} +impl WidgetHandle> WidgetIdFn for F {} + +pub trait IdLike { + type Widget: Widget + ?Sized + 'static; + fn id(&self) -> Id; +} + +impl IdLike for WidgetHandle { + type Widget = W; + fn id(&self) -> Id { + self.id + } +} + +impl IdLike for WidgetView { + type Widget = W; + fn id(&self) -> Id { + self.id + } +} + +impl, U: ?Sized> CoerceUnsized> for WidgetHandle {} +impl, U: ?Sized> CoerceUnsized> for WidgetView {} + +impl Clone for WidgetView { + fn clone(&self) -> Self { + *self + } +} +impl Copy for WidgetView {} +impl PartialEq for WidgetView { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl PartialEq for WidgetHandle { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl std::fmt::Debug for WidgetHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.id.fmt(f) + } +} diff --git a/core/src/widget/id.rs b/core/src/widget/id.rs deleted file mode 100644 index ca1ae7b..0000000 --- a/core/src/widget/id.rs +++ /dev/null @@ -1,152 +0,0 @@ -use std::{any::TypeId, marker::PhantomData, sync::mpsc::Sender}; - -use crate::{ - Ui, Widget, - util::{Id, RefCounter}, -}; - -pub struct AnyWidget; - -/// An identifier for a widget that can index a UI to get the associated widget. -/// It should always remain valid; it keeps a ref count and removes the widget from the UI if all -/// references are dropped. -/// -/// W does not need to implement widget so that AnyWidget is valid; -/// Instead, add generic bounds on methods that take an ID if they need specific data. -/// -/// TODO: ergonomic clones when they get put in rust-analyzer & don't cause ICEs? -#[repr(C)] -pub struct WidgetHandle { - pub(super) ty: TypeId, - pub(super) id: Id, - counter: RefCounter, - send: Sender, - _pd: PhantomData, -} - -#[repr(C)] -pub struct WidgetView { - pub(super) ty: TypeId, - pub(super) id: Id, - counter: RefCounter, - send: Sender, - _pd: PhantomData, -} - -impl PartialEq for WidgetHandle { - fn eq(&self, other: &Self) -> bool { - self.ty == other.ty && self.id == other.id - } -} - -impl std::fmt::Debug for WidgetHandle { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.id.fmt(f) - } -} - -impl Clone for WidgetHandle { - fn clone(&self) -> Self { - Self { - id: self.id, - ty: self.ty, - counter: self.counter.clone(), - send: self.send.clone(), - _pd: PhantomData, - } - } -} - -impl WidgetHandle { - pub(crate) fn new(id: Id, ty: TypeId, send: Sender) -> Self { - Self { - ty, - id, - counter: RefCounter::new(), - send, - _pd: PhantomData, - } - } - - pub fn any(self) -> WidgetHandle { - self.cast_type() - } - - pub fn as_any(&self) -> &WidgetHandle { - // SAFETY: self is repr(C) and generic only used for phantom data - unsafe { std::mem::transmute(self) } - } - - pub fn id(&self) -> Id { - self.id - } - - pub(super) fn cast_type(self) -> WidgetHandle { - // SAFETY: self is repr(C) and generic only used for phantom data - unsafe { std::mem::transmute(self) } - } - - pub fn refs(&self) -> u32 { - self.counter.refs() - } - - pub fn weak(&self) -> WidgetView { - let Self { - ty, - id, - ref counter, - ref send, - _pd, - } = *self; - WidgetView { - ty, - id, - counter: counter.quiet_clone(), - send: send.clone(), - _pd, - } - } -} - -impl Drop for WidgetHandle { - fn drop(&mut self) { - if self.counter.drop() { - let _ = self.send.send(self.id); - } - } -} - -pub trait WidgetIdFn: FnOnce(&mut Ui) -> WidgetHandle {} -impl WidgetHandle> WidgetIdFn for F {} - -pub trait WidgetRet: FnOnce(&mut Ui) -> WidgetHandle {} -impl WidgetHandle> WidgetRet for F {} - -pub trait WidgetIdLike { - fn id(self, send: &Sender) -> WidgetHandle; -} - -impl WidgetIdLike for &WidgetHandle { - fn id(self, _: &Sender) -> WidgetHandle { - self.clone() - } -} - -pub trait IdLike { - type Widget: Widget + 'static; - fn id(&self) -> Id; -} - -impl IdLike for WidgetHandle { - type Widget = W; - fn id(&self) -> Id { - self.id - } -} - -impl IdLike for WidgetView { - type Widget = W; - fn id(&self) -> Id { - self.id - } -} diff --git a/core/src/widget/like.rs b/core/src/widget/like.rs index d37070c..1bfa5bc 100644 --- a/core/src/widget/like.rs +++ b/core/src/widget/like.rs @@ -1,7 +1,8 @@ use super::*; +use std::marker::Unsize; pub trait WidgetLike { - type Widget: 'static; + type Widget: Widget + ?Sized + Unsize + 'static; fn add(self, ui: &mut Ui) -> WidgetHandle; @@ -27,24 +28,15 @@ pub trait WidgetLike { } pub trait WidgetArrLike { - type Ws; - fn ui(self, ui: &mut Ui) -> WidgetArr; + fn ui(self, ui: &mut Ui) -> WidgetArr; } -impl WidgetArrLike for WidgetArr { - type Ws = Ws; - fn ui(self, _: &mut Ui) -> WidgetArr { +impl WidgetArrLike for WidgetArr { + fn ui(self, _: &mut Ui) -> WidgetArr { self } } -impl> WidgetArrLike<1, WidgetTag> for W { - type Ws = (W::Widget,); - fn ui(self, ui: &mut Ui) -> WidgetArr<1, (W::Widget,)> { - WidgetArr::new([self.add(ui).any()]) - } -} - // I hate this language it's so bad why do I even use it macro_rules! impl_widget_arr { ($n:expr;$($W:ident)*) => { @@ -52,12 +44,11 @@ macro_rules! impl_widget_arr { }; ($n:expr;$($W:ident)*;$($Tag:ident)*) => { impl<$($W: WidgetLike<$Tag>,$Tag,)*> WidgetArrLike<$n, ($($Tag,)*)> for ($($W,)*) { - type Ws = ($($W::Widget,)*); - fn ui(self, ui: &mut Ui) -> WidgetArr<$n, ($($W::Widget,)*)> { + fn ui(self, ui: &mut Ui) -> WidgetArr<$n> { #[allow(non_snake_case)] let ($($W,)*) = self; WidgetArr::new( - [$($W.add(ui).cast_type(),)*], + [$($W.add(ui),)*], ) } } diff --git a/core/src/widget/mod.rs b/core/src/widget/mod.rs index 3dbdf41..0fcb03b 100644 --- a/core/src/widget/mod.rs +++ b/core/src/widget/mod.rs @@ -1,12 +1,12 @@ use crate::{Len, Painter, SizeCtx, Ui}; -use std::{any::Any, marker::PhantomData}; +use std::any::Any; -mod id; +mod handle; mod like; mod tag; mod widgets; -pub use id::*; +pub use handle::*; pub use like::*; pub use tag::*; pub use widgets::*; @@ -30,20 +30,16 @@ impl Widget for () { /// A function that returns a widget given a UI. /// Useful for defining trait functions on widgets that create a parent widget so that the children /// don't need to be IDs yet -pub trait WidgetFn: FnOnce(&mut Ui) -> W {} -impl W> WidgetFn for F {} +pub trait WidgetFn: FnOnce(&mut Ui) -> W {} +impl W> WidgetFn for F {} -pub struct WidgetArr { +pub struct WidgetArr { pub arr: [WidgetHandle; LEN], - _pd: PhantomData, } -impl WidgetArr { +impl WidgetArr { pub fn new(arr: [WidgetHandle; LEN]) -> Self { - Self { - arr, - _pd: PhantomData, - } + Self { arr } } } diff --git a/core/src/widget/tag.rs b/core/src/widget/tag.rs index a15a2cb..afe33c3 100644 --- a/core/src/widget/tag.rs +++ b/core/src/widget/tag.rs @@ -1,3 +1,5 @@ +use std::marker::Unsize; + use super::*; pub struct ArrTag; @@ -19,7 +21,7 @@ impl W> WidgetLike for F { } pub struct IdTag; -impl WidgetLike for WidgetHandle { +impl + 'static> WidgetLike for WidgetHandle { type Widget = W; fn add(self, _: &mut Ui) -> WidgetHandle { self @@ -27,10 +29,11 @@ impl WidgetLike for WidgetHandle { } pub struct IdFnTag; -impl WidgetHandle> WidgetLike for F { +impl + 'static, F: FnOnce(&mut Ui) -> WidgetHandle> + WidgetLike for F +{ type Widget = W; fn add(self, ui: &mut Ui) -> WidgetHandle { self(ui) } } - diff --git a/core/src/widget/widgets.rs b/core/src/widget/widgets.rs index acb195f..f8fae2c 100644 --- a/core/src/widget/widgets.rs +++ b/core/src/widget/widgets.rs @@ -46,11 +46,11 @@ impl Widgets { WidgetWrapper::new(data.widget.as_mut(), &mut data.borrowed) } - pub fn get(&self, id: &I) -> Option<&I::Widget> { + pub fn get(&self, id: &I) -> Option<&I::Widget> where I::Widget: Sized { self.get_dyn(id.id())?.as_any().downcast_ref() } - pub fn get_mut(&mut self, id: &I) -> Option<&mut I::Widget> { + pub fn get_mut(&mut self, id: &I) -> Option<&mut I::Widget> where I::Widget: Sized { self.get_dyn_mut(id.id())?.as_any_mut().downcast_mut() } diff --git a/src/bin/test/main.rs b/src/bin/test/main.rs index f4265c0..ada9dc8 100644 --- a/src/bin/test/main.rs +++ b/src/bin/test/main.rs @@ -1,3 +1,5 @@ +use std::{cell::RefCell, rc::Rc}; + use cosmic_text::Family; use iris::prelude::*; use len_fns::*; @@ -8,7 +10,7 @@ fn main() { } pub struct Client { - info: WidgetHandle, + info: WidgetView, } event_ctx!(Client); @@ -37,7 +39,8 @@ impl DefaultAppState for Client { .width(rest(3)), ) .span(Dir::RIGHT) - .add(ui); + .add(ui) + .handles(); let span_test = ( rrect.color(Color::GREEN).width(100), @@ -50,8 +53,7 @@ impl DefaultAppState for Client { .span(Dir::LEFT) .add(ui); - let span_add = Span::empty(Dir::RIGHT).add(ui); - let span_add_ = span_add.clone(); + let span_add = Span::empty(Dir::RIGHT).add(ui).handles(); let add_button = rect(Color::LIME) .radius(30) @@ -60,21 +62,20 @@ impl DefaultAppState for Client { .ui .add(image(include_bytes!("assets/sungals.png")).center()) .any(); - ctx.ui[&span_add_].children.push(child); + ctx.ui[span_add.1].children.push(child); }) .sized((150, 150)) .align(Align::BOT_RIGHT); - let span_add_ = span_add.clone(); let del_button = rect(Color::RED) .radius(30) .on(CursorSense::click(), move |ctx| { - ctx.ui[&span_add_].children.pop(); + ctx.ui[span_add.1].children.pop(); }) .sized((150, 150)) .align(Align::BOT_LEFT); - let span_add_test = (span_add, add_button, del_button).stack().add(ui); + let span_add_test = (span_add.0, add_button, del_button).stack().add(ui); let btext = |content| wtext(content).size(30); @@ -99,8 +100,8 @@ impl DefaultAppState for Client { .span(Dir::DOWN) .add(ui); - let texts = Span::empty(Dir::DOWN).gap(10).add(ui); - let msg_area = texts.clone().scroll().masked().background(rect(Color::SKY)); + let texts = Span::empty(Dir::DOWN).gap(10).add(ui).handles(); + let msg_area = texts.0.scroll().masked().background(rect(Color::SKY)); let add_text = wtext("add") .editable(false) .text_align(Align::LEFT) @@ -115,18 +116,19 @@ impl DefaultAppState for Client { .wrap(true) .attr::(()); let msg_box = text.background(rect(Color::WHITE.darker(0.5))).add(ctx.ui); - ctx.ui[&texts].children.push(msg_box.any()); + ctx.ui[texts.1].children.push(msg_box.any()); }) - .add(ui); + .add(ui) + .handles(); let text_edit_scroll = ( msg_area.height(rest(1)), ( Rect::new(Color::WHITE.darker(0.9)), ( - add_text.clone().width(rest(1)), + add_text.0.width(rest(1)), Rect::new(Color::GREEN) .on(CursorSense::click(), move |ctx| { - ctx.ui.run_event(ctx.state, &add_text, Submit, ()); + ctx.ui.run_event(ctx.state, add_text.1, Submit, ()); }) .sized((40, 40)), ) @@ -141,13 +143,26 @@ impl DefaultAppState for Client { .span(Dir::DOWN) .add(ui); - let main = pad_test.clone().pad(10).add(ui); + let main = WidgetPtr::new().add(ui).handles(); - let switch_button = |color, to: WidgetHandle, label| { - let main_ = main.clone(); + let vals = Rc::new(RefCell::new((0, Vec::new()))); + let mut switch_button = |color, to: WidgetHandle, label| { + let vec = &mut vals.borrow_mut().1; + let i = vec.len(); + if vec.is_empty() { + vec.push(None); + ui[main.1].set(to); + } else { + vec.push(Some(to)); + } + let vals = vals.clone(); let rect = rect(color) .on(CursorSense::click(), move |ctx| { - ctx.ui[&main_.clone()].inner = to.clone(); + let (prev, vec) = &mut *vals.borrow_mut(); + if let Some(h) = vec[i].take() { + vec[*prev] = ctx.ui[main.1].replace(h); + *prev = i; + } ctx.ui[ctx.id].color = color.darker(0.3); }) .on( @@ -163,26 +178,26 @@ impl DefaultAppState for Client { }; let tabs = ( - switch_button(Color::RED, pad_test.any(), "pad"), - switch_button(Color::GREEN, span_test.any(), "span"), - switch_button(Color::BLUE, span_add_test.any(), "image span"), - switch_button(Color::MAGENTA, text_test.any(), "text layout"), + switch_button(Color::RED, pad_test.0, "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"), switch_button( Color::YELLOW.mul_rgb(0.5), - text_edit_scroll.any(), + text_edit_scroll, "text edit scroll", ), ) .span(Dir::RIGHT); - let info = wtext("").add(ui); - let info_sect = info.clone().pad(10).align(Align::RIGHT); + let info = wtext("").add(ui).handles(); + let info_sect = info.0.pad(10).align(Align::RIGHT); - ((tabs.height(40), main).span(Dir::DOWN), info_sect) + ((tabs.height(40), main.0.pad(10)).span(Dir::DOWN), info_sect) .stack() .set_root(ui); - Self { info } + Self { info: info.1 } } fn window_event(&mut self, _: WindowEvent, ui: &mut Ui, state: &UiState) { @@ -192,8 +207,8 @@ impl DefaultAppState for Client { ui.active_widgets(), state.renderer.ui.view_count() ); - if new != *ui[&self.info].content { - *ui[&self.info].content = new; + if new != *ui[self.info].content { + *ui[self.info].content = new; } if ui.needs_redraw() { state.window.request_redraw(); diff --git a/src/default/attr.rs b/src/default/attr.rs index a827dd0..acae2c7 100644 --- a/src/default/attr.rs +++ b/src/default/attr.rs @@ -5,23 +5,18 @@ use winit::dpi::{LogicalPosition, LogicalSize}; pub struct Selector; impl WidgetAttr for Selector { - type Input = WidgetHandle; + type Input = WidgetView; - fn run(ui: &mut Ui, container: &WidgetHandle, id: Self::Input) { - let container = container.clone(); - ui.register_event( - &container.clone(), - CursorSense::click_or_drag(), - move |mut ctx| { - let ui = ctx.ui; - let region = ui.window_region(&id).unwrap(); - let id_pos = region.top_left; - let container_pos = ui.window_region(&container).unwrap().top_left; - ctx.data.cursor += container_pos - id_pos; - ctx.data.size = region.size(); - select(ui, id.clone(), ctx.state, ctx.data); - }, - ); + fn run(ui: &mut Ui, container: WidgetView, id: Self::Input) { + ui.register_event(&container, CursorSense::click_or_drag(), move |mut ctx| { + let ui = ctx.ui; + let region = ui.window_region(&id).unwrap(); + let id_pos = region.top_left; + let container_pos = ui.window_region(&container).unwrap().top_left; + ctx.data.cursor += container_pos - id_pos; + ctx.data.size = region.size(); + select(ui, id, ctx.state, ctx.data); + }); } } @@ -30,15 +25,14 @@ pub struct Selectable; impl WidgetAttr for Selectable { type Input = (); - fn run(ui: &mut Ui, id: &WidgetHandle, _: Self::Input) { - let id = id.clone(); - ui.register_event(&id.clone(), CursorSense::click_or_drag(), move |ctx| { - select(ctx.ui, id.clone(), ctx.state, ctx.data); + fn run(ui: &mut Ui, id: WidgetView, _: Self::Input) { + ui.register_event(&id, CursorSense::click_or_drag(), move |ctx| { + select(ctx.ui, id, ctx.state, ctx.data); }); } } -fn select(ui: &mut Ui, id: WidgetHandle, state: &mut UiState, data: CursorData) { +fn select(ui: &mut Ui, id: WidgetView, state: &mut UiState, data: CursorData) { let now = Instant::now(); let recent = (now - state.last_click) < Duration::from_millis(300); state.last_click = now; diff --git a/src/default/mod.rs b/src/default/mod.rs index c1e037f..6ebce72 100644 --- a/src/default/mod.rs +++ b/src/default/mod.rs @@ -30,7 +30,7 @@ pub struct DefaultState { pub struct UiState { pub renderer: UiRenderer, pub input: Input, - pub focus: Option>, + pub focus: Option>, pub clipboard: Clipboard, pub window: Arc, pub ime: usize, @@ -91,7 +91,7 @@ impl AppState for DefaultState { let input_changed = ui_state.input.event(&event); let cursor_state = ui_state.cursor_state().clone(); - let old = ui_state.focus.clone(); + let old = ui_state.focus; if cursor_state.buttons.left.is_start() { ui_state.focus = None; } @@ -121,10 +121,9 @@ impl AppState for DefaultState { ui_state.renderer.resize(size) } WindowEvent::KeyboardInput { event, .. } => { - if let Some(sel) = &ui_state.focus + if let Some(sel) = ui_state.focus && event.state.is_pressed() { - let sel = &sel.clone(); let mut text = sel.edit(ui); match text.apply_event(event, &ui_state.input.modifiers) { TextInputResult::Unfocus => { diff --git a/src/event.rs b/src/event.rs index 9e74977..eacc093 100644 --- a/src/event.rs +++ b/src/event.rs @@ -9,20 +9,20 @@ macro_rules! event_ctx { #[allow(unused_imports)] use $crate::prelude::*; - pub trait EventableCtx { + pub trait EventableCtx, Tag, Ctx: 'static> { fn on( self, event: E, - f: impl WidgetEventFn, - ) -> impl WidgetIdFn + EventableCtx; + f: impl WidgetEventFn, + ) -> impl WidgetIdFn; } - impl, Tag> EventableCtx for WL { + impl, Tag> EventableCtx for WL { fn on( self, event: E, f: impl WidgetEventFn<$ty, E::Data, WL::Widget>, - ) -> impl WidgetIdFn + EventableCtx { + ) -> impl WidgetIdFn { eventable::Eventable::on(self, event, f) } } @@ -47,7 +47,7 @@ pub mod eventable { let id_ = id.weak(); ui.register_event(&id, event, move |ctx| { f(EventIdCtx { - id: &id_, + id: id_, state: ctx.state, data: ctx.data, ui: ctx.ui, diff --git a/src/lib.rs b/src/lib.rs index 2cc3d32..e563464 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ #![feature(fn_traits)] #![feature(gen_blocks)] #![feature(associated_type_defaults)] +#![feature(unsize)] mod default; mod event; diff --git a/src/widget/position/span.rs b/src/widget/position/span.rs index 019ab1b..007455c 100644 --- a/src/widget/position/span.rs +++ b/src/widget/position/span.rs @@ -158,7 +158,7 @@ impl, Tag> FnOnce<(&mut Ui,)> extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output { Span { - children: self.children.ui(args.0).arr.to_vec(), + children: self.children.ui(args.0).arr.into_iter().collect(), dir: self.dir, gap: self.gap, } diff --git a/src/widget/position/stack.rs b/src/widget/position/stack.rs index a673eb9..f25b874 100644 --- a/src/widget/position/stack.rs +++ b/src/widget/position/stack.rs @@ -55,7 +55,7 @@ impl, Tag> FnOnce<(&mut Ui,)> extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output { Stack { - children: self.children.ui(args.0).arr.to_vec(), + children: self.children.ui(args.0).arr.into_iter().collect(), size: self.size, } } diff --git a/src/widget/ptr.rs b/src/widget/ptr.rs index 9d0f3f6..6856103 100644 --- a/src/widget/ptr.rs +++ b/src/widget/ptr.rs @@ -1,4 +1,5 @@ use crate::prelude::*; +use std::marker::{Sized, Unsize}; #[derive(Default)] pub struct WidgetPtr { @@ -28,3 +29,19 @@ impl Widget for WidgetPtr { } } } + +impl WidgetPtr { + pub fn new() -> Self { + Self::default() + } + pub fn set>(&mut self, to: WidgetHandle) { + self.inner = Some(to) + } + + pub fn replace>( + &mut self, + to: WidgetHandle, + ) -> Option { + self.inner.replace(to) + } +} diff --git a/src/widget/trait_fns.rs b/src/widget/trait_fns.rs index 684d8f1..5c6a061 100644 --- a/src/widget/trait_fns.rs +++ b/src/widget/trait_fns.rs @@ -123,7 +123,7 @@ widget_trait! { } } - fn to_any(self) -> impl WidgetRet { + fn to_any(self) -> impl WidgetIdFn { |ui| self.add(ui).any() } }