diff --git a/TODO b/TODO new file mode 100644 index 0000000..5dcf131 --- /dev/null +++ b/TODO @@ -0,0 +1,6 @@ +images +text +ui ctx = ui + app ctx for sensors +free / delete widgets +abstract sensors to work with any event, maybe associate data as well? +make senses orable so you can select multiple diff --git a/src/core/stack.rs b/src/core/stack.rs index 855c7ea..994d565 100644 --- a/src/core/stack.rs +++ b/src/core/stack.rs @@ -7,7 +7,7 @@ pub struct Stack { impl Widget for Stack { fn draw(&self, painter: &mut crate::Painter) { for child in &self.children { - painter.draw(child); + painter.draw_within(child, painter.region); } } } diff --git a/src/layout/id.rs b/src/layout/id.rs new file mode 100644 index 0000000..65a0cad --- /dev/null +++ b/src/layout/id.rs @@ -0,0 +1,147 @@ +use std::{ + any::TypeId, + marker::PhantomData, + sync::{ + Arc, + atomic::{AtomicU32, Ordering}, + mpsc::Sender, + }, +}; + +use crate::{FnTag, Ui, Widget, WidgetLike, WidgetTag, util::Id}; + +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. +#[repr(C)] +pub struct WidgetId { + pub(super) ty: TypeId, + pub(super) id: Id, + pub(super) refcount: Arc, + pub(super) send: Sender, + _pd: PhantomData, +} + +impl std::fmt::Debug for WidgetId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.id.fmt(f) + } +} + +impl Clone for WidgetId { + fn clone(&self) -> Self { + self.refcount.fetch_add(1, Ordering::Release); + Self { + id: self.id.duplicate(), + ty: self.ty, + refcount: self.refcount.clone(), + send: self.send.clone(), + _pd: PhantomData, + } + } +} + +impl WidgetId { + pub(super) fn new(id: Id, ty: TypeId, send: Sender) -> Self { + Self { + ty, + id, + refcount: AtomicU32::new(0).into(), + send, + _pd: PhantomData, + } + } + + pub fn erase_type(self) -> WidgetId { + self.cast_type() + } + + pub fn as_any(&self) -> &WidgetId { + // safety: self is repr(C) and generic only used for phantom data + unsafe { std::mem::transmute(self) } + } + + pub(super) fn cast_type(self) -> WidgetId { + unsafe { std::mem::transmute(self) } + } + + pub fn refs(&self) -> u32 { + self.refcount.load(Ordering::Acquire) + } +} + +impl Drop for WidgetId { + fn drop(&mut self) { + let refs = self.refcount.fetch_sub(1, Ordering::Release); + if refs == 0 { + let _ = self.send.send(self.id.duplicate()); + } + } +} + +pub struct IdTag; + +// pub trait WidgetIdFn = FnOnce(&mut Ui) -> WidgetId; +macro_rules! WidgetIdFnRet { + ($W:ty, $Ctx:ty) => { + impl FnOnce(&mut $crate::Ui<$Ctx>) -> $crate::WidgetId<$W> + }; + ($W:ty, $Ctx:ty, $($use:tt)*) => { + impl FnOnce(&mut $crate::Ui<$Ctx>) -> $crate::WidgetId<$W> + use<$($use)*> + }; +} +pub(crate) use WidgetIdFnRet; + +pub trait Idable { + type Widget: Widget; + fn set(self, ui: &mut Ui, id: &WidgetId); + fn id<'a>( + self, + id: &WidgetId, + ) -> WidgetIdFnRet!(Self::Widget, Ctx, 'a, Self, Ctx, Tag) + where + Self: Sized, + { + let id = id.clone(); + move |ui| { + self.set(ui, &id); + id + } + } +} + +impl, Ctx> Idable for W { + type Widget = W; + + fn set(self, ui: &mut Ui, id: &WidgetId) { + ui.set(id, self); + } +} + +impl) -> W, W: Widget, Ctx> Idable for F { + type Widget = W; + + fn set(self, ui: &mut Ui, id: &WidgetId) { + let w = self(ui); + ui.set(id, w); + } +} + +impl WidgetLike for WidgetId { + type Widget = W; + fn add(self, _: &mut Ui) -> WidgetId { + self + } +} + +impl) -> WidgetId, Ctx> WidgetLike for F { + type Widget = W; + fn add(self, ui: &mut Ui) -> WidgetId { + self(ui) + } +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 0d1f9da..4e8f600 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -5,6 +5,7 @@ mod sense; mod ui; mod vec2; mod widget; +mod id; pub use color::*; pub use painter::*; @@ -13,5 +14,6 @@ pub use sense::*; pub use ui::*; pub use vec2::*; pub use widget::*; +pub use id::*; pub type UiColor = Color; diff --git a/src/layout/region.rs b/src/layout/region.rs index dd6672c..22e0b89 100644 --- a/src/layout/region.rs +++ b/src/layout/region.rs @@ -12,27 +12,19 @@ pub struct UiPos { } impl UiPos { - pub const fn anchor_offset(anchor_x: f32, anchor_y: f32, offset_x: f32, offset_y: f32) -> Self { - Self { - anchor: vec2(anchor_x, anchor_y), - offset: vec2(offset_x, offset_y), - } - } - pub const fn center() -> Self { - Self::anchor_offset(0.5, 0.5, 0.0, 0.0) + Self::anchor(vec2(0.5, 0.5)) } pub const fn anchor(anchor: Vec2) -> Self { - Self::anchor_offset(anchor.x, anchor.y, 0.0, 0.0) + Self { + anchor, + offset: Vec2::ZERO, + } } - pub const fn top_left() -> Self { - Self::anchor_offset(0.0, 0.0, 0.0, 0.0) - } - - pub const fn bottom_right() -> Self { - Self::anchor_offset(1.0, 1.0, 0.0, 0.0) + pub const fn corner(corner: Corner) -> Self { + Self::anchor(corner.anchor()) } pub const fn shift(&mut self, offset: Vec2) { @@ -126,8 +118,8 @@ pub struct UiRegion { impl UiRegion { pub const fn full() -> Self { Self { - top_left: UiPos::top_left(), - bot_right: UiPos::bottom_right(), + top_left: UiPos::corner(Corner::TopLeft), + bot_right: UiPos::corner(Corner::BotRight), } } pub fn center() -> Self { @@ -178,17 +170,10 @@ impl UiRegion { self } - pub fn top_left() -> Self { + pub fn corner(corner: Corner) -> Self { Self { - top_left: UiPos::top_left(), - bot_right: UiPos::top_left(), - } - } - - pub fn bottom_right() -> Self { - Self { - top_left: UiPos::bottom_right(), - bot_right: UiPos::bottom_right(), + top_left: UiPos::corner(corner), + bot_right: UiPos::corner(corner), } } @@ -236,3 +221,22 @@ impl UIScalarView<'_> { } } } + +#[derive(Clone, Copy, Debug)] +pub enum Corner { + TopLeft, + TopRight, + BotLeft, + BotRight, +} + +impl Corner { + pub const fn anchor(&self) -> Vec2 { + match self { + Corner::TopLeft => vec2(0.0, 0.0), + Corner::TopRight => vec2(1.0, 0.0), + Corner::BotLeft => vec2(0.0, 1.0), + Corner::BotRight => vec2(1.0, 1.0), + } + } +} diff --git a/src/layout/ui.rs b/src/layout/ui.rs index 64f66cb..d345812 100644 --- a/src/layout/ui.rs +++ b/src/layout/ui.rs @@ -6,6 +6,7 @@ use crate::{ use std::{ any::{Any, TypeId}, ops::{Index, IndexMut}, + sync::mpsc::{Receiver, Sender, channel}, }; pub struct Ui { @@ -13,6 +14,8 @@ pub struct Ui { base: Option, widgets: Widgets, updates: Vec, + del_recv: Receiver, + del_send: Sender, pub(super) active_sensors: ActiveSensors, pub(super) sensor_map: SensorMap, primitives: Primitives, @@ -35,9 +38,9 @@ impl Ui { } pub fn push>(&mut self, w: W) -> WidgetId { - let id = self.ids.next(); - self.widgets.insert(id.duplicate(), w); - WidgetId::new(id, TypeId::of::()) + let id = self.id(); + self.widgets.insert(id.id.duplicate(), w); + id } pub fn set>(&mut self, i: &WidgetId, w: W) { @@ -65,7 +68,7 @@ impl Ui { } pub fn id>(&mut self) -> WidgetId { - WidgetId::new(self.ids.next(), TypeId::of::()) + WidgetId::new(self.ids.next(), TypeId::of::(), self.del_send.clone()) } pub fn redraw_all(&mut self, ctx: &mut Ctx) @@ -89,6 +92,10 @@ impl Ui { where Ctx: 'static, { + while let Ok(id) = self.del_recv.try_recv() { + self.widgets.delete(id); + } + if self.full_redraw { self.redraw_all(ctx); self.full_redraw = false; @@ -105,6 +112,10 @@ impl Ui { pub fn needs_redraw(&self) -> bool { self.full_redraw || !self.updates.is_empty() } + + pub fn num_widgets(&self) -> usize { + self.widgets.len() + } } impl, Ctx> Index<&WidgetId> for Ui { @@ -146,6 +157,18 @@ impl Widgets { pub fn insert_any(&mut self, id: Id, widget: Box>) { self.0.insert(id, widget); } + + pub fn delete(&mut self, id: Id) { + self.0.remove(&id); + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } } impl dyn Widget { @@ -160,6 +183,7 @@ impl dyn Widget { impl Default for Ui { fn default() -> Self { + let (del_send, del_recv) = channel(); Self { ids: Default::default(), base: Default::default(), @@ -169,6 +193,8 @@ impl Default for Ui { full_redraw: false, active_sensors: Default::default(), sensor_map: Default::default(), + del_send, + del_recv, } } } diff --git a/src/layout/widget.rs b/src/layout/widget.rs index 6be44d2..26388ab 100644 --- a/src/layout/widget.rs +++ b/src/layout/widget.rs @@ -1,72 +1,12 @@ -use std::{ - any::{Any, TypeId}, - marker::PhantomData, -}; - -use crate::{Painter, Ui, util::Id}; +use crate::{Painter, Ui, WidgetId, WidgetIdFnRet}; +use std::{any::Any, marker::PhantomData}; pub trait Widget: Any { fn draw(&self, painter: &mut Painter); } -pub struct AnyWidget; - -/// An identifier for a widget. -/// Can index a UI to get the associated widget. -/// -/// 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. -#[repr(C)] -#[derive(Eq, Hash, PartialEq)] -pub struct WidgetId { - pub(super) ty: TypeId, - pub(super) id: Id, - _pd: PhantomData, -} - -impl std::fmt::Debug for WidgetId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.id.fmt(f) - } -} - -// TODO: temp -impl Clone for WidgetId { - fn clone(&self) -> Self { - Self::new(self.id.duplicate(), self.ty) - } -} - -impl WidgetId { - pub(super) fn new(id: Id, ty: TypeId) -> Self { - Self { - ty, - id, - _pd: PhantomData, - } - } - - pub fn erase_type(self) -> WidgetId { - self.cast_type() - } - - pub fn as_any(&self) -> &WidgetId { - // safety: self is repr(C) and generic only used for phantom data - unsafe { std::mem::transmute(self) } - } - - fn cast_type(self) -> WidgetId { - WidgetId { - ty: self.ty, - id: self.id, - _pd: PhantomData, - } - } -} - pub struct WidgetTag; pub struct FnTag; -pub struct IdTag; pub trait WidgetLike { type Widget: 'static; @@ -85,63 +25,18 @@ pub trait WidgetLike { } } -// 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, Ctx> = FnOnce(&mut Ui) -> W; -// pub trait WidgetIdFn = FnOnce(&mut Ui) -> WidgetId; -// copium for rust analyzer +/// 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 +/// currently a macro for rust analyzer (doesn't support trait aliases atm) macro_rules! WidgetFnRet { ($W:ty, $Ctx:ty) => { impl FnOnce(&mut $crate::Ui<$Ctx>) -> $W }; } pub(crate) use WidgetFnRet; -macro_rules! WidgetIdFnRet { - ($W:ty, $Ctx:ty) => { - impl FnOnce(&mut $crate::Ui<$Ctx>) -> $crate::WidgetId<$W> - }; - ($W:ty, $Ctx:ty, $($use:tt)*) => { - impl FnOnce(&mut $crate::Ui<$Ctx>) -> $crate::WidgetId<$W> + use<$($use)*> - }; -} -pub(crate) use WidgetIdFnRet; - -pub trait Idable { - type Widget: Widget; - fn set(self, ui: &mut Ui, id: &WidgetId); - fn id<'a>( - self, - id: &WidgetId, - ) -> WidgetIdFnRet!(Self::Widget, Ctx, 'a, Self, Ctx, Tag) - where - Self: Sized, - { - let id = id.clone(); - move |ui| { - self.set(ui, &id); - id - } - } -} - -impl, Ctx> Idable for W { - type Widget = W; - - fn set(self, ui: &mut Ui, id: &WidgetId) { - ui.set(id, self); - } -} - -impl) -> W, W: Widget, Ctx> Idable for F { - type Widget = W; - - fn set(self, ui: &mut Ui, id: &WidgetId) { - let w = self(ui); - ui.set(id, w); - } -} impl, Ctx, F: FnOnce(&mut Ui) -> W> WidgetLike for F { type Widget = W; @@ -151,13 +46,6 @@ impl, Ctx, F: FnOnce(&mut Ui) -> W> WidgetLike f } } -impl) -> WidgetId, Ctx> WidgetLike for F { - type Widget = W; - fn add(self, ui: &mut Ui) -> WidgetId { - self(ui) - } -} - impl, Ctx> WidgetLike for W { type Widget = W; fn add(self, ui: &mut Ui) -> WidgetId { @@ -165,13 +53,6 @@ impl, Ctx> WidgetLike for W { } } -impl WidgetLike for WidgetId { - type Widget = W; - fn add(self, _: &mut Ui) -> WidgetId { - self - } -} - pub struct WidgetArr { pub arr: [WidgetId; LEN], _pd: PhantomData, diff --git a/src/testing/mod.rs b/src/testing/mod.rs index 89e76bf..a451058 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -22,13 +22,13 @@ pub struct Client { } pub struct UiIds { - test: WidgetId, + span_add: WidgetId, } impl Client { pub fn create_ui() -> (Ui, UiIds) { let mut ui = Ui::new(); - let test = ui.id(); + let span_add = ui.id(); let rect = Rect { color: UiColor::WHITE, radius: 20.0, @@ -73,7 +73,7 @@ impl Client { ], ), ); - let span_add_test = ui.add(Span::empty(Dir::RIGHT).id(&test)); + let span_add_test = ui.add(Span::empty(Dir::RIGHT).id(&span_add)); let main: WidgetId = ui.id(); fn switch_button( @@ -100,7 +100,7 @@ impl Client { }) } - let buttons = ui.add( + let tabs = ui.add( ( switch_button(UiColor::RED, &main, &pad_test), switch_button(UiColor::GREEN, &main, &span_test), @@ -108,22 +108,36 @@ impl Client { ) .span(Dir::RIGHT, [1, 1, 1]), ); + let test_button = Rect::new(Color::PURPLE) + .radius(30) + .on(Sense::PressStart, move |ui, _| { + println!("{}", ui.num_widgets()); + }) + .region( + UiRegion::corner(Corner::BotRight) + .size((150, 150)) + .shifted((-75, -75)), + ); + + let s = span_add.clone(); + let del_button = Rect::new(Color::RED) + .radius(30) + .on(Sense::PressStart, move |ui, _| { + ui[&s].children.pop(); + }) + .region( + UiRegion::corner(Corner::BotLeft) + .size((150, 150)) + .shifted((75, -75)), + ); ui.set_base( ( - buttons, - ( - pad_test.pad(10).id(&main), - Rect::new(Color::PURPLE).radius(30).region( - UiRegion::bottom_right() - .size((150, 150)) - .shifted((-75, -75)), - ), - ) - .stack(), + tabs, + (pad_test.pad(10).id(&main), test_button, del_button).stack(), ) .span(Dir::DOWN, [fixed(40), ratio(1)]), ); - (ui, UiIds { test }) + (ui, UiIds { span_add }) } pub fn new(window: Arc, ui: UiIds) -> Self { @@ -151,7 +165,7 @@ impl Client { WindowEvent::KeyboardInput { event, .. } => { if event.state.is_pressed() { let child = ui.add(Rect::new(Color::YELLOW)).erase_type(); - ui[&self.ui.test].children.push((child, fixed(20.0))); + ui[&self.ui.span_add].children.push((child, fixed(20.0))); self.renderer.window().request_redraw(); } }