spans now good (other than direction) + refactor

This commit is contained in:
2025-08-11 02:24:27 -04:00
parent 132113f09e
commit 95a07786bb
22 changed files with 545 additions and 371 deletions

51
src/layout/color.rs Normal file
View File

@@ -0,0 +1,51 @@
#![allow(clippy::multiple_bound_locations)]
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Zeroable)]
pub struct Color<T: ColorNum> {
r: T,
g: T,
b: T,
a: T,
}
impl<T: ColorNum> Color<T> {
pub const BLACK: Self = Self::rgb(T::MIN, T::MIN, T::MIN);
pub const WHITE: Self = Self::rgb(T::MAX, T::MAX, T::MAX);
pub const RED: Self = Self::rgb(T::MAX, T::MIN, T::MIN);
pub const ORANGE: Self = Self::rgb(T::MAX, T::MID, T::MIN);
pub const YELLOW: Self = Self::rgb(T::MAX, T::MAX, T::MIN);
pub const LIME: Self = Self::rgb(T::MID, T::MAX, T::MIN);
pub const GREEN: Self = Self::rgb(T::MIN, T::MAX, T::MIN);
pub const CYAN: Self = Self::rgb(T::MIN, T::MAX, T::MAX);
pub const BLUE: Self = Self::rgb(T::MIN, T::MIN, T::MAX);
pub const MAGENTA: Self = Self::rgb(T::MAX, T::MIN, T::MAX);
}
impl<T: ColorNum> Color<T> {
pub const fn new(r: T, g: T, b: T, a: T) -> Self {
Self { r, g, b, a }
}
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 {
const MIN: Self;
const MID: Self;
const MAX: Self;
}
impl ColorNum for u8 {
const MIN: Self = u8::MIN;
const MID: Self = u8::MAX / 2;
const MAX: Self = u8::MAX;
}
unsafe impl bytemuck::Pod for Color<u8> {}

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>;

44
src/layout/painter.rs Normal file
View File

@@ -0,0 +1,44 @@
use crate::{
UIRegion, WidgetId, Widgets,
primitive::{PrimitiveData, PrimitiveInstance, Primitives},
};
pub struct Painter<'a> {
nodes: &'a Widgets,
primitives: Primitives,
pub region: UIRegion,
}
impl<'a> Painter<'a> {
pub fn new(nodes: &'a Widgets) -> Self {
Self {
nodes,
primitives: Primitives::default(),
region: UIRegion::full(),
}
}
pub fn write<Data: PrimitiveData>(&mut self, data: Data) {
let ptr = self.primitives.data.len() as u32;
let region = self.region;
self.primitives
.instances
.push(PrimitiveInstance { region, ptr });
self.primitives.data.push(Data::DISCRIM);
self.primitives
.data
.extend_from_slice(bytemuck::cast_slice::<_, u32>(&[data]));
}
pub fn draw(&mut self, node: &WidgetId) {
self.nodes.get(node).draw(self);
}
pub fn draw_within(&mut self, node: &WidgetId, region: UIRegion) {
let old = self.region;
self.region.select(&region);
self.draw(node);
self.region = old;
}
pub fn finish(self) -> Primitives {
self.primitives
}
}

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},

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,7 +3,7 @@ use std::{
marker::PhantomData,
};
use crate::{primitive::Painter, util::ID, UIBuilder};
use crate::{Painter, util::ID, UIBuilder};
pub trait Widget: 'static + Any {
fn draw(&self, painter: &mut Painter);