From 6d829dbe81535edad0b3240567932a2afc0def6a Mon Sep 17 00:00:00 2001 From: shadow cat Date: Thu, 25 Sep 2025 19:59:18 -0400 Subject: [PATCH] stack & padding fix sorta, preparing for scroll areas --- src/core/empty.rs | 0 src/core/pad.rs | 16 ++++++++-- src/core/span.rs | 2 +- src/core/stack.rs | 71 ++++++++++++++++++++++++++++++++++++++++--- src/core/trait_fns.rs | 12 +++----- src/layout/layer.rs | 26 +++++++++++----- src/layout/painter.rs | 25 +++++++++++---- src/layout/pos.rs | 6 ++-- src/layout/widget.rs | 4 +-- src/testing/mod.rs | 34 +++++++++++++-------- 10 files changed, 151 insertions(+), 45 deletions(-) create mode 100644 src/core/empty.rs diff --git a/src/core/empty.rs b/src/core/empty.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/core/pad.rs b/src/core/pad.rs index 8851e30..aa69a32 100644 --- a/src/core/pad.rs +++ b/src/core/pad.rs @@ -12,8 +12,20 @@ impl Widget for Padded { fn desired_size(&mut self, ctx: &mut SizeCtx) -> UiVec2 { let mut size = ctx.size(&self.inner); - size.abs.x += self.padding.left + self.padding.right; - size.abs.y += self.padding.top + self.padding.bottom; + // TODO: this is currently a hack + // the correct solution is that the position is not linear + // becauase if you have 0.5 rel + 100 abs, it's linear + // until you fill up parent rel (in abs units) and then 1.0 rel (piecewise linear) + // so I guess yet another detection system is needed + // to determine when abs translated to rel + rel > 1.0 + // hopefully find a way to get working on gpu so no cpu changing needed + // eg by sending size to calculate above and then doing... something? + if size.rel.x != 1.0 { + size.abs.x += self.padding.left + self.padding.right; + } + if size.rel.y != 1.0 { + size.abs.y += self.padding.top + self.padding.bottom; + } size } } diff --git a/src/core/span.rs b/src/core/span.rs index 0704e80..87accde 100644 --- a/src/core/span.rs +++ b/src/core/span.rs @@ -47,7 +47,7 @@ impl Widget for Span { } else { UiScalar::new(total.rel, total.abs) }; - let mut max_ortho = UiScalar::default(); + let mut max_ortho = UiScalar::ZERO; for (child, _) in &self.children { let size = ctx.size(child); max_ortho = max_ortho.max(size.axis(!self.dir.axis)); diff --git a/src/core/stack.rs b/src/core/stack.rs index e880ba7..57d3a21 100644 --- a/src/core/stack.rs +++ b/src/core/stack.rs @@ -1,19 +1,82 @@ +use std::marker::PhantomData; + use crate::prelude::*; pub struct Stack { pub children: Vec, + pub size: StackSize, + pub offset: usize, } impl Widget for Stack { fn draw(&mut self, painter: &mut Painter) { + for _ in 0..self.offset { + painter.layer = painter.next_layer(); + } let base = painter.layer; for child in &self.children { - if painter.layer == base { - painter.layer = painter.child_layer(); + painter.layer = if painter.layer == base { + painter.child_layer() } else { - painter.layer = painter.next_layer(); - } + painter.next_layer() + }; painter.widget(child); } } + + fn desired_size(&mut self, ctx: &mut SizeCtx) -> UiVec2 { + match self.size { + StackSize::Full => UiVec2::MAX_SIZE, + StackSize::Child(i) => ctx.size(&self.children[i]), + } + } +} + +#[derive(Default, Debug)] +pub enum StackSize { + #[default] + Full, + Child(usize), +} + +pub struct StackBuilder, Tag> { + pub children: Wa, + pub size: StackSize, + pub offset: usize, + _pd: PhantomData, +} + +impl, Tag> FnOnce<(&mut Ui,)> + for StackBuilder +{ + type Output = Stack; + + extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output { + Stack { + children: self.children.ui(args.0).arr.to_vec(), + offset: self.offset, + size: self.size, + } + } +} + +impl, Tag> StackBuilder { + pub fn new(children: Wa) -> Self { + Self { + children, + size: StackSize::default(), + offset: 0, + _pd: PhantomData, + } + } + + pub fn stack_size(mut self, size: StackSize) -> Self { + self.size = size; + self + } + + pub fn offset_layer(mut self, offset: usize) -> Self { + self.offset = offset; + self + } } diff --git a/src/core/trait_fns.rs b/src/core/trait_fns.rs index fac192c..cb26707 100644 --- a/src/core/trait_fns.rs +++ b/src/core/trait_fns.rs @@ -44,12 +44,12 @@ impl, Tag> CoreWidget for W { } } -pub trait CoreWidgetArr { +pub trait CoreWidgetArr, Tag> { fn span(self, dir: Dir, lengths: impl IntoSpanLens) -> impl WidgetFn; - fn stack(self) -> impl WidgetFn; + fn stack(self) -> StackBuilder; } -impl, Tag> CoreWidgetArr for Wa { +impl, Tag> CoreWidgetArr for Wa { fn span(self, dir: Dir, lengths: impl IntoSpanLens) -> impl WidgetFn { let lengths = lengths.into_lens(); move |ui| Span { @@ -57,10 +57,8 @@ impl, Tag> CoreWidgetArr dir, } } - fn stack(self) -> impl WidgetFn { - move |ui| Stack { - children: self.ui(ui).arr.to_vec(), - } + fn stack(self) -> StackBuilder { + StackBuilder::new(self) } } diff --git a/src/layout/layer.rs b/src/layout/layer.rs index 099202c..b8233b9 100644 --- a/src/layout/layer.rs +++ b/src/layout/layer.rs @@ -27,6 +27,8 @@ enum Ptr { /// is that realistically desired? pub struct Layers { vec: Vec, + /// index of last layer at top level (start at first = 0) + last: usize, } /// TODO: this can be replaced with Primitives itself atm @@ -45,6 +47,7 @@ impl Layers { pub fn new() -> Layers { Self { vec: vec![LayerNode::head()], + last: 0, } } @@ -73,7 +76,7 @@ impl Layers { match self.vec[i_new].next { Ptr::Next(i) => self.vec[i].prev = Ptr::Next(i_new), Ptr::Parent(i) => self.vec[i].child.as_mut().unwrap().tail = i_new, - Ptr::None => (), + Ptr::None => self.last = i_new, } i_new } @@ -95,11 +98,15 @@ impl Layers { } pub fn iter_mut(&mut self) -> LayerIteratorMut<'_> { - LayerIteratorMut::new(&mut self.vec) + LayerIteratorMut::new(&mut self.vec, self.last) + } + + pub fn iter(&self) -> impl Iterator { + self.indices().map(|i| (i, &self.vec[i].data)) } pub fn indices(&self) -> LayerIndexIterator<'_> { - LayerIndexIterator::new(&self.vec) + LayerIndexIterator::new(&self.vec, self.last) } pub fn write( @@ -179,9 +186,9 @@ impl<'a> DoubleEndedIterator for LayerIteratorMut<'a> { } impl<'a> LayerIteratorMut<'a> { - fn new(vec: &'a mut Vec) -> Self { + fn new(vec: &'a mut Vec, last: usize) -> Self { Self { - inner: LayerIndexIterator::new(vec), + inner: LayerIndexIterator::new(vec, last), } } } @@ -203,7 +210,10 @@ impl<'a> Iterator for LayerIndexIterator<'a> { } else if let Ptr::Next(i) = node.next { Some(i) } else if let Ptr::Parent(i) = node.next { - let node = &self.vec[i]; + let mut node = &self.vec[i]; + while let Ptr::Parent(i) = node.next { + node = &self.vec[i]; + } if let Ptr::Next(i) = node.next { Some(i) } else { @@ -243,8 +253,8 @@ impl<'a> DoubleEndedIterator for LayerIndexIterator<'a> { } impl<'a> LayerIndexIterator<'a> { - fn new(vec: &'a Vec) -> Self { - let mut last = 0; + fn new(vec: &'a Vec, last: usize) -> Self { + let mut last = last; while let Some(c) = vec[last].child { last = c.tail; } diff --git a/src/layout/painter.rs b/src/layout/painter.rs index 215b44e..83fdc0f 100644 --- a/src/layout/painter.rs +++ b/src/layout/painter.rs @@ -69,9 +69,19 @@ impl<'a> PainterCtx<'a> { }; if let Some((rid, size)) = &active.resize { - self.redraw(&rid.duplicate()); - if self.drawing.contains(id) { - return; + let checked = &mut HashMap::default(); + let mut ctx = SizeCtx { + checked, + text: self.text, + textures: self.textures, + widgets: self.widgets, + }; + let desired = ctx.size_inner(id); + if *size != desired { + self.redraw(&rid.duplicate()); + if self.drawing.contains(id) { + return; + } } } @@ -298,14 +308,17 @@ pub struct SizeCtx<'a> { } impl SizeCtx<'_> { + fn size_inner(&mut self, id: &Id) -> UiVec2 { + let size = self.widgets.get_dyn_dynamic(id).desired_size(self); + self.checked.insert(id.duplicate(), size); + size + } pub fn size(&mut self, id: &WidgetId) -> UiVec2 { // TODO: determine if this is useful // if let Some(size) = self.checked.get(&id.id) { // return Some(*size); // } - let size = self.widgets.get_dyn_dynamic(&id.id).desired_size(self); - self.checked.insert(id.id.duplicate(), size); - size + self.size_inner(&id.id) } pub fn draw_text( &mut self, diff --git a/src/layout/pos.rs b/src/layout/pos.rs index 91a0400..bb15aa3 100644 --- a/src/layout/pos.rs +++ b/src/layout/pos.rs @@ -100,9 +100,7 @@ impl UiVec2 { self.rel * size + self.abs } - pub const fn max_size() -> Self { - Self::rel(Vec2::ONE) - } + pub const MAX_SIZE: Self = Self::rel(Vec2::ONE); pub const fn from_axis(axis: Axis, aligned: UiScalar, ortho: UiScalar) -> Self { Self { @@ -134,6 +132,8 @@ impl_op!(UiScalar Add add; rel abs); impl_op!(UiScalar Sub sub; rel abs); impl UiScalar { + pub const ZERO: Self = Self { rel: 0.0, abs: 0.0 }; + pub fn new(rel: f32, abs: f32) -> Self { Self { rel, abs } } diff --git a/src/layout/widget.rs b/src/layout/widget.rs index 9763ce9..f386bb1 100644 --- a/src/layout/widget.rs +++ b/src/layout/widget.rs @@ -5,7 +5,7 @@ use std::{any::Any, marker::PhantomData}; pub trait Widget: Any { fn draw(&mut self, painter: &mut Painter); fn desired_size(&mut self, _: &mut SizeCtx) -> UiVec2 { - UiVec2::max_size() + UiVec2::MAX_SIZE } } @@ -76,7 +76,7 @@ impl WidgetArr { } pub struct ArrTag; -pub trait WidgetArrLike { +pub trait WidgetArrLike { type Ws; fn ui(self, ui: &mut Ui) -> WidgetArr; } diff --git a/src/testing/mod.rs b/src/testing/mod.rs index 3813082..e1a61f7 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -138,27 +138,37 @@ impl Client { let content = client.ui.text(id).take(); let text = text_edit(content) .font_size(30) + .text_align(Align::Left) .id_on(Sense::click(), |id, client: &mut Client, ctx| { client.ui.text(id).select(ctx.cursor, ctx.size); client.focus = Some(id.clone()); - }) - .pad(10) + }); + let msg_box = (rect(Color::WHITE.darker(0.5)), text) + .stack() + .stack_size(StackSize::Child(1)) .add(&mut client.ui); - client.ui[texts].children.push((text.any(), sized())); + client.ui[texts].children.push((msg_box.any(), sized())); }) .add(&mut ui); let text_edit_scroll = ( (Rect::new(Color::SKY), texts).stack(), ( - add_text.clone(), - Rect::new(Color::GREEN) - .on(Sense::click(), move |client: &mut Client, _| { - client.run_event(&add_text, Submit, ()); - }) - .size(40), + Rect::new(Color::WHITE.darker(0.9)), + ( + add_text.clone(), + Rect::new(Color::GREEN) + .on(Sense::click(), move |client: &mut Client, _| { + client.run_event(&add_text, Submit, ()); + }) + .size(40), + ) + .span(Dir::RIGHT, [ratio(1), sized()]) + .pad(10), ) - .span(Dir::RIGHT, [ratio(1), sized()]) - .pad(30), + .stack() + .stack_size(StackSize::Child(1)) + .offset_layer(1) + .align(Align::Bot), ) .span(Dir::DOWN, [ratio(1), sized()]) .add_static(&mut ui); @@ -192,7 +202,7 @@ impl Client { .span(Dir::RIGHT, ratio(1)); let info = text("").add(&mut ui); - let info_sect = info.clone().pad(10).align(Align::BotLeft); + let info_sect = info.clone().pad(10).align(Align::Right); ( (tabs, main).span(Dir::DOWN, [fixed(40), ratio(1)]),