5 Commits

Author SHA1 Message Date
fa930180c1 spans now good (other than direction) + refactor 2025-08-11 02:26:28 -04:00
95a07786bb spans now good (other than direction) + refactor 2025-08-11 02:24:27 -04:00
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
25 changed files with 684 additions and 546 deletions

View File

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

45
src/base/frame.rs Normal file
View File

@@ -0,0 +1,45 @@
use crate::{Painter, UINum, UIRegion, Widget, WidgetId};
pub struct Regioned {
pub region: UIRegion,
pub inner: WidgetId,
}
impl Widget for Regioned {
fn draw(&self, painter: &mut Painter) {
painter.region.select(&self.region);
painter.draw(&self.inner);
}
}
pub struct Padding {
left: f32,
right: f32,
top: f32,
bottom: f32,
}
impl Padding {
pub fn uniform(amt: f32) -> Self {
Self {
left: amt,
right: amt,
top: amt,
bottom: amt,
}
}
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
}
}
impl<T: UINum> From<T> for Padding {
fn from(amt: T) -> Self {
Self::uniform(amt.to_f32())
}
}

View File

@@ -1,167 +1,11 @@
use std::ops::Range;
mod frame;
mod num;
mod rect;
mod span;
mod trait_fns;
use crate::{
primitive::{Axis, Painter, RoundedRectData, UIRegion},
ToId, UIColor, Widget, WidgetArrLike, WidgetFn, WidgetId, WidgetIdLikeTuple, WidgetLike,
WidgetLikeTuple,
};
#[derive(Clone, Copy)]
pub struct RoundedRect {
pub color: UIColor,
pub radius: f32,
pub thickness: f32,
pub inner_radius: f32,
}
impl RoundedRect {
pub fn color(mut self, color: UIColor) -> Self {
self.color = color;
self
}
}
impl Widget for RoundedRect {
fn draw(&self, painter: &mut Painter) {
painter.write(RoundedRectData {
color: self.color,
radius: self.radius,
thickness: self.thickness,
inner_radius: self.inner_radius,
});
}
}
pub struct Span {
pub elements: Vec<(Range<f32>, WidgetId)>,
pub axis: Axis,
}
impl Widget for Span {
fn draw(&self, painter: &mut Painter) {
for (span, child) in &self.elements {
let mut sub_region = UIRegion::full();
let view = sub_region.axis_mut(self.axis);
*view.top_left.anchor = span.start;
*view.bot_right.anchor = span.end;
painter.draw_within(child, sub_region);
}
}
}
impl Span {
pub fn proportioned<const LEN: usize>(
axis: Axis,
ratios: [impl UINum; LEN],
elements: [WidgetId; LEN],
) -> Self {
let ratios = ratios.map(|r| r.to_f32());
let total: f32 = ratios.iter().sum();
let mut start = 0.0;
Self {
elements: elements
.into_iter()
.zip(ratios)
.map(|(e, r)| {
let end = start + r / total;
let res = (start..end, e);
start = end;
res
})
.collect(),
axis,
}
}
}
pub struct Regioned {
region: UIRegion,
inner: WidgetId,
}
impl Widget for Regioned {
fn draw(&self, painter: &mut Painter) {
painter.region.select(&self.region);
painter.draw(&self.inner);
}
}
pub struct Padding {
left: f32,
right: f32,
top: f32,
bottom: f32,
}
impl Padding {
pub fn uniform(amt: f32) -> Self {
Self {
left: amt,
right: amt,
top: amt,
bottom: amt,
}
}
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
}
}
impl<T: UINum> From<T> for Padding {
fn from(amt: T) -> Self {
Self::uniform(amt.to_f32())
}
}
pub trait WidgetUtil {
fn pad(self, padding: impl Into<Padding>) -> impl WidgetLike<Widget = Regioned>;
}
impl<W: WidgetLike> WidgetUtil for W {
fn pad(self, padding: impl Into<Padding>) -> impl WidgetLike<Widget = Regioned> {
WidgetFn(|ui| Regioned {
region: padding.into().region(),
inner: self.add(ui).erase_type(),
})
}
}
pub trait WidgetArrUtil<const LEN: usize> {
fn span(self, axis: Axis, ratios: [impl UINum; LEN]) -> impl WidgetLike<Widget = Span>;
}
impl<const LEN: usize, Wa: WidgetArrLike<LEN>> WidgetArrUtil<LEN> for Wa
where
<Wa::Ws as WidgetLikeTuple<LEN>>::Wrap<ToId>: WidgetIdLikeTuple<LEN>,
{
fn span(self, axis: Axis, ratios: [impl UINum; LEN]) -> impl WidgetLike<Widget = Span> {
WidgetFn(move |ui| Span::proportioned(axis, ratios, self.ui(ui).erase_types()))
}
}
pub trait UINum {
fn to_f32(self) -> f32;
}
impl UINum for f32 {
fn to_f32(self) -> f32 {
self
}
}
impl UINum for u32 {
fn to_f32(self) -> f32 {
self as f32
}
}
impl UINum for i32 {
fn to_f32(self) -> f32 {
self as f32
}
}
pub use frame::*;
pub use num::*;
pub use rect::*;
pub use span::*;
pub use trait_fns::*;

50
src/base/num.rs Normal file
View File

@@ -0,0 +1,50 @@
pub trait UINum {
fn to_f32(self) -> f32;
}
impl UINum for f32 {
fn to_f32(self) -> f32 {
self
}
}
impl UINum for u32 {
fn to_f32(self) -> f32 {
self as f32
}
}
impl UINum for i32 {
fn to_f32(self) -> f32 {
self as f32
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Axis {
X,
Y,
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct Dir {
pub axis: Axis,
pub sign: Sign,
}
impl Dir {
pub const fn new(axis: Axis, dir: Sign) -> Self {
Self { axis, sign: dir }
}
pub const LEFT: Self = Self::new(Axis::X, Sign::Neg);
pub const RIGHT: Self = Self::new(Axis::X, Sign::Pos);
pub const UP: Self = Self::new(Axis::Y, Sign::Neg);
pub const DOWN: Self = Self::new(Axis::Y, Sign::Pos);
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum Sign {
Neg,
Pos,
}

27
src/base/rect.rs Normal file
View File

@@ -0,0 +1,27 @@
use crate::{Painter, UIColor, Widget, primitive::RoundedRectData};
#[derive(Clone, Copy)]
pub struct Rect {
pub color: UIColor,
pub radius: f32,
pub thickness: f32,
pub inner_radius: f32,
}
impl Rect {
pub fn color(mut self, color: UIColor) -> Self {
self.color = color;
self
}
}
impl Widget for Rect {
fn draw(&self, painter: &mut Painter) {
painter.write(RoundedRectData {
color: self.color,
radius: self.radius,
thickness: self.thickness,
inner_radius: self.inner_radius,
});
}
}

89
src/base/span.rs Normal file
View File

@@ -0,0 +1,89 @@
use crate::{Dir, Painter, UINum, UIRegion, UIScalar, Widget, WidgetId};
pub struct Span {
pub children: Vec<(WidgetId, SpanLen)>,
pub dir: Dir,
}
impl Widget for Span {
fn draw(&self, painter: &mut Painter) {
let total = self.sums();
let mut start = UIScalar::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;
}
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);
}
SpanLen::Relative(rel) => {
start.anchor += rel;
axis.bot_right.set(start);
}
}
painter.draw_within(child, child_region);
}
}
}
#[derive(Default)]
pub struct SpanLenSums {
pub fixed: f32,
pub ratio: f32,
pub relative: f32,
}
impl Span {
pub fn sums(&self) -> SpanLenSums {
self.lengths().fold(SpanLenSums::default(), |mut s, l| {
match l {
SpanLen::Fixed(v) => s.fixed += v,
SpanLen::Ratio(v) => s.ratio += v,
SpanLen::Relative(v) => s.relative += v,
}
s
})
}
pub fn lengths(&self) -> impl ExactSizeIterator<Item = &SpanLen> {
self.children.iter().map(|(_, s)| s)
}
}
#[derive(Clone, Copy)]
pub enum SpanLen {
/// exact (non dynamic) size
Fixed(f32),
/// relative to remaining free space and other ratios
/// eg. 1 and 2 would take up 1/3 and 2/3 of the remaining space (after others)
Ratio(f32),
/// relative to the total space
/// eg. 0.5 means 1/2 of the total space
Relative(f32),
}
pub fn fixed<N: UINum>(x: N) -> SpanLen {
SpanLen::Fixed(x.to_f32())
}
pub fn ratio<N: UINum>(x: N) -> SpanLen {
SpanLen::Ratio(x.to_f32())
}
pub fn rel<N: UINum>(x: N) -> SpanLen {
SpanLen::Relative(x.to_f32())
}
impl<N: UINum> From<N> for SpanLen {
fn from(value: N) -> Self {
Self::Ratio(value.to_f32())
}
}

37
src/base/trait_fns.rs Normal file
View File

@@ -0,0 +1,37 @@
use super::*;
use crate::{UIRegion, Vec2, WidgetArrLike, WidgetFn, WidgetLike};
pub trait WidgetUtil {
fn pad(self, padding: impl Into<Padding>) -> impl WidgetLike<Widget = Regioned>;
fn center(self, size: impl Into<Vec2>) -> impl WidgetLike<Widget = Regioned>;
}
impl<W: WidgetLike> WidgetUtil for W {
fn pad(self, padding: impl Into<Padding>) -> impl WidgetLike<Widget = Regioned> {
WidgetFn(|ui| Regioned {
region: padding.into().region(),
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> {
fn span(self, dir: Dir, lengths: [impl Into<SpanLen>; LEN]) -> impl WidgetLike<Widget = Span>;
}
impl<const LEN: usize, Wa: WidgetArrLike<LEN>> WidgetArrUtil<LEN> for Wa {
fn span(self, dir: Dir, lengths: [impl Into<SpanLen>; LEN]) -> impl WidgetLike<Widget = Span> {
let lengths = lengths.map(Into::into);
WidgetFn(move |ui| Span {
dir,
children: self.ui(ui).arr.into_iter().zip(lengths).collect(),
})
}
}

View File

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

View File

@@ -1,8 +1,15 @@
mod color;
mod painter;
mod region;
mod ui;
mod vec2;
mod widget;
pub use color::*;
pub use region::*;
pub use ui::*;
pub use vec2::*;
pub use widget::*;
pub use painter::*;
use crate::primitive::Color;
pub type UIColor = Color<u8>;

View File

@@ -1,21 +1,7 @@
mod color;
mod def;
mod format;
mod point;
pub use color::*;
pub use def::*;
pub use format::*;
pub use point::*;
use crate::{render::data::PrimitiveInstance, WidgetId, Widgets};
use bytemuck::Pod;
#[derive(Default)]
pub struct Primitives {
pub instances: Vec<PrimitiveInstance>,
pub data: Vec<u32>,
}
use crate::{
UIRegion, WidgetId, Widgets,
primitive::{PrimitiveData, PrimitiveInstance, Primitives},
};
pub struct Painter<'a> {
nodes: &'a Widgets,
@@ -23,11 +9,6 @@ pub struct Painter<'a> {
pub region: UIRegion,
}
/// NOTE: Self must have at least u32 alignment
pub trait PrimitiveData: Pod {
const DISCRIM: u32;
}
impl<'a> Painter<'a> {
pub fn new(nodes: &'a Widgets) -> Self {
Self {

159
src/layout/region.rs Normal file
View File

@@ -0,0 +1,159 @@
use crate::{
Axis, Vec2,
layout::vec2,
util::{F32Util, impl_op},
};
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
pub struct UIPos {
pub anchor: Vec2,
pub offset: Vec2,
}
impl UIPos {
pub const fn anchor_offset(anchor_x: f32, anchor_y: f32, offset_x: f32, offset_y: f32) -> Self {
Self {
anchor: vec2(anchor_x, anchor_y),
offset: vec2(offset_x, offset_y),
}
}
pub const fn center() -> Self {
Self::anchor_offset(0.5, 0.5, 0.0, 0.0)
}
pub const fn top_left() -> Self {
Self::anchor_offset(0.0, 0.0, 0.0, 0.0)
}
pub const fn bottom_right() -> Self {
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 {
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 fn axis_mut(&mut self, axis: Axis) -> UIScalarView<'_> {
match axis {
Axis::X => UIScalarView {
anchor: &mut self.anchor.x,
offset: &mut self.offset.x,
},
Axis::Y => UIScalarView {
anchor: &mut self.anchor.y,
offset: &mut self.offset.y,
},
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct UIScalar {
pub anchor: f32,
pub offset: f32,
}
impl_op!(UIScalar Add add; anchor offset);
impl_op!(UIScalar Sub sub; anchor offset);
impl UIScalar {
pub fn new(anchor: f32, offset: f32) -> Self {
Self { anchor, offset }
}
pub fn min() -> Self {
Self::new(0.0, 0.0)
}
pub fn max() -> Self {
Self::new(1.0, 0.0)
}
pub fn from_anchor(anchor: f32) -> Self {
Self::new(anchor, 0.0)
}
pub fn offset(mut self, amt: f32) -> Self {
self.offset += 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 }
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct UIRegion {
pub top_left: UIPos,
pub bot_right: UIPos,
}
impl UIRegion {
pub const fn full() -> Self {
Self {
top_left: UIPos::top_left(),
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 {
Self {
top_left: self.top_left.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<'_> {
UIRegionAxisView {
top_left: self.top_left.axis_mut(axis),
bot_right: self.bot_right.axis_mut(axis),
}
}
}
pub struct UIRegionAxisView<'a> {
pub top_left: UIScalarView<'a>,
pub bot_right: 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;
}
pub fn get(self) -> UIScalar {
UIScalar {
anchor: *self.anchor,
offset: *self.offset,
}
}
}

View File

@@ -1,7 +1,7 @@
use crate::{
primitive::{Painter, Primitives},
util::{IDTracker, ID},
HashMap, Widget, WidgetId, WidgetLike, WidgetRef,
HashMap, Painter, Widget, WidgetId, WidgetLike, WidgetRef,
primitive::Primitives,
util::{ID, IDTracker},
};
use std::{
any::{Any, TypeId},
@@ -31,11 +31,15 @@ impl From<UI> for UIBuilder {
}
impl UIBuilder {
pub fn add<W: Widget>(&mut self, w: W) -> WidgetRef<W> {
WidgetRef::new(self.clone(), (self.push(w),))
pub fn add<W: Widget>(&mut self, w: impl WidgetLike<Widget = W>) -> WidgetRef<W> {
WidgetRef::new([w.add(self).erase_type()])
}
pub fn push<W: Widget>(&mut self, w: W) -> WidgetId<W> {
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 {
let mut ui = self.ui.borrow_mut();
let id = ui.ids.next();
ui.widgets.insert(id.duplicate(), w);

59
src/layout/vec2.rs Normal file
View File

@@ -0,0 +1,59 @@
use crate::util::{F32Util, impl_op};
use std::ops::*;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vec2 {
pub x: f32,
pub y: f32,
}
pub const fn vec2(x: f32, y: f32) -> Vec2 {
Vec2::new(x, y)
}
impl Vec2 {
pub const ZERO: Self = Self::new(0.0, 0.0);
pub const ONE: Self = Self::new(1.0, 1.0);
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
pub const fn lerp(self, from: impl const Into<Self>, to: impl const Into<Self>) -> Self {
let from = from.into();
let to = to.into();
Self {
x: self.x.lerp(from.x, to.x),
y: self.y.lerp(from.y, to.y),
}
}
}
impl const From<f32> for Vec2 {
fn from(v: f32) -> Self {
Self { x: v, y: v }
}
}
// this version looks kinda cool... is it more readable? more annoying to copy and change though
impl_op!(impl Add for Vec2: add x y);
impl_op!(Vec2 Sub sub; x y);
impl_op!(Vec2 Mul mul; x y);
impl_op!(Vec2 Div div; x y);
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

@@ -3,11 +3,7 @@ use std::{
marker::PhantomData,
};
use crate::{
primitive::Painter,
util::{impl_tuple, ID},
UIBuilder,
};
use crate::{Painter, util::ID, UIBuilder};
pub trait Widget: 'static + Any {
fn draw(&self, painter: &mut Painter);
@@ -19,21 +15,19 @@ impl<W: Widget> Widget for (W,) {
}
}
pub struct AnyWidget;
#[derive(Eq, Hash, PartialEq, Debug)]
pub struct WidgetId<W = ()> {
pub struct WidgetId<W = AnyWidget> {
pub(super) ty: TypeId,
pub(super) id: ID,
_pd: PhantomData<W>,
}
// TODO: temp
impl<W: Widget> Clone for WidgetId<W> {
impl<W> Clone for WidgetId<W> {
fn clone(&self) -> Self {
Self {
ty: self.ty,
id: self.id.duplicate(),
_pd: self._pd,
}
Self::new(self.id.duplicate(), self.ty)
}
}
@@ -45,7 +39,7 @@ impl<W> WidgetId<W> {
_pd: PhantomData,
}
}
pub fn erase_type(self) -> WidgetId<()> {
pub fn erase_type(self) -> WidgetId<AnyWidget> {
self.cast_type()
}
@@ -59,7 +53,7 @@ impl<W> WidgetId<W> {
}
pub trait WidgetLike {
type Widget: Widget;
type Widget;
fn add(self, ui: &mut UIBuilder) -> WidgetId<Self::Widget>;
}
@@ -70,109 +64,96 @@ impl<W: Widget, F: FnOnce(&mut UIBuilder) -> W> WidgetLike for WidgetFn<F, W> {
type Widget = W;
fn add(self, ui: &mut UIBuilder) -> WidgetId<W> {
let w = (self.0)(ui);
ui.add(w).to_id()
ui.add_widget(w).to_id()
}
}
impl<W: Widget> WidgetLike for W {
type Widget = W;
fn add(self, ui: &mut UIBuilder) -> WidgetId<W> {
ui.add(self).to_id()
ui.add_widget(self).to_id()
}
}
impl<W: Widget> WidgetLike for WidgetId<W> {
impl<W> WidgetLike for WidgetId<W> {
type Widget = W;
fn add(self, _: &mut UIBuilder) -> WidgetId<W> {
self
}
}
impl<W: Widget> WidgetLike for WidgetArr<1, (W,)> {
impl<W> WidgetLike for WidgetArr<1, (W,)> {
type Widget = W;
fn add(self, _: &mut UIBuilder) -> WidgetId<W> {
self.arr.0
let [id] = self.arr;
id.cast_type()
}
}
pub struct WidgetArr<const LEN: usize, Ws: WidgetLikeTuple<LEN>> {
pub ui: UIBuilder,
pub arr: Ws::Wrap<ToId>,
pub struct WidgetArr<const LEN: usize, Ws> {
pub arr: [WidgetId; LEN],
_pd: PhantomData<Ws>,
}
impl<const LEN: usize, Ws: WidgetLikeTuple<LEN>> WidgetArr<LEN, Ws>
where
Ws::Wrap<ToId>: WidgetIdLikeTuple<LEN>,
{
pub fn new(ui: UIBuilder, arr: Ws::Wrap<ToId>) -> Self {
Self { ui, arr }
}
pub fn erase_types(self) -> [WidgetId; LEN] {
self.arr.map::<EraseId>(&mut ())
impl<const LEN: usize, Ws> WidgetArr<LEN, Ws> {
pub fn new(arr: [WidgetId; LEN]) -> Self {
Self {
arr,
_pd: PhantomData,
}
}
}
pub type WidgetRef<W> = WidgetArr<1, (W,)>;
impl<W: WidgetLike> WidgetRef<W> {
pub fn handle(&self) -> WidgetId<W::Widget> {
self.arr.0.clone()
impl<W> WidgetRef<W> {
pub fn handle(&self) -> WidgetId<W> {
let [id] = &self.arr;
id.clone().cast_type()
}
pub fn to_id(self) -> WidgetId<W::Widget> {
self.arr.0
pub fn to_id(self) -> WidgetId<W> {
let [id] = self.arr;
id.cast_type()
}
}
pub trait WidgetArrLike<const LEN: usize> {
type Ws: WidgetLikeTuple<LEN>;
type Ws;
fn ui(self, ui: &mut UIBuilder) -> WidgetArr<LEN, Self::Ws>;
}
impl<const LEN: usize, Ws: WidgetLikeTuple<LEN>> WidgetArrLike<LEN> 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> {
self
}
}
impl_tuple!(Widget);
impl_tuple!(WidgetLike);
impl_tuple!(WidgetIdLike);
pub trait WidgetIdLike {
fn erase_type(self) -> WidgetId;
// I hate this language it's so bad why do I even use it
macro_rules! impl_widget_arr {
($n:expr;$($T:tt)*) => {
impl<$($T: WidgetLike,)*> WidgetArrLike<$n> for ($($T,)*) {
type Ws = ($($T::Widget,)*);
fn ui(self, ui: &mut UIBuilder) -> WidgetArr<$n, ($($T::Widget,)*)> {
#[allow(non_snake_case)]
let ($($T,)*) = self;
WidgetArr::new(
[$($T.add(ui).cast_type(),)*],
)
}
}
};
}
impl<W> WidgetIdLike for WidgetId<W> {
fn erase_type(self) -> WidgetId {
self.erase_type()
}
}
pub struct ToId;
impl WidgetLikeWrapper for ToId {
type Wrap<T: WidgetLike> = WidgetId<T::Widget>;
type Ctx = UIBuilder;
fn wrap<T: WidgetLike>(t: T, ctx: &mut Self::Ctx) -> Self::Wrap<T> {
t.add(ctx)
}
}
struct EraseId;
impl WidgetIdLikeMapper for EraseId {
type Map = WidgetId<()>;
type Ctx = ();
fn map<Id: WidgetIdLike>(t: Id, _: &mut Self::Ctx) -> Self::Map {
t.erase_type()
}
}
impl<T: WidgetLikeTuple<LEN>, const LEN: usize> WidgetArrLike<LEN> for T
where
T::Wrap<ToId>: WidgetIdLikeTuple<LEN>,
{
type Ws = T;
fn ui(self, ui: &mut UIBuilder) -> WidgetArr<LEN, T> {
WidgetArr::new(ui.clone(), self.wrap::<ToId>(ui))
}
}
impl_widget_arr!(1;A);
impl_widget_arr!(2;A B);
impl_widget_arr!(3;A B C);
impl_widget_arr!(4;A B C D);
impl_widget_arr!(5;A B C D E);
impl_widget_arr!(6;A B C D E F);
impl_widget_arr!(7;A B C D E F G);
impl_widget_arr!(8;A B C D E F G H);
impl_widget_arr!(9;A B C D E F G H I);
impl_widget_arr!(10;A B C D E F G H I J);
impl_widget_arr!(11;A B C D E F G H I J K);
impl_widget_arr!(12;A B C D E F G H I J K L);

View File

@@ -1,12 +1,6 @@
use crate::primitive::UIRegion;
use wgpu::VertexAttribute;
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct PrimitiveInstance {
pub region: UIRegion,
pub ptr: u32,
}
use crate::UIRegion;
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
@@ -15,6 +9,13 @@ pub struct WindowUniform {
pub height: f32,
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct PrimitiveInstance {
pub region: UIRegion,
pub ptr: u32,
}
impl PrimitiveInstance {
const ATTRIBS: [VertexAttribute; 5] = wgpu::vertex_attr_array![
0 => Float32x2,

View File

@@ -1,7 +1,4 @@
use crate::{
render::{data::PrimitiveInstance, util::ArrBuf},
UI,
};
use crate::{UI, primitive::PrimitiveInstance, render::util::ArrBuf};
use data::WindowUniform;
use wgpu::{
util::{BufferInitDescriptor, DeviceExt},

27
src/render/primitive.rs Normal file
View File

@@ -0,0 +1,27 @@
use crate::Color;
pub use super::data::PrimitiveInstance;
#[derive(Default)]
pub struct Primitives {
pub instances: Vec<PrimitiveInstance>,
pub data: Vec<u32>,
}
/// NOTE: Self must have at least u32 alignment
pub trait PrimitiveData: bytemuck::Pod {
const DISCRIM: u32;
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct RoundedRectData {
pub color: Color<u8>,
pub radius: f32,
pub thickness: f32,
pub inner_radius: f32,
}
impl PrimitiveData for RoundedRectData {
const DISCRIM: u32 = 0;
}

View File

@@ -1,14 +0,0 @@
use crate::primitive::{Color, PrimitiveData};
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct RoundedRectData {
pub color: Color<u8>,
pub radius: f32,
pub thickness: f32,
pub inner_radius: f32,
}
impl PrimitiveData for RoundedRectData {
const DISCRIM: u32 = 0;
}

View File

@@ -1,97 +0,0 @@
use crate::primitive::{point::point, Point};
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
pub struct UIPos {
pub anchor: Point,
pub offset: Point,
}
impl UIPos {
pub const fn anchor_offset(anchor_x: f32, anchor_y: f32, offset_x: f32, offset_y: f32) -> Self {
Self {
anchor: point(anchor_x, anchor_y),
offset: point(offset_x, offset_y),
}
}
pub const fn top_left() -> Self {
Self::anchor_offset(0.0, 0.0, 0.0, 0.0)
}
pub const fn bottom_right() -> Self {
Self::anchor_offset(1.0, 1.0, 0.0, 0.0)
}
pub const fn within(&self, region: &UIRegion) -> UIPos {
let range = region.bot_right.anchor - region.top_left.anchor;
let region_offset = region
.top_left
.offset
.lerp(region.bot_right.offset, self.anchor);
UIPos {
anchor: region.top_left.anchor + self.anchor * range,
offset: self.offset + region_offset,
}
}
pub fn axis_mut(&mut self, axis: Axis) -> UIPosAxisView<'_> {
match axis {
Axis::X => UIPosAxisView {
anchor: &mut self.anchor.x,
offset: &mut self.offset.x,
},
Axis::Y => UIPosAxisView {
anchor: &mut self.anchor.y,
offset: &mut self.offset.y,
},
}
}
}
pub struct UIPosAxisView<'a> {
pub anchor: &'a mut f32,
pub offset: &'a mut f32,
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct UIRegion {
pub top_left: UIPos,
pub bot_right: UIPos,
}
impl UIRegion {
pub const fn full() -> Self {
Self {
top_left: UIPos::top_left(),
bot_right: UIPos::bottom_right(),
}
}
pub fn within(&self, parent: &Self) -> Self {
Self {
top_left: self.top_left.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<'_> {
UIRegionAxisView {
top_left: self.top_left.axis_mut(axis),
bot_right: self.bot_right.axis_mut(axis),
}
}
}
pub struct UIRegionAxisView<'a> {
pub top_left: UIPosAxisView<'a>,
pub bot_right: UIPosAxisView<'a>,
}
#[derive(Copy, Clone)]
pub enum Axis {
X,
Y,
}

View File

@@ -1,84 +0,0 @@
use std::ops::*;
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Point {
pub x: f32,
pub y: f32,
}
pub const fn point(x: f32, y: f32) -> Point {
Point::new(x, y)
}
impl Point {
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
pub const fn lerp(self, to: Self, amt: impl const Into<Self>) -> Self {
let amt = amt.into();
Self {
x: lerp(self.x, to.x, amt.x),
y: lerp(self.y, to.y, amt.y),
}
}
}
const fn lerp(x: f32, y: f32, amt: f32) -> f32 {
(1.0 - amt) * x + y * amt
}
impl const From<f32> for Point {
fn from(v: f32) -> Self {
Self { x: v, y: v }
}
}
macro_rules! impl_op_inner {
($op:ident $fn:ident $opa:ident $fna:ident) => {
impl const $op for Point {
type Output = Self;
fn $fn(self, rhs: Self) -> Self::Output {
Self {
x: self.x.$fn(rhs.x),
y: self.y.$fn(rhs.y),
}
}
}
impl $opa for Point {
fn $fna(&mut self, rhs: Self) {
self.x.$fna(rhs.x);
self.y.$fna(rhs.y);
}
}
impl const $op<f32> for Point {
type Output = Self;
fn $fn(self, rhs: f32) -> Self::Output {
Self {
x: self.x.$fn(rhs),
y: self.y.$fn(rhs),
}
}
}
impl $opa<f32> for Point {
fn $fna(&mut self, rhs: f32) {
self.x.$fna(rhs);
self.y.$fna(rhs);
}
}
};
}
macro_rules! impl_op {
($op:ident $fn:ident) => {
impl_op_inner!($op $fn ${concat($op,Assign)} ${concat($fn,_assign)});
};
}
impl_op!(Add add);
impl_op!(Sub sub);
impl_op!(Mul mul);
impl_op!(Div div);

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use app::App;
use gui::{primitive::Axis, RoundedRect, UIColor, WidgetArrUtil, WidgetUtil, UI};
use gui::{Dir, Rect, UI, UIColor, WidgetArrUtil, WidgetUtil, fixed, ratio, rel};
use render::Renderer;
use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window};
@@ -19,9 +19,9 @@ pub struct Client {
impl Client {
pub fn new(window: Arc<Window>) -> Self {
let mut renderer = Renderer::new(window);
let rect = RoundedRect {
let rect = Rect {
color: UIColor::WHITE,
radius: 10.0,
radius: 20.0,
thickness: 0.0,
inner_radius: 0.0,
};
@@ -33,21 +33,39 @@ impl Client {
(
blue,
(
rect.color(UIColor::RED),
rect.color(UIColor::RED).center((100.0, 100.0)),
(
rect.color(UIColor::ORANGE),
rect.color(UIColor::LIME).pad(10.0),
)
.span(Axis::Y, [1, 1]),
.span(Dir::RIGHT, [1, 1]),
rect.color(UIColor::YELLOW),
)
.span(Axis::X, [2, 2, 1])
.span(Dir::RIGHT, [2, 2, 1])
.pad(10),
)
.span(Axis::X, [1, 3]),
rect.color(UIColor::GREEN),
.span(Dir::RIGHT, [1, 3]),
(
rect.color(UIColor::GREEN),
rect.color(UIColor::ORANGE),
rect.color(UIColor::CYAN),
rect.color(UIColor::BLUE),
rect.color(UIColor::MAGENTA),
rect.color(UIColor::RED),
)
.span(
Dir::RIGHT,
[
fixed(100),
ratio(1),
ratio(1),
rel(0.5),
fixed(100),
fixed(100),
],
),
)
.span(Axis::Y, [3, 1])
.span(Dir::DOWN, [3, 1])
.pad(10),
);
ui.widgets.get_mut(&handle).unwrap().color = UIColor::MAGENTA;

View File

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

58
src/util/math.rs Normal file
View File

@@ -0,0 +1,58 @@
use std::ops::*;
#[const_trait]
pub trait F32Util {
fn lerp<T: const Mul<f32, Output = T> + const Add<Output = T>>(self, from: T, to: T) -> T;
}
impl const F32Util for f32 {
fn lerp<T: const Mul<f32, Output = T> + const Add<Output = T>>(self, from: T, to: T) -> T {
from * (1.0 - self) + to * self
}
}
macro_rules! impl_op {
($T:ident $op:ident $fn:ident $opa:ident $fna:ident; $($field:ident)*) => {
mod ${concat(op_, $fn, _impl)} {
use super::*;
#[allow(unused_imports)]
use std::ops::*;
impl const $op for $T {
type Output = Self;
fn $fn(self, rhs: Self) -> Self::Output {
Self {
$($field: self.$field.$fn(rhs.$field),)*
}
}
}
impl $opa for $T {
fn $fna(&mut self, rhs: Self) {
$(self.$field.$fna(rhs.$field);)*
}
}
impl const $op<f32> for $T {
type Output = Self;
fn $fn(self, rhs: f32) -> Self::Output {
Self {
$($field: self.$field.$fn(rhs),)*
}
}
}
impl $opa<f32> for $T {
fn $fna(&mut self, rhs: f32) {
$(self.$field.$fna(rhs);)*
}
}
}
};
($T:ident $op:ident $fn:ident; $($field:ident)*) => {
impl_op!($T $op $fn ${concat($op,Assign)} ${concat($fn,_assign)}; $($field)*);
};
(impl $op:ident for $T:ident: $fn:ident $($field:ident)*) => {
impl_op!($T $op $fn ${concat($op,Assign)} ${concat($fn,_assign)}; $($field)*);
};
}
pub(crate) use impl_op;

View File

@@ -1,5 +1,5 @@
mod id;
mod tuple;
mod math;
pub use id::*;
pub use tuple::*;
pub use math::*;

View File

@@ -1,54 +0,0 @@
macro_rules! impl_tuple {
($Tuple:ident $Bound:ident $Wrapper:ident $Mapper:ident $n:expr;$TL:tt $($T:tt)*) => {
#[allow(non_snake_case)]
impl<$($T: $Bound,)* $TL: $Bound> $Tuple<$n> for ($($T,)* $TL,) {
type Wrap<W: $Wrapper> = ($(W::Wrap<$T>,)* W::Wrap<$TL>,);
type Map<M: $Mapper> = [M::Map; $n];
fn wrap<W: $Wrapper>(self, ctx: &mut W::Ctx) -> ($(W::Wrap<$T>,)* W::Wrap<$TL>,) {
let ($($T,)* $TL,) = self;
($(W::wrap($T, ctx),)* W::wrap($TL, ctx),)
}
fn map<M: $Mapper>(self, ctx: &mut M::Ctx) -> [M::Map; $n] {
let ($($T,)* $TL,) = self;
[$(M::map($T, ctx),)* M::map($TL, ctx)]
}
}
};
($Tuple:ident, $Bound:ident, $Wrapper:ident, $Mapper:ident) => {
pub trait $Wrapper {
type Wrap<T: $Bound>;
type Ctx;
fn wrap<T: $Bound>(t: T, ctx: &mut Self::Ctx) -> Self::Wrap<T>;
}
pub trait $Mapper {
type Map;
type Ctx;
fn map<T: $Bound>(t: T, ctx: &mut Self::Ctx) -> Self::Map;
}
pub trait $Tuple<const LEN: usize> {
type Wrap<W: $Wrapper>;
type Map<M: $Mapper>;
fn map<M: $Mapper>(self, ctx: &mut M::Ctx) -> [M::Map; LEN];
fn wrap<W: $Wrapper>(self, ctx: &mut W::Ctx) -> Self::Wrap<W>;
}
impl_tuple!($Tuple $Bound $Wrapper $Mapper 1;A);
impl_tuple!($Tuple $Bound $Wrapper $Mapper 2;A B);
impl_tuple!($Tuple $Bound $Wrapper $Mapper 3;A B C);
impl_tuple!($Tuple $Bound $Wrapper $Mapper 4;A B C D);
impl_tuple!($Tuple $Bound $Wrapper $Mapper 5;A B C D E);
impl_tuple!($Tuple $Bound $Wrapper $Mapper 6;A B C D E F);
// impl_tuple!($Tuple $Bound $Wrapper $Mapper 7;A B C D E F G);
// impl_tuple!($Tuple $Bound $Wrapper $Mapper 8;A B C D E F G H);
// impl_tuple!($Tuple $Bound $Wrapper $Mapper 9;A B C D E F G H I);
// impl_tuple!($Tuple $Bound $Wrapper $Mapper 10;A B C D E F G H I J);
// impl_tuple!($Tuple $Bound $Wrapper $Mapper 11;A B C D E F G H I J K);
// impl_tuple!($Tuple $Bound $Wrapper $Mapper 12;A B C D E F G H I J K L);
};
($Bound:ident) => {
impl_tuple!(${concat($Bound, Tuple)}, $Bound, ${concat($Bound, Wrapper)}, ${concat($Bound, Mapper)});
}
}
pub(crate) use impl_tuple;