alignment!!!

This commit is contained in:
2025-08-28 21:55:34 -04:00
parent 46c7d8ba26
commit 1204e3728e
11 changed files with 190 additions and 86 deletions

17
src/core/align.rs Normal file
View File

@@ -0,0 +1,17 @@
use crate::prelude::*;
pub struct Aligned {
pub inner: WidgetId,
pub align: Align,
}
impl Widget for Aligned {
fn draw(&mut self, painter: &mut Painter) {
let region = UiRegion::from_size_align(painter.size(&self.inner), self.align);
painter.draw_within(&self.inner, region);
}
fn size(&mut self, ctx: &mut SizeCtx) -> Vec2 {
ctx.size(&self.inner)
}
}

View File

@@ -1,16 +1,20 @@
mod align;
mod frame; mod frame;
mod image; mod image;
mod rect; mod rect;
mod sense; mod sense;
mod sized;
mod span; mod span;
mod stack; mod stack;
mod text; mod text;
mod trait_fns; mod trait_fns;
pub use align::*;
pub use frame::*; pub use frame::*;
pub use image::*; pub use image::*;
pub use rect::*; pub use rect::*;
pub use sense::*; pub use sense::*;
pub use sized::*;
pub use span::*; pub use span::*;
pub use stack::*; pub use stack::*;
pub use text::*; pub use text::*;

16
src/core/sized.rs Normal file
View File

@@ -0,0 +1,16 @@
use crate::prelude::*;
pub struct Sized {
pub inner: WidgetId,
pub size: Vec2,
}
impl Widget for Sized {
fn draw(&mut self, painter: &mut Painter) {
painter.draw(&self.inner);
}
fn size(&mut self, _: &mut SizeCtx) -> Vec2 {
self.size
}
}

View File

@@ -33,9 +33,7 @@ impl Widget for Span {
let offset = size.axis(self.dir.axis); let offset = size.axis(self.dir.axis);
start.offset += offset; start.offset += offset;
*axis.bot_right.offset = start.offset; *axis.bot_right.offset = start.offset;
child_region.bot_right.anchor = child_region.top_left.anchor; *axis.bot_right.anchor = *axis.top_left.anchor;
let opposite = !self.dir.axis;
*child_region.bot_right.offset.axis_mut(opposite) += size.axis(opposite);
} }
} }
if self.dir.sign == Sign::Neg { if self.dir.sign == Sign::Neg {

View File

@@ -1,13 +1,13 @@
use super::*; use super::*;
use crate::layout::{ use crate::prelude::*;
Dir, UiPos, UiRegion, Vec2, WidgetArrLike, WidgetFnRet, WidgetIdFnRet, WidgetLike,
};
pub trait CoreWidget<W, Tag> { pub trait CoreWidget<W, Tag> {
fn pad(self, padding: impl Into<Padding>) -> WidgetFnRet!(Regioned); fn pad(self, padding: impl Into<Padding>) -> WidgetFnRet!(Regioned);
fn center(self, size: impl Into<Vec2>) -> WidgetFnRet!(Regioned); fn align(self, align: Align) -> WidgetFnRet!(Aligned);
fn center(self) -> WidgetFnRet!(Aligned);
fn region(self, region: UiRegion) -> WidgetFnRet!(Regioned); fn region(self, region: UiRegion) -> WidgetFnRet!(Regioned);
fn label(self, label: impl Into<String>) -> WidgetIdFnRet!(W); fn label(self, label: impl Into<String>) -> WidgetIdFnRet!(W);
fn sized(self, size: impl Into<Vec2>) -> WidgetFnRet!(Sized);
} }
impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W { impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
@@ -18,13 +18,17 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
} }
} }
fn center(self, size: impl Into<Vec2>) -> WidgetFnRet!(Regioned) { fn align(self, align: Align) -> WidgetFnRet!(Aligned) {
|ui| Regioned { move |ui| Aligned {
region: UiPos::center().expand(size.into()),
inner: self.add(ui).erase_type(), inner: self.add(ui).erase_type(),
align,
} }
} }
fn center(self) -> WidgetFnRet!(Aligned) {
self.align(Align::Center)
}
fn region(self, region: UiRegion) -> WidgetFnRet!(Regioned) { fn region(self, region: UiRegion) -> WidgetFnRet!(Regioned) {
move |ui| Regioned { move |ui| Regioned {
region, region,
@@ -39,19 +43,26 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
id id
} }
} }
fn sized(self, size: impl Into<Vec2>) -> WidgetFnRet!(Sized) {
move |ui| Sized {
inner: self.add(ui).erase_type(),
size: size.into(),
}
}
} }
pub trait CoreWidgetArr<const LEN: usize, Tag> { pub trait CoreWidgetArr<const LEN: usize, Tag> {
fn span(self, dir: Dir, lengths: [impl Into<SpanLen>; LEN]) -> WidgetFnRet!(Span); fn span(self, dir: Dir, lengths: impl IntoSpanLens<LEN>) -> WidgetFnRet!(Span);
fn stack(self) -> WidgetFnRet!(Stack); fn stack(self) -> WidgetFnRet!(Stack);
} }
impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> CoreWidgetArr<LEN, Tag> for Wa { impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> CoreWidgetArr<LEN, Tag> for Wa {
fn span(self, dir: Dir, lengths: [impl Into<SpanLen>; LEN]) -> WidgetFnRet!(Span) { fn span(self, dir: Dir, lengths: impl IntoSpanLens<LEN>) -> WidgetFnRet!(Span) {
let lengths = lengths.map(Into::into); let lengths = lengths.into_lens();
move |ui| Span { move |ui| Span {
dir,
children: self.ui(ui).arr.into_iter().zip(lengths).collect(), children: self.ui(ui).arr.into_iter().zip(lengths).collect(),
dir,
} }
} }
fn stack(self) -> WidgetFnRet!(Stack) { fn stack(self) -> WidgetFnRet!(Stack) {
@@ -60,3 +71,37 @@ impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> CoreWidgetArr<LEN, Tag>
} }
} }
} }
// pub struct SpanBuilder<const LEN: usize, Tag, Wa: WidgetArrLike<LEN, Tag>> {
// children: Wa,
// dir: Dir,
// align: Align,
// }
//
// impl WidgetLike<FnTag> for SpanBuilder {
// type Widget = Span;
//
// fn add(self, ui: &mut Ui) -> WidgetId<Self::Widget> {
// ui.add_widget(Span {
// children: self.children,
// dir: self.dir,
// align: self.align,
// })
// }
// }
pub trait IntoSpanLens<const LEN: usize> {
fn into_lens(self) -> [SpanLen; LEN];
}
impl<const LEN: usize, T: Into<SpanLen>> IntoSpanLens<LEN> for [T; LEN] {
fn into_lens(self) -> [SpanLen; LEN] {
self.map(Into::into)
}
}
impl<const LEN: usize> IntoSpanLens<LEN> for SpanLen {
fn into_lens(self) -> [SpanLen; LEN] {
[self; LEN]
}
}

View File

@@ -1,20 +1,21 @@
#[const_trait]
pub trait UiNum { pub trait UiNum {
fn to_f32(self) -> f32; fn to_f32(self) -> f32;
} }
impl UiNum for f32 { impl const UiNum for f32 {
fn to_f32(self) -> f32 { fn to_f32(self) -> f32 {
self self
} }
} }
impl UiNum for u32 { impl const UiNum for u32 {
fn to_f32(self) -> f32 { fn to_f32(self) -> f32 {
self as f32 self as f32
} }
} }
impl UiNum for i32 { impl const UiNum for i32 {
fn to_f32(self) -> f32 { fn to_f32(self) -> f32 {
self as f32 self as f32
} }

View File

@@ -2,25 +2,6 @@ use std::ops::Not;
use crate::layout::{Vec2, vec2}; use crate::layout::{Vec2, vec2};
#[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),
}
}
}
#[derive(Copy, Clone, Eq, PartialEq)] #[derive(Copy, Clone, Eq, PartialEq)]
pub enum Axis { pub enum Axis {
X, X,
@@ -89,3 +70,33 @@ impl Vec2 {
} }
} }
} }
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Align {
TopLeft,
Top,
TopRight,
Left,
Center,
Right,
BotLeft,
Bot,
BotRight,
}
impl Align {
pub const fn anchor(&self) -> Vec2 {
match self {
Self::TopLeft => vec2(0.0, 0.0),
Self::Top => vec2(0.5, 0.0),
Self::TopRight => vec2(1.0, 0.0),
Self::Left => vec2(0.0, 0.5),
Self::Center => vec2(0.5, 0.5),
Self::Right => vec2(1.0, 0.5),
Self::BotLeft => vec2(0.0, 1.0),
Self::Bot => vec2(0.5, 1.0),
Self::BotRight => vec2(1.0, 1.0),
}
}
}

View File

@@ -156,7 +156,7 @@ impl<'a> Painter<'a> {
.size(&mut self.size_ctx()) .size(&mut self.size_ctx())
} }
pub fn size_ctx(&mut self) -> SizeCtx { pub fn size_ctx(&mut self) -> SizeCtx<'_> {
SizeCtx { SizeCtx {
size: self.region().in_size(self.screen_size), size: self.region().in_size(self.screen_size),
text: self.text, text: self.text,

View File

@@ -1,5 +1,5 @@
use crate::{ use crate::{
layout::{Axis, Corner, Vec2, vec2}, layout::{Align, Axis, Vec2},
util::{F32Util, impl_op}, util::{F32Util, impl_op},
}; };
@@ -20,10 +20,6 @@ impl UiPos {
} }
} }
pub const fn center() -> Self {
Self::anchor(vec2(0.5, 0.5))
}
pub const fn anchor(anchor: Vec2) -> Self { pub const fn anchor(anchor: Vec2) -> Self {
Self { Self {
anchor, anchor,
@@ -38,10 +34,6 @@ impl UiPos {
} }
} }
pub const fn corner(corner: Corner) -> Self {
Self::anchor(corner.anchor())
}
pub const fn shift(&mut self, offset: impl const Into<Vec2>) { pub const fn shift(&mut self, offset: impl const Into<Vec2>) {
self.offset += offset.into(); self.offset += offset.into();
} }
@@ -90,6 +82,18 @@ impl UiPos {
} }
} }
impl const From<Align> for UiPos {
fn from(align: Align) -> Self {
Self::anchor(align.anchor())
}
}
impl Align {
pub fn pos(self) -> UiPos {
UiPos::from(self)
}
}
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct UIScalar { pub struct UIScalar {
pub anchor: f32, pub anchor: f32,
@@ -137,8 +141,8 @@ pub struct UiRegion {
impl UiRegion { impl UiRegion {
pub const fn full() -> Self { pub const fn full() -> Self {
Self { Self {
top_left: UiPos::corner(Corner::TopLeft), top_left: Align::TopLeft.into(),
bot_right: UiPos::corner(Corner::BotRight), bot_right: Align::BotRight.into(),
} }
} }
pub fn anchor(anchor: Vec2) -> Self { pub fn anchor(anchor: Vec2) -> Self {
@@ -153,9 +157,6 @@ impl UiRegion {
bot_right: self.bot_right.within(parent), bot_right: self.bot_right.within(parent),
} }
} }
pub fn select(&mut self, inner: &Self) {
*self = inner.within(self);
}
pub fn axis_mut(&mut self, axis: Axis) -> UIRegionAxisView<'_> { pub fn axis_mut(&mut self, axis: Axis) -> UIRegionAxisView<'_> {
UIRegionAxisView { UIRegionAxisView {
top_left: self.top_left.axis_mut(axis), top_left: self.top_left.axis_mut(axis),
@@ -188,12 +189,27 @@ impl UiRegion {
} }
pub fn center(&self) -> UiPos { pub fn center(&self) -> UiPos {
UiPos::center().within(self) Align::Center.pos().within(self)
} }
pub fn in_size(&self, size: Vec2) -> Vec2 { pub fn in_size(&self, size: Vec2) -> Vec2 {
self.bot_right.to_size(size) - self.top_left.to_size(size) self.bot_right.to_size(size) - self.top_left.to_size(size)
} }
pub fn select_aligned(&self, size: Vec2, align: Align) -> Self {
Self::from_size_align(size, align).within(self)
}
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());
Self {
top_left,
bot_right,
}
}
} }
#[derive(Debug)] #[derive(Debug)]

View File

@@ -54,9 +54,12 @@ impl Vec2 {
} }
} }
impl const From<f32> for Vec2 { impl<T: const UiNum + Copy> const From<T> for Vec2 {
fn from(v: f32) -> Self { fn from(v: T) -> Self {
Self { x: v, y: v } Self {
x: v.to_f32(),
y: v.to_f32(),
}
} }
} }

View File

@@ -39,7 +39,7 @@ impl Client {
( (
rect.color(Color::BLUE), rect.color(Color::BLUE),
( (
rect.color(Color::RED).center((100.0, 100.0)), rect.color(Color::RED).sized((100.0, 100.0)),
(rect.color(Color::ORANGE), rect.color(Color::LIME).pad(10.0)) (rect.color(Color::ORANGE), rect.color(Color::LIME).pad(10.0))
.span(Dir::RIGHT, [1, 1]), .span(Dir::RIGHT, [1, 1]),
rect.color(Color::YELLOW), rect.color(Color::YELLOW),
@@ -91,62 +91,55 @@ impl Client {
let text_test = ui.add_static( let text_test = ui.add_static(
( (
text("this is a").size(30), text("this is a").size(30).align(Align::Left),
text("teeeeeeeest").size(30), text("teeeeeeeest").size(30).align(Align::Left),
text("okkk\nokkkkkk!").size(30), text("okkk\nokkkkkk!").size(30).align(Align::Left),
text("hmm").size(30), text("hmm").size(30),
text("a").size(30), text("a").size(30),
( (
text("'").size(30).family(Family::Monospace), text("'").size(30).family(Family::Monospace),
text("'").size(30).family(Family::Monospace), text("'").size(30).family(Family::Monospace),
text(":gamer mode").size(30).family(Family::Monospace), text(":gamer mode").size(30).family(Family::Monospace),
Rect::new(Color::BLUE).sized(100),
) )
.span(Dir::RIGHT, [sized(); _]), .span(Dir::RIGHT, sized())
.center(),
text("pretty cool right?").size(30), text("pretty cool right?").size(30),
) )
.span(Dir::DOWN, [sized(); _]), .span(Dir::DOWN, sized()),
); );
let tabs = ui.add( let tabs = (
( switch_button(Color::RED, pad_test, "pad test"),
switch_button(Color::RED, pad_test, "pad test"), switch_button(Color::GREEN, span_test, "span test"),
switch_button(Color::GREEN, span_test, "span test"), switch_button(Color::BLUE, span_add, "span add test"),
switch_button(Color::BLUE, span_add, "span add test"), switch_button(Color::MAGENTA, text_test, "text test"),
switch_button(Color::MAGENTA, text_test, "text test"), )
) .span(Dir::RIGHT, ratio(1));
.span(Dir::RIGHT, [1; _]),
);
let add_button = Rect::new(Color::LIME) let add_button = Rect::new(Color::LIME)
.radius(30) .radius(30)
.on(PRESS_START, move |ui| { .on(PRESS_START, move |ui| {
let child = ui let child = ui
.add(image(include_bytes!("assets/sungals.png"))) .add(image(include_bytes!("assets/sungals.png")).center())
.erase_type(); .erase_type();
ui[span_add].children.push((child, sized())); ui[span_add].children.push((child, sized()));
}) })
.region( .sized(150)
UiPos::corner(Corner::BotRight) .align(Align::BotRight);
.expand((150, 150))
.shifted((-75, -75)),
);
let del_button = Rect::new(Color::RED) let del_button = Rect::new(Color::RED)
.radius(30) .radius(30)
.on(PRESS_START, move |ui| { .on(PRESS_START, move |ui| {
ui[span_add].children.pop(); ui[span_add].children.pop();
}) })
.region( .sized(150)
UiPos::corner(Corner::BotLeft) .align(Align::BotLeft);
.expand((150, 150))
.shifted((75, -75)),
);
let info = ui.add(text("")); let info = ui.add(text(""));
let info_sect = info.clone().region( let info_sect = info
UiPos::corner(Corner::TopRight) .clone()
.expand((150, 150)) .region(Align::TopRight.pos().expand((150, 150)).shifted((-75, 0)));
.shifted((-75, 0)),
);
ui.set_base( ui.set_base(
( (
tabs.label("tabs"), tabs.label("tabs"),