From f9097807a2d75956c19e50eaa871bb72b4241031 Mon Sep 17 00:00:00 2001 From: shadow cat Date: Tue, 16 Sep 2025 17:31:54 -0400 Subject: [PATCH] sizing actually working correctly now --- src/core/align.rs | 4 +- src/core/image.rs | 4 +- src/core/mod.rs | 4 +- src/core/{frame.rs => pad.rs} | 14 +-- src/core/sized.rs | 4 +- src/core/span.rs | 54 ++++----- src/core/text.rs | 8 +- src/core/text_edit.rs | 33 +++++- src/layout/color.rs | 2 + src/layout/painter.rs | 77 +++++++------ src/layout/pos.rs | 202 ++++++++++++++++++++++------------ src/layout/sense.rs | 10 +- src/layout/ui.rs | 8 ++ src/layout/widget.rs | 6 +- src/render/primitive.rs | 5 +- src/testing/mod.rs | 82 ++++++++------ src/util/id.rs | 10 ++ 17 files changed, 333 insertions(+), 194 deletions(-) rename src/core/{frame.rs => pad.rs} (68%) diff --git a/src/core/align.rs b/src/core/align.rs index 3addb0d..af1e093 100644 --- a/src/core/align.rs +++ b/src/core/align.rs @@ -7,11 +7,11 @@ pub struct Aligned { impl Widget for Aligned { fn draw(&mut self, painter: &mut Painter) { - let region = UiRegion::from_size_align(painter.size(&self.inner), self.align); + let region = UiRegion::from_ui_size_align(painter.size(&self.inner), self.align); painter.widget_within(&self.inner, region); } - fn get_size(&mut self, ctx: &mut SizeCtx) -> Vec2 { + fn desired_size(&mut self, ctx: &mut SizeCtx) -> UiVec2 { ctx.size(&self.inner) } } diff --git a/src/core/image.rs b/src/core/image.rs index 6fab6de..3dc19e2 100644 --- a/src/core/image.rs +++ b/src/core/image.rs @@ -10,8 +10,8 @@ impl Widget for Image { painter.texture(&self.handle); } - fn get_size(&mut self, _: &mut SizeCtx) -> Vec2 { - self.handle.size() + fn desired_size(&mut self, _: &mut SizeCtx) -> UiVec2 { + UiVec2::abs(self.handle.size()) } } diff --git a/src/core/mod.rs b/src/core/mod.rs index ed3066b..033ddb8 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,6 +1,6 @@ mod align; -mod frame; mod image; +mod pad; mod rect; mod sense; mod sized; @@ -11,8 +11,8 @@ mod text_edit; mod trait_fns; pub use align::*; -pub use frame::*; pub use image::*; +pub use pad::*; pub use rect::*; pub use sense::*; pub use sized::*; diff --git a/src/core/frame.rs b/src/core/pad.rs similarity index 68% rename from src/core/frame.rs rename to src/core/pad.rs index 6a6c609..8851e30 100644 --- a/src/core/frame.rs +++ b/src/core/pad.rs @@ -10,10 +10,10 @@ impl Widget for Padded { painter.widget_within(&self.inner, self.padding.region()); } - fn get_size(&mut self, ctx: &mut SizeCtx) -> Vec2 { + fn desired_size(&mut self, ctx: &mut SizeCtx) -> UiVec2 { let mut size = ctx.size(&self.inner); - size.x += self.padding.left + self.padding.right; - size.y += self.padding.top + self.padding.bottom; + size.abs.x += self.padding.left + self.padding.right; + size.abs.y += self.padding.top + self.padding.bottom; size } } @@ -36,10 +36,10 @@ impl Padding { } pub fn region(&self) -> UiRegion { let mut region = UiRegion::full(); - region.top_left.offset.x += self.left; - region.top_left.offset.y += self.top; - region.bot_right.offset.x -= self.right; - region.bot_right.offset.y -= self.bottom; + region.top_left.abs.x += self.left; + region.top_left.abs.y += self.top; + region.bot_right.abs.x -= self.right; + region.bot_right.abs.y -= self.bottom; region } } diff --git a/src/core/sized.rs b/src/core/sized.rs index 2aef81d..7231608 100644 --- a/src/core/sized.rs +++ b/src/core/sized.rs @@ -10,7 +10,7 @@ impl Widget for Sized { painter.widget(&self.inner); } - fn get_size(&mut self, _: &mut SizeCtx) -> Vec2 { - self.size + fn desired_size(&mut self, _: &mut SizeCtx) -> UiVec2 { + UiVec2::abs(self.size) } } diff --git a/src/core/span.rs b/src/core/span.rs index 4ae4e80..0704e80 100644 --- a/src/core/span.rs +++ b/src/core/span.rs @@ -8,34 +8,30 @@ pub struct Span { impl Widget for Span { fn draw(&mut self, painter: &mut Painter) { let total = self.setup(&mut painter.size_ctx()); - let mut start = UIScalar::min(); + let mut start = UiScalar::rel_min(); for (child, length) in &self.children { let mut child_region = UiRegion::full(); let mut axis = child_region.axis_mut(self.dir.axis); axis.top_left.set(start); match *length { SpanLen::Fixed(offset) => { - start.offset += offset; - *axis.bot_right.offset = start.offset; - *axis.bot_right.anchor = *axis.top_left.anchor; + start.abs += offset; } SpanLen::Ratio(ratio) => { - let offset = UIScalar::new(total.relative, total.fixed); - let rel_end = UIScalar::from_anchor(ratio / total.ratio); - start = rel_end.within(start, (UIScalar::max() + start) - offset); - axis.bot_right.set(start); + let offset = UiScalar::new(total.rel, total.abs); + let rel_end = UiScalar::from_anchor(ratio / total.ratio); + start = rel_end.within(start, (UiScalar::rel_max() + start) - offset); } SpanLen::Relative(rel) => { - start.anchor += rel; - axis.bot_right.set(start); + start.rel += rel; } SpanLen::Sized(size) => { - let offset = size.axis(self.dir.axis); - start.offset += offset; - *axis.bot_right.offset = start.offset; - *axis.bot_right.anchor = *axis.top_left.anchor; + let size_axis = size.axis(self.dir.axis); + start.abs += size_axis.abs; + start.rel += size_axis.rel; } } + axis.bot_right.set(start); if self.dir.sign == Sign::Neg { child_region.flip(); } @@ -43,24 +39,28 @@ impl Widget for Span { } } - fn get_size(&mut self, ctx: &mut SizeCtx) -> Vec2 { + fn desired_size(&mut self, ctx: &mut SizeCtx) -> UiVec2 { let total = self.setup(ctx); let axis = self.dir.axis; let dir_len = if total.ratio != 0.0 { - ctx.size.axis(axis) + UiScalar::rel_max() } else { - total.fixed + total.relative * ctx.size.axis(axis) + UiScalar::new(total.rel, total.abs) }; - Vec2::from_axis(axis, dir_len, total.max_sized) + let mut max_ortho = UiScalar::default(); + for (child, _) in &self.children { + let size = ctx.size(child); + max_ortho = max_ortho.max(size.axis(!self.dir.axis)); + } + UiVec2::from_axis(axis, dir_len, max_ortho) } } #[derive(Default)] pub struct SpanLenSums { - pub fixed: f32, + pub abs: f32, pub ratio: f32, - pub relative: f32, - pub max_sized: f32, + pub rel: f32, } impl Span { @@ -76,15 +76,15 @@ impl Span { .iter_mut() .fold(SpanLenSums::default(), |mut s, (id, l)| { match l { - SpanLen::Fixed(v) => s.fixed += *v, + SpanLen::Fixed(v) => s.abs += *v, SpanLen::Ratio(v) => s.ratio += *v, - SpanLen::Relative(v) => s.relative += *v, + SpanLen::Relative(v) => s.rel += *v, SpanLen::Sized(v) => { let size = ctx.size(id); let len = size.axis(self.dir.axis); - s.max_sized = s.max_sized.max(size.axis(!self.dir.axis)); *v = size; - s.fixed += len; + s.abs += len.abs; + s.rel += len.rel; } } s @@ -105,7 +105,7 @@ pub enum SpanLen { /// size determined by the child widget itself /// the value is not used externally, I just don't wanna make a duplicate enum /// there are util functions instead so - Sized(Vec2), + Sized(UiVec2), } pub fn fixed(x: N) -> SpanLen { @@ -121,7 +121,7 @@ pub fn relative(x: N) -> SpanLen { } pub fn sized() -> SpanLen { - SpanLen::Sized(Vec2::ZERO) + SpanLen::Sized(UiVec2::default()) } impl From for SpanLen { diff --git a/src/core/text.rs b/src/core/text.rs index b0cbfbd..4437f53 100644 --- a/src/core/text.rs +++ b/src/core/text.rs @@ -66,15 +66,15 @@ impl Widget for Text { let dims = handle.size(); self.size = offset.size(&handle); let mut region = self.region(); - region.top_left.offset += offset.top_left; - region.bot_right.offset = region.top_left.offset + dims; + region.top_left.abs += offset.top_left; + region.bot_right.abs = region.top_left.abs + dims; painter.texture_within(&handle, region); } - fn get_size(&mut self, ctx: &mut SizeCtx) -> Vec2 { + fn desired_size(&mut self, ctx: &mut SizeCtx) -> UiVec2 { self.update_buf(&mut ctx.text.font_system); let (handle, offset) = ctx.draw_text(&mut self.buf, &self.attrs); - offset.size(&handle) + UiVec2::abs(offset.size(&handle)) } } diff --git a/src/core/text_edit.rs b/src/core/text_edit.rs index 2c2016e..2adb655 100644 --- a/src/core/text_edit.rs +++ b/src/core/text_edit.rs @@ -24,16 +24,17 @@ impl TextEdit { impl Widget for TextEdit { fn draw(&mut self, painter: &mut Painter) { let font_system = &mut painter.text_data().font_system; - self.buf.shape_until_scroll(font_system, false); self.attrs.apply(font_system, &mut self.buf); + self.buf.shape_until_scroll(font_system, false); let (handle, tex_offset) = painter.render_text(&mut self.buf, &self.attrs); let dims = handle.size(); self.size = tex_offset.size(&handle); let region = self.region(); let mut tex_region = region; - tex_region.top_left.offset += tex_offset.top_left; - tex_region.bot_right.offset = tex_region.top_left.offset + dims; + tex_region.top_left.abs += tex_offset.top_left; + tex_region.bot_right.abs = tex_region.top_left.abs + dims; painter.texture_within(&handle, tex_region); + if let Some(cursor) = &self.cursor && let Some(pos) = cursor_pos(cursor, &self.buf) { @@ -45,12 +46,15 @@ impl Widget for TextEdit { .shifted(offset) .within(®ion), ); + } else { + // keep number of primitives constant so shifting isn't needed + painter.primitive(RectPrimitive::color(Color::NONE)); } } - fn get_size(&mut self, ctx: &mut SizeCtx) -> Vec2 { + fn desired_size(&mut self, ctx: &mut SizeCtx) -> UiVec2 { let (handle, offset) = ctx.draw_text(&mut self.buf, &self.attrs); - offset.size(&handle) + UiVec2::abs(offset.size(&handle)) } } @@ -105,6 +109,10 @@ impl TextEditBuilder { self.attrs.line_height = height; self } + pub fn text_align(mut self, align: Align) -> Self { + self.align = align; + self + } } pub struct TextEditCtx<'a> { @@ -113,6 +121,21 @@ pub struct TextEditCtx<'a> { } impl<'a> TextEditCtx<'a> { + pub fn take(&mut self) -> String { + let text = self + .text + .buf + .lines + .drain(..) + .map(|l| l.into_text()) + .collect::>() + .join("\n"); + self.text + .buf + .set_text(self.font_system, "", &Attrs::new(), Shaping::Advanced); + text + } + pub fn motion(&mut self, motion: Motion) { if let Some(cursor) = self.text.cursor && let Some((cursor, _)) = diff --git a/src/layout/color.rs b/src/layout/color.rs index 4d0de1a..8b6e629 100644 --- a/src/layout/color.rs +++ b/src/layout/color.rs @@ -25,6 +25,8 @@ impl Color { pub const PURPLE: Self = Self::rgb(T::MID, T::MIN, T::MAX); pub const MAGENTA: Self = Self::rgb(T::MAX, T::MIN, T::MAX); + pub const NONE: Self = Self::new(T::MIN, T::MIN, T::MIN, T::MIN); + pub const fn new(r: T, g: T, b: T, a: T) -> Self { Self { r, g, b, a } } diff --git a/src/layout/painter.rs b/src/layout/painter.rs index 3efd734..ad71fdb 100644 --- a/src/layout/painter.rs +++ b/src/layout/painter.rs @@ -3,10 +3,10 @@ use std::ops::Range; use crate::{ layout::{ Active, TextAttrs, TextBuffer, TextData, TextOffset, TextureHandle, Textures, UiRegion, - Vec2, WidgetId, Widgets, + UiVec2, Vec2, WidgetId, Widgets, }, render::{Primitive, PrimitiveHandle, Primitives}, - util::{HashSet, Id}, + util::{HashMap, HashSet, Id}, }; pub struct Painter<'a, 'c> { @@ -15,7 +15,7 @@ pub struct Painter<'a, 'c> { textures: Vec, primitives: Vec, children: Vec, - sized_children: HashSet, + sized_children: HashMap, id: Id, } @@ -36,7 +36,7 @@ pub struct WidgetInstance { pub textures: Vec, pub primitives: Vec, pub children: Vec, - pub resize: Option, + pub resize: Option<(Id, UiVec2)>, pub span: Range, } @@ -66,7 +66,7 @@ impl<'a> PainterCtx<'a> { return; }; - if let Some(rid) = &active.resize { + if let Some((rid, size)) = &active.resize { self.redraw(&rid.duplicate()); if self.drawing.contains(id) { return; @@ -76,15 +76,20 @@ impl<'a> PainterCtx<'a> { let Some(active) = self.remove(id) else { return; }; + + let parent = active.parent(); + drop(active.textures); + self.textures.free(); + self.primitives.set_pos(active.span.start); - self.draw_inner(id, active.region, active.parent(), Some(active.children)); + self.draw_inner(id, active.region, parent, Some(active.children)); self.active.widgets.get_mut(id).unwrap().resize = active.resize; let delta = self.primitives.apply(active.span.clone()); if delta != 0 && let Some(parent) = active.parent { - self.shift_parent(parent, active.span.start, delta); + self.shift_parent(id, parent, active.span.start, delta); } } @@ -113,12 +118,12 @@ impl<'a> PainterCtx<'a> { } let mut old_children = old_children.unwrap_or_default(); let mut resize = None; - if let Some(active) = self.active.widgets.get(id) { + if let Some(active) = self.active.widgets.get_mut(id) { if active.parent != parent { panic!("Cannot draw the same widget twice (2)"); } if active.region == region { - self.primitives.skip(&active.span); + self.primitives.skip(&mut active.span); return; } // TODO: @@ -126,6 +131,8 @@ impl<'a> PainterCtx<'a> { let active = self.remove(id).unwrap(); old_children = active.children; resize = active.resize; + drop(active.textures); + self.textures.free(); } let mut painter = Painter { @@ -135,7 +142,7 @@ impl<'a> PainterCtx<'a> { primitives: Vec::new(), ctx: self, children: Vec::new(), - sized_children: HashSet::new(), + sized_children: Default::default(), }; // draw widgets @@ -156,9 +163,9 @@ impl<'a> PainterCtx<'a> { children: painter.children, resize, }; - for cid in sized_children { + for (cid, size) in sized_children { if let Some(w) = self.active.widgets.get_mut(&cid) { - w.resize = Some(id.duplicate()) + w.resize = Some((id.duplicate(), size)) } } for c in &old_children { @@ -193,28 +200,31 @@ impl<'a> PainterCtx<'a> { /// shifts the primitive spans for all widgets above this one in the tree /// also goes into children of them and modifies those that come after this one /// should be done after applying primitives to ensure active spans are correct - fn shift_parent(&mut self, parent: Id, start: usize, delta: isize) { + fn shift_parent(&mut self, original: &Id, parent: Id, start: usize, delta: isize) { let instance = self.active.widgets.get_mut(&parent).unwrap(); let end = &mut instance.span.end; *end = end.strict_add_signed(delta); - let parent = instance.parent(); + let parent_parent = instance.parent(); for child in instance .children .iter() + // skip original + .skip_while(|id| *id != original) + .skip(1) .map(|id| id.duplicate()) .collect::>() { self.shift_child(&child, start, delta); } - if let Some(parent) = parent { - self.shift_parent(parent, start, delta); + if let Some(parent_parent) = parent_parent { + self.shift_parent(&parent, parent_parent, start, delta); } } fn shift_child(&mut self, child: &Id, start: usize, delta: isize) { let instance = self.active.widgets.get_mut(child).unwrap(); // = also prevents the original id from getting shifted - if instance.span.start <= start { + if instance.span.start < start { return; } instance.span.start = instance.span.start.strict_add_signed(delta); @@ -232,11 +242,11 @@ impl<'a> PainterCtx<'a> { impl<'a, 'c> Painter<'a, 'c> { fn primitive_at(&mut self, primitive: P, region: UiRegion) { - self.primitives.push( - self.ctx - .primitives - .write(self.id.duplicate(), primitive, region), - ); + let h = self + .ctx + .primitives + .write(self.id.duplicate(), primitive, region); + self.primitives.push(h); } /// Writes a primitive to be rendered @@ -293,17 +303,12 @@ impl<'a, 'c> Painter<'a, 'c> { self.region } - pub fn size(&mut self, id: &WidgetId) -> Vec2 { - self.sized_children.insert(id.id.duplicate()); - self.ctx - .widgets - .get_dyn_dynamic(&id.id) - .get_size(&mut self.size_ctx()) + pub fn size(&mut self, id: &WidgetId) -> UiVec2 { + self.size_ctx().size(id) } pub fn size_ctx(&mut self) -> SizeCtx<'_> { SizeCtx { - size: self.region.in_size(self.ctx.screen_size), text: self.ctx.text, textures: self.ctx.textures, widgets: self.ctx.widgets, @@ -317,17 +322,21 @@ impl<'a, 'c> Painter<'a, 'c> { } pub struct SizeCtx<'a> { - pub size: Vec2, pub text: &'a mut TextData, pub textures: &'a mut Textures, widgets: &'a Widgets, - checked: &'a mut HashSet, + checked: &'a mut HashMap, } impl SizeCtx<'_> { - pub fn size(&mut self, id: &WidgetId) -> Vec2 { - self.checked.insert(id.id.duplicate()); - self.widgets.get_dyn_dynamic(&id.id).get_size(self) + 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 } pub fn draw_text( &mut self, diff --git a/src/layout/pos.rs b/src/layout/pos.rs index a3a676f..91a0400 100644 --- a/src/layout/pos.rs +++ b/src/layout/pos.rs @@ -5,12 +5,12 @@ use crate::{ #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable, Default)] -pub struct UiPos { - pub anchor: Vec2, - pub offset: Vec2, +pub struct UiVec2 { + pub rel: Vec2, + pub abs: Vec2, } -impl UiPos { +impl UiVec2 { /// expands this position into a sized region centered at self pub fn expand(&self, size: impl Into) -> UiRegion { let size = size.into(); @@ -21,21 +21,29 @@ impl UiPos { } pub const fn anchor(anchor: Vec2) -> Self { - Self { - anchor, - offset: Vec2::ZERO, - } + Self::rel(anchor) } pub const fn offset(offset: Vec2) -> Self { + Self::abs(offset) + } + + pub const fn abs(abs: Vec2) -> Self { Self { - anchor: Vec2::ZERO, - offset, + rel: Vec2::ZERO, + abs, + } + } + + pub const fn rel(rel: Vec2) -> Self { + Self { + rel, + abs: Vec2::ZERO, } } pub const fn shift(&mut self, offset: impl const Into) { - self.offset += offset.into(); + self.abs += offset.into(); } pub const fn shifted(mut self, offset: Vec2) -> Self { @@ -43,33 +51,44 @@ impl UiPos { self } - pub const fn within(&self, region: &UiRegion) -> UiPos { - let anchor = self - .anchor - .lerp(region.top_left.anchor, region.bot_right.anchor); - let offset = self.offset - + self - .anchor - .lerp(region.top_left.offset, region.bot_right.offset); - UiPos { anchor, offset } + pub const fn within(&self, region: &UiRegion) -> UiVec2 { + let anchor = self.rel.lerp(region.top_left.rel, region.bot_right.rel); + let offset = self.abs + self.rel.lerp(region.top_left.abs, region.bot_right.abs); + UiVec2 { + rel: anchor, + abs: offset, + } } - pub fn axis_mut(&mut self, axis: Axis) -> UIScalarView<'_> { + pub fn axis_mut(&mut self, axis: Axis) -> UiScalarView<'_> { match axis { - Axis::X => UIScalarView { - anchor: &mut self.anchor.x, - offset: &mut self.offset.x, + Axis::X => UiScalarView { + anchor: &mut self.rel.x, + offset: &mut self.abs.x, }, - Axis::Y => UIScalarView { - anchor: &mut self.anchor.y, - offset: &mut self.offset.y, + Axis::Y => UiScalarView { + anchor: &mut self.rel.y, + offset: &mut self.abs.y, + }, + } + } + + pub fn axis(&self, axis: Axis) -> UiScalar { + match axis { + Axis::X => UiScalar { + rel: self.rel.x, + abs: self.abs.x, + }, + Axis::Y => UiScalar { + rel: self.rel.y, + abs: self.abs.y, }, } } pub fn flip(&mut self) { - self.anchor = 1.0 - self.anchor; - self.offset = -self.offset; + self.rel = 1.0 - self.rel; + self.abs = -self.abs; } pub fn flipped(mut self) -> Self { @@ -78,64 +97,92 @@ impl UiPos { } pub fn to_size(&self, size: Vec2) -> Vec2 { - self.anchor * size + self.offset + self.rel * size + self.abs + } + + pub const fn max_size() -> Self { + Self::rel(Vec2::ONE) + } + + pub const fn from_axis(axis: Axis, aligned: UiScalar, ortho: UiScalar) -> Self { + Self { + rel: Vec2::from_axis(axis, aligned.rel, ortho.rel), + abs: Vec2::from_axis(axis, aligned.abs, ortho.abs), + } } } -impl const From for UiPos { +impl const From for UiVec2 { fn from(align: Align) -> Self { Self::anchor(align.anchor()) } } impl Align { - pub fn pos(self) -> UiPos { - UiPos::from(self) + pub fn pos(self) -> UiVec2 { + UiVec2::from(self) } } -#[derive(Clone, Copy, Debug)] -pub struct UIScalar { - pub anchor: f32, - pub offset: f32, +#[derive(Clone, Copy, Debug, Default)] +pub struct UiScalar { + pub rel: f32, + pub abs: f32, } -impl_op!(UIScalar Add add; anchor offset); -impl_op!(UIScalar Sub sub; anchor offset); +impl_op!(UiScalar Add add; rel abs); +impl_op!(UiScalar Sub sub; rel abs); -impl UIScalar { - pub fn new(anchor: f32, offset: f32) -> Self { - Self { anchor, offset } +impl UiScalar { + pub fn new(rel: f32, abs: f32) -> Self { + Self { rel, abs } } - pub fn min() -> Self { + pub fn rel_min() -> Self { Self::new(0.0, 0.0) } - pub fn max() -> Self { + pub fn rel_max() -> Self { Self::new(1.0, 0.0) } + pub fn max(&self, other: Self) -> Self { + Self { + rel: self.rel.max(other.rel), + abs: self.abs.max(other.abs), + } + } + + pub fn min(&self, other: Self) -> Self { + Self { + rel: self.rel.min(other.rel), + abs: self.abs.min(other.abs), + } + } + pub fn from_anchor(anchor: f32) -> Self { Self::new(anchor, 0.0) } pub fn offset(mut self, amt: f32) -> Self { - self.offset += amt; + self.abs += amt; self } - pub fn within(&self, start: UIScalar, end: UIScalar) -> Self { - let anchor = self.anchor.lerp(start.anchor, end.anchor); - let offset = self.offset + self.anchor.lerp(start.offset, end.offset); - Self { anchor, offset } + pub fn within(&self, start: UiScalar, end: UiScalar) -> Self { + let anchor = self.rel.lerp(start.rel, end.rel); + let offset = self.abs + self.rel.lerp(start.abs, end.abs); + Self { + rel: anchor, + abs: offset, + } } } #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] pub struct UiRegion { - pub top_left: UiPos, - pub bot_right: UiPos, + pub top_left: UiVec2, + pub bot_right: UiVec2, } impl UiRegion { @@ -147,8 +194,8 @@ impl UiRegion { } pub fn anchor(anchor: Vec2) -> Self { Self { - top_left: UiPos::anchor(anchor), - bot_right: UiPos::anchor(anchor), + top_left: UiVec2::anchor(anchor), + bot_right: UiVec2::anchor(anchor), } } pub fn within(&self, parent: &Self) -> Self { @@ -183,12 +230,12 @@ impl UiRegion { pub fn to_screen(&self, size: Vec2) -> ScreenRegion { ScreenRegion { - top_left: self.top_left.anchor * size + self.top_left.offset, - bot_right: self.bot_right.anchor * size + self.bot_right.offset, + top_left: self.top_left.rel * size + self.top_left.abs, + bot_right: self.bot_right.rel * size + self.bot_right.abs, } } - pub fn center(&self) -> UiPos { + pub fn center(&self) -> UiVec2 { Align::Center.pos().within(self) } @@ -201,10 +248,23 @@ impl UiRegion { } pub fn from_size_align(size: Vec2, align: Align) -> Self { - let mut top_left = UiPos::from(align); - top_left.offset -= size * align.anchor(); - let mut bot_right = UiPos::from(align); - bot_right.offset += size * (Vec2::ONE - align.anchor()); + let mut top_left = UiVec2::from(align); + top_left.abs -= size * align.anchor(); + let mut bot_right = UiVec2::from(align); + bot_right.abs += size * (Vec2::ONE - align.anchor()); + Self { + top_left, + bot_right, + } + } + + pub fn from_ui_size_align(size: UiVec2, align: Align) -> Self { + let mut top_left = UiVec2::from(align); + top_left.abs -= size.abs * align.anchor(); + top_left.rel -= size.rel * align.anchor(); + let mut bot_right = UiVec2::from(align); + bot_right.abs += size.abs * (Vec2::ONE - align.anchor()); + bot_right.rel += size.rel * (Vec2::ONE - align.anchor()); Self { top_left, bot_right, @@ -227,24 +287,24 @@ impl ScreenRegion { } pub struct UIRegionAxisView<'a> { - pub top_left: UIScalarView<'a>, - pub bot_right: UIScalarView<'a>, + pub top_left: UiScalarView<'a>, + pub bot_right: UiScalarView<'a>, } -pub struct UIScalarView<'a> { +pub struct UiScalarView<'a> { pub anchor: &'a mut f32, pub offset: &'a mut f32, } -impl UIScalarView<'_> { - pub fn set(&mut self, scalar: UIScalar) { - *self.anchor = scalar.anchor; - *self.offset = scalar.offset; +impl UiScalarView<'_> { + pub fn set(&mut self, scalar: UiScalar) { + *self.anchor = scalar.rel; + *self.offset = scalar.abs; } - pub fn get(self) -> UIScalar { - UIScalar { - anchor: *self.anchor, - offset: *self.offset, + pub fn get(self) -> UiScalar { + UiScalar { + rel: *self.anchor, + abs: *self.offset, } } } diff --git a/src/layout/sense.rs b/src/layout/sense.rs index edff13d..96f7165 100644 --- a/src/layout/sense.rs +++ b/src/layout/sense.rs @@ -101,6 +101,9 @@ pub trait UiCtx { Self: Sized; } +/// TODO: this takes ui.sensor_map and then puts it back +/// it extends so you can add to it, but you can't actually index sensors with it +/// should something be done about this? pub fn run_sensors(ctx: &mut Ctx, cursor: &CursorState, window_size: Vec2) { let active = std::mem::take(&mut ctx.ui().active.sensors); let mut map = std::mem::take(&mut ctx.ui().sensor_map); @@ -128,8 +131,11 @@ pub fn run_sensors(ctx: &mut Ctx, cursor: &CursorState, window_size: break; } } - ctx.ui().sensor_map = map; - ctx.ui().active.sensors = active; + let ui = ctx.ui(); + std::mem::swap(&mut ui.sensor_map, &mut map); + ui.sensor_map.extend(map); + assert!(ui.active.sensors.is_empty()); + ui.active.sensors = active; } pub fn should_run(senses: &Senses, cursor: &CursorButtons, hover: ActivationState) -> bool { diff --git a/src/layout/ui.rs b/src/layout/ui.rs index 0e98970..d5af0e2 100644 --- a/src/layout/ui.rs +++ b/src/layout/ui.rs @@ -66,6 +66,10 @@ impl Ui { self.widgets.data_mut(&id.id).unwrap().label = label; } + pub fn label(&self, id: &WidgetId) -> &String { + &self.widgets.data(&id.id).unwrap().label + } + pub fn add_widget(&mut self, w: W) -> WidgetId { self.push(w) } @@ -281,6 +285,10 @@ impl Widgets { self.map.get(id) } + pub fn label(&self, id: &Id) -> &String { + &self.data(id).unwrap().label + } + pub fn data_mut(&mut self, id: &Id) -> Option<&mut WidgetData> { self.map.get_mut(id) } diff --git a/src/layout/widget.rs b/src/layout/widget.rs index 9007551..afe95bc 100644 --- a/src/layout/widget.rs +++ b/src/layout/widget.rs @@ -1,11 +1,11 @@ -use crate::layout::{Painter, SizeCtx, StaticWidgetId, Ui, Vec2, WidgetId, WidgetIdFn}; +use crate::layout::{Painter, SizeCtx, StaticWidgetId, Ui, UiVec2, WidgetId, WidgetIdFn}; use std::{any::Any, marker::PhantomData}; pub trait Widget: Any { fn draw(&mut self, painter: &mut Painter); - fn get_size(&mut self, ctx: &mut SizeCtx) -> Vec2 { - ctx.size + fn desired_size(&mut self, _: &mut SizeCtx) -> UiVec2 { + UiVec2::max_size() } } diff --git a/src/render/primitive.rs b/src/render/primitive.rs index 6aefa70..1b43cda 100644 --- a/src/render/primitive.rs +++ b/src/render/primitive.rs @@ -128,7 +128,7 @@ impl Primitives { self.run.len() != 0 } - pub fn skip(&mut self, range: &Range) { + pub fn skip(&mut self, range: &mut Range) { if self.running() { self.run.data.extend(&self.instances.data[range.clone()]); self.run.assoc.extend( @@ -137,7 +137,9 @@ impl Primitives { .map(|i| i.duplicate()), ); } + range.start = self.pos; self.pos += range.len(); + range.end = self.pos; } pub(crate) fn clear(&mut self) { @@ -184,6 +186,7 @@ impl Primitives { } } +#[derive(Debug)] pub struct PrimitiveHandle { idx: usize, binding: u32, diff --git a/src/testing/mod.rs b/src/testing/mod.rs index 44ccd2c..567c816 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -21,7 +21,7 @@ pub struct Client { input: Input, ui: Ui, info: WidgetId, - selected: Option>, + focus: Option>, } impl Client { @@ -38,7 +38,7 @@ impl Client { rrect.color(Color::ORANGE), rrect.color(Color::LIME).pad(10.0), ) - .span(Dir::RIGHT, [1, 1]), + .span(Dir::RIGHT, ratio(1)), rrect.color(Color::YELLOW), ) .span(Dir::RIGHT, [2, 2, 1]) @@ -110,6 +110,7 @@ impl Client { btext(":gamer mode").family(Family::Monospace), rect(Color::CYAN).size(10).center(), rect(Color::RED).size(100).center(), + rect(Color::PURPLE).size(50).align(Align::Top), ) .span(Dir::RIGHT, sized()) .center(), @@ -119,14 +120,34 @@ impl Client { .add_static(&mut ui); let texts = Span::empty(Dir::DOWN).add(&mut ui); + let add_text = text_edit("add") + .text_align(Align::Left) + .font_size(30) + .id_on(Sense::click(), |id, client: &mut Client, ctx| { + client.ui.text(id).select(ctx.cursor, ctx.size); + client.focus = Some(id.clone()); + }) + .add(&mut ui); let text_edit_scroll = ( - texts, - text_edit("add") - .font_size(30) - .id_on(Sense::click(), |id, client: &mut Client, ctx| { - client.ui.text(id).select(ctx.cursor, ctx.size); - client.selected = Some(id.clone()); - }) + (Rect::new(Color::SKY), texts.clone()).stack(), + ( + add_text.clone(), + Rect::new(Color::GREEN) + .on(Sense::click(), move |client: &mut Client, _| { + let content = client.ui.text(&add_text).take(); + let text = text_edit(content) + .font_size(30) + .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) + .add(&mut client.ui); + client.ui[&texts].children.push((text.any(), sized())); + }) + .size(40), + ) + .span(Dir::RIGHT, [ratio(1), sized()]) .pad(30), ) .span(Dir::DOWN, [ratio(1), sized()]) @@ -161,24 +182,15 @@ impl Client { .span(Dir::RIGHT, ratio(1)); let info = text("").add(&mut ui); - let info_sect = info.clone().pad(10).align(Align::BotLeft); - ui.set_root( - ( - tabs.label("tabs"), - (main, info_sect.label("info sect")) - .stack() - .label("main stack"), - ) - .span(Dir::DOWN, [fixed(40), ratio(1)]) - .label("root"), - ); + // let info_sect = info.clone().pad(10).align(Align::BotLeft); + ui.set_root((tabs, main).span(Dir::DOWN, [fixed(40), ratio(1)])); Self { renderer, input: Input::default(), ui, info, - selected: None, + focus: None, } } @@ -186,6 +198,12 @@ impl Client { self.input.event(&event); let cursor_state = self.cursor_state().clone(); let window_size = self.window_size(); + if let Some(focus) = &self.focus + && cursor_state.buttons.left.is_start() + { + self.ui.text(focus).deselect(); + self.focus = None; + } run_sensors(self, &cursor_state, window_size); match event { WindowEvent::CloseRequested => event_loop.exit(), @@ -199,24 +217,24 @@ impl Client { self.renderer.resize(&size) } WindowEvent::KeyboardInput { event, .. } => { - if let Some(sel) = &self.selected + if let Some(sel) = &self.focus && event.state.is_pressed() && self.ui.text(sel).apply_event(&event).unfocus() { - self.selected = None; + self.focus = None; } } _ => (), } - let new = format!( - "widgets: {}\nactive:{}\nviews: {}", - self.ui.num_widgets(), - self.ui.active_widgets(), - self.renderer.ui.view_count() - ); - if new != self.ui[&self.info].content { - self.ui[&self.info].content = new; - } + // let new = format!( + // "widgets: {}\nactive:{}\nviews: {}", + // self.ui.num_widgets(), + // self.ui.active_widgets(), + // self.renderer.ui.view_count() + // ); + // if new != self.ui[&self.info].content { + // self.ui[&self.info].content = new; + // } if self.ui.needs_redraw() { self.renderer.window().request_redraw(); } diff --git a/src/util/id.rs b/src/util/id.rs index e07745f..79632e2 100644 --- a/src/util/id.rs +++ b/src/util/id.rs @@ -54,3 +54,13 @@ impl CopyId { Id(self.0) } } + +pub trait IdUtil { + fn duplicate(&self) -> Self; +} + +impl IdUtil for Option { + fn duplicate(&self) -> Self { + self.as_ref().map(|i| i.duplicate()) + } +}