From 58d9b32077d4b9ebf098cfbf7a14ee033dc26979 Mon Sep 17 00:00:00 2001 From: shadow cat Date: Sun, 10 Aug 2025 02:05:35 -0400 Subject: [PATCH] reactivity base --- Cargo.lock | 195 +--------------------------- Cargo.toml | 2 - data/test.rhai | 15 --- src/{layout/node.rs => base/mod.rs} | 43 +++--- src/layout/mod.rs | 68 +--------- src/layout/ui.rs | 101 ++++++++++++++ src/layout/widget.rs | 157 ++++++++++++++++++++++ src/lib.rs | 6 + src/render/primitive/mod.rs | 51 +++++--- src/testing/mod.rs | 38 +++--- src/util/id.rs | 43 ++++++ src/util/mod.rs | 3 + 12 files changed, 394 insertions(+), 328 deletions(-) delete mode 100644 data/test.rhai rename src/{layout/node.rs => base/mod.rs} (73%) create mode 100644 src/layout/ui.rs create mode 100644 src/layout/widget.rs create mode 100644 src/util/id.rs create mode 100644 src/util/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 33618ca..858fea3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,8 +25,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "const-random", - "getrandom 0.3.3", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -269,26 +268,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom 0.2.16", - "once_cell", - "tiny-keccak", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -453,15 +432,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - [[package]] name = "gethostname" version = "0.4.3" @@ -472,17 +442,6 @@ dependencies = [ "windows-targets 0.48.5", ] -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.3.3" @@ -492,7 +451,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi", ] [[package]] @@ -583,9 +542,7 @@ name = "gui" version = "0.1.0" dependencies = [ "bytemuck", - "notify", "pollster", - "rhai", "wgpu", "winit", ] @@ -632,35 +589,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "inotify" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" -dependencies = [ - "bitflags 2.9.1", - "inotify-sys", - "libc", -] - -[[package]] -name = "inotify-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" -dependencies = [ - "libc", -] - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "jni" version = "0.21.1" @@ -689,7 +617,7 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ - "getrandom 0.3.3", + "getrandom", "libc", ] @@ -720,26 +648,6 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" -[[package]] -name = "kqueue" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" -dependencies = [ - "kqueue-sys", - "libc", -] - -[[package]] -name = "kqueue-sys" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" -dependencies = [ - "bitflags 1.3.2", - "libc", -] - [[package]] name = "libc" version = "0.2.174" @@ -846,18 +754,6 @@ dependencies = [ "paste", ] -[[package]] -name = "mio" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "log", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", -] - [[package]] name = "naga" version = "26.0.0" @@ -914,30 +810,6 @@ dependencies = [ "jni-sys", ] -[[package]] -name = "notify" -version = "8.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" -dependencies = [ - "bitflags 2.9.1", - "fsevent-sys", - "inotify", - "kqueue", - "libc", - "log", - "mio", - "notify-types", - "walkdir", - "windows-sys 0.60.2", -] - -[[package]] -name = "notify-types" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" - [[package]] name = "num-traits" version = "0.2.19" @@ -1187,9 +1059,6 @@ name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -dependencies = [ - "portable-atomic", -] [[package]] name = "orbclient" @@ -1410,32 +1279,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" -[[package]] -name = "rhai" -version = "1.22.2" -source = "git+https://github.com/rhaiscript/rhai#9508db6a828d8d8b2ac868054d985098c62ba904" -dependencies = [ - "ahash", - "bitflags 2.9.1", - "instant", - "num-traits", - "once_cell", - "rhai_codegen", - "smallvec", - "smartstring", - "thin-vec", -] - -[[package]] -name = "rhai_codegen" -version = "3.1.0" -source = "git+https://github.com/rhaiscript/rhai#9508db6a828d8d8b2ac868054d985098c62ba904" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "rustc-hash" version = "1.1.0" @@ -1555,17 +1398,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "smartstring" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" -dependencies = [ - "autocfg", - "static_assertions", - "version_check", -] - [[package]] name = "smithay-client-toolkit" version = "0.19.2" @@ -1641,12 +1473,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "thin-vec" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" - [[package]] name = "thiserror" version = "1.0.69" @@ -1687,15 +1513,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tiny-skia" version = "0.11.4" @@ -1794,12 +1611,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" diff --git a/Cargo.toml b/Cargo.toml index 776bc92..ae82260 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,8 +8,6 @@ edition = "2021" [dependencies] pollster = "0.4.0" winit = "0.30.11" -rhai = { git = "https://github.com/rhaiscript/rhai", features = ["f32_float"] } -notify = "8.0.0" wgpu = "26.0.1" bytemuck = "1.23.1" diff --git a/data/test.rhai b/data/test.rhai deleted file mode 100644 index 5fd237d..0000000 --- a/data/test.rhai +++ /dev/null @@ -1,15 +0,0 @@ -let a = rect(); -a.top_left = anchor_offset(0.5, 0.5, -200.0, -100.0); -a.bottom_right = anchor_offset(0.5, 0.5, 100.0, 100.0); -a.radius = 40.0; -a.inner_radius = 30.0; -a.thickness = 10.0; - -let b = rect(); -b.top_left = anchor_offset(0.25, 0.25, -100.0, -100.0); -b.bottom_right = anchor_offset(0.25, 0.25, 100.0, 100.0); -b.radius = 40.0; -b.inner_radius = 30.0; -b.thickness = 10.0; - -[a, b] diff --git a/src/layout/node.rs b/src/base/mod.rs similarity index 73% rename from src/layout/node.rs rename to src/base/mod.rs index c3ae1b7..18758f6 100644 --- a/src/layout/node.rs +++ b/src/base/mod.rs @@ -2,7 +2,7 @@ use std::ops::Range; use crate::{ primitive::{Axis, Painter, RoundedRectData, UIRegion}, - NodeArray, UIColor, UINode, + UIColor, Widget, WidgetArrLike, WidgetFn, WidgetId, WidgetLike, }; #[derive(Clone, Copy)] @@ -20,7 +20,7 @@ impl RoundedRect { } } -impl UINode for RoundedRect { +impl Widget for RoundedRect { fn draw(&self, painter: &mut Painter) { painter.write(RoundedRectData { color: self.color, @@ -32,11 +32,11 @@ impl UINode for RoundedRect { } pub struct Span { - pub elements: Vec<(Range, Box)>, + pub elements: Vec<(Range, WidgetId)>, pub axis: Axis, } -impl UINode for Span { +impl Widget for Span { fn draw(&self, painter: &mut Painter) { for (span, child) in &self.elements { let mut sub_region = UIRegion::full(); @@ -52,14 +52,13 @@ impl Span { pub fn proportioned( axis: Axis, ratios: [impl UINum; LEN], - elements: impl NodeArray, + elements: [WidgetId; LEN], ) -> Self { let ratios = ratios.map(|r| r.to_f32()); let total: f32 = ratios.iter().sum(); let mut start = 0.0; Self { elements: elements - .to_arr() .into_iter() .zip(ratios) .map(|(e, r)| { @@ -74,15 +73,15 @@ impl Span { } } -pub struct Regioned { +pub struct Regioned { region: UIRegion, - inner: N, + inner: WidgetId, } -impl UINode for Regioned { +impl Widget for Regioned { fn draw(&self, painter: &mut Painter) { painter.region.select(&self.region); - self.inner.draw(painter); + painter.draw(&self.inner); } } @@ -118,26 +117,26 @@ impl From for Padding { } } -pub trait NodeUtil: Sized + UINode { - fn pad(self, padding: impl Into) -> Regioned; +pub trait WidgetUtil { + fn pad(self, padding: impl Into) -> impl WidgetLike; } -impl NodeUtil for T { - fn pad(self, padding: impl Into) -> Regioned { - Regioned { +impl> WidgetUtil for WL { + fn pad(self, padding: impl Into) -> impl WidgetLike { + WidgetFn(|ui| Regioned { region: padding.into().region(), - inner: self, - } + inner: self.id(ui).erase_type(), + }) } } -pub trait NodeArrayUtil { - fn proportioned(self, axis: Axis, ratios: [impl UINum; LEN]) -> Span; +pub trait WidgetArrUtil { + fn span(self, axis: Axis, ratios: [impl UINum; LEN]) -> impl WidgetLike; } -impl, const LEN: usize> NodeArrayUtil for T { - fn proportioned(self, axis: Axis, ratios: [impl UINum; LEN]) -> Span { - Span::proportioned(axis, ratios, self) +impl> WidgetArrUtil for Wa { + fn span(self, axis: Axis, ratios: [impl UINum; LEN]) -> impl WidgetLike { + WidgetFn(move |ui| Span::proportioned(axis, ratios, self.ui(ui).arr)) } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 8845245..1d98bbf 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,66 +1,8 @@ -mod node; -pub use node::*; +mod ui; +mod widget; -use crate::primitive::{Color, Painter}; +pub use ui::*; +pub use widget::*; +use crate::primitive::Color; pub type UIColor = Color; - -pub trait UINode: 'static { - fn draw(&self, painter: &mut Painter); -} - -pub struct UI { - base: Box, -} - -impl UI { - pub fn to_primitives(&self) -> Painter { - let mut painter = Painter::default(); - self.base.draw(&mut painter); - painter - } -} - -impl From for UI { - fn from(node: N) -> Self { - Self { - base: Box::new(node), - } - } -} - -impl UINode for Box { - fn draw(&self, painter: &mut Painter) { - self.as_ref().draw(painter); - } -} - -pub trait NodeArray { - fn to_arr(self) -> [Box; LEN]; -} - -// I hate this language it's so bad why do I even use it -macro_rules! impl_node_arr { - ($n:expr;$($T:tt)+) => { - impl<$($T: UINode,)*> NodeArray<$n> for ($($T,)*) { - fn to_arr(self) -> [Box; $n] { - #[allow(non_snake_case)] - let ($($T,)*) = self; - [$(Box::new($T),)*] - } - } - }; -} - -impl_node_arr!(1;A); -impl_node_arr!(2;A B); -impl_node_arr!(3;A B C); -impl_node_arr!(4;A B C D); -impl_node_arr!(5;A B C D E); -impl_node_arr!(6;A B C D E F); -impl_node_arr!(7;A B C D E F G); -impl_node_arr!(8;A B C D E F G H); -impl_node_arr!(9;A B C D E F G H I); -impl_node_arr!(10;A B C D E F G H I J); -impl_node_arr!(11;A B C D E F G H I J K); -impl_node_arr!(12;A B C D E F G H I J K L); diff --git a/src/layout/ui.rs b/src/layout/ui.rs new file mode 100644 index 0000000..0ddc123 --- /dev/null +++ b/src/layout/ui.rs @@ -0,0 +1,101 @@ +use crate::{ + primitive::{Painter, Primitives}, + util::{IDTracker, ID}, + HashMap, Widget, WidgetId, WidgetLike, WidgetRef, +}; +use std::{ + any::{Any, TypeId}, + cell::RefCell, + rc::Rc, +}; + +pub struct UI { + ids: IDTracker, + base: Option, + pub widgets: Widgets, +} + +pub struct Widgets(HashMap>); + +#[derive(Clone)] +pub struct UIBuilder { + ui: Rc>, +} + +impl From for UIBuilder { + fn from(ui: UI) -> Self { + UIBuilder { + ui: Rc::new(RefCell::new(ui)), + } + } +} + +impl UIBuilder { + pub fn add(&mut self, w: W) -> WidgetRef { + WidgetRef::new(self.clone(), [self.push(w)]) + } + + pub fn push(&mut self, w: W) -> WidgetId { + let mut ui = self.ui.borrow_mut(); + let id = ui.ids.next(); + ui.widgets.insert(id.duplicate(), w); + WidgetId::new(id, TypeId::of::()) + } + + pub fn finish, W>(mut self, base: WL) -> UI { + let base = base.id(&mut self).erase_type(); + let mut ui = Rc::into_inner(self.ui).unwrap().into_inner(); + ui.base = Some(base); + ui + } +} + +impl UI { + pub fn build() -> UIBuilder { + Self::empty().into() + } + + pub fn empty() -> Self { + Self { + ids: IDTracker::new(), + base: None, + widgets: Widgets::new(), + } + } + + pub fn to_primitives(&self) -> Primitives { + let mut painter = Painter::new(&self.widgets); + if let Some(base) = &self.base { + painter.draw(base); + } + painter.finish() + } +} + +impl Widgets { + fn new() -> Self { + Self(HashMap::new()) + } + + pub fn get(&self, id: &WidgetId) -> &dyn Widget { + self.0.get(&id.id).unwrap().as_ref() + } + + pub fn get_mut(&mut self, id: &WidgetId) -> Option<&mut W> { + self.0.get_mut(&id.id).unwrap().as_any_mut().downcast_mut() + } + + pub fn insert(&mut self, id: ID, widget: impl Widget) { + self.0.insert(id, Box::new(widget)); + } + + pub fn insert_any(&mut self, id: ID, widget: Box) { + self.0.insert(id, widget); + } +} + +impl dyn Widget { + pub fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} diff --git a/src/layout/widget.rs b/src/layout/widget.rs new file mode 100644 index 0000000..1b44f99 --- /dev/null +++ b/src/layout/widget.rs @@ -0,0 +1,157 @@ +use std::{ + any::{Any, TypeId}, + marker::PhantomData, +}; + +use crate::{primitive::Painter, util::ID, UIBuilder}; + +pub trait Widget: 'static + Any { + fn draw(&self, painter: &mut Painter); +} + +impl Widget for (W,) { + fn draw(&self, painter: &mut Painter) { + self.0.draw(painter); + } +} + +#[derive(Eq, Hash, PartialEq, Debug)] +pub struct WidgetId { + pub(super) ty: TypeId, + pub(super) id: ID, + _pd: PhantomData, +} + +// TODO: temp +impl Clone for WidgetId { + fn clone(&self) -> Self { + Self { + ty: self.ty, + id: self.id.duplicate(), + _pd: self._pd, + } + } +} + +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() + } + + fn cast_type(self) -> WidgetId { + WidgetId { + ty: self.ty, + id: self.id, + _pd: PhantomData, + } + } +} + +pub trait WidgetLike { + fn id(self, ui: &mut UIBuilder) -> WidgetId; +} + +/// wouldn't be needed if negative trait bounds & disjoint impls existed +pub struct WidgetFn W, W>(pub F); + +impl W> WidgetLike for WidgetFn { + fn id(self, ui: &mut UIBuilder) -> WidgetId { + let w = (self.0)(ui); + ui.add(w).to_id() + } +} + +impl WidgetLike for W { + fn id(self, ui: &mut UIBuilder) -> WidgetId { + ui.add(self).to_id() + } +} + +impl WidgetLike for WidgetId { + fn id(self, _: &mut UIBuilder) -> WidgetId { + self + } +} + +impl WidgetLike for WidgetArr<1, (W,)> { + fn id(self, _: &mut UIBuilder) -> WidgetId { + let [id] = self.arr; + id.cast_type() + } +} + +pub struct WidgetArr { + pub ui: UIBuilder, + pub arr: [WidgetId<()>; LEN], + _pd: PhantomData, +} + +impl WidgetArr { + pub fn new(ui: UIBuilder, arr: [WidgetId<()>; LEN]) -> Self { + Self { + ui, + arr, + _pd: PhantomData, + } + } +} + +pub type WidgetRef = WidgetArr<1, (W,)>; + +impl WidgetRef { + pub fn handle(&self) -> WidgetId { + let [id] = &self.arr; + id.clone().cast_type() + } + pub fn to_id(self) -> WidgetId { + let [id] = self.arr; + id.cast_type() + } +} + +pub trait WidgetArrLike { + fn ui(self, ui: &mut UIBuilder) -> WidgetArr; +} + +impl WidgetArrLike for WidgetArr { + fn ui(self, _: &mut UIBuilder) -> WidgetArr { + self + } +} + +// I hate this language it's so bad why do I even use it +macro_rules! impl_node_arr { + ($n:expr;$($T:tt)*) => { + impl<$($T,${concat($T,$T)}: WidgetLike<$T>,)*> WidgetArrLike<$n, ($($T,)*)> for ($(${concat($T,$T)},)*) { + #[allow(unused_variables)] + fn ui(self, ui: &mut UIBuilder) -> WidgetArr<$n, ($($T,)*)> { + #[allow(non_snake_case)] + let ($($T,)*) = self; + WidgetArr::new( + ui.clone(), + [$($T.id(ui).cast_type(),)*], + ) + } + } + }; +} + +impl_node_arr!(1;A); +impl_node_arr!(2;A B); +impl_node_arr!(3;A B C); +impl_node_arr!(4;A B C D); +impl_node_arr!(5;A B C D E); +impl_node_arr!(6;A B C D E F); +impl_node_arr!(7;A B C D E F G); +impl_node_arr!(8;A B C D E F G H); +impl_node_arr!(9;A B C D E F G H I); +impl_node_arr!(10;A B C D E F G H I J); +impl_node_arr!(11;A B C D E F G H I J K); +impl_node_arr!(12;A B C D E F G H I J K L); diff --git a/src/lib.rs b/src/lib.rs index e2ed257..89d2b0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,9 +2,15 @@ #![feature(const_ops)] #![feature(const_trait_impl)] #![feature(const_from)] +#![feature(trait_alias)] mod layout; mod render; +mod util; +mod base; pub use layout::*; pub use render::*; +pub use base::*; + +pub type HashMap = std::collections::HashMap; diff --git a/src/render/primitive/mod.rs b/src/render/primitive/mod.rs index e0b6ae5..6f972bb 100644 --- a/src/render/primitive/mod.rs +++ b/src/render/primitive/mod.rs @@ -8,23 +8,19 @@ pub use def::*; pub use format::*; pub use point::*; -use crate::{render::data::PrimitiveInstance, UINode}; +use crate::{render::data::PrimitiveInstance, WidgetId, Widgets}; use bytemuck::Pod; -pub struct Painter { - pub region: UIRegion, +#[derive(Default)] +pub struct Primitives { pub instances: Vec, pub data: Vec, } -impl Default for Painter { - fn default() -> Self { - Self { - region: UIRegion::full(), - instances: Default::default(), - data: Default::default(), - } - } +pub struct Painter<'a> { + nodes: &'a Widgets, + primitives: Primitives, + pub region: UIRegion, } /// NOTE: Self must have at least u32 alignment @@ -32,19 +28,36 @@ pub trait PrimitiveData: Pod { const DISCRIM: u32; } -impl Painter { - pub fn write(&mut self, data: D) { - let ptr = self.data.len() as u32; +impl<'a> Painter<'a> { + pub fn new(nodes: &'a Widgets) -> Self { + Self { + nodes, + primitives: Primitives::default(), + region: UIRegion::full(), + } + } + pub fn write(&mut self, data: Data) { + let ptr = self.primitives.data.len() as u32; let region = self.region; - self.instances.push(PrimitiveInstance { region, ptr }); - self.data.push(D::DISCRIM); - self.data + self.primitives + .instances + .push(PrimitiveInstance { region, ptr }); + self.primitives.data.push(Data::DISCRIM); + self.primitives + .data .extend_from_slice(bytemuck::cast_slice::<_, u32>(&[data])); } - pub fn draw_within(&mut self, node: &impl UINode, region: UIRegion) { + pub fn draw(&mut self, node: &WidgetId) { + self.nodes.get(node).draw(self); + } + pub fn draw_within(&mut self, node: &WidgetId, region: UIRegion) { let old = self.region; self.region.select(®ion); - node.draw(self); + self.draw(node); self.region = old; } + + pub fn finish(self) -> Primitives { + self.primitives + } } diff --git a/src/testing/mod.rs b/src/testing/mod.rs index 5512d15..9d79ae3 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use app::App; -use gui::{primitive::Axis, NodeArrayUtil, NodeUtil, RoundedRect, UIColor}; +use gui::{primitive::Axis, RoundedRect, UIColor, WidgetArrLike, WidgetArrUtil, WidgetUtil, UI}; use render::Renderer; use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window}; @@ -25,24 +25,32 @@ impl Client { thickness: 0.0, inner_radius: 0.0, }; - let ui = ( + let mut ui = UI::build(); + let blue = ui.add(rect.color(UIColor::BLUE)); + let handle = blue.handle(); + let mut ui = ui.finish( ( - rect.color(UIColor::BLUE), ( - rect.color(UIColor::RED), - (rect.color(UIColor::ORANGE), rect.color(UIColor::LIME)) - .proportioned(Axis::Y, [1, 1]), - rect.color(UIColor::YELLOW), + blue, + ( + rect.color(UIColor::RED), + ( + rect.color(UIColor::ORANGE), + rect.color(UIColor::LIME).pad(10.0), + ) + .span(Axis::Y, [1, 1]), + rect.color(UIColor::YELLOW), + ) + .span(Axis::X, [2, 2, 1]) + .pad(10), ) - .proportioned(Axis::X, [2, 2, 1]) - .pad(10), + .span(Axis::X, [1, 3]), + rect.color(UIColor::GREEN), ) - .proportioned(Axis::X, [1, 3]), - rect.color(UIColor::GREEN), - ) - .proportioned(Axis::Y, [3, 1]) - .pad(10) - .into(); + .span(Axis::Y, [3, 1]) + .pad(10), + ); + ui.widgets.get_mut(&handle).unwrap().color = UIColor::MAGENTA; renderer.update(&ui); Self { renderer } } diff --git a/src/util/id.rs b/src/util/id.rs new file mode 100644 index 0000000..ad6fc08 --- /dev/null +++ b/src/util/id.rs @@ -0,0 +1,43 @@ +/// intentionally does not implement copy or clone +/// which should make it harder to misuse; +/// the idea is to generally try to guarantee all IDs +/// point to something valid, although duplicate +/// gets around this if needed +#[derive(Eq, Hash, PartialEq, Debug)] +pub struct ID(usize); + +#[derive(Default)] +pub struct IDTracker { + free: Vec, + cur: usize, +} + +impl IDTracker { + pub fn new() -> Self { + Self::default() + } + + #[allow(clippy::should_implement_trait)] + pub fn next(&mut self) -> ID { + if let Some(id) = self.free.pop() { + return id; + } + let id = ID(self.cur); + self.cur += 1; + id + } + + pub fn free(&mut self, id: ID) { + self.free.push(id); + } +} + +impl ID { + /// this must be used carefully to make sure + /// all IDs are still valid references; + /// named weirdly to indicate this. + /// generally should not be used in "user" code + pub fn duplicate(&self) -> Self { + Self(self.0) + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..3e76920 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,3 @@ +mod id; + +pub use id::*;