learn how workspaces + proc macros work & restructure everything

This commit is contained in:
2025-12-09 01:52:45 -05:00
parent 434e3c3af7
commit e44bb8eca4
76 changed files with 610 additions and 576 deletions

View File

@@ -0,0 +1,35 @@
use crate::prelude::*;
pub struct Aligned {
pub inner: WidgetRef,
pub align: Align,
}
impl Widget for Aligned {
fn draw(&mut self, painter: &mut Painter) {
let region = match self.align.tuple() {
(Some(x), Some(y)) => painter
.size(&self.inner)
.to_uivec2()
.align(RegionAlign { x, y }),
(Some(x), None) => {
let x = painter.size_ctx().width(&self.inner).apply_rest().align(x);
UiRegion::new(x, UiSpan::FULL)
}
(None, Some(y)) => {
let y = painter.size_ctx().height(&self.inner).apply_rest().align(y);
UiRegion::new(UiSpan::FULL, y)
}
(None, None) => UiRegion::FULL,
};
painter.widget_within(&self.inner, region);
}
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
ctx.width(&self.inner)
}
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
ctx.height(&self.inner)
}
}

View File

@@ -0,0 +1,23 @@
use crate::prelude::*;
pub struct LayerOffset {
pub inner: WidgetRef,
pub offset: usize,
}
impl Widget for LayerOffset {
fn draw(&mut self, painter: &mut Painter) {
for _ in 0..self.offset {
painter.next_layer();
}
painter.widget(&self.inner);
}
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
ctx.width(&self.inner)
}
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
ctx.height(&self.inner)
}
}

View File

@@ -0,0 +1,48 @@
use crate::prelude::*;
pub struct MaxSize {
pub inner: WidgetRef,
pub x: Option<Len>,
pub y: Option<Len>,
}
impl MaxSize {
fn apply_to_outer(&self, ctx: &mut SizeCtx) {
if let Some(x) = self.x {
ctx.outer.x.select_len(x.apply_rest());
}
if let Some(y) = self.y {
ctx.outer.y.select_len(y.apply_rest());
}
}
}
impl Widget for MaxSize {
fn draw(&mut self, painter: &mut Painter) {
painter.widget(&self.inner);
}
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
self.apply_to_outer(ctx);
let width = ctx.width(&self.inner);
if let Some(x) = self.x {
let width_px = width.apply_rest().to_abs(ctx.output_size().x);
let x_px = x.apply_rest().to_abs(ctx.output_size().x);
if width_px > x_px { x } else { width }
} else {
width
}
}
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
self.apply_to_outer(ctx);
let height = ctx.height(&self.inner);
if let Some(y) = self.y {
let height_px = height.apply_rest().to_abs(ctx.output_size().y);
let y_px = y.apply_rest().to_abs(ctx.output_size().y);
if height_px > y_px { y } else { height }
} else {
height
}
}
}

View File

@@ -0,0 +1,19 @@
mod align;
mod layer;
mod max_size;
mod offset;
mod pad;
mod scroll;
mod sized;
mod span;
mod stack;
pub use align::*;
pub use layer::*;
pub use max_size::*;
pub use offset::*;
pub use pad::*;
pub use scroll::*;
pub use sized::*;
pub use span::*;
pub use stack::*;

View File

@@ -0,0 +1,21 @@
use crate::prelude::*;
pub struct Offset {
pub inner: WidgetRef,
pub amt: UiVec2,
}
impl Widget for Offset {
fn draw(&mut self, painter: &mut Painter) {
let region = UiRegion::FULL.offset(self.amt);
painter.widget_within(&self.inner, region);
}
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
ctx.width(&self.inner)
}
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
ctx.height(&self.inner)
}
}

134
src/widget/position/pad.rs Normal file
View File

@@ -0,0 +1,134 @@
use crate::prelude::*;
pub struct Pad {
pub padding: Padding,
pub inner: WidgetRef,
}
impl Widget for Pad {
fn draw(&mut self, painter: &mut Painter) {
painter.widget_within(&self.inner, self.padding.region());
}
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
let width = self.padding.left + self.padding.right;
let height = self.padding.top + self.padding.bottom;
ctx.outer.x.abs -= width;
ctx.outer.y.abs -= height;
let mut size = ctx.width(&self.inner);
size.abs += width;
size
}
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
let width = self.padding.left + self.padding.right;
let height = self.padding.top + self.padding.bottom;
ctx.outer.x.abs -= width;
ctx.outer.y.abs -= height;
let mut size = ctx.height(&self.inner);
size.abs += height;
size
}
}
pub struct Padding {
pub left: f32,
pub right: f32,
pub top: f32,
pub bottom: f32,
}
impl Padding {
pub const ZERO: Self = Self {
left: 0.0,
right: 0.0,
top: 0.0,
bottom: 0.0,
};
pub fn uniform(amt: impl UiNum) -> Self {
let amt = amt.to_f32();
Self {
left: amt,
right: amt,
top: amt,
bottom: amt,
}
}
pub fn region(&self) -> UiRegion {
let mut region = UiRegion::FULL;
region.x.start.abs += self.left;
region.y.start.abs += self.top;
region.x.end.abs -= self.right;
region.y.end.abs -= self.bottom;
region
}
pub fn x(amt: impl UiNum) -> Self {
let amt = amt.to_f32();
Self {
left: amt,
right: amt,
top: 0.0,
bottom: 0.0,
}
}
pub fn y(amt: impl UiNum) -> Self {
let amt = amt.to_f32();
Self {
left: 0.0,
right: 0.0,
top: amt,
bottom: amt,
}
}
pub fn top(amt: impl UiNum) -> Self {
let mut s = Self::ZERO;
s.top = amt.to_f32();
s
}
pub fn bottom(amt: impl UiNum) -> Self {
let mut s = Self::ZERO;
s.bottom = amt.to_f32();
s
}
pub fn left(amt: impl UiNum) -> Self {
let mut s = Self::ZERO;
s.left = amt.to_f32();
s
}
pub fn right(amt: impl UiNum) -> Self {
let mut s = Self::ZERO;
s.right = amt.to_f32();
s
}
pub fn with_top(mut self, amt: impl UiNum) -> Self {
self.top = amt.to_f32();
self
}
pub fn with_bottom(mut self, amt: impl UiNum) -> Self {
self.bottom = amt.to_f32();
self
}
pub fn with_left(mut self, amt: impl UiNum) -> Self {
self.left = amt.to_f32();
self
}
pub fn with_right(mut self, amt: impl UiNum) -> Self {
self.right = amt.to_f32();
self
}
}
impl<T: UiNum> From<T> for Padding {
fn from(amt: T) -> Self {
Self::uniform(amt.to_f32())
}
}

View File

@@ -0,0 +1,66 @@
use crate::prelude::*;
pub struct Scroll {
inner: WidgetRef,
axis: Axis,
amt: f32,
snap_end: bool,
container_len: f32,
content_len: f32,
}
impl Widget for Scroll {
fn draw(&mut self, painter: &mut Painter) {
let output_len = painter.output_size().axis(self.axis);
let container_len = painter.region().axis(self.axis).len();
let content_len = painter
.len_axis(&self.inner, self.axis)
.apply_rest()
.within_len(container_len)
.to_abs(output_len);
self.container_len = container_len.to_abs(output_len);
self.content_len = content_len;
if self.snap_end {
self.amt = self.content_len - self.container_len;
}
self.update_amt();
let mut region = UiRegion::FULL.offset(Vec2::from_axis(self.axis, -self.amt, 0.0));
region.axis_mut(self.axis).end = region.axis(self.axis).start.offset(self.content_len);
painter.widget_within(&self.inner, region);
}
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
ctx.width(&self.inner)
}
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
ctx.height(&self.inner)
}
}
impl Scroll {
pub fn new(inner: WidgetRef, axis: Axis) -> Self {
Self {
inner,
axis,
amt: 0.0,
snap_end: true,
container_len: 0.0,
content_len: 0.0,
}
}
pub fn update_amt(&mut self) {
self.amt = self.amt.max(0.0);
let len = (self.content_len - self.container_len).max(0.0);
self.amt = self.amt.min(len);
self.snap_end = self.amt == len;
}
pub fn scroll(&mut self, amt: f32) {
self.amt -= amt;
self.update_amt();
}
}

View File

@@ -0,0 +1,34 @@
use crate::prelude::*;
pub struct Sized {
pub inner: WidgetRef,
pub x: Option<Len>,
pub y: Option<Len>,
}
impl Sized {
fn apply_to_outer(&self, ctx: &mut SizeCtx) {
if let Some(x) = self.x {
ctx.outer.x.select_len(x.apply_rest());
}
if let Some(y) = self.y {
ctx.outer.y.select_len(y.apply_rest());
}
}
}
impl Widget for Sized {
fn draw(&mut self, painter: &mut Painter) {
painter.widget(&self.inner);
}
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
self.apply_to_outer(ctx);
self.x.unwrap_or_else(|| ctx.width(&self.inner))
}
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
self.apply_to_outer(ctx);
self.y.unwrap_or_else(|| ctx.height(&self.inner))
}
}

196
src/widget/position/span.rs Normal file
View File

@@ -0,0 +1,196 @@
use crate::prelude::*;
use std::marker::PhantomData;
pub struct Span {
pub children: Vec<WidgetRef>,
pub dir: Dir,
pub gap: f32,
}
impl Widget for Span {
fn draw(&mut self, painter: &mut Painter) {
let total = self.len_sum(&mut painter.size_ctx());
let mut start = UiScalar::rel_min();
for child in &self.children {
let mut span = UiSpan::FULL;
span.start = start;
let len = painter.len_axis(child, self.dir.axis);
if len.rest > 0.0 {
let offset = UiScalar::new(total.rel, total.abs);
let rel_end = UiScalar::rel(len.rest / total.rest);
let end = (UiScalar::rel_max() + start) - offset;
start = rel_end.within(&start.to(end));
}
start.abs += len.abs;
start.rel += len.rel;
span.end = start;
let mut child_region = UiRegion::from_axis(self.dir.axis, span, UiSpan::FULL);
if self.dir.sign == Sign::Neg {
child_region.flip(self.dir.axis);
}
painter.widget_within(child, child_region);
start.abs += self.gap;
}
}
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
match self.dir.axis {
Axis::X => self.desired_len(ctx),
Axis::Y => self.desired_ortho(ctx),
}
}
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
match self.dir.axis {
Axis::X => self.desired_ortho(ctx),
Axis::Y => self.desired_len(ctx),
}
}
}
impl Span {
pub fn empty(dir: Dir) -> Self {
Self {
children: Vec::new(),
dir,
gap: 0.0,
}
}
pub fn gap(mut self, gap: impl UiNum) -> Self {
self.gap = gap.to_f32();
self
}
fn len_sum(&mut self, ctx: &mut SizeCtx) -> Len {
let gap = self.gap * self.children.len().saturating_sub(1) as f32;
self.children.iter().fold(Len::abs(gap), |mut s, id| {
// it's tempting to subtract the abs & rel from the ctx outer,
// but that would create inconsistent sizing if you put
// a rest first vs last & only speed up in one direction.
// I think this is only solvable by restricting how you can
// compute size, bc currently you need child to define parent's
// sectioning and you need parent's sectioning to define child.
// Fortunately, that doesn't matter in most cases
let len = ctx.len_axis(id, self.dir.axis);
s += len;
s
})
}
fn desired_len(&mut self, ctx: &mut SizeCtx) -> Len {
let len = self.len_sum(ctx);
if len.rest == 0.0 && len.rel == 0.0 {
len
} else {
Len::default()
}
}
fn desired_ortho(&mut self, ctx: &mut SizeCtx) -> Len {
// this is a weird hack to get text wrapping to work properly when in a downward span
// the correct solution here is to add a function to widget that lets them
// request that ctx.outer has an axis "resolved" before checking the other,
// and panicking or warning if two request opposite axis (unsolvable in that case)
let outer = ctx.outer.axis(self.dir.axis);
if self.dir.axis == Axis::X {
// so....... this literally copies draw so that the lengths are correctly set in the
// context, which makes this slow and not cool
let total = self.len_sum(ctx);
let mut start = UiScalar::rel_min();
let mut ortho_len = Len::ZERO;
for child in &self.children {
let mut span = UiSpan::FULL;
span.start = start;
let len = ctx.len_axis(child, self.dir.axis);
if len.rest > 0.0 {
let offset = UiScalar::new(total.rel, total.abs);
let rel_end = UiScalar::rel(len.rest / total.rest);
let end = (UiScalar::rel_max() + start) - offset;
start = rel_end.within(&start.to(end));
}
start.abs += len.abs;
start.rel += len.rel;
span.end = start;
let scalar = span.len();
*ctx.outer.axis_mut(self.dir.axis) = outer.select_len(scalar);
let ortho = ctx.len_axis(child, !self.dir.axis);
// TODO: rel shouldn't do this, but no easy way before actually calculating pixels
if ortho.rel > 0.0 || ortho.rest > 0.0 {
ortho_len.rest = 1.0;
ortho_len.abs = 0.0;
break;
}
ortho_len.abs = ortho_len.abs.max(ortho.abs);
start.abs += self.gap;
}
ortho_len
} else {
let mut ortho_len = Len::ZERO;
let ortho = !self.dir.axis;
for child in &self.children {
let len = ctx.len_axis(child, ortho);
// TODO: rel shouldn't do this, but no easy way before actually calculating pixels
if len.rel > 0.0 || len.rest > 0.0 {
ortho_len.rest = 1.0;
ortho_len.abs = 0.0;
break;
}
ortho_len.abs = ortho_len.abs.max(len.abs);
}
ortho_len
}
}
}
pub struct SpanBuilder<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
pub children: Wa,
pub dir: Dir,
pub gap: f32,
_pd: PhantomData<Tag>,
}
impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> FnOnce<(&mut Ui,)>
for SpanBuilder<LEN, Wa, Tag>
{
type Output = Span;
extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
Span {
children: self.children.ui(args.0).arr.to_vec(),
dir: self.dir,
gap: self.gap,
}
}
}
impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> SpanBuilder<LEN, Wa, Tag> {
pub fn new(children: Wa, dir: Dir) -> Self {
Self {
children,
dir,
gap: 0.0,
_pd: PhantomData,
}
}
pub fn gap(mut self, gap: impl UiNum) -> Self {
self.gap = gap.to_f32();
self
}
}
impl std::ops::Deref for Span {
type Target = Vec<WidgetRef>;
fn deref(&self) -> &Self::Target {
&self.children
}
}
impl std::ops::DerefMut for Span {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.children
}
}

View File

@@ -0,0 +1,77 @@
use std::marker::PhantomData;
use crate::prelude::*;
pub struct Stack {
pub children: Vec<WidgetRef>,
pub size: StackSize,
}
impl Widget for Stack {
fn draw(&mut self, painter: &mut Painter) {
let mut iter = self.children.iter();
if let Some(child) = iter.next() {
painter.child_layer();
painter.widget(child);
}
for child in iter {
painter.next_layer();
painter.widget(child);
}
}
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
match self.size {
StackSize::Default => Len::default(),
StackSize::Child(i) => ctx.width(&self.children[i]),
}
}
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
match self.size {
StackSize::Default => Len::default(),
StackSize::Child(i) => ctx.height(&self.children[i]),
}
}
}
#[derive(Default, Debug)]
pub enum StackSize {
#[default]
Default,
Child(usize),
}
pub struct StackBuilder<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
pub children: Wa,
pub size: StackSize,
_pd: PhantomData<Tag>,
}
impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> FnOnce<(&mut Ui,)>
for StackBuilder<LEN, Wa, Tag>
{
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(),
size: self.size,
}
}
}
impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> StackBuilder<LEN, Wa, Tag> {
pub fn new(children: Wa) -> Self {
Self {
children,
size: StackSize::default(),
_pd: PhantomData,
}
}
pub fn size(mut self, size: StackSize) -> Self {
self.size = size;
self
}
}