5 Commits

Author SHA1 Message Date
132113f09e center w size 2025-08-10 20:40:14 -04:00
f2cbf90d1d center anchors on 0 0 2025-08-10 20:29:16 -04:00
848347e6b3 clean up 2025-08-10 19:10:26 -04:00
577d62b1da small stuff 2025-08-10 14:35:01 -04:00
b6e43c157b assoc types 2025-08-10 05:19:00 -04:00
11 changed files with 142 additions and 92 deletions

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "gui" name = "gui"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -1,8 +1,8 @@
use std::ops::Range; use std::ops::Range;
use crate::{ use crate::{
primitive::{Axis, Painter, RoundedRectData, UIRegion},
UIColor, Widget, WidgetArrLike, WidgetFn, WidgetId, WidgetLike, UIColor, Widget, WidgetArrLike, WidgetFn, WidgetId, WidgetLike,
primitive::{Axis, Painter, RoundedRectData, UIRegion, Vec2},
}; };
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@@ -56,13 +56,13 @@ impl Span {
) -> Self { ) -> Self {
let ratios = ratios.map(|r| r.to_f32()); let ratios = ratios.map(|r| r.to_f32());
let total: f32 = ratios.iter().sum(); let total: f32 = ratios.iter().sum();
let mut start = 0.0; let mut start = -1.0;
Self { Self {
elements: elements elements: elements
.into_iter() .into_iter()
.zip(ratios) .zip(ratios)
.map(|(e, r)| { .map(|(e, r)| {
let end = start + r / total; let end = start + (r / total) * 2.0;
let res = (start..end, e); let res = (start..end, e);
start = end; start = end;
res res
@@ -117,25 +117,33 @@ impl<T: UINum> From<T> for Padding {
} }
} }
pub trait WidgetUtil<W> { pub trait WidgetUtil {
fn pad(self, padding: impl Into<Padding>) -> impl WidgetLike<Regioned>; fn pad(self, padding: impl Into<Padding>) -> impl WidgetLike<Widget = Regioned>;
fn center(self, size: impl Into<Vec2>) -> impl WidgetLike<Widget = Regioned>;
} }
impl<W: Widget, WL: WidgetLike<W>> WidgetUtil<W> for WL { impl<W: WidgetLike> WidgetUtil for W {
fn pad(self, padding: impl Into<Padding>) -> impl WidgetLike<Regioned> { fn pad(self, padding: impl Into<Padding>) -> impl WidgetLike<Widget = Regioned> {
WidgetFn(|ui| Regioned { WidgetFn(|ui| Regioned {
region: padding.into().region(), region: padding.into().region(),
inner: self.id(ui).erase_type(), inner: self.add(ui).erase_type(),
})
}
fn center(self, size: impl Into<Vec2>) -> impl WidgetLike<Widget = Regioned> {
WidgetFn(|ui| Regioned {
region: UIRegion::center(size.into()),
inner: self.add(ui).erase_type(),
}) })
} }
} }
pub trait WidgetArrUtil<const LEN: usize, Ws> { pub trait WidgetArrUtil<const LEN: usize> {
fn span(self, axis: Axis, ratios: [impl UINum; LEN]) -> impl WidgetLike<Span>; fn span(self, axis: Axis, ratios: [impl UINum; LEN]) -> impl WidgetLike<Widget = Span>;
} }
impl<const LEN: usize, Ws, Wa: WidgetArrLike<LEN, Ws>> WidgetArrUtil<LEN, Ws> for Wa { impl<const LEN: usize, Wa: WidgetArrLike<LEN>> WidgetArrUtil<LEN> for Wa {
fn span(self, axis: Axis, ratios: [impl UINum; LEN]) -> impl WidgetLike<Span> { fn span(self, axis: Axis, ratios: [impl UINum; LEN]) -> impl WidgetLike<Widget = Span> {
WidgetFn(move |ui| Span::proportioned(axis, ratios, self.ui(ui).arr)) WidgetFn(move |ui| Span::proportioned(axis, ratios, self.ui(ui).arr))
} }
} }

View File

@@ -31,8 +31,12 @@ impl From<UI> for UIBuilder {
} }
impl UIBuilder { impl UIBuilder {
pub fn add<W: Widget>(&mut self, w: W) -> WidgetRef<W> { pub fn add<W: Widget>(&mut self, w: impl WidgetLike<Widget = W>) -> WidgetRef<W> {
WidgetRef::new(self.clone(), [self.push(w)]) WidgetRef::new([w.add(self).erase_type()])
}
pub fn add_widget<W: Widget>(&mut self, w: W) -> WidgetRef<W> {
WidgetRef::new([self.push(w)])
} }
pub fn push<W: Widget>(&mut self, w: W) -> WidgetId { pub fn push<W: Widget>(&mut self, w: W) -> WidgetId {
@@ -42,8 +46,8 @@ impl UIBuilder {
WidgetId::new(id, TypeId::of::<W>()) WidgetId::new(id, TypeId::of::<W>())
} }
pub fn finish<WL: WidgetLike<W>, W>(mut self, base: WL) -> UI { pub fn finish<W: WidgetLike>(mut self, base: W) -> UI {
let base = base.id(&mut self).erase_type(); let base = base.add(&mut self).erase_type();
let mut ui = Rc::into_inner(self.ui).unwrap().into_inner(); let mut ui = Rc::into_inner(self.ui).unwrap().into_inner();
ui.base = Some(base); ui.base = Some(base);
ui ui

View File

@@ -15,21 +15,19 @@ impl<W: Widget> Widget for (W,) {
} }
} }
pub struct AnyWidget;
#[derive(Eq, Hash, PartialEq, Debug)] #[derive(Eq, Hash, PartialEq, Debug)]
pub struct WidgetId<W = ()> { pub struct WidgetId<W = AnyWidget> {
pub(super) ty: TypeId, pub(super) ty: TypeId,
pub(super) id: ID, pub(super) id: ID,
_pd: PhantomData<W>, _pd: PhantomData<W>,
} }
// TODO: temp // TODO: temp
impl Clone for WidgetId { impl<W> Clone for WidgetId<W> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self::new(self.id.duplicate(), self.ty)
ty: self.ty,
id: self.id.duplicate(),
_pd: self._pd,
}
} }
} }
@@ -41,7 +39,7 @@ impl<W> WidgetId<W> {
_pd: PhantomData, _pd: PhantomData,
} }
} }
pub fn erase_type(self) -> WidgetId<()> { pub fn erase_type(self) -> WidgetId<AnyWidget> {
self.cast_type() self.cast_type()
} }
@@ -54,49 +52,52 @@ impl<W> WidgetId<W> {
} }
} }
pub trait WidgetLike<W> { pub trait WidgetLike {
fn id(self, ui: &mut UIBuilder) -> WidgetId<W>; type Widget;
fn add(self, ui: &mut UIBuilder) -> WidgetId<Self::Widget>;
} }
/// wouldn't be needed if negative trait bounds & disjoint impls existed /// wouldn't be needed if negative trait bounds & disjoint impls existed
pub struct WidgetFn<F: FnOnce(&mut UIBuilder) -> W, W>(pub F); pub struct WidgetFn<F: FnOnce(&mut UIBuilder) -> W, W>(pub F);
impl<W: Widget, F: FnOnce(&mut UIBuilder) -> W> WidgetLike<W> for WidgetFn<F, W> { impl<W: Widget, F: FnOnce(&mut UIBuilder) -> W> WidgetLike for WidgetFn<F, W> {
fn id(self, ui: &mut UIBuilder) -> WidgetId<W> { type Widget = W;
fn add(self, ui: &mut UIBuilder) -> WidgetId<W> {
let w = (self.0)(ui); let w = (self.0)(ui);
ui.add(w).to_id() ui.add_widget(w).to_id()
} }
} }
impl<W: Widget> WidgetLike<W> for W { impl<W: Widget> WidgetLike for W {
fn id(self, ui: &mut UIBuilder) -> WidgetId<W> { type Widget = W;
ui.add(self).to_id() fn add(self, ui: &mut UIBuilder) -> WidgetId<W> {
ui.add_widget(self).to_id()
} }
} }
impl<W> WidgetLike<W> for WidgetId<W> { impl<W> WidgetLike for WidgetId<W> {
fn id(self, _: &mut UIBuilder) -> WidgetId<W> { type Widget = W;
fn add(self, _: &mut UIBuilder) -> WidgetId<W> {
self self
} }
} }
impl<W> WidgetLike<W> for WidgetArr<1, (W,)> { impl<W> WidgetLike for WidgetArr<1, (W,)> {
fn id(self, _: &mut UIBuilder) -> WidgetId<W> { type Widget = W;
fn add(self, _: &mut UIBuilder) -> WidgetId<W> {
let [id] = self.arr; let [id] = self.arr;
id.cast_type() id.cast_type()
} }
} }
pub struct WidgetArr<const LEN: usize, Ws> { pub struct WidgetArr<const LEN: usize, Ws> {
pub ui: UIBuilder, pub arr: [WidgetId; LEN],
pub arr: [WidgetId<()>; LEN],
_pd: PhantomData<Ws>, _pd: PhantomData<Ws>,
} }
impl<const LEN: usize, Ws> WidgetArr<LEN, Ws> { impl<const LEN: usize, Ws> WidgetArr<LEN, Ws> {
pub fn new(ui: UIBuilder, arr: [WidgetId<()>; LEN]) -> Self { pub fn new(arr: [WidgetId; LEN]) -> Self {
Self { Self {
ui,
arr, arr,
_pd: PhantomData, _pd: PhantomData,
} }
@@ -116,42 +117,43 @@ impl<W> WidgetRef<W> {
} }
} }
pub trait WidgetArrLike<const LEN: usize, Ws> { pub trait WidgetArrLike<const LEN: usize> {
fn ui(self, ui: &mut UIBuilder) -> WidgetArr<LEN, Ws>; type Ws;
fn ui(self, ui: &mut UIBuilder) -> WidgetArr<LEN, Self::Ws>;
} }
impl<const LEN: usize, Ws> WidgetArrLike<LEN, Ws> for WidgetArr<LEN, Ws> { impl<const LEN: usize, Ws> WidgetArrLike<LEN> for WidgetArr<LEN, Ws> {
type Ws = Ws;
fn ui(self, _: &mut UIBuilder) -> WidgetArr<LEN, Ws> { fn ui(self, _: &mut UIBuilder) -> WidgetArr<LEN, Ws> {
self self
} }
} }
// I hate this language it's so bad why do I even use it // I hate this language it's so bad why do I even use it
macro_rules! impl_node_arr { macro_rules! impl_widget_arr {
($n:expr;$($T:tt)*) => { ($n:expr;$($T:tt)*) => {
impl<$($T,${concat($T,$T)}: WidgetLike<$T>,)*> WidgetArrLike<$n, ($($T,)*)> for ($(${concat($T,$T)},)*) { impl<$($T: WidgetLike,)*> WidgetArrLike<$n> for ($($T,)*) {
#[allow(unused_variables)] type Ws = ($($T::Widget,)*);
fn ui(self, ui: &mut UIBuilder) -> WidgetArr<$n, ($($T,)*)> { fn ui(self, ui: &mut UIBuilder) -> WidgetArr<$n, ($($T::Widget,)*)> {
#[allow(non_snake_case)] #[allow(non_snake_case)]
let ($($T,)*) = self; let ($($T,)*) = self;
WidgetArr::new( WidgetArr::new(
ui.clone(), [$($T.add(ui).cast_type(),)*],
[$($T.id(ui).cast_type(),)*],
) )
} }
} }
}; };
} }
impl_node_arr!(1;A); impl_widget_arr!(1;A);
impl_node_arr!(2;A B); impl_widget_arr!(2;A B);
impl_node_arr!(3;A B C); impl_widget_arr!(3;A B C);
impl_node_arr!(4;A B C D); impl_widget_arr!(4;A B C D);
impl_node_arr!(5;A B C D E); impl_widget_arr!(5;A B C D E);
impl_node_arr!(6;A B C D E F); impl_widget_arr!(6;A B C D E F);
impl_node_arr!(7;A B C D E F G); impl_widget_arr!(7;A B C D E F G);
impl_node_arr!(8;A B C D E F G H); impl_widget_arr!(8;A B C D E F G H);
impl_node_arr!(9;A B C D E F G H I); impl_widget_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_widget_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_widget_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); impl_widget_arr!(12;A B C D E F G H I J K L);

View File

@@ -30,6 +30,10 @@ impl<T: ColorNum> Color<T> {
pub const fn rgb(r: T, g: T, b: T) -> Self { pub const fn rgb(r: T, g: T, b: T) -> Self {
Self { r, g, b, a: T::MAX } Self { r, g, b, a: T::MAX }
} }
pub fn alpha(mut self, a: T) -> Self {
self.a = a;
self
}
} }
pub trait ColorNum { pub trait ColorNum {

View File

@@ -1,10 +1,10 @@
use crate::primitive::{point::point, Point}; use crate::primitive::{Vec2, vec2::point};
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)] #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
pub struct UIPos { pub struct UIPos {
pub anchor: Point, pub anchor: Vec2,
pub offset: Point, pub offset: Vec2,
} }
impl UIPos { impl UIPos {
@@ -15,24 +15,32 @@ impl UIPos {
} }
} }
pub const fn top_left() -> Self { pub const fn center() -> Self {
Self::anchor_offset(0.0, 0.0, 0.0, 0.0) Self::anchor_offset(0.0, 0.0, 0.0, 0.0)
} }
pub const fn top_left() -> Self {
Self::anchor_offset(-1.0, -1.0, 0.0, 0.0)
}
pub const fn bottom_right() -> Self { pub const fn bottom_right() -> Self {
Self::anchor_offset(1.0, 1.0, 0.0, 0.0) Self::anchor_offset(1.0, 1.0, 0.0, 0.0)
} }
pub const fn offset(mut self, offset: Vec2) -> Self {
self.offset = offset;
self
}
pub const fn within(&self, region: &UIRegion) -> UIPos { pub const fn within(&self, region: &UIRegion) -> UIPos {
let range = region.bot_right.anchor - region.top_left.anchor; let lerp = self.anchor_01();
let region_offset = region let anchor = region.top_left.anchor.lerp(region.bot_right.anchor, lerp);
.top_left let offset = self.offset + region.top_left.offset.lerp(region.bot_right.offset, lerp);
.offset UIPos { anchor, offset }
.lerp(region.bot_right.offset, self.anchor); }
UIPos {
anchor: region.top_left.anchor + self.anchor * range, pub const fn anchor_01(&self) -> Vec2 {
offset: self.offset + region_offset, (self.anchor + 1.0) / 2.0
}
} }
pub fn axis_mut(&mut self, axis: Axis) -> UIPosAxisView<'_> { pub fn axis_mut(&mut self, axis: Axis) -> UIPosAxisView<'_> {
@@ -68,6 +76,12 @@ impl UIRegion {
bot_right: UIPos::bottom_right(), bot_right: UIPos::bottom_right(),
} }
} }
pub fn center(size: Vec2) -> Self {
Self {
top_left: UIPos::center().offset(-size / 2.0),
bot_right: UIPos::center().offset(size / 2.0),
}
}
pub fn within(&self, parent: &Self) -> Self { pub fn within(&self, parent: &Self) -> Self {
Self { Self {
top_left: self.top_left.within(parent), top_left: self.top_left.within(parent),

View File

@@ -1,12 +1,12 @@
mod color; mod color;
mod def; mod def;
mod format; mod format;
mod point; mod vec2;
pub use color::*; pub use color::*;
pub use def::*; pub use def::*;
pub use format::*; pub use format::*;
pub use point::*; pub use vec2::*;
use crate::{render::data::PrimitiveInstance, WidgetId, Widgets}; use crate::{render::data::PrimitiveInstance, WidgetId, Widgets};
use bytemuck::Pod; use bytemuck::Pod;

View File

@@ -1,17 +1,17 @@
use std::ops::*; use std::ops::*;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Debug, Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Point { pub struct Vec2 {
pub x: f32, pub x: f32,
pub y: f32, pub y: f32,
} }
pub const fn point(x: f32, y: f32) -> Point { pub const fn point(x: f32, y: f32) -> Vec2 {
Point::new(x, y) Vec2::new(x, y)
} }
impl Point { impl Vec2 {
pub const fn new(x: f32, y: f32) -> Self { pub const fn new(x: f32, y: f32) -> Self {
Self { x, y } Self { x, y }
} }
@@ -29,7 +29,7 @@ const fn lerp(x: f32, y: f32, amt: f32) -> f32 {
(1.0 - amt) * x + y * amt (1.0 - amt) * x + y * amt
} }
impl const From<f32> for Point { impl const From<f32> for Vec2 {
fn from(v: f32) -> Self { fn from(v: f32) -> Self {
Self { x: v, y: v } Self { x: v, y: v }
} }
@@ -37,7 +37,7 @@ impl const From<f32> for Point {
macro_rules! impl_op_inner { macro_rules! impl_op_inner {
($op:ident $fn:ident $opa:ident $fna:ident) => { ($op:ident $fn:ident $opa:ident $fna:ident) => {
impl const $op for Point { impl const $op for Vec2 {
type Output = Self; type Output = Self;
fn $fn(self, rhs: Self) -> Self::Output { fn $fn(self, rhs: Self) -> Self::Output {
@@ -47,13 +47,13 @@ macro_rules! impl_op_inner {
} }
} }
} }
impl $opa for Point { impl $opa for Vec2 {
fn $fna(&mut self, rhs: Self) { fn $fna(&mut self, rhs: Self) {
self.x.$fna(rhs.x); self.x.$fna(rhs.x);
self.y.$fna(rhs.y); self.y.$fna(rhs.y);
} }
} }
impl const $op<f32> for Point { impl const $op<f32> for Vec2 {
type Output = Self; type Output = Self;
fn $fn(self, rhs: f32) -> Self::Output { fn $fn(self, rhs: f32) -> Self::Output {
@@ -63,7 +63,7 @@ macro_rules! impl_op_inner {
} }
} }
} }
impl $opa<f32> for Point { impl $opa<f32> for Vec2 {
fn $fna(&mut self, rhs: f32) { fn $fna(&mut self, rhs: f32) {
self.x.$fna(rhs); self.x.$fna(rhs);
self.y.$fna(rhs); self.y.$fna(rhs);
@@ -82,3 +82,19 @@ impl_op!(Add add);
impl_op!(Sub sub); impl_op!(Sub sub);
impl_op!(Mul mul); impl_op!(Mul mul);
impl_op!(Div div); impl_op!(Div div);
impl Neg for Vec2 {
type Output = Self;
fn neg(mut self) -> Self::Output {
self.x = -self.x;
self.y = -self.y;
self
}
}
impl From<(f32, f32)> for Vec2 {
fn from((x, y): (f32, f32)) -> Self {
Self { x, y }
}
}

View File

@@ -42,8 +42,8 @@ fn vs_main(
) -> VertexOutput { ) -> VertexOutput {
var out: VertexOutput; var out: VertexOutput;
let top_left = in.top_left_anchor * window.dim + in.top_left_offset; let top_left = (in.top_left_anchor + 1.0) / 2.0 * window.dim + in.top_left_offset;
let bot_right = in.bottom_right_anchor * window.dim + in.bottom_right_offset; let bot_right = (in.bottom_right_anchor + 1.0) / 2.0 * window.dim + in.bottom_right_offset;
let size = bot_right - top_left; let size = bot_right - top_left;
var pos = top_left + vec2<f32>( var pos = top_left + vec2<f32>(

View File

@@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use app::App; use app::App;
use gui::{primitive::Axis, RoundedRect, UIColor, WidgetArrLike, WidgetArrUtil, WidgetUtil, UI}; use gui::{RoundedRect, UI, UIColor, WidgetArrUtil, WidgetUtil, primitive::Axis};
use render::Renderer; use render::Renderer;
use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window}; use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window};
@@ -33,7 +33,7 @@ impl Client {
( (
blue, blue,
( (
rect.color(UIColor::RED), rect.color(UIColor::RED).center((100.0, 100.0)),
( (
rect.color(UIColor::ORANGE), rect.color(UIColor::ORANGE),
rect.color(UIColor::LIME).pad(10.0), rect.color(UIColor::LIME).pad(10.0),
@@ -50,6 +50,7 @@ impl Client {
.span(Axis::Y, [3, 1]) .span(Axis::Y, [3, 1])
.pad(10), .pad(10),
); );
// let mut ui = ui.finish((blue, rect.color(UIColor::RED)).span(Axis::X, [1, 1]));
ui.widgets.get_mut(&handle).unwrap().color = UIColor::MAGENTA; ui.widgets.get_mut(&handle).unwrap().color = UIColor::MAGENTA;
renderer.update(&ui); renderer.update(&ui);
Self { renderer } Self { renderer }

View File

@@ -27,6 +27,7 @@ impl IDTracker {
id id
} }
#[allow(dead_code)]
pub fn free(&mut self, id: ID) { pub fn free(&mut self, id: ID) {
self.free.push(id); self.free.push(id);
} }