1 Commits

Author SHA1 Message Date
14435de6a5 bruh 2025-08-10 05:07:17 -04:00
73 changed files with 1385 additions and 8226 deletions

1
.gitignore vendored
View File

@@ -1,2 +1 @@
/target
perf.data*

1820
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,13 @@
[package]
name = "iris"
name = "gui"
version = "0.1.0"
edition = "2024"
default-run = "test"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
pollster = "0.4.0"
winit = "0.30.12"
wgpu = "27.0.1"
winit = "0.30.11"
wgpu = "26.0.1"
bytemuck = "1.23.1"
image = "0.25.6"
cosmic-text = "0.15.0"
unicode-segmentation = "1.12.0"
fxhash = "0.2.1"
arboard = { version = "3.6.1", features = ["wayland-data-control"] }

34
TODO
View File

@@ -1,34 +0,0 @@
images
settings (sampler)
text
figure out ways to speed up / what costs the most
resizing (per frame) is really slow (assuming painter isn't griefing)
masks r just made to bare minimum work
scaling
could be just a simple scaling factor that multiplies abs
and need to ensure text uses raw abs and not scaled abs
naming? (pt, px)
want to keep (drawn) regions using px? or should I add another field to UiScalar/Vec
field could be best solution so redrawing stuff isn't needed & you can specify both as user
WidgetRef<W> or smth instead of Id
enum that's either an Id or an actual concrete instance of W
painter takes them in instead of (or in addition to) id
then type wrapper widgets to contain them
allows for compile time optimization if a widget wrapper's inner is known at compile time
and the id of inner is not needed anywhere
maybe introduce InnerWidget trait to allow for editors to expose & modify inner type
maybe could also store a parent widget and keep using InnerWidget trait? unsure if possible
really weird limitation:
I don't think you can currently remove an element from a parent and put it in a child of the same parent
because it removes the unused children after the entire parent redraw
but the child gets drawn during that, so it will think the child is still active !!!
or something like that idk, maybe I need a special enum for parent that includes a undecided state where it may or may not get redrawn by the parent
or just do ref counting and ensure all drawn things == 1 afterwards (seems like best way)
tags
vecs for each widget type?

View File

@@ -1,26 +0,0 @@
# iris
my take on a rust ui library (also my first ui library)
it's called iris because it's the structure around what you actually want to display and colorful
there's a `main.rs` that runs a testing window, so can just `cargo run` to see it working
goals, in general order:
1. does what I want it to (video, text, animations)
2. very easy to use ignoring ergonomic ref counting
3. reasonably fast / efficient (a lot faster than electron, save battery life)
not targeting web rn cause wanna use actual nice gpu features & entire point of this is to make desktop apps
general ideas trynna use rn / experiment with:
- retained mode
- specifically designed around wgpu
- postfix functions for most things to prevent unreadable indentation (going very well)
- no macros in user code / actual LSP typechecking (variadic generics if you can hear me please save us)
- relative anchor + absolute offset coord system (+ "rest" / leftover during widget layout)
- single threaded ui & pass context around to make non async usage straightforward (pretty unsure about this)
- widgets store outside of the actual rendering so they can be moved around and swapped easily (unsure about this but seems to work good for now)
under heavy initial development so not gonna try to explain status, check TODO for that maybe
sizable chance it gets a rewrite once I know everything I need and what seems to work best

165
src/base/mod.rs Normal file
View File

@@ -0,0 +1,165 @@
use std::ops::Range;
use crate::{
primitive::{Axis, Painter, RoundedRectData, UIRegion},
UIColor, Widget, WidgetArrLike, WidgetFn, WidgetId, WidgetLike,
};
#[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(
axis: Axis,
elements: impl IntoIterator<Item = (WidgetId, impl UINum)>,
) -> Self {
// TODO: update
let elements = elements
.into_iter()
.map(|(w, r)| (w, r.to_f32()))
.collect::<Vec<_>>();
let total: f32 = elements.iter().map(|(_, r)| r).sum();
let mut start = 0.0;
Self {
elements: elements
.into_iter()
.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<W> {
fn pad(self, padding: impl Into<Padding>) -> impl WidgetLike<Widget = Regioned>;
}
impl<W: WidgetLike> WidgetUtil<W::Widget> for W {
fn pad(self, padding: impl Into<Padding>) -> impl WidgetLike<Widget = Regioned> {
WidgetFn(|ui| Regioned {
region: padding.into().region(),
inner: self.id(ui).erase_type(),
})
}
}
pub trait WidgetArrUtil<Wa: WidgetArrLike> {
fn span(self, axis: Axis, ratios: [impl UINum; Wa::LEN]) -> impl WidgetLike<Widget = Span>;
}
impl<Wa: WidgetArrLike> WidgetArrUtil<Wa> for Wa {
fn span(self, axis: Axis, ratios: [impl UINum; Wa::LEN]) -> impl WidgetLike<Widget = Span> {
WidgetFn(move |ui| Span::proportioned(axis, self.ui(ui).ids().into_iter().zip(ratios)))
}
}
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
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -1,78 +0,0 @@
use crate::Client;
use iris::{
core::{CursorState, Modifiers},
layout::Vec2,
};
use winit::{
event::{MouseButton, MouseScrollDelta, WindowEvent},
keyboard::{Key, NamedKey},
};
#[derive(Default)]
pub struct Input {
cursor: CursorState,
pub modifiers: Modifiers,
}
impl Input {
pub fn event(&mut self, event: &WindowEvent) -> bool {
match event {
WindowEvent::CursorMoved { position, .. } => {
self.cursor.pos = Vec2::new(position.x as f32, position.y as f32);
self.cursor.exists = true;
}
WindowEvent::MouseInput { state, button, .. } => {
let buttons = &mut self.cursor.buttons;
let pressed = state.is_pressed();
match button {
MouseButton::Left => buttons.left.update(pressed),
MouseButton::Right => buttons.right.update(pressed),
MouseButton::Middle => buttons.middle.update(pressed),
_ => (),
}
}
WindowEvent::MouseWheel { delta, .. } => {
let delta = match *delta {
MouseScrollDelta::LineDelta(x, y) => Vec2::new(x, y),
MouseScrollDelta::PixelDelta(pos) => Vec2::new(pos.x as f32, pos.y as f32),
};
self.cursor.scroll_delta = delta;
}
WindowEvent::CursorLeft { .. } => {
self.cursor.exists = false;
self.modifiers.clear();
}
WindowEvent::KeyboardInput { event, .. } => {
if let Key::Named(named) = event.logical_key {
let pressed = event.state.is_pressed();
match named {
NamedKey::Control => {
self.modifiers.control = pressed;
}
NamedKey::Shift => {
self.modifiers.shift = pressed;
}
_ => (),
}
}
}
_ => return false,
}
true
}
pub fn end_frame(&mut self) {
self.cursor.end_frame();
}
}
impl Client {
pub fn window_size(&self) -> Vec2 {
let size = self.renderer.window().inner_size();
(size.width, size.height).into()
}
pub fn cursor_state(&self) -> &CursorState {
&self.input.cursor
}
}

View File

@@ -1,294 +0,0 @@
use app::App;
use arboard::Clipboard;
use cosmic_text::Family;
use input::Input;
use iris::prelude::*;
use len_fns::*;
use render::Renderer;
use std::sync::Arc;
use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window};
mod app;
mod input;
mod render;
fn main() {
App::run();
}
pub struct Client {
renderer: Renderer,
input: Input,
ui: Ui,
info: WidgetId<Text>,
focus: Option<WidgetId<TextEdit>>,
clipboard: Clipboard,
}
#[derive(Eq, PartialEq, Hash, Clone)]
struct Submit;
impl DefaultEvent for Submit {
type Data = ();
}
impl Client {
pub fn new(window: Arc<Window>) -> Self {
let renderer = Renderer::new(window);
let mut ui = Ui::new();
let rrect = rect(Color::WHITE).radius(20);
let pad_test = (
rrect.color(Color::BLUE),
(
rrect
.color(Color::RED)
.sized((100, 100))
.center()
.width(rest(2)),
(
rrect.color(Color::ORANGE),
rrect.color(Color::LIME).pad(10.0),
)
.span(Dir::RIGHT)
.width(rest(2)),
rrect.color(Color::YELLOW),
)
.span(Dir::RIGHT)
.pad(10)
.width(rest(3)),
)
.span(Dir::RIGHT)
.add_static(&mut ui);
let span_test = (
rrect.color(Color::GREEN).width(100),
rrect.color(Color::ORANGE),
rrect.color(Color::CYAN),
rrect.color(Color::BLUE).width(rel(0.5)),
rrect.color(Color::MAGENTA).width(100),
rrect.color(Color::RED).width(100),
)
.span(Dir::LEFT)
.add_static(&mut ui);
let span_add = Span::empty(Dir::RIGHT).add_static(&mut ui);
let add_button = rect(Color::LIME)
.radius(30)
.on(CursorSense::click(), move |ctx: &mut Client, _| {
let child = ctx
.ui
.add(image(include_bytes!("assets/sungals.png")).center())
.any();
ctx.ui[span_add].children.push(child);
})
.sized((150, 150))
.align(Align::BOT_RIGHT);
let del_button = rect(Color::RED)
.radius(30)
.on(CursorSense::click(), move |ctx: &mut Client, _| {
ctx.ui[span_add].children.pop();
})
.sized((150, 150))
.align(Align::BOT_LEFT);
let span_add_test = (span_add, add_button, del_button)
.stack()
.add_static(&mut ui);
let btext = |content| text(content).size(30);
let text_test = (
btext("this is a").align(Align::LEFT),
btext("teeeeeeeest").align(Align::RIGHT),
btext("okkk\nokkkkkk!").align(Align::LEFT),
btext("hmm"),
btext("a"),
(
btext("'").family(Family::Monospace).align(Align::TOP),
btext("'").family(Family::Monospace),
btext(":gamer mode").family(Family::Monospace),
rect(Color::CYAN).sized((10, 10)).center(),
rect(Color::RED).sized((100, 100)).center(),
rect(Color::PURPLE).sized((50, 50)).align(Align::TOP),
)
.span(Dir::RIGHT)
.center(),
text("pretty cool right?").size(50),
)
.span(Dir::DOWN)
.add_static(&mut ui);
let texts = Span::empty(Dir::DOWN)
.gap(10)
.add_static(&mut ui);
let msg_area = texts.scroll().masked().background(rect(Color::SKY));
let add_text = text("add")
.editable()
.text_align(Align::LEFT)
.size(30)
.id_on(CursorSense::click(), |id, client: &mut Client, ctx| {
client.ui.text(id).select(ctx.cursor, ctx.size);
client.focus = Some(id.clone());
})
.id_on(Submit, move |id, client: &mut Client, _| {
let content = client.ui.text(id).take();
let text = text(content)
.editable()
.size(30)
.text_align(Align::LEFT)
.wrap(true)
.id_on(CursorSense::click(), |id, client: &mut Client, ctx| {
client.ui.text(id).select(ctx.cursor, ctx.size);
client.focus = Some(id.clone());
});
let msg_box = text
.background(rect(Color::WHITE.darker(0.5)))
.add(&mut client.ui);
client.ui[texts].children.push(msg_box.any());
})
.add(&mut ui);
let text_edit_scroll = (
msg_area,
(
Rect::new(Color::WHITE.darker(0.9)),
(
add_text.clone().width(rest(1)),
Rect::new(Color::GREEN)
.on(CursorSense::click(), move |client: &mut Client, _| {
client.run_event(&add_text, Submit, ());
})
.sized((40, 40)),
)
.span(Dir::RIGHT)
.pad(10),
)
.stack()
.size(StackSize::Child(1))
.offset_layer(1)
.align(Align::BOT),
)
.span(Dir::DOWN)
.add_static(&mut ui);
let main = pad_test.pad(10).add_static(&mut ui);
let switch_button = |color, to, label| {
let rect = rect(color)
.id_on(CursorSense::click(), move |id, ui: &mut Ui, _| {
ui[main].inner.set_static(to);
ui[id].color = color.darker(0.3);
})
.edit_on(
CursorSense::HoverStart | CursorSense::unclick(),
move |r, _| {
r.color = color.brighter(0.2);
},
)
.edit_on(CursorSense::HoverEnd, move |r, _| {
r.color = color;
});
(rect, text(label).size(30)).stack()
};
let tabs = (
switch_button(Color::RED, pad_test.any(), "pad"),
switch_button(Color::GREEN, span_test.any(), "span"),
switch_button(Color::BLUE, span_add_test.any(), "image span"),
switch_button(Color::MAGENTA, text_test.any(), "text layout"),
switch_button(
Color::YELLOW.mul_rgb(0.5),
text_edit_scroll.any(),
"text edit scroll",
),
)
.span(Dir::RIGHT);
let info = text("").add(&mut ui);
let info_sect = info.clone().pad(10).align(Align::RIGHT);
((tabs.height(40), main).span(Dir::DOWN), info_sect)
.stack()
.set_root(&mut ui);
Self {
renderer,
input: Input::default(),
ui,
info,
focus: None,
clipboard: Clipboard::new().unwrap(),
}
}
pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
let input_changed = self.input.event(&event);
let cursor_state = self.cursor_state().clone();
if let Some(focus) = &self.focus
&& cursor_state.buttons.left.is_start()
{
self.ui.text(focus).deselect();
self.focus = None;
}
if input_changed {
let window_size = self.window_size();
self.run_sensors(&cursor_state, window_size);
self.ui.run_sensors(&cursor_state, window_size);
}
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => {
self.ui.update();
self.renderer.update(&mut self.ui);
self.renderer.draw()
}
WindowEvent::Resized(size) => {
self.ui.resize((size.width, size.height));
self.renderer.resize(&size)
}
WindowEvent::KeyboardInput { event, .. } => {
if let Some(sel) = &self.focus
&& event.state.is_pressed()
{
let mut text = self.ui.text(sel);
match text.apply_event(&event, &self.input.modifiers) {
TextInputResult::Unfocus => {
self.focus = None;
}
TextInputResult::Submit => {
self.run_event(&sel.clone(), Submit, ());
}
TextInputResult::Paste => {
if let Ok(t) = self.clipboard.get_text() {
text.insert(&t);
}
}
TextInputResult::Unused | TextInputResult::Used => (),
}
}
}
_ => (),
}
let new = format!(
"widgets: {}\nactive:{}\nviews: {}",
self.ui.num_widgets(),
self.ui.active_widgets(),
self.renderer.ui.view_count()
);
if new != *self.ui[&self.info].content {
*self.ui[&self.info].content = new;
}
if self.ui.needs_redraw() {
self.renderer.window().request_redraw();
}
self.input.end_frame();
}
}
impl UiCtx for Client {
fn ui(&mut self) -> &mut Ui {
&mut self.ui
}
}

View File

View File

@@ -1,55 +0,0 @@
use crate::prelude::*;
use image::DynamicImage;
pub struct Image {
handle: TextureHandle,
}
impl Widget for Image {
fn draw(&mut self, painter: &mut Painter) {
painter.texture(&self.handle);
}
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
Len::abs(self.handle.size().x)
}
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
Len::abs(self.handle.size().y)
}
}
pub fn image(image: impl LoadableImage) -> impl WidgetFn<Image> {
let image = image.get_image().expect("Failed to load image");
move |ui| Image {
handle: ui.add_texture(image),
}
}
pub trait LoadableImage {
fn get_image(self) -> Result<DynamicImage, String>;
}
impl LoadableImage for &str {
fn get_image(self) -> Result<DynamicImage, String> {
image::open(self).map_err(|e| format!("{e:?}"))
}
}
impl LoadableImage for String {
fn get_image(self) -> Result<DynamicImage, String> {
image::open(self).map_err(|e| format!("{e:?}"))
}
}
impl<const LEN: usize> LoadableImage for &[u8; LEN] {
fn get_image(self) -> Result<DynamicImage, String> {
image::load_from_memory(self).map_err(|e| format!("{e:?}"))
}
}
impl LoadableImage for DynamicImage {
fn get_image(self) -> Result<DynamicImage, String> {
Ok(self)
}
}

View File

@@ -1,20 +0,0 @@
use crate::prelude::*;
pub struct Masked {
pub inner: WidgetId,
}
impl Widget for Masked {
fn draw(&mut self, painter: &mut Painter) {
painter.set_mask(painter.region());
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

@@ -1,17 +0,0 @@
mod image;
mod mask;
mod position;
mod ptr;
mod rect;
mod sense;
mod text;
mod trait_fns;
pub use image::*;
pub use mask::*;
pub use position::*;
pub use ptr::*;
pub use rect::*;
pub use sense::*;
pub use text::*;
pub use trait_fns::*;

View File

@@ -1,35 +0,0 @@
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 = 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

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

View File

@@ -1,21 +0,0 @@
use crate::prelude::*;
pub struct Offset {
pub inner: WidgetId,
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)
}
}

View File

@@ -1,83 +0,0 @@
use crate::prelude::*;
pub struct Pad {
pub padding: Padding,
pub inner: WidgetId,
}
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 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,
}
}
}
impl<T: UiNum> From<T> for Padding {
fn from(amt: T) -> Self {
Self::uniform(amt.to_f32())
}
}

View File

@@ -1,63 +0,0 @@
use crate::prelude::*;
pub struct Scroll {
inner: WidgetId,
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 region = UiRegion::FULL.offset(Vec2::from_axis(self.axis, -self.amt, 0.0));
painter.widget_within(&self.inner, region);
}
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
Len::default()
}
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
Len::default()
}
}
impl Scroll {
pub fn new(inner: WidgetId, 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

@@ -1,34 +0,0 @@
use crate::prelude::*;
pub struct Sized {
pub inner: WidgetId,
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))
}
}

View File

@@ -1,182 +0,0 @@
use crate::prelude::*;
use std::marker::PhantomData;
pub struct Span {
pub children: Vec<WidgetId>,
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
}
}

View File

@@ -1,89 +0,0 @@
use std::marker::PhantomData;
use crate::prelude::*;
pub struct Stack {
pub children: Vec<WidgetId>,
pub size: StackSize,
pub offset: usize,
}
impl Widget for Stack {
fn draw(&mut self, painter: &mut Painter) {
for _ in 0..self.offset {
painter.next_layer();
}
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,
pub offset: usize,
_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(),
offset: self.offset,
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(),
offset: 0,
_pd: PhantomData,
}
}
pub fn size(mut self, size: StackSize) -> Self {
self.size = size;
self
}
pub fn offset_layer(mut self, offset: usize) -> Self {
self.offset = offset;
self
}
}

View File

@@ -1,30 +0,0 @@
use crate::prelude::*;
#[derive(Default)]
pub struct WidgetPtr {
pub inner: Option<WidgetId>,
}
impl Widget for WidgetPtr {
fn draw(&mut self, painter: &mut Painter) {
if let Some(id) = &self.inner {
painter.widget(id);
}
}
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
if let Some(id) = &self.inner {
ctx.width(id)
} else {
Len::ZERO
}
}
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
if let Some(id) = &self.inner {
ctx.height(id)
} else {
Len::ZERO
}
}
}

View File

@@ -1,51 +0,0 @@
use crate::prelude::*;
#[derive(Clone, Copy)]
pub struct Rect {
pub color: UiColor,
pub radius: f32,
pub thickness: f32,
pub inner_radius: f32,
}
impl Rect {
pub fn new(color: UiColor) -> Self {
Self {
color,
radius: 0.0,
inner_radius: 0.0,
thickness: 0.0,
}
}
pub fn color(mut self, color: UiColor) -> Self {
self.color = color;
self
}
pub fn radius(mut self, radius: impl UiNum) -> Self {
self.radius = radius.to_f32();
self
}
}
impl Widget for Rect {
fn draw(&mut self, painter: &mut Painter) {
painter.primitive(RectPrimitive {
color: self.color,
radius: self.radius,
thickness: self.thickness,
inner_radius: self.inner_radius,
});
}
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
Len::rest(1)
}
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
Len::rest(1)
}
}
pub fn rect(color: UiColor) -> Rect {
Rect::new(color)
}

View File

@@ -1,372 +0,0 @@
use crate::prelude::*;
use std::{
ops::{BitOr, Deref, DerefMut},
rc::Rc,
};
use crate::{
layout::{UiModule, UiRegion, Vec2},
util::{HashMap, Id},
};
#[derive(PartialEq)]
pub enum Button {
Left,
Right,
Middle,
}
#[derive(PartialEq)]
pub enum CursorSense {
PressStart(Button),
Pressing(Button),
PressEnd(Button),
HoverStart,
Hovering,
HoverEnd,
Scroll,
}
pub struct CursorSenses(Vec<CursorSense>);
impl CursorSense {
pub fn click() -> Self {
Self::PressStart(Button::Left)
}
pub fn unclick() -> Self {
Self::PressEnd(Button::Left)
}
}
#[derive(Default, Clone)]
pub struct CursorState {
pub pos: Vec2,
pub exists: bool,
pub buttons: CursorButtons,
pub scroll_delta: Vec2,
}
#[derive(Default, Clone)]
pub struct CursorButtons {
pub left: ActivationState,
pub middle: ActivationState,
pub right: ActivationState,
}
impl CursorButtons {
pub fn select(&self, button: &Button) -> &ActivationState {
match button {
Button::Left => &self.left,
Button::Right => &self.right,
Button::Middle => &self.middle,
}
}
pub fn end_frame(&mut self) {
self.left.end_frame();
self.middle.end_frame();
self.right.end_frame();
}
}
impl CursorState {
pub fn end_frame(&mut self) {
self.buttons.end_frame();
self.scroll_delta = Vec2::ZERO;
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub enum ActivationState {
Start,
On,
End,
#[default]
Off,
}
/// this and other similar stuff has a generic
/// because I kind of want to make CursorModule generic
/// or basically have some way to have custom senses
/// that depend on active widget positions
/// but I'm not sure how or if worth it
pub struct Sensor<Ctx, Data> {
pub senses: CursorSenses,
pub f: Rc<dyn EventFn<Ctx, Data>>,
}
pub type SensorMap<Ctx, Data> = HashMap<Id, SensorGroup<Ctx, Data>>;
pub type SenseShape = UiRegion;
pub struct SensorGroup<Ctx, Data> {
pub hover: ActivationState,
pub sensors: Vec<Sensor<Ctx, Data>>,
}
#[derive(Clone)]
pub struct CursorData {
pub cursor: Vec2,
pub size: Vec2,
pub scroll_delta: Vec2,
}
pub struct CursorModule<Ctx> {
map: SensorMap<Ctx, CursorData>,
active: HashMap<usize, HashMap<Id, SenseShape>>,
}
impl<Ctx: 'static> UiModule for CursorModule<Ctx> {
fn on_draw(&mut self, inst: &WidgetInstance) {
if self.map.contains_key(&inst.id) {
self.active
.entry(inst.layer)
.or_default()
.insert(inst.id, inst.region);
}
}
fn on_undraw(&mut self, inst: &WidgetInstance) {
if let Some(layer) = self.active.get_mut(&inst.layer) {
layer.remove(&inst.id);
}
}
fn on_remove(&mut self, id: &Id) {
self.map.remove(id);
for layer in self.active.values_mut() {
layer.remove(id);
}
}
fn on_move(&mut self, inst: &WidgetInstance) {
if let Some(map) = self.active.get_mut(&inst.layer)
&& let Some(region) = map.get_mut(&inst.id)
{
*region = inst.region;
}
}
}
impl<Ctx> CursorModule<Ctx> {
pub fn merge(&mut self, other: Self) {
for (id, group) in other.map {
for sensor in group.sensors {
self.map.entry(id).or_default().sensors.push(sensor);
}
}
}
}
pub trait SensorCtx: UiCtx {
fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2);
}
impl<Ctx: UiCtx + 'static> SensorCtx for Ctx {
fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2) {
CursorModule::<Ctx>::run(self, cursor, window_size);
}
}
impl<Ctx: UiCtx + 'static> CursorModule<Ctx> {
pub fn run(ctx: &mut Ctx, cursor: &CursorState, window_size: Vec2) {
let layers = std::mem::take(&mut ctx.ui().data.layers);
let mut module = std::mem::take(ctx.ui().data.modules.get_mut::<Self>());
for i in layers.indices().rev() {
let Some(list) = module.active.get_mut(&i) else {
continue;
};
let mut sensed = false;
for (id, shape) in list.iter() {
let group = module.map.get_mut(id).unwrap();
let region = shape.to_px(window_size);
let in_shape = cursor.exists && region.contains(cursor.pos);
group.hover.update(in_shape);
if group.hover == ActivationState::Off {
continue;
}
sensed = true;
for sensor in &mut group.sensors {
if should_run(&sensor.senses, cursor, group.hover) {
let data = CursorData {
cursor: cursor.pos - region.top_left,
size: region.bot_right - region.top_left,
scroll_delta: cursor.scroll_delta,
};
(sensor.f)(ctx, data);
}
}
}
if sensed {
break;
}
}
let ui_mod = ctx.ui().data.modules.get_mut::<Self>();
std::mem::swap(ui_mod, &mut module);
ui_mod.merge(module);
ctx.ui().data.layers = layers;
}
}
pub fn should_run(senses: &CursorSenses, cursor: &CursorState, hover: ActivationState) -> bool {
for sense in senses.iter() {
if match sense {
CursorSense::PressStart(button) => cursor.buttons.select(button).is_start(),
CursorSense::Pressing(button) => cursor.buttons.select(button).is_on(),
CursorSense::PressEnd(button) => cursor.buttons.select(button).is_end(),
CursorSense::HoverStart => hover.is_start(),
CursorSense::Hovering => hover.is_on(),
CursorSense::HoverEnd => hover.is_end(),
CursorSense::Scroll => cursor.scroll_delta != Vec2::ZERO,
} {
return true;
}
}
false
}
impl ActivationState {
pub fn is_start(&self) -> bool {
*self == Self::Start
}
pub fn is_on(&self) -> bool {
*self == Self::Start || *self == Self::On
}
pub fn is_end(&self) -> bool {
*self == Self::End
}
pub fn is_off(&self) -> bool {
*self == Self::End || *self == Self::Off
}
pub fn update(&mut self, on: bool) {
*self = match *self {
Self::Start => match on {
true => Self::On,
false => Self::End,
},
Self::On => match on {
true => Self::On,
false => Self::End,
},
Self::End => match on {
true => Self::Start,
false => Self::Off,
},
Self::Off => match on {
true => Self::Start,
false => Self::Off,
},
}
}
pub fn end_frame(&mut self) {
match self {
Self::Start => *self = Self::On,
Self::End => *self = Self::Off,
_ => (),
}
}
}
impl Event for CursorSenses {
type Module<Ctx: 'static> = CursorModule<Ctx>;
type Data = CursorData;
}
impl Event for CursorSense {
type Module<Ctx: 'static> = CursorModule<Ctx>;
type Data = CursorData;
}
impl<E: Event<Data = <CursorSenses as Event>::Data> + Into<CursorSenses>, Ctx: 'static> EventModule<E, Ctx>
for CursorModule<Ctx>
{
fn register(&mut self, id: Id, senses: E, f: impl EventFn<Ctx, <E as Event>::Data>) {
// TODO: does not add to active if currently active
self.map.entry(id).or_default().sensors.push(Sensor {
senses: senses.into(),
f: Rc::new(f),
});
}
fn run<'a>(&self, id: &Id, event: E) -> Option<impl Fn(&mut Ctx, E::Data) + use<'a, E, Ctx>> {
let senses = event.into();
if let Some(group) = self.map.get(id) {
let fs: Vec<_> = group
.sensors
.iter()
.filter_map(|sensor| {
if sensor.senses.iter().any(|s| senses.contains(s)) {
Some(sensor.f.clone())
} else {
None
}
})
.collect();
Some(move |ctx: &mut Ctx, data: CursorData| {
for f in &fs {
f(ctx, data.clone());
}
})
} else {
None
}
}
}
impl<Ctx, Data> Default for SensorGroup<Ctx, Data> {
fn default() -> Self {
Self {
hover: Default::default(),
sensors: Default::default(),
}
}
}
impl Deref for CursorSenses {
type Target = Vec<CursorSense>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for CursorSenses {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<CursorSense> for CursorSenses {
fn from(val: CursorSense) -> Self {
CursorSenses(vec![val])
}
}
impl BitOr for CursorSense {
type Output = CursorSenses;
fn bitor(self, rhs: Self) -> Self::Output {
CursorSenses(vec![self, rhs])
}
}
impl BitOr<CursorSense> for CursorSenses {
type Output = Self;
fn bitor(mut self, rhs: CursorSense) -> Self::Output {
self.0.push(rhs);
self
}
}
impl<Ctx> Default for CursorModule<Ctx> {
fn default() -> Self {
Self {
map: Default::default(),
active: Default::default(),
}
}
}

View File

@@ -1,108 +0,0 @@
use std::marker::{PhantomData, Sized};
use crate::prelude::*;
use cosmic_text::{Attrs, Family, Metrics};
pub trait TextBuilderOutput: Sized {
type Output;
fn run(ui: &mut Ui, builder: TextBuilder<Self>) -> Self::Output;
}
pub struct TextBuilder<O = TextOutput> {
pub content: String,
pub attrs: TextAttrs,
_pd: PhantomData<O>,
}
impl<O> TextBuilder<O> {
pub fn size(mut self, size: impl UiNum) -> Self {
self.attrs.font_size = size.to_f32();
self.attrs.line_height = self.attrs.font_size * 1.1;
self
}
pub fn color(mut self, color: UiColor) -> Self {
self.attrs.color = color;
self
}
pub fn family(mut self, family: Family<'static>) -> Self {
self.attrs.family = family;
self
}
pub fn line_height(mut self, height: f32) -> Self {
self.attrs.line_height = height;
self
}
pub fn text_align(mut self, align: impl Into<RegionAlign>) -> Self {
self.attrs.align = align.into();
self
}
pub fn wrap(mut self, wrap: bool) -> Self {
self.attrs.wrap = wrap;
self
}
pub fn editable(self) -> TextBuilder<TextEditOutput> {
TextBuilder {
content: self.content,
attrs: self.attrs,
_pd: PhantomData,
}
}
}
pub struct TextOutput;
impl TextBuilderOutput for TextOutput {
type Output = Text;
fn run(ui: &mut Ui, builder: TextBuilder<Self>) -> Self::Output {
let mut buf = TextBuffer::new_empty(Metrics::new(
builder.attrs.font_size,
builder.attrs.line_height,
));
let font_system = &mut ui.data.text.font_system;
buf.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None);
let mut text = Text {
content: builder.content.into(),
view: TextView::new(buf, builder.attrs),
};
text.content.changed = false;
builder.attrs.apply(font_system, &mut text.view.buf, None);
text
}
}
pub struct TextEditOutput;
impl TextBuilderOutput for TextEditOutput {
type Output = TextEdit;
fn run(ui: &mut Ui, builder: TextBuilder<Self>) -> Self::Output {
let buf = TextBuffer::new_empty(Metrics::new(
builder.attrs.font_size,
builder.attrs.line_height,
));
let mut text = TextEdit {
view: TextView::new(buf, builder.attrs),
cursor: None,
};
let font_system = &mut ui.data.text.font_system;
text.buf
.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None);
builder.attrs.apply(font_system, &mut text.buf, None);
text
}
}
impl<O: TextBuilderOutput> FnOnce<(&mut Ui,)> for TextBuilder<O> {
type Output = O::Output;
extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
O::run(args.0, self)
}
}
pub fn text(content: impl Into<String>) -> TextBuilder {
TextBuilder {
content: content.into(),
attrs: TextAttrs::default(),
_pd: PhantomData,
}
}

View File

@@ -1,288 +0,0 @@
use std::ops::{Deref, DerefMut};
use crate::prelude::*;
use cosmic_text::{Affinity, Attrs, Cursor, FontSystem, Motion};
use unicode_segmentation::UnicodeSegmentation;
use winit::{
event::KeyEvent,
keyboard::{Key, NamedKey},
};
pub struct TextEdit {
pub(super) view: TextView,
pub(super) cursor: Option<Cursor>,
}
impl TextEdit {
pub fn region(&self) -> UiRegion {
self.tex()
.map(|t| t.size())
.unwrap_or(Vec2::ZERO)
.align(self.align)
}
pub fn content(&self) -> String {
self.buf
.lines
.iter()
.map(|l| l.text())
.collect::<Vec<_>>()
.join("\n")
}
}
impl Widget for TextEdit {
fn draw(&mut self, painter: &mut Painter) {
let tex = self.view.draw(&mut painter.size_ctx());
let region = text_region(&tex, self.align);
painter.texture_within(&tex.handle, region);
if let Some(cursor) = &self.cursor
&& let Some(offset) = cursor_pos(cursor, &self.buf)
{
let size = vec2(1, self.attrs.line_height);
painter.primitive_within(
RectPrimitive::color(Color::WHITE),
size.align(Align::TOP_LEFT).offset(offset).within(&region),
);
}
}
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
Len::abs(self.view.draw(ctx).size().x)
}
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
Len::abs(self.view.draw(ctx).size().y)
}
}
/// copied & modified from fn found in Editor in cosmic_text
fn cursor_pos(cursor: &Cursor, buf: &TextBuffer) -> Option<(f32, f32)> {
let mut prev = None;
for run in buf
.layout_runs()
.skip_while(|r| r.line_i < cursor.line)
.take_while(|r| r.line_i == cursor.line)
{
prev = Some((run.line_w, run.line_top));
for glyph in run.glyphs.iter() {
if cursor.index == glyph.start {
return Some((glyph.x, run.line_top));
} else if cursor.index > glyph.start && cursor.index < glyph.end {
// Guess x offset based on characters
let mut before = 0;
let mut total = 0;
let cluster = &run.text[glyph.start..glyph.end];
for (i, _) in cluster.grapheme_indices(true) {
if glyph.start + i < cursor.index {
before += 1;
}
total += 1;
}
let offset = glyph.w * (before as f32) / (total as f32);
return Some((glyph.x + offset, run.line_top));
}
}
}
prev
}
pub struct TextEditCtx<'a> {
pub text: &'a mut TextEdit,
pub font_system: &'a mut FontSystem,
}
impl<'a> TextEditCtx<'a> {
pub fn take(&mut self) -> String {
let text = self
.text
.buf
.lines
.drain(..)
.map(|l| l.into_text())
.collect::<Vec<_>>()
.join("\n");
self.text
.buf
.set_text(self.font_system, "", &Attrs::new(), SHAPING, None);
if let Some(cursor) = &mut self.text.cursor {
cursor.line = 0;
cursor.index = 0;
cursor.affinity = Affinity::default();
}
text
}
pub fn motion(&mut self, motion: Motion) {
if let Some(cursor) = self.text.cursor
&& let Some((cursor, _)) =
self.text
.buf
.cursor_motion(self.font_system, cursor, None, motion)
{
self.text.cursor = Some(cursor);
}
}
pub fn replace(&mut self, len: usize, text: &str) {
for _ in 0..len {
self.delete();
}
self.insert_inner(text, false);
}
pub fn insert(&mut self, text: &str) {
let mut lines = text.split('\n');
let Some(first) = lines.next() else {
return;
};
self.insert_inner(first, true);
for line in lines {
self.newline();
self.insert_inner(line, true);
}
}
fn insert_inner(&mut self, text: &str, mov: bool) {
if let Some(cursor) = &mut self.text.cursor {
let line = &mut self.text.view.buf.lines[cursor.line];
let mut line_text = line.text().to_string();
line_text.insert_str(cursor.index, text);
line.set_text(line_text, line.ending(), line.attrs_list().clone());
if mov {
for _ in 0..text.chars().count() {
self.motion(Motion::Right);
}
}
}
}
pub fn newline(&mut self) {
if let Some(cursor) = &mut self.text.cursor {
let lines = &mut self.text.view.buf.lines;
let line = &mut lines[cursor.line];
let new = line.split_off(cursor.index);
cursor.line += 1;
lines.insert(cursor.line, new);
cursor.index = 0;
}
}
pub fn backspace(&mut self) {
if let Some(cursor) = &mut self.text.cursor
&& (cursor.index != 0 || cursor.line != 0)
{
self.motion(Motion::Left);
self.delete();
}
}
pub fn delete(&mut self) {
if let Some(cursor) = &mut self.text.cursor {
let lines = &mut self.text.view.buf.lines;
let line = &mut lines[cursor.line];
if cursor.index == line.text().len() {
if cursor.line == lines.len() - 1 {
return;
}
let add = lines.remove(cursor.line + 1).into_text();
let line = &mut lines[cursor.line];
let mut cur = line.text().to_string();
cur.push_str(&add);
line.set_text(cur, line.ending(), line.attrs_list().clone());
} else {
let mut text = line.text().to_string();
text.remove(cursor.index);
line.set_text(text, line.ending(), line.attrs_list().clone());
}
}
}
pub fn select(&mut self, pos: Vec2, size: Vec2) {
let pos = pos - self.text.region().top_left().to_abs(size);
self.text.cursor = self.text.buf.hit(pos.x, pos.y);
}
pub fn deselect(&mut self) {
self.text.cursor = None;
}
pub fn apply_event(&mut self, event: &KeyEvent, modifiers: &Modifiers) -> TextInputResult {
match &event.logical_key {
Key::Named(named) => match named {
NamedKey::Backspace => self.backspace(),
NamedKey::Delete => self.delete(),
NamedKey::Space => self.insert(" "),
NamedKey::Enter => {
if modifiers.shift {
self.newline();
} else {
return TextInputResult::Submit;
}
}
NamedKey::ArrowRight => self.motion(Motion::Right),
NamedKey::ArrowLeft => self.motion(Motion::Left),
NamedKey::ArrowUp => self.motion(Motion::Up),
NamedKey::ArrowDown => self.motion(Motion::Down),
NamedKey::Escape => {
self.deselect();
return TextInputResult::Unfocus;
}
_ => return TextInputResult::Unused,
},
Key::Character(text) => {
if modifiers.control && text == "v" {
return TextInputResult::Paste;
} else {
self.insert(text)
}
}
_ => return TextInputResult::Unused,
}
TextInputResult::Used
}
}
#[derive(Default)]
pub struct Modifiers {
pub shift: bool,
pub control: bool,
}
impl Modifiers {
pub fn clear(&mut self) {
self.shift = false;
self.control = false;
}
}
pub enum TextInputResult {
Used,
Unused,
Unfocus,
Submit,
Paste,
}
impl TextInputResult {
pub fn unfocus(&self) -> bool {
matches!(self, TextInputResult::Unfocus)
}
}
impl Deref for TextEdit {
type Target = TextView;
fn deref(&self) -> &Self::Target {
&self.view
}
}
impl DerefMut for TextEdit {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.view
}
}

View File

@@ -1,139 +0,0 @@
mod build;
mod edit;
pub use build::*;
pub use edit::*;
use crate::{prelude::*, util::MutDetect};
use cosmic_text::{Attrs, Metrics, Shaping};
use std::ops::{Deref, DerefMut};
pub const SHAPING: Shaping = Shaping::Advanced;
pub struct Text {
pub content: MutDetect<String>,
view: TextView,
}
pub struct TextView {
pub attrs: MutDetect<TextAttrs>,
pub buf: MutDetect<TextBuffer>,
// cache
tex: Option<TextTexture>,
width: Option<f32>,
}
impl TextView {
pub fn new(buf: TextBuffer, attrs: TextAttrs) -> Self {
Self {
attrs: attrs.into(),
buf: buf.into(),
tex: None,
width: None,
}
}
pub fn draw(&mut self, ctx: &mut SizeCtx) -> TextTexture {
let width = if self.attrs.wrap {
Some(ctx.px_size().x)
} else {
None
};
if width == self.width
&& let Some(tex) = &self.tex
&& !self.attrs.changed
&& !self.buf.changed
{
return tex.clone();
}
self.width = width;
let font_system = &mut ctx.text.font_system;
self.attrs.apply(font_system, &mut self.buf, width);
self.buf.shape_until_scroll(font_system, false);
let tex = ctx.draw_text(&mut self.buf, &self.attrs);
self.tex = Some(tex.clone());
self.attrs.changed = false;
self.buf.changed = false;
tex
}
pub fn tex(&self) -> Option<&TextTexture> {
self.tex.as_ref()
}
}
impl Text {
pub fn new(content: impl Into<String>) -> Self {
let attrs = TextAttrs::default();
let buf = TextBuffer::new_empty(Metrics::new(attrs.font_size, attrs.line_height));
Self {
content: content.into().into(),
view: TextView::new(buf, attrs),
}
}
fn update_buf(&mut self, ctx: &mut SizeCtx) -> TextTexture {
if self.content.changed {
self.content.changed = false;
self.view.buf.set_text(
&mut ctx.text.font_system,
&self.content,
&Attrs::new().family(self.view.attrs.family),
SHAPING,
None,
);
}
self.view.draw(ctx)
}
}
impl Widget for Text {
fn draw(&mut self, painter: &mut Painter) {
let tex = self.update_buf(&mut painter.size_ctx());
let region = text_region(&tex, self.align);
painter.texture_within(&tex.handle, region);
}
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
Len::abs(self.update_buf(ctx).size().x)
}
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
Len::abs(self.update_buf(ctx).size().y)
}
}
pub fn text_region(tex: &TextTexture, align: RegionAlign) -> UiRegion {
let tex_dims = tex.handle.size();
let mut region = tex.size().align(align);
region.x.start.abs += tex.top_left.x;
region.y.start.abs += tex.top_left.y;
region.x.end.abs = region.x.start.abs + tex_dims.x;
region.y.end.abs = region.y.start.abs + tex_dims.y;
region
}
impl Deref for Text {
type Target = TextAttrs;
fn deref(&self) -> &Self::Target {
&self.view
}
}
impl DerefMut for Text {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.view
}
}
impl Deref for TextView {
type Target = TextAttrs;
fn deref(&self) -> &Self::Target {
&self.attrs
}
}
impl DerefMut for TextView {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.attrs
}
}

View File

@@ -1,125 +0,0 @@
use super::*;
use crate::prelude::*;
pub trait CoreWidget<W, Tag> {
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad>;
fn align(self, align: impl Into<Align>) -> impl WidgetFn<Aligned>;
fn center(self) -> impl WidgetFn<Aligned>;
fn label(self, label: impl Into<String>) -> impl WidgetIdFn<W>;
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Sized>;
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Sized>;
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Sized>;
fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<Offset>;
fn scroll(self) -> impl WidgetIdFn<Scroll>;
fn masked(self) -> impl WidgetFn<Masked>;
fn background<T>(self, w: impl WidgetLike<T>) -> impl WidgetFn<Stack>;
fn z_offset(self, offset: usize) -> impl WidgetFn<Stack>;
}
impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad> {
|ui| Pad {
padding: padding.into(),
inner: self.add(ui).any(),
}
}
fn align(self, align: impl Into<Align>) -> impl WidgetFn<Aligned> {
move |ui| Aligned {
inner: self.add(ui).any(),
align: align.into(),
}
}
fn center(self) -> impl WidgetFn<Aligned> {
self.align(Align::CENTER)
}
fn label(self, label: impl Into<String>) -> impl WidgetIdFn<W::Widget> {
|ui| {
let id = self.add(ui);
ui.set_label(&id, label.into());
id
}
}
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Sized> {
let size = size.into();
move |ui| Sized {
inner: self.add(ui).any(),
x: Some(size.x),
y: Some(size.y),
}
}
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Sized> {
let len = len.into();
move |ui| Sized {
inner: self.add(ui).any(),
x: Some(len),
y: None,
}
}
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Sized> {
let len = len.into();
move |ui| Sized {
inner: self.add(ui).any(),
x: None,
y: Some(len),
}
}
fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<Offset> {
move |ui| Offset {
inner: self.add(ui).any(),
amt: amt.into(),
}
}
fn scroll(self) -> impl WidgetIdFn<Scroll> {
move |ui| {
Scroll::new(self.add(ui).any(), Axis::Y)
.edit_on(CursorSense::Scroll, |w, data| {
w.scroll(data.scroll_delta.y * 50.0);
})
.add(ui)
}
}
fn masked(self) -> impl WidgetFn<Masked> {
move |ui| Masked {
inner: self.add(ui).any(),
}
}
fn background<T>(self, w: impl WidgetLike<T>) -> impl WidgetFn<Stack> {
move |ui| Stack {
children: vec![w.add(ui).any(), self.add(ui).any()],
size: StackSize::Child(1),
offset: 0,
}
}
fn z_offset(self, offset: usize) -> impl WidgetFn<Stack> {
move |ui| Stack {
children: vec![self.add(ui).any()],
size: StackSize::Child(0),
offset,
}
}
}
pub trait CoreWidgetArr<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
fn span(self, dir: Dir) -> SpanBuilder<LEN, Wa, Tag>;
fn stack(self) -> StackBuilder<LEN, Wa, Tag>;
}
impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> CoreWidgetArr<LEN, Wa, Tag> for Wa {
fn span(self, dir: Dir) -> SpanBuilder<LEN, Wa, Tag> {
SpanBuilder::new(self, dir)
}
fn stack(self) -> StackBuilder<LEN, Wa, Tag> {
StackBuilder::new(self)
}
}

View File

@@ -1,138 +0,0 @@
#![allow(clippy::multiple_bound_locations)]
/// stored in linear for sane manipulation
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Zeroable, Debug)]
pub struct Color<T: ColorNum> {
pub r: T,
pub g: T,
pub b: T,
pub 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 TURQUOISE: Self = Self::rgb(T::MIN, T::MAX, T::MID);
pub const CYAN: Self = Self::rgb(T::MIN, T::MAX, T::MAX);
pub const SKY: Self = Self::rgb(T::MIN, T::MID, T::MAX);
pub const BLUE: Self = Self::rgb(T::MIN, T::MIN, T::MAX);
pub const PURPLE: Self = Self::rgb(T::MID, T::MIN, T::MAX);
pub const MAGENTA: Self = Self::rgb(T::MAX, T::MIN, T::MAX);
pub const NONE: Self = Self::new(T::MIN, T::MIN, T::MIN, T::MIN);
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 fn as_arr(self) -> [T; 4] {
[self.r, self.g, self.b, self.a]
}
}
pub const trait F32Conversion {
fn to(self) -> f32;
fn from(x: f32) -> Self;
}
pub trait ColorNum {
const MIN: Self;
const MID: Self;
const MAX: Self;
}
impl<T: ColorNum + F32Conversion> Color<T> {
pub fn mul_rgb(self, amt: impl F32Conversion) -> Self {
let amt = amt.to();
self.map_rgb(|x| T::from(x.to() * amt))
}
pub fn add_rgb(self, amt: impl F32Conversion) -> Self {
let amt = amt.to();
self.map_rgb(|x| T::from(x.to() + amt))
}
pub fn darker(self, amt: f32) -> Self {
self.mul_rgb(1.0 - amt)
}
pub fn brighter(self, amt: f32) -> Self {
self.map_rgb(|x| {
let x = x.to();
T::from(x + (1.0 - x) * amt)
})
}
pub fn map_rgb(self, f: impl Fn(T) -> T) -> Self {
Self {
r: f(self.r),
g: f(self.g),
b: f(self.b),
a: self.a,
}
}
pub fn srgb(r: T, g: T, b: T) -> Self {
Self {
r: s_to_l(r),
g: s_to_l(g),
b: s_to_l(b),
a: T::MAX,
}
}
}
fn s_to_l<T: F32Conversion>(x: T) -> T {
let x = x.to();
T::from(if x <= 0.0405 {
x / 12.92
} else {
((x + 0.055) / 1.055).powf(2.4)
})
}
impl ColorNum for u8 {
const MIN: Self = u8::MIN;
const MID: Self = u8::MAX / 2;
const MAX: Self = u8::MAX;
}
impl ColorNum for f32 {
const MIN: Self = 0.0;
const MID: Self = 0.5;
const MAX: Self = 1.0;
}
unsafe impl bytemuck::Pod for Color<u8> {}
impl const F32Conversion for f32 {
fn to(self) -> f32 {
self
}
fn from(x: f32) -> Self {
x
}
}
impl const F32Conversion for u8 {
fn to(self) -> f32 {
self as f32 / 255.0
}
fn from(x: f32) -> Self {
(x * 255.0).clamp(0.0, 255.0) as Self
}
}

View File

@@ -1,27 +0,0 @@
use std::any::{Any, TypeId};
use crate::util::{HashMap, Id};
#[derive(Default)]
pub struct UiData {
map: HashMap<TypeId, Box<dyn Any>>,
}
impl UiData {
pub fn get<T: 'static>(&self) -> Option<&T> {
self.map
.get(&TypeId::of::<T>())
.map(|d| d.downcast_ref().unwrap())
}
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.map
.get_mut(&TypeId::of::<T>())
.map(|d| d.downcast_mut().unwrap())
}
pub fn emit_remove(&mut self, id: &Id) {
for (tid, f) in &mut self.on_remove {
let data = self.map.get_mut(tid).unwrap().downcast_ref().unwrap();
}
}
}

View File

@@ -1,202 +0,0 @@
use std::{hash::Hash, rc::Rc};
use crate::{
layout::{IdFnTag, Ui, UiModule, Widget, WidgetId, WidgetIdFn, WidgetLike},
util::{HashMap, Id},
};
pub trait UiCtx {
fn ui(&mut self) -> &mut Ui;
}
impl UiCtx for Ui {
fn ui(&mut self) -> &mut Ui {
self
}
}
pub trait Event: Sized {
type Module<Ctx: 'static>: EventModule<Self, Ctx>;
type Data: Clone;
}
pub trait EventFn<Ctx, Data>: Fn(&mut Ctx, Data) + 'static {}
impl<F: Fn(&mut Ctx, Data) + 'static, Ctx, Data> EventFn<Ctx, Data> for F {}
pub trait Eventable<W, Tag> {
fn on<E: Event, Ctx: 'static>(
self,
event: E,
f: impl EventFn<Ctx, E::Data>,
) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag>;
fn id_on<E: Event, Ctx: 'static>(
self,
event: E,
f: impl Fn(&WidgetId<W>, &mut Ctx, E::Data) + 'static,
) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag>
where
W: Widget;
fn edit_on<E: Event>(
self,
event: E,
f: impl Fn(&mut W, E::Data) + 'static,
) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag>
where
W: Widget;
}
impl<W: WidgetLike<Tag>, Tag> Eventable<W::Widget, Tag> for W {
fn on<E: Event, Ctx: 'static>(
self,
event: E,
f: impl EventFn<Ctx, E::Data>,
) -> impl WidgetIdFn<W::Widget> {
move |ui| {
let id = self.add(ui);
ui.data
.modules
.get_mut::<E::Module<Ctx>>()
.register(id.id, event, f);
id
}
}
fn id_on<E: Event, Ctx: 'static>(
self,
event: E,
f: impl Fn(&WidgetId<W::Widget>, &mut Ctx, E::Data) + 'static,
) -> impl WidgetIdFn<W::Widget>
where
W::Widget: Widget,
{
self.with_id(move |ui, id| {
let id2 = id.clone();
id.on(event, move |ctx, pos| f(&id2, ctx, pos)).add(ui)
})
}
fn edit_on<E: Event>(
self,
event: E,
f: impl Fn(&mut W::Widget, E::Data) + 'static,
) -> impl WidgetIdFn<W::Widget>
where
W::Widget: Widget,
{
self.id_on(event, move |id, ui: &mut Ui, pos| f(&mut ui[id], pos))
}
}
pub trait DefaultEvent: Hash + Eq + 'static {
type Data: Clone;
}
impl<E: DefaultEvent> Event for E {
type Module<Ctx: 'static> = DefaultEventModule<E, Ctx>;
type Data = E::Data;
}
pub trait EventModule<E: Event, Ctx>: UiModule + Default {
fn register(&mut self, id: Id, event: E, f: impl EventFn<Ctx, E::Data>);
fn run<'a>(
&self,
id: &Id,
event: E,
) -> Option<impl Fn(&mut Ctx, E::Data) + use<'a, Self, E, Ctx>>;
}
type EventFnMap<Ctx, Data> = HashMap<Id, Vec<Rc<dyn EventFn<Ctx, Data>>>>;
pub struct DefaultEventModule<E: Event, Ctx> {
map: HashMap<E, EventFnMap<Ctx, <E as Event>::Data>>,
}
impl<E: Event + 'static, Ctx: 'static> UiModule for DefaultEventModule<E, Ctx> {
fn on_remove(&mut self, id: &Id) {
for map in self.map.values_mut() {
map.remove(id);
}
}
}
pub trait HashableEvent: Event + Hash + Eq + 'static {}
impl<E: Event + Hash + Eq + 'static> HashableEvent for E {}
impl<E: HashableEvent, Ctx: 'static> EventModule<E, Ctx> for DefaultEventModule<E, Ctx> {
fn register(&mut self, id: Id, event: E, f: impl EventFn<Ctx, <E as Event>::Data>) {
self.map
.entry(event)
.or_default()
.entry(id)
.or_default()
.push(Rc::new(f));
}
fn run<'a>(&self, id: &Id, event: E) -> Option<impl Fn(&mut Ctx, E::Data) + use<'a, E, Ctx>> {
if let Some(map) = self.map.get(&event)
&& let Some(fs) = map.get(id)
{
let fs = fs.clone();
Some(move |ctx: &mut Ctx, data: E::Data| {
for f in &fs {
f(ctx, data.clone())
}
})
} else {
None
}
}
}
impl<E: HashableEvent, Ctx: 'static> DefaultEventModule<E, Ctx> {
pub fn run_all(&self, ctx: &mut Ctx, event: E, data: E::Data)
where
E::Data: Clone,
{
if let Some(map) = self.map.get(&event) {
for fs in map.values() {
for f in fs {
f(ctx, data.clone())
}
}
}
}
}
impl<E: Event + 'static, Ctx: 'static> Default for DefaultEventModule<E, Ctx> {
fn default() -> Self {
Self {
map: Default::default(),
}
}
}
impl Ui {
pub fn run_event<E: Event, Ctx: UiCtx + 'static, W>(
ctx: &mut Ctx,
id: &WidgetId<W>,
event: E,
data: E::Data,
) {
if let Some(f) = ctx
.ui()
.data
.modules
.get_mut::<E::Module<Ctx>>()
.run(&id.id, event)
{
f(ctx, data);
}
}
}
pub trait EventCtx: UiCtx {
fn run_event<E: Event + Clone, W>(&mut self, id: &WidgetId<W>, event: E, data: E::Data);
}
impl<Ctx: UiCtx + 'static> EventCtx for Ctx {
fn run_event<E: Event + Clone, W>(&mut self, id: &WidgetId<W>, event: E, data: E::Data) {
Ui::run_event(self, id, event.clone(), data.clone());
}
}

View File

@@ -1,250 +0,0 @@
use std::{
any::TypeId,
marker::PhantomData,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
mpsc::Sender,
},
};
use crate::{
layout::{FnTag, Ui, Widget, WidgetLike, WidgetTag},
util::{Id, RefCounter},
};
pub struct AnyWidget;
/// An identifier for a widget that can index a UI to get the associated widget.
/// It should always remain valid; it keeps a ref count and removes the widget from the UI if all
/// references are dropped.
///
/// W does not need to implement widget so that AnyWidget is valid;
/// Instead, add generic bounds on methods that take an ID if they need specific data.
///
/// TODO: ergonomic clones when they get put in rust-analyzer & don't cause ICEs?
#[repr(C)]
pub struct WidgetId<W = AnyWidget> {
pub(super) ty: TypeId,
pub(super) id: Id,
counter: RefCounter,
send: Sender<Id>,
is_static: Arc<AtomicBool>,
_pd: PhantomData<W>,
}
/// A WidgetId for a static widget that cannot be removed from a Ui.
/// Useful because ergonomic clones don't exist yet so you can easily use these in closures.
/// Do not use this if you want the widget to be freeable.
///
/// This is currently not perfectly efficient and just creates new WidgetIds every time it's used,
/// but they don't send drop messages to Ui.
/// Ideally I'd have an enum or something that lets you use either, but that doesn't seem worth it
/// right now; it's good enough and relatively cheap.
#[repr(C)]
pub struct StaticWidgetId<W = AnyWidget> {
pub(super) ty: TypeId,
pub(super) id: Id,
_pd: PhantomData<W>,
}
impl<W> std::fmt::Debug for WidgetId<W> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.id.fmt(f)
}
}
impl<W> Clone for WidgetId<W> {
fn clone(&self) -> Self {
Self {
id: self.id,
ty: self.ty,
counter: self.counter.clone(),
send: self.send.clone(),
is_static: self.is_static.clone(),
_pd: PhantomData,
}
}
}
impl<W> WidgetId<W> {
pub(super) fn new(id: Id, ty: TypeId, send: Sender<Id>, is_static: bool) -> Self {
Self {
ty,
id,
counter: RefCounter::new(),
send,
is_static: Arc::new(is_static.into()),
_pd: PhantomData,
}
}
pub fn any(self) -> WidgetId<AnyWidget> {
self.cast_type()
}
pub fn as_any(&self) -> &WidgetId<AnyWidget> {
// SAFETY: self is repr(C) and generic only used for phantom data
unsafe { std::mem::transmute(self) }
}
pub fn key(&self) -> Id {
self.id
}
pub(super) fn cast_type<W2>(self) -> WidgetId<W2> {
// SAFETY: self is repr(C) and generic only used for phantom data
unsafe { std::mem::transmute(self) }
}
pub fn refs(&self) -> u32 {
self.counter.refs()
}
pub fn into_static(self) -> StaticWidgetId<W> {
self.is_static.store(true, Ordering::Release);
StaticWidgetId {
ty: self.ty,
id: self.id,
_pd: PhantomData,
}
}
}
impl WidgetId {
pub fn set_static<W>(&mut self, other: StaticWidgetId<W>) {
let send = self.send.clone();
drop(std::mem::replace(
self,
Self::new(other.id, self.ty, send, true),
));
}
}
impl<W> Drop for WidgetId<W> {
fn drop(&mut self) {
if self.counter.drop() && !self.is_static.load(Ordering::Acquire) {
let _ = self.send.send(self.id);
}
}
}
pub struct IdTag;
pub struct IdFnTag;
pub trait WidgetIdFn<W>: FnOnce(&mut Ui) -> WidgetId<W> {}
impl<W, F: FnOnce(&mut Ui) -> WidgetId<W>> WidgetIdFn<W> for F {}
/// TODO: does this ever make sense to use? it allows for invalid ids
pub trait Idable<Tag> {
type Widget: Widget;
fn set(self, ui: &mut Ui, id: &WidgetId<Self::Widget>);
fn id(self, id: &WidgetId<Self::Widget>) -> impl WidgetIdFn<Self::Widget>
where
Self: Sized,
{
let id = id.clone();
move |ui| {
self.set(ui, &id);
id
}
}
fn id_static(self, id: StaticWidgetId<Self::Widget>) -> impl WidgetIdFn<Self::Widget>
where
Self: Sized,
{
move |ui| {
let id = id.id(&ui.send);
self.set(ui, &id);
id
}
}
}
impl<W: Widget> Idable<WidgetTag> for W {
type Widget = W;
fn set(self, ui: &mut Ui, id: &WidgetId<Self::Widget>) {
ui.set(id, self);
}
}
impl<F: FnOnce(&mut Ui) -> W, W: Widget> Idable<FnTag> for F {
type Widget = W;
fn set(self, ui: &mut Ui, id: &WidgetId<Self::Widget>) {
let w = self(ui);
ui.set(id, w);
}
}
impl<W: 'static> WidgetLike<IdTag> for WidgetId<W> {
type Widget = W;
fn add(self, _: &mut Ui) -> WidgetId<W> {
self
}
}
impl<W: 'static, F: FnOnce(&mut Ui) -> WidgetId<W>> WidgetLike<IdFnTag> for F {
type Widget = W;
fn add(self, ui: &mut Ui) -> WidgetId<W> {
self(ui)
}
}
impl<W> StaticWidgetId<W> {
pub fn to_id(&self, send: &Sender<Id>) -> WidgetId<W> {
WidgetId::new(self.id, self.ty, send.clone(), true)
}
pub fn any(self) -> StaticWidgetId<AnyWidget> {
// SAFETY: self is repr(C)
unsafe { std::mem::transmute(self) }
}
}
impl<W: 'static> WidgetLike<IdTag> for StaticWidgetId<W> {
type Widget = W;
fn add(self, ui: &mut Ui) -> WidgetId<W> {
self.id(&ui.send)
}
}
impl<W> Clone for StaticWidgetId<W> {
fn clone(&self) -> Self {
*self
}
}
impl<W> Copy for StaticWidgetId<W> {}
pub trait WidgetIdLike<W> {
fn id(self, send: &Sender<Id>) -> WidgetId<W>;
}
impl<W> WidgetIdLike<W> for &WidgetId<W> {
fn id(self, _: &Sender<Id>) -> WidgetId<W> {
self.clone()
}
}
impl<W> WidgetIdLike<W> for StaticWidgetId<W> {
fn id(self, send: &Sender<Id>) -> WidgetId<W> {
self.to_id(send)
}
}
pub trait IdLike<W> {
fn id(&self) -> Id;
}
impl<W> IdLike<W> for WidgetId<W> {
fn id(&self) -> Id {
self.id
}
}
impl<W> IdLike<W> for StaticWidgetId<W> {
fn id(&self) -> Id {
self.id
}
}

View File

@@ -1,257 +0,0 @@
use std::ops::{Index, IndexMut};
use crate::render::{MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives};
struct LayerNode {
next: Ptr,
prev: Ptr,
child: Option<Child>,
data: Layer,
}
#[derive(Clone, Copy, Debug)]
enum Ptr {
/// continue on same level
Next(usize),
/// go back to parent
Parent(usize),
/// end
None,
}
/// TODO: currently this does not ever free layers
/// is that realistically desired?
pub struct Layers {
vec: Vec<LayerNode>,
/// index of last layer at top level (start at first = 0)
last: usize,
}
/// TODO: this can be replaced with Primitives itself atm
#[derive(Default)]
pub struct Layer {
pub primitives: Primitives,
}
#[derive(Clone, Copy)]
struct Child {
head: usize,
tail: usize,
}
impl Layers {
pub fn new() -> Layers {
Self {
vec: vec![LayerNode::head()],
last: 0,
}
}
pub fn clear(&mut self) {
self.vec.clear();
self.vec.push(LayerNode::head());
}
fn push(&mut self, node: LayerNode) -> usize {
let i = self.vec.len();
self.vec.push(node);
i
}
pub fn next(&mut self, i: usize) -> usize {
if let Ptr::Next(i) = self.vec[i].next {
return i;
}
let i_new = self.push(LayerNode::new(
Layer::default(),
self.vec[i].next,
Ptr::Next(i),
));
self.vec[i].next = Ptr::Next(i_new);
self.vec[i_new].prev = Ptr::Next(i);
match self.vec[i_new].next {
Ptr::Next(i) => self.vec[i].prev = Ptr::Next(i_new),
Ptr::Parent(i) => self.vec[i].child.as_mut().unwrap().tail = i_new,
Ptr::None => self.last = i_new,
}
i_new
}
pub fn child(&mut self, i: usize) -> usize {
if let Some(c) = self.vec[i].child {
return c.head;
}
let i_child = self.push(LayerNode::new(
Layer::default(),
Ptr::Parent(i),
Ptr::Parent(i),
));
self.vec[i].child = Some(Child {
head: i_child,
tail: i_child,
});
i_child
}
pub fn iter_mut(&mut self) -> LayerIteratorMut<'_> {
LayerIteratorMut::new(&mut self.vec, self.last)
}
pub fn iter(&self) -> impl Iterator<Item = (usize, &Layer)> {
self.indices().map(|i| (i, &self.vec[i].data))
}
pub fn indices(&self) -> LayerIndexIterator<'_> {
LayerIndexIterator::new(&self.vec, self.last)
}
pub fn write<P: Primitive>(&mut self, layer: usize, info: PrimitiveInst<P>) -> PrimitiveHandle {
self[layer].primitives.write(layer, info)
}
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
self[h.layer].primitives.free(h)
}
}
impl Default for Layers {
fn default() -> Self {
Self::new()
}
}
impl Index<usize> for Layers {
type Output = Layer;
fn index(&self, index: usize) -> &Self::Output {
&self.vec[index].data
}
}
impl IndexMut<usize> for Layers {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.vec[index].data
}
}
impl LayerNode {
pub fn new(data: Layer, next: Ptr, prev: Ptr) -> Self {
Self {
next,
prev,
child: None,
data,
}
}
pub fn head() -> Self {
Self::new(Layer::default(), Ptr::None, Ptr::None)
}
}
pub struct LayerIteratorMut<'a> {
inner: LayerIndexIterator<'a>,
}
impl<'a> Iterator for LayerIteratorMut<'a> {
type Item = (usize, &'a mut Layer);
fn next(&mut self) -> Option<Self::Item> {
let i = self.inner.next()?;
// SAFETY: requires index iterator to work properly
#[allow(mutable_transmutes)]
let layer = unsafe { std::mem::transmute::<&Layer, &mut Layer>(&self.inner.vec[i].data) };
Some((i, layer))
}
}
impl<'a> DoubleEndedIterator for LayerIteratorMut<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
let i = self.inner.next_back()?;
// SAFETY: requires index iterator to work properly
#[allow(mutable_transmutes)]
let layer = unsafe { std::mem::transmute::<&Layer, &mut Layer>(&self.inner.vec[i].data) };
Some((i, layer))
}
}
impl<'a> LayerIteratorMut<'a> {
fn new(vec: &'a mut Vec<LayerNode>, last: usize) -> Self {
Self {
inner: LayerIndexIterator::new(vec, last),
}
}
}
pub struct LayerIndexIterator<'a> {
next: Option<usize>,
next_back: Option<usize>,
vec: &'a Vec<LayerNode>,
}
impl<'a> Iterator for LayerIndexIterator<'a> {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
let ret_i = self.next?;
let node = &self.vec[ret_i];
self.next = if let Some(c) = node.child {
Some(c.head)
} else if let Ptr::Next(i) = node.next {
Some(i)
} else if let Ptr::Parent(i) = node.next {
let mut node = &self.vec[i];
while let Ptr::Parent(i) = node.next {
node = &self.vec[i];
}
if let Ptr::Next(i) = node.next {
Some(i)
} else {
None
}
} else {
None
};
if self.next_back.unwrap() == ret_i {
self.next = None;
self.next_back = None;
}
Some(ret_i)
}
}
impl<'a> DoubleEndedIterator for LayerIndexIterator<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
let ret_i = self.next_back?;
let node = &self.vec[ret_i];
self.next_back = if let Ptr::Next(mut i) = node.prev {
while let Some(c) = self.vec[i].child {
i = c.tail
}
Some(i)
} else if let Ptr::Parent(i) = node.prev {
Some(i)
} else {
None
};
if self.next.unwrap() == ret_i {
self.next = None;
self.next_back = None;
}
Some(ret_i)
}
}
impl<'a> LayerIndexIterator<'a> {
fn new(vec: &'a Vec<LayerNode>, last: usize) -> Self {
let mut last = last;
while let Some(c) = vec[last].child {
last = c.tail;
}
Self {
next: Some(0),
next_back: Some(last),
vec,
}
}
}

View File

@@ -1,46 +0,0 @@
//! tree structure for masking
use crate::layout::UiRegion;
pub struct Masks {
data: Vec<MaskNode>,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct MaskPtr(u32);
#[repr(C)]
pub struct MaskNode {
/// TODO: this is just a rect for now,
/// but would like to support arbitrary masks
/// at some point; custom shader
/// would probably handle that case
/// bc you'd need to render to a special target
/// anyways
region: UiRegion,
prev: MaskPtr,
}
impl MaskPtr {
const NONE: Self = Self(u32::MAX);
}
impl Masks {
pub fn push(&mut self, parent: MaskPtr, region: UiRegion) -> MaskPtr {
match parent.0 {
_ => {
}
u32::MAX => {
let i = self.data.len();
self.data.push(MaskNode {
region,
prev: parent,
});
MaskPtr(i as u32)
}
}
}
pub fn pop(&mut self, i: usize) {}
}

View File

@@ -1,31 +1,8 @@
mod color;
mod event;
mod id;
mod layer;
mod module;
mod num;
mod orientation;
mod painter;
mod text;
mod texture;
mod ui;
mod vec2;
mod widget;
mod widgets;
pub use color::*;
pub use event::*;
pub use id::*;
pub use layer::*;
pub use module::*;
pub use num::*;
pub use orientation::*;
pub use painter::*;
pub use text::*;
pub use texture::*;
pub use ui::*;
pub use vec2::*;
pub use widget::*;
pub use widgets::*;
pub type UiColor = Color<u8>;
use crate::primitive::Color;
pub type UIColor = Color<u8>;

View File

@@ -1,35 +0,0 @@
use std::any::{Any, TypeId};
use crate::{
layout::WidgetInstance,
util::{HashMap, Id},
};
#[allow(unused_variables)]
pub trait UiModule: Any {
fn on_draw(&mut self, inst: &WidgetInstance) {}
fn on_undraw(&mut self, inst: &WidgetInstance) {}
fn on_remove(&mut self, id: &Id) {}
fn on_move(&mut self, inst: &WidgetInstance) {}
}
#[derive(Default)]
pub struct Modules {
map: HashMap<TypeId, Box<dyn UiModule>>,
}
impl Modules {
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut (dyn UiModule + 'static)> {
self.map.values_mut().map(|m| m.as_mut())
}
pub fn get_mut<M: UiModule + Default>(&mut self) -> &mut M {
let rf = self
.map
.entry(TypeId::of::<M>())
.or_insert_with(|| Box::new(M::default()))
.as_mut();
let any: &mut dyn Any = &mut *rf;
any.downcast_mut().unwrap()
}
}

View File

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

View File

@@ -1,200 +0,0 @@
use crate::layout::vec2;
use super::*;
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Align {
pub x: Option<AxisAlign>,
pub y: Option<AxisAlign>,
}
impl Align {
pub const TOP_LEFT: RegionAlign = RegionAlign::TOP_LEFT;
pub const TOP_CENTER: RegionAlign = RegionAlign::TOP_CENTER;
pub const TOP_RIGHT: RegionAlign = RegionAlign::TOP_RIGHT;
pub const CENTER_LEFT: RegionAlign = RegionAlign::CENTER_LEFT;
pub const CENTER: RegionAlign = RegionAlign::CENTER;
pub const CENTER_RIGHT: RegionAlign = RegionAlign::CENTER_RIGHT;
pub const BOT_LEFT: RegionAlign = RegionAlign::BOT_LEFT;
pub const BOT_CENTER: RegionAlign = RegionAlign::BOT_CENTER;
pub const BOT_RIGHT: RegionAlign = RegionAlign::BOT_RIGHT;
pub const LEFT: CardinalAlign = CardinalAlign::LEFT;
pub const H_CENTER: CardinalAlign = CardinalAlign::H_CENTER;
pub const RIGHT: CardinalAlign = CardinalAlign::RIGHT;
pub const TOP: CardinalAlign = CardinalAlign::TOP;
pub const V_CENTER: CardinalAlign = CardinalAlign::V_CENTER;
pub const BOT: CardinalAlign = CardinalAlign::BOT;
pub fn tuple(&self) -> (Option<AxisAlign>, Option<AxisAlign>) {
(self.x, self.y)
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum AxisAlign {
Neg,
Center,
Pos,
}
impl AxisAlign {
pub const fn rel(&self) -> f32 {
match self {
Self::Neg => 0.0,
Self::Center => 0.5,
Self::Pos => 1.0,
}
}
}
pub struct CardinalAlign {
axis: Axis,
align: AxisAlign,
}
impl CardinalAlign {
pub const LEFT: Self = Self::new(Axis::X, AxisAlign::Neg);
pub const H_CENTER: Self = Self::new(Axis::X, AxisAlign::Center);
pub const RIGHT: Self = Self::new(Axis::X, AxisAlign::Pos);
pub const TOP: Self = Self::new(Axis::Y, AxisAlign::Neg);
pub const V_CENTER: Self = Self::new(Axis::Y, AxisAlign::Center);
pub const BOT: Self = Self::new(Axis::Y, AxisAlign::Pos);
pub const fn new(axis: Axis, align: AxisAlign) -> Self {
Self { axis, align }
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct RegionAlign {
pub x: AxisAlign,
pub y: AxisAlign,
}
impl RegionAlign {
pub const TOP_LEFT: Self = Self::new(AxisAlign::Neg, AxisAlign::Neg);
pub const TOP_CENTER: Self = Self::new(AxisAlign::Center, AxisAlign::Neg);
pub const TOP_RIGHT: Self = Self::new(AxisAlign::Pos, AxisAlign::Neg);
pub const CENTER_LEFT: Self = Self::new(AxisAlign::Neg, AxisAlign::Center);
pub const CENTER: Self = Self::new(AxisAlign::Center, AxisAlign::Center);
pub const CENTER_RIGHT: Self = Self::new(AxisAlign::Pos, AxisAlign::Center);
pub const BOT_LEFT: Self = Self::new(AxisAlign::Neg, AxisAlign::Pos);
pub const BOT_CENTER: Self = Self::new(AxisAlign::Center, AxisAlign::Pos);
pub const BOT_RIGHT: Self = Self::new(AxisAlign::Pos, AxisAlign::Pos);
pub const fn new(x: AxisAlign, y: AxisAlign) -> Self {
Self { x, y }
}
pub const fn rel(&self) -> Vec2 {
vec2(self.x.rel(), self.y.rel())
}
}
impl UiVec2 {
pub fn partial_align(&self, align: Align) -> UiRegion {
UiRegion {
x: if let Some(align) = align.x {
self.x.align(align)
} else {
UiSpan::FULL
},
y: if let Some(align) = align.y {
self.y.align(align)
} else {
UiSpan::FULL
},
}
}
pub fn align(&self, align: RegionAlign) -> UiRegion {
UiRegion {
x: self.x.align(align.x),
y: self.y.align(align.y),
}
}
}
impl Vec2 {
pub fn partial_align(&self, align: Align) -> UiRegion {
let s = UiVec2::from(*self);
UiRegion {
x: if let Some(align) = align.x {
s.x.align(align)
} else {
UiSpan::FULL
},
y: if let Some(align) = align.y {
s.y.align(align)
} else {
UiSpan::FULL
},
}
}
pub fn align(&self, align: RegionAlign) -> UiRegion {
let s = UiVec2::from(*self);
UiRegion {
x: s.x.align(align.x),
y: s.y.align(align.y),
}
}
}
impl UiScalar {
pub const fn align(&self, align: AxisAlign) -> UiSpan {
let rel = align.rel();
let mut start = UiScalar::rel(rel);
start.abs -= self.abs * rel;
start.rel -= self.rel * rel;
let mut end = UiScalar::rel(rel);
end.abs += self.abs * (1.0 - rel);
end.rel += self.rel * (1.0 - rel);
UiSpan { start, end }
}
}
impl From<RegionAlign> for Align {
fn from(region: RegionAlign) -> Self {
Self {
x: Some(region.x),
y: Some(region.y),
}
}
}
impl From<Align> for RegionAlign {
fn from(align: Align) -> Self {
Self {
x: align.x.unwrap_or(AxisAlign::Center),
y: align.y.unwrap_or(AxisAlign::Center),
}
}
}
impl From<CardinalAlign> for RegionAlign {
fn from(align: CardinalAlign) -> Self {
Align::from(align).into()
}
}
impl From<CardinalAlign> for Align {
fn from(cardinal: CardinalAlign) -> Self {
let align = Some(cardinal.align);
match cardinal.axis {
Axis::X => Self { x: align, y: None },
Axis::Y => Self { x: None, y: align },
}
}
}
impl const From<RegionAlign> for UiVec2 {
fn from(align: RegionAlign) -> Self {
Self::rel(align.rel())
}
}
impl RegionAlign {
pub const fn pos(self) -> UiVec2 {
UiVec2::from(self)
}
}

View File

@@ -1,70 +0,0 @@
use super::*;
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Axis {
X,
Y,
}
impl std::ops::Not for Axis {
type Output = Self;
fn not(self) -> Self::Output {
match self {
Self::X => Self::Y,
Self::Y => Self::X,
}
}
}
#[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,
}
impl Vec2 {
pub fn axis(&self, axis: Axis) -> f32 {
match axis {
Axis::X => self.x,
Axis::Y => self.y,
}
}
pub fn axis_mut(&mut self, axis: Axis) -> &mut f32 {
match axis {
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
pub const fn from_axis(axis: Axis, aligned: f32, ortho: f32) -> Self {
Self {
x: match axis {
Axis::X => aligned,
Axis::Y => ortho,
},
y: match axis {
Axis::Y => aligned,
Axis::X => ortho,
},
}
}
}

View File

@@ -1,187 +0,0 @@
use super::*;
use crate::{layout::UiNum, util::impl_op};
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct Size {
pub x: Len,
pub y: Len,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Len {
pub abs: f32,
pub rel: f32,
pub rest: f32,
}
impl<N: UiNum> From<N> for Len {
fn from(value: N) -> Self {
Len::abs(value.to_f32())
}
}
impl<Nx: UiNum, Ny: UiNum> From<(Nx, Ny)> for Size {
fn from((x, y): (Nx, Ny)) -> Self {
Self {
x: x.into(),
y: y.into(),
}
}
}
impl From<Len> for Size {
fn from(value: Len) -> Self {
Self { x: value, y: value }
}
}
impl Size {
pub const ZERO: Self = Self {
x: Len::ZERO,
y: Len::ZERO,
};
pub fn abs(v: Vec2) -> Self {
Self {
x: Len::abs(v.x),
y: Len::abs(v.y),
}
}
pub fn rel(v: Vec2) -> Self {
Self {
x: Len::rel(v.x),
y: Len::rel(v.y),
}
}
pub fn rest(v: Vec2) -> Self {
Self {
x: Len::rest(v.x),
y: Len::rest(v.y),
}
}
pub fn to_uivec2(self) -> UiVec2 {
UiVec2 {
x: self.x.apply_rest(),
y: self.y.apply_rest(),
}
}
pub fn from_axis(axis: Axis, aligned: Len, ortho: Len) -> Self {
match axis {
Axis::X => Self {
x: aligned,
y: ortho,
},
Axis::Y => Self {
x: ortho,
y: aligned,
},
}
}
pub fn axis(&self, axis: Axis) -> Len {
match axis {
Axis::X => self.x,
Axis::Y => self.y,
}
}
}
impl Len {
pub const ZERO: Self = Self {
abs: 0.0,
rel: 0.0,
rest: 0.0,
};
pub fn apply_rest(&self) -> UiScalar {
UiScalar {
rel: self.rel + if self.rest > 0.0 { 1.0 } else { 0.0 },
abs: self.abs,
}
}
pub fn abs(abs: impl UiNum) -> Self {
Self {
abs: abs.to_f32(),
rel: 0.0,
rest: 0.0,
}
}
pub fn rel(rel: impl UiNum) -> Self {
Self {
abs: 0.0,
rel: rel.to_f32(),
rest: 0.0,
}
}
pub fn rest(ratio: impl UiNum) -> Self {
Self {
abs: 0.0,
rel: 0.0,
rest: ratio.to_f32(),
}
}
}
pub mod len_fns {
use super::*;
pub fn abs(abs: impl UiNum) -> Len {
Len {
abs: abs.to_f32(),
rel: 0.0,
rest: 0.0,
}
}
pub fn rel(rel: impl UiNum) -> Len {
Len {
abs: 0.0,
rel: rel.to_f32(),
rest: 0.0,
}
}
pub fn rest(ratio: impl UiNum) -> Len {
Len {
abs: 0.0,
rel: 0.0,
rest: ratio.to_f32(),
}
}
}
impl_op!(Len Add add; abs rel rest);
impl_op!(Len Sub sub; abs rel rest);
impl_op!(Size Add add; x y);
impl_op!(Size Sub sub; x y);
impl Default for Len {
fn default() -> Self {
Self::rest(1.0)
}
}
impl std::fmt::Display for Size {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
impl std::fmt::Display for Len {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.abs != 0.0 {
write!(f, "{} abs;", self.abs)?;
}
if self.rel != 0.0 {
write!(f, "{} rel;", self.rel)?;
}
if self.rest != 0.0 {
write!(f, "{} rest;", self.rest)?;
}
Ok(())
}
}

View File

@@ -1,11 +0,0 @@
mod align;
mod axis;
mod len;
mod pos;
use super::vec2::*;
pub use align::*;
pub use axis::*;
pub use len::*;
pub use pos::*;

View File

@@ -1,464 +0,0 @@
use std::{fmt::Display, hash::Hash, marker::Destruct};
use super::*;
use crate::{
layout::UiNum,
util::{LerpUtil, impl_op},
};
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, bytemuck::Pod, bytemuck::Zeroable, Default)]
pub struct UiVec2 {
pub x: UiScalar,
pub y: UiScalar,
}
impl UiVec2 {
pub const ZERO: Self = Self {
x: UiScalar::ZERO,
y: UiScalar::ZERO,
};
pub const fn new(x: UiScalar, y: UiScalar) -> Self {
Self { x, y }
}
pub const fn abs(abs: impl const Into<Vec2>) -> Self {
let abs = abs.into();
Self {
x: UiScalar::abs(abs.x),
y: UiScalar::abs(abs.y),
}
}
pub const fn rel(rel: impl const Into<Vec2>) -> Self {
let rel = rel.into();
Self {
x: UiScalar::rel(rel.x),
y: UiScalar::rel(rel.y),
}
}
pub const fn shift(&mut self, offset: impl const Into<UiVec2>) {
let offset = offset.into();
*self += offset;
}
pub const fn offset(mut self, offset: impl const Into<UiVec2>) -> Self {
self.shift(offset);
self
}
pub const fn within(&self, region: &UiRegion) -> UiVec2 {
UiVec2 {
x: self.x.within(&region.x),
y: self.y.within(&region.y),
}
}
pub const fn outside(&self, region: &UiRegion) -> UiVec2 {
UiVec2 {
x: self.x.outside(&region.x),
y: self.y.outside(&region.y),
}
}
pub fn axis_mut(&mut self, axis: Axis) -> &mut UiScalar {
match axis {
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
pub fn axis(&self, axis: Axis) -> UiScalar {
match axis {
Axis::X => self.x,
Axis::Y => self.y,
}
}
pub fn to_abs(&self, rel: Vec2) -> Vec2 {
Vec2 {
x: self.x.to_abs(rel.x),
y: self.y.to_abs(rel.y),
}
}
pub const FULL_SIZE: Self = Self::rel(Vec2::ONE);
pub const fn from_axis(axis: Axis, aligned: UiScalar, ortho: UiScalar) -> Self {
match axis {
Axis::X => Self {
x: aligned,
y: ortho,
},
Axis::Y => Self {
x: ortho,
y: aligned,
},
}
}
pub fn get_abs(&self) -> Vec2 {
(self.x.abs, self.y.abs).into()
}
pub fn get_rel(&self) -> Vec2 {
(self.x.rel, self.y.rel).into()
}
pub fn abs_mut(&mut self) -> Vec2View<'_> {
Vec2View {
x: &mut self.x.abs,
y: &mut self.y.abs,
}
}
}
impl Display for UiVec2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "rel{};abs{}", self.get_rel(), self.get_abs())
}
}
impl_op!(UiVec2 Add add; x y);
impl_op!(UiVec2 Sub sub; x y);
impl const From<Vec2> for UiVec2 {
fn from(abs: Vec2) -> Self {
Self::abs(abs)
}
}
impl<T: const UiNum, U: const UiNum> const From<(T, U)> for UiVec2
where
(T, U): const Destruct,
{
fn from(abs: (T, U)) -> Self {
Self::abs(abs)
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, Default, bytemuck::Zeroable)]
pub struct UiScalar {
pub rel: f32,
pub abs: f32,
}
impl Eq for UiScalar {}
impl Hash for UiScalar {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u32(self.rel.to_bits());
state.write_u32(self.abs.to_bits());
}
}
impl_op!(UiScalar Add add; rel abs);
impl_op!(UiScalar Sub sub; rel abs);
impl UiScalar {
pub const ZERO: Self = Self { rel: 0.0, abs: 0.0 };
pub const FULL: Self = Self { rel: 1.0, abs: 0.0 };
pub const fn new(rel: f32, abs: f32) -> Self {
Self { rel, abs }
}
pub const fn rel(rel: f32) -> Self {
Self { rel, abs: 0.0 }
}
pub const fn abs(abs: f32) -> Self {
Self { rel: 0.0, abs }
}
pub const fn rel_min() -> Self {
Self::new(0.0, 0.0)
}
pub const fn rel_max() -> Self {
Self::new(1.0, 0.0)
}
pub const fn max(&self, other: Self) -> Self {
Self {
rel: self.rel.max(other.rel),
abs: self.abs.max(other.abs),
}
}
pub const fn min(&self, other: Self) -> Self {
Self {
rel: self.rel.min(other.rel),
abs: self.abs.min(other.abs),
}
}
pub const fn offset(mut self, amt: f32) -> Self {
self.abs += amt;
self
}
pub const fn within(&self, span: &UiSpan) -> Self {
let anchor = self.rel.lerp(span.start.rel, span.end.rel);
let offset = self.abs + self.rel.lerp(span.start.abs, span.end.abs);
Self {
rel: anchor,
abs: offset,
}
}
pub const fn outside(&self, span: &UiSpan) -> Self {
let rel = self.rel.lerp_inv(span.start.rel, span.end.rel);
let abs = self.abs - rel.lerp(span.start.abs, span.end.abs);
Self { rel, abs }
}
pub fn within_len(&self, len: UiScalar) -> Self {
self.within(&UiSpan {
start: UiScalar::ZERO,
end: len,
})
}
pub fn select_len(&self, len: UiScalar) -> Self {
len.within_len(*self)
}
pub const fn flip(&mut self) {
self.rel = 1.0 - self.rel;
self.abs = -self.abs;
}
pub const fn to(&self, end: Self) -> UiSpan {
UiSpan { start: *self, end }
}
pub const fn to_abs(&self, rel: f32) -> f32 {
self.rel * rel + self.abs
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
pub struct UiSpan {
pub start: UiScalar,
pub end: UiScalar,
}
impl UiSpan {
pub const FULL: Self = Self {
start: UiScalar::ZERO,
end: UiScalar::FULL,
};
pub const fn rel(rel: f32) -> Self {
Self {
start: UiScalar::rel(rel),
end: UiScalar::rel(rel),
}
}
pub const fn new(start: UiScalar, end: UiScalar) -> Self {
Self { start, end }
}
pub const fn flip(&mut self) {
self.start.flip();
self.end.flip();
std::mem::swap(&mut self.start.rel, &mut self.end.rel);
std::mem::swap(&mut self.start.abs, &mut self.end.abs);
}
pub const fn shift(&mut self, offset: UiScalar) {
self.start += offset;
self.end += offset;
}
pub const fn within(&self, parent: &Self) -> Self {
Self {
start: self.start.within(parent),
end: self.end.within(parent),
}
}
pub const fn outside(&self, parent: &Self) -> Self {
Self {
start: self.start.outside(parent),
end: self.end.outside(parent),
}
}
pub const fn len(&self) -> UiScalar {
self.end - self.start
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
pub struct UiRegion {
pub x: UiSpan,
pub y: UiSpan,
}
impl UiRegion {
pub const FULL: Self = Self {
x: UiSpan::FULL,
y: UiSpan::FULL,
};
pub const fn new(x: UiSpan, y: UiSpan) -> Self {
Self { x, y }
}
pub const fn rel(rel: Vec2) -> Self {
Self {
x: UiSpan::rel(rel.x),
y: UiSpan::rel(rel.y),
}
}
pub const fn within(&self, parent: &Self) -> Self {
Self {
x: self.x.within(&parent.x),
y: self.y.within(&parent.y),
}
}
pub const fn outside(&self, parent: &Self) -> Self {
Self {
x: self.x.outside(&parent.x),
y: self.y.outside(&parent.y),
}
}
pub const fn axis(&mut self, axis: Axis) -> &UiSpan {
match axis {
Axis::X => &self.x,
Axis::Y => &self.y,
}
}
pub const fn axis_mut(&mut self, axis: Axis) -> &mut UiSpan {
match axis {
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
pub const fn flip(&mut self, axis: Axis) {
match axis {
Axis::X => self.x.flip(),
Axis::Y => self.y.flip(),
}
}
pub fn shift(&mut self, offset: impl Into<UiVec2>) {
let offset = offset.into();
self.x.shift(offset.x);
self.y.shift(offset.y);
}
pub fn offset(mut self, offset: impl Into<UiVec2>) -> Self {
self.shift(offset);
self
}
pub fn to_px(&self, size: Vec2) -> PixelRegion {
PixelRegion {
top_left: self.top_left().get_rel() * size + self.top_left().get_abs(),
bot_right: self.bot_right().get_rel() * size + self.bot_right().get_abs(),
}
}
pub const fn center(&self) -> UiVec2 {
Align::CENTER.pos().within(self)
}
pub const fn size(&self) -> UiVec2 {
UiVec2 {
x: self.x.len(),
y: self.y.len(),
}
}
pub const fn top_left(&self) -> UiVec2 {
UiVec2 {
x: self.x.start,
y: self.y.start,
}
}
pub const fn bot_right(&self) -> UiVec2 {
UiVec2 {
x: self.x.end,
y: self.y.end,
}
}
pub const fn from_axis(axis: Axis, aligned: UiSpan, ortho: UiSpan) -> Self {
Self {
x: match axis {
Axis::X => aligned,
Axis::Y => ortho,
},
y: match axis {
Axis::X => ortho,
Axis::Y => aligned,
},
}
}
}
impl Display for UiRegion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} -> {} (size: {})",
self.top_left(),
self.bot_right(),
self.size()
)
}
}
#[derive(Debug)]
pub struct PixelRegion {
pub top_left: Vec2,
pub bot_right: Vec2,
}
impl PixelRegion {
pub fn contains(&self, pos: Vec2) -> bool {
pos.x >= self.top_left.x
&& pos.x <= self.bot_right.x
&& pos.y >= self.top_left.y
&& pos.y <= self.bot_right.y
}
pub fn size(&self) -> Vec2 {
self.bot_right - self.top_left
}
}
impl Display for PixelRegion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} -> {}", self.top_left, self.bot_right)
}
}
pub struct Vec2View<'a> {
pub x: &'a mut f32,
pub y: &'a mut f32,
}
impl Vec2View<'_> {
pub fn set(&mut self, other: Vec2) {
*self.x = other.x;
*self.y = other.y;
}
pub fn add(&mut self, other: Vec2) {
*self.x += other.x;
*self.y += other.y;
}
}

View File

@@ -1,590 +0,0 @@
use crate::{
layout::{
Axis, Layers, Len, Modules, Size, TextAttrs, TextBuffer, TextData, TextTexture,
TextureHandle, Textures, UiRegion, UiVec2, Vec2, WidgetId, Widgets,
},
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
util::{HashMap, HashSet, Id, TrackedArena},
};
/// makes your surfaces look pretty
pub struct Painter<'a, 'c> {
ctx: &'a mut PainterCtx<'c>,
region: UiRegion,
mask: MaskIdx,
textures: Vec<TextureHandle>,
primitives: Vec<PrimitiveHandle>,
children: Vec<Id>,
children_width: HashMap<Id, (UiVec2, Len)>,
children_height: HashMap<Id, (UiVec2, Len)>,
/// whether this widget depends on region's final pixel size or not
/// TODO: decide if point (pt) should be used here instead of px
pub layer: usize,
id: Id,
}
/// context for a painter; lets you draw and redraw widgets
struct PainterCtx<'a> {
pub widgets: &'a Widgets,
pub active: &'a mut HashMap<Id, WidgetInstance>,
pub layers: &'a mut Layers,
pub textures: &'a mut Textures,
pub masks: &'a mut TrackedArena<Mask, u32>,
pub text: &'a mut TextData,
pub output_size: Vec2,
pub modules: &'a mut Modules,
pub cache_width: HashMap<Id, (UiVec2, Len)>,
pub cache_height: HashMap<Id, (UiVec2, Len)>,
pub needs_redraw: HashSet<Id>,
draw_started: HashSet<Id>,
}
/// stores information for children about the highest level parent that needed their size
/// so that they can redraw the parent if their size changes
#[derive(Clone, Copy, Debug, Default)]
pub struct ResizeRef {
x: Option<(Id, (UiVec2, Len))>,
y: Option<(Id, (UiVec2, Len))>,
}
/// important non rendering data for retained drawing
#[derive(Debug)]
pub struct WidgetInstance {
pub id: Id,
pub region: UiRegion,
pub parent: Option<Id>,
pub textures: Vec<TextureHandle>,
pub primitives: Vec<PrimitiveHandle>,
pub children: Vec<Id>,
pub resize: ResizeRef,
pub mask: MaskIdx,
pub layer: usize,
}
/// data to be stored in Ui to create PainterCtxs easily
#[derive(Default)]
pub struct PainterData {
pub widgets: Widgets,
pub active: HashMap<Id, WidgetInstance>,
pub layers: Layers,
pub textures: Textures,
pub text: TextData,
pub output_size: Vec2,
pub modules: Modules,
pub px_dependent: HashSet<Id>,
pub masks: TrackedArena<Mask, u32>,
}
impl<'a> PainterCtx<'a> {
/// redraws a widget that's currently active (drawn)
/// can be called on something already drawn or removed,
/// will just return if so
pub fn redraw(&mut self, id: Id) {
self.needs_redraw.remove(&id);
if self.draw_started.contains(&id) {
return;
}
let Some(active) = self.active.get(&id) else {
return;
};
let mut resize = active.resize;
// set resize back after redrawing
let finish = |s: &mut Self, resize| {
if let Some(active) = s.active.get_mut(&id) {
// might need to get_or_insert here instead of just assuming
active.resize = resize;
}
};
// check if a parent depends on the desired size of this, if so then redraw it first
// TODO: this is stupid having 2 of these, don't ask me what the consequences are
let mut ret = false;
if let Some((rid, (outer, old_desired))) = &mut resize.x {
let new_desired = SizeCtx {
source: id,
cache_width: &mut self.cache_width,
cache_height: &mut self.cache_height,
text: self.text,
textures: self.textures,
widgets: self.widgets,
outer: *outer,
output_size: self.output_size,
checked_width: &mut Default::default(),
checked_height: &mut Default::default(),
id,
}
.width_inner(id);
if new_desired != *old_desired {
// unsure if I need to walk down the tree here
self.redraw(*rid);
*old_desired = new_desired;
if self.draw_started.contains(&id) {
ret = true;
}
}
}
if let Some((rid, (outer, old_desired))) = &mut resize.y {
// NOTE: might need hack in Span here (or also do it properly here)
let new_desired = SizeCtx {
source: id,
cache_width: &mut self.cache_width,
cache_height: &mut self.cache_height,
text: self.text,
textures: self.textures,
widgets: self.widgets,
outer: *outer,
output_size: self.output_size,
checked_width: &mut Default::default(),
checked_height: &mut Default::default(),
id,
}
.height_inner(id);
if new_desired != *old_desired {
self.redraw(*rid);
*old_desired = new_desired;
if self.draw_started.contains(&id) {
ret = true;
}
}
}
if ret {
return finish(self, resize);
}
let Some(active) = self.remove(id) else {
return;
};
self.draw_inner(
active.layer,
id,
active.region,
active.parent,
active.mask,
Some(active.children),
);
finish(self, resize);
}
fn draw_inner(
&mut self,
layer: usize,
id: Id,
region: UiRegion,
parent: Option<Id>,
mask: MaskIdx,
old_children: Option<Vec<Id>>,
) {
// I have no idea if these checks work lol
// the idea is u can't redraw stuff u already drew,
// and if parent is different then there's another copy with a different parent
// but this has a very weird issue where you can't move widgets unless u remove first
// so swapping is impossible rn I think?
// there's definitely better solutions like a counter (>1 = panic) but don't care rn
if self.draw_started.contains(&id) {
panic!(
"Cannot draw the same widget ({}) twice (1)",
self.widgets.data(&id).unwrap().label
);
}
let mut old_children = old_children.unwrap_or_default();
let mut resize = ResizeRef::default();
if let Some(active) = self.active.get_mut(&id)
&& !self.needs_redraw.contains(&id)
{
// check to see if we can skip drawing first
if active.parent != parent {
panic!("Cannot draw the same widget twice (2)");
}
if active.region == region {
return;
} else if active.region.size() == region.size() {
// TODO: epsilon?
let from = active.region;
self.mov(id, from, region);
return;
}
// if not, then maintain resize and track old children to remove unneeded
let active = self.remove(id).unwrap();
old_children = active.children;
resize = active.resize;
}
// draw widget
self.draw_started.insert(id);
let mut painter = Painter {
region,
mask,
layer,
id,
textures: Vec::new(),
primitives: Vec::new(),
ctx: self,
children: Vec::new(),
children_width: Default::default(),
children_height: Default::default(),
};
painter.ctx.widgets.get_dyn_dynamic(id).draw(&mut painter);
let children_width = painter.children_width;
let children_height = painter.children_height;
// add to active
let instance = WidgetInstance {
id,
region,
parent,
textures: painter.textures,
primitives: painter.primitives,
children: painter.children,
resize,
mask: painter.mask,
layer,
};
// set resize for children who's size this widget depends on
for (cid, outer) in children_width {
if let Some(w) = self.active.get_mut(&cid)
&& w.resize.x.is_none()
{
w.resize.x = Some((id, outer))
}
}
for (cid, outer) in children_height {
if let Some(w) = self.active.get_mut(&cid)
&& w.resize.y.is_none()
{
w.resize.y = Some((id, outer))
}
}
// remove old children that weren't kept
for c in &old_children {
if !instance.children.contains(c) {
self.remove_rec(*c);
}
}
// update modules
for m in self.modules.iter_mut() {
m.on_draw(&instance);
}
self.active.insert(id, instance);
}
fn mov(&mut self, id: Id, from: UiRegion, to: UiRegion) {
let active = self.active.get_mut(&id).unwrap();
for h in &active.primitives {
let region = self.layers[h.layer].primitives.region_mut(h);
*region = region.outside(&from).within(&to);
}
active.region = active.region.outside(&from).within(&to);
for m in self.modules.iter_mut() {
m.on_move(active);
}
// children will not be changed, so this technically should not be needed
// probably need unsafe
let children = active.children.clone();
for child in children {
self.mov(child, from, to);
}
}
/// NOTE: instance textures are cleared and self.textures freed
fn remove(&mut self, id: Id) -> Option<WidgetInstance> {
let mut inst = self.active.remove(&id);
if let Some(inst) = &mut inst {
for h in &inst.primitives {
let mask = self.layers.free(h);
if mask != MaskIdx::NONE {
self.masks.remove(mask);
}
}
inst.textures.clear();
self.textures.free();
for m in self.modules.iter_mut() {
m.on_undraw(inst);
}
}
inst
}
fn remove_rec(&mut self, id: Id) -> Option<WidgetInstance> {
let inst = self.remove(id);
if let Some(inst) = &inst {
for c in &inst.children {
self.remove_rec(*c);
}
}
inst
}
}
impl PainterData {
fn ctx(&mut self, needs_redraw: HashSet<Id>) -> PainterCtx<'_> {
PainterCtx {
widgets: &self.widgets,
active: &mut self.active,
layers: &mut self.layers,
textures: &mut self.textures,
text: &mut self.text,
output_size: self.output_size,
modules: &mut self.modules,
masks: &mut self.masks,
cache_width: Default::default(),
cache_height: Default::default(),
draw_started: Default::default(),
needs_redraw,
}
}
pub fn draw(&mut self, id: Id) {
let mut ctx = self.ctx(Default::default());
ctx.draw_started.clear();
ctx.layers.clear();
ctx.draw_inner(0, id, UiRegion::FULL, None, MaskIdx::NONE, None);
}
pub fn redraw(&mut self, ids: HashSet<Id>) {
let mut ctx = self.ctx(ids);
while let Some(&id) = ctx.needs_redraw.iter().next() {
ctx.redraw(id);
}
}
}
impl<'a, 'c> Painter<'a, 'c> {
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
let h = self.ctx.layers.write(
self.layer,
PrimitiveInst {
id: self.id,
primitive,
region,
mask_idx: self.mask,
},
);
if self.mask != MaskIdx::NONE {
// TODO: I have no clue if this works at all :joy:
self.ctx.masks.push_ref(self.mask);
}
self.primitives.push(h);
}
/// Writes a primitive to be rendered
pub fn primitive<P: Primitive>(&mut self, primitive: P) {
self.primitive_at(primitive, self.region)
}
pub fn primitive_within<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
self.primitive_at(primitive, region.within(&self.region));
}
pub fn set_mask(&mut self, region: UiRegion) {
assert!(self.mask == MaskIdx::NONE);
self.mask = self.ctx.masks.push(Mask { region });
}
/// Draws a widget within this widget's region.
pub fn widget<W>(&mut self, id: &WidgetId<W>) {
self.widget_at(id, self.region);
}
/// Draws a widget somewhere within this one.
/// Useful for drawing child widgets in select areas.
pub fn widget_within<W>(&mut self, id: &WidgetId<W>, region: UiRegion) {
self.widget_at(id, region.within(&self.region));
}
fn widget_at<W>(&mut self, id: &WidgetId<W>, region: UiRegion) {
self.children.push(id.id);
self.ctx
.draw_inner(self.layer, id.id, region, Some(self.id), self.mask, None);
}
pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) {
self.textures.push(handle.clone());
self.primitive_at(handle.primitive(), region.within(&self.region));
}
pub fn texture(&mut self, handle: &TextureHandle) {
self.textures.push(handle.clone());
self.primitive(handle.primitive());
}
pub fn texture_at(&mut self, handle: &TextureHandle, region: UiRegion) {
self.textures.push(handle.clone());
self.primitive_at(handle.primitive(), region);
}
/// returns (handle, offset from top left)
pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> TextTexture {
self.ctx.text.draw(buffer, attrs, self.ctx.textures)
}
pub fn region(&self) -> UiRegion {
self.region
}
pub fn size<W>(&mut self, id: &WidgetId<W>) -> Size {
self.size_ctx().size(id)
}
pub fn len_axis<W>(&mut self, id: &WidgetId<W>, axis: Axis) -> Len {
match axis {
Axis::X => self.size_ctx().width(id),
Axis::Y => self.size_ctx().height(id),
}
}
pub fn size_ctx(&mut self) -> SizeCtx<'_> {
SizeCtx {
text: self.ctx.text,
textures: self.ctx.textures,
widgets: self.ctx.widgets,
output_size: self.ctx.output_size,
checked_width: &mut self.children_width,
checked_height: &mut self.children_height,
cache_width: &mut self.ctx.cache_width,
cache_height: &mut self.ctx.cache_height,
source: self.id,
id: self.id,
outer: self.region.size(),
}
}
pub fn output_size(&self) -> Vec2 {
self.ctx.output_size
}
pub fn px_size(&mut self) -> Vec2 {
self.region.size().to_abs(self.ctx.output_size)
}
pub fn text_data(&mut self) -> &mut TextData {
self.ctx.text
}
pub fn child_layer(&mut self) {
self.layer = self.ctx.layers.child(self.layer);
}
pub fn next_layer(&mut self) {
self.layer = self.ctx.layers.next(self.layer);
}
pub fn label(&self) -> &str {
&self.ctx.widgets.data(&self.id).unwrap().label
}
pub fn id(&self) -> &Id {
&self.id
}
}
pub struct SizeCtx<'a> {
pub text: &'a mut TextData,
pub textures: &'a mut Textures,
source: Id,
widgets: &'a Widgets,
cache_width: &'a mut HashMap<Id, (UiVec2, Len)>,
cache_height: &'a mut HashMap<Id, (UiVec2, Len)>,
checked_width: &'a mut HashMap<Id, (UiVec2, Len)>,
checked_height: &'a mut HashMap<Id, (UiVec2, Len)>,
/// TODO: should this be pub? rn used for sized
pub outer: UiVec2,
output_size: Vec2,
id: Id,
}
impl SizeCtx<'_> {
pub fn id(&self) -> &Id {
&self.id
}
pub fn source(&self) -> &Id {
&self.source
}
fn width_inner(&mut self, id: Id) -> Len {
// first check cache
// TODO: is this needed? broken rn bc does not store children during upper size check,
// so if something actually using check_* hits cache it fails to add them
// if let Some(&(outer, len)) = self.cache_width.get(&id)
// && outer == self.outer
// {
// self.checked_width.insert(id, (self.outer, len));
// return len;
// }
// store self vars that need to be maintained
let self_outer = self.outer;
let self_id = self.id;
// get size of input id
self.id = id;
let len = self.widgets.get_dyn_dynamic(id).desired_width(self);
// restore vars & update cache + checked
self.outer = self_outer;
self.id = self_id;
self.cache_width.insert(id, (self.outer, len));
self.checked_width.insert(id, (self.outer, len));
len
}
// TODO: should be refactored to share code w width_inner
fn height_inner(&mut self, id: Id) -> Len {
// if let Some(&(outer, len)) = self.cache_height.get(&id)
// && outer == self.outer
// {
// prntln!("FAIL {id:?}");
// self.checked_height.insert(id, (self.outer, len));
// return len;
// }
let self_outer = self.outer;
let self_id = self.id;
self.id = id;
let len = self.widgets.get_dyn_dynamic(id).desired_height(self);
self.outer = self_outer;
self.id = self_id;
self.cache_height.insert(id, (self.outer, len));
self.checked_height.insert(id, (self.outer, len));
len
}
pub fn width<W>(&mut self, id: &WidgetId<W>) -> Len {
self.width_inner(id.id)
}
pub fn height<W>(&mut self, id: &WidgetId<W>) -> Len {
self.height_inner(id.id)
}
pub fn len_axis<W>(&mut self, id: &WidgetId<W>, axis: Axis) -> Len {
match axis {
Axis::X => self.width(id),
Axis::Y => self.height(id),
}
}
pub fn size<W>(&mut self, id: &WidgetId<W>) -> Size {
Size {
x: self.width(id),
y: self.height(id),
}
}
pub fn px_size(&mut self) -> Vec2 {
self.outer.to_abs(self.output_size)
}
pub fn draw_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> TextTexture {
self.text.draw(buffer, attrs, self.textures)
}
pub fn label(&self, id: &Id) -> &String {
self.widgets.label(id)
}
}

View File

@@ -1,177 +0,0 @@
use cosmic_text::{
Attrs, AttrsList, Buffer, Family, FontSystem, Metrics, SwashCache, SwashContent,
};
use image::{Rgba, RgbaImage};
use crate::{
layout::{Align, RegionAlign, TextureHandle, Textures, UiColor, Vec2},
util::HashMap,
};
/// TODO: properly wrap this
pub mod text_lib {
pub use cosmic_text::*;
}
pub struct TextData {
pub font_system: FontSystem,
pub swash_cache: SwashCache,
}
impl Default for TextData {
fn default() -> Self {
Self {
font_system: FontSystem::new(),
swash_cache: SwashCache::new(),
}
}
}
#[derive(Clone, Copy)]
pub struct TextAttrs {
pub color: UiColor,
pub font_size: f32,
pub line_height: f32,
pub family: Family<'static>,
pub wrap: bool,
/// inner alignment of text region (within where its drawn)
pub align: RegionAlign,
}
impl TextAttrs {
pub fn apply(&self, font_system: &mut FontSystem, buf: &mut Buffer, width: Option<f32>) {
buf.set_metrics_and_size(
font_system,
Metrics::new(self.font_size, self.line_height),
width,
None,
);
let attrs = Attrs::new().family(self.family);
let list = AttrsList::new(&attrs);
for line in &mut buf.lines {
line.set_attrs_list(list.clone());
}
}
}
pub type TextBuffer = Buffer;
impl Default for TextAttrs {
fn default() -> Self {
let size = 14.0;
Self {
color: UiColor::WHITE,
font_size: size,
line_height: size * 1.2,
family: Family::SansSerif,
wrap: false,
align: Align::CENTER,
}
}
}
impl TextData {
pub fn draw(
&mut self,
buffer: &mut TextBuffer,
attrs: &TextAttrs,
textures: &mut Textures,
) -> TextTexture {
// TODO: either this or the layout stuff (or both) is super slow,
// should probably do texture packing and things if possible.
// very visible if you add just a couple of wrapping texts and resize window
// should also be timed to figure out exactly what points need to be sped up
let mut pixels = HashMap::<_, [u8; 4]>::default();
let mut min_x = 0;
let mut min_y = 0;
let mut max_x = 0;
let mut max_y = 0;
let cosmic_color = {
let c = attrs.color;
cosmic_text::Color::rgba(c.r, c.g, c.b, c.a)
};
let mut max_width = 0.0f32;
let mut height = 0.0;
for run in buffer.layout_runs() {
for glyph in run.glyphs.iter() {
let physical_glyph = glyph.physical((0., 0.), 1.0);
let glyph_color = match glyph.color_opt {
Some(some) => some,
None => cosmic_color,
};
if let Some(img) = self
.swash_cache
.get_image(&mut self.font_system, physical_glyph.cache_key)
{
let mut pos = img.placement;
pos.left += physical_glyph.x;
pos.top = physical_glyph.y + run.line_y as i32 - pos.top;
min_x = min_x.min(pos.left);
min_y = min_y.min(pos.top);
max_x = max_x.max(pos.left + pos.width as i32);
max_y = max_y.max(pos.top + pos.height as i32);
let mut merge = |i, color: [u8; 4]| {
let x = i % pos.width as usize;
let y = i / pos.width as usize;
let pos = (x as i32 + pos.left, y as i32 + pos.top);
if let Some(pixel) = pixels.get_mut(&pos) {
for i in 0..4 {
// TODO: no clue if proper alpha blending should be done
pixel[i] = color[i].saturating_add(pixel[i]);
}
} else {
pixels.insert(pos, color);
}
};
match img.content {
SwashContent::Mask => {
for (i, a) in img.data.iter().enumerate() {
let mut color = glyph_color.as_rgba();
color[3] = ((color[3] as u32 * *a as u32) / u8::MAX as u32) as u8;
merge(i, color);
}
}
SwashContent::SubpixelMask => todo!(),
SwashContent::Color => {
let (colors, _) = img.data.as_chunks::<4>();
for (i, color) in colors.iter().enumerate() {
merge(i, *color);
}
}
}
}
}
max_width = max_width.max(run.line_w);
height += run.line_height;
}
let img_width = (max_x - min_x + 1) as u32;
let img_height = (max_y - min_y + 1) as u32;
let mut image = RgbaImage::new(img_width, img_height);
for ((x, y), color) in pixels {
let x = (x - min_x) as u32;
let y = (y - min_y) as u32;
image.put_pixel(x, y, Rgba(color));
}
TextTexture {
handle: textures.add(image),
top_left: Vec2::new(min_x as f32, min_y as f32),
bot_right: Vec2::new(max_width - max_x as f32, height - max_y as f32),
}
}
}
#[derive(Clone)]
pub struct TextTexture {
pub handle: TextureHandle,
pub top_left: Vec2,
pub bot_right: Vec2,
}
impl TextTexture {
pub fn size(&self) -> Vec2 {
self.handle.size() - self.top_left + self.bot_right
}
}

View File

@@ -1,135 +0,0 @@
use std::{
ops::Index,
sync::mpsc::{Receiver, Sender, channel},
};
use image::{DynamicImage, GenericImageView};
use crate::{layout::Vec2, render::TexturePrimitive, util::RefCounter};
#[derive(Debug, Clone)]
pub struct TextureHandle {
inner: TexturePrimitive,
size: Vec2,
counter: RefCounter,
send: Sender<u32>,
}
/// a texture manager for a ui
/// note that this is heavily oriented towards wgpu's renderer so the primitives don't need mapped
pub struct Textures {
free: Vec<u32>,
images: Vec<Option<DynamicImage>>,
updates: Vec<Update>,
send: Sender<u32>,
recv: Receiver<u32>,
}
pub enum TextureUpdate<'a> {
Push(&'a DynamicImage),
Set(u32, &'a DynamicImage),
Free(u32),
PushFree,
SetFree,
}
enum Update {
Push(u32),
Set(u32),
Free(u32),
}
impl Textures {
pub fn new() -> Self {
let (send, recv) = channel();
Self {
free: Vec::new(),
images: Vec::new(),
updates: Vec::new(),
send,
recv,
}
}
pub fn add(&mut self, image: impl Into<DynamicImage>) -> TextureHandle {
let image = image.into();
let size = image.dimensions().into();
let view_idx = self.push(image);
// 0 == default in renderer; TODO: actually create samplers here
let sampler_idx = 0;
TextureHandle {
inner: TexturePrimitive {
view_idx,
sampler_idx,
},
size,
counter: RefCounter::new(),
send: self.send.clone(),
}
}
fn push(&mut self, image: DynamicImage) -> u32 {
if let Some(i) = self.free.pop() {
self.images[i as usize] = Some(image);
self.updates.push(Update::Set(i));
i
} else {
let i = self.images.len() as u32;
self.images.push(Some(image));
self.updates.push(Update::Push(i));
i
}
}
pub fn free(&mut self) {
for idx in self.recv.try_iter() {
self.images[idx as usize] = None;
self.updates.push(Update::Free(idx));
self.free.push(idx);
}
}
pub fn updates(&mut self) -> impl Iterator<Item = TextureUpdate<'_>> {
self.updates.drain(..).map(|u| match u {
Update::Push(i) => self.images[i as usize]
.as_ref()
.map(TextureUpdate::Push)
.unwrap_or(TextureUpdate::PushFree),
Update::Set(i) => self.images[i as usize]
.as_ref()
.map(|img| TextureUpdate::Set(i, img))
.unwrap_or(TextureUpdate::SetFree),
Update::Free(i) => TextureUpdate::Free(i),
})
}
}
impl TextureHandle {
pub fn primitive(&self) -> TexturePrimitive {
self.inner
}
pub fn size(&self) -> Vec2 {
self.size
}
}
impl Drop for TextureHandle {
fn drop(&mut self) {
if self.counter.drop() {
let _ = self.send.send(self.inner.view_idx);
}
}
}
impl Index<&TextureHandle> for Textures {
type Output = DynamicImage;
fn index(&self, index: &TextureHandle) -> &Self::Output {
self.images[index.inner.view_idx as usize].as_ref().unwrap()
}
}
impl Default for Textures {
fn default() -> Self {
Self::new()
}
}

View File

@@ -1,241 +1,101 @@
use image::DynamicImage;
use crate::{
core::{TextEdit, TextEditCtx},
layout::{
IdLike, PainterData, PixelRegion, StaticWidgetId, TextureHandle, Vec2, Widget,
WidgetId, WidgetInstance, WidgetLike,
},
util::{HashSet, Id},
primitive::{Painter, Primitives},
util::{IDTracker, ID},
HashMap, Widget, WidgetId, WidgetLike, WidgetRef,
};
use std::{
any::{Any, TypeId},
ops::{Index, IndexMut},
sync::mpsc::{Receiver, Sender, channel},
cell::RefCell,
rc::Rc,
};
pub struct Ui {
// TODO: make this at least pub(super)
pub(crate) data: PainterData,
root: Option<WidgetId>,
updates: HashSet<Id>,
recv: Receiver<Id>,
pub(super) send: Sender<Id>,
full_redraw: bool,
resized: bool,
pub struct UI {
ids: IDTracker,
base: Option<WidgetId>,
pub widgets: Widgets,
}
impl Ui {
pub fn add<W: Widget, Tag>(&mut self, w: impl WidgetLike<Tag, Widget = W>) -> WidgetId<W> {
w.add(self)
}
pub struct Widgets(HashMap<ID, Box<dyn Widget>>);
pub fn add_static<W: Widget, Tag>(
&mut self,
w: impl WidgetLike<Tag, Widget = W>,
) -> StaticWidgetId<W> {
let id = w.add(self);
id.into_static()
}
#[derive(Clone)]
pub struct UIBuilder {
ui: Rc<RefCell<UI>>,
}
/// useful for debugging
pub fn set_label<W>(&mut self, id: &WidgetId<W>, label: String) {
self.data.widgets.data_mut(&id.id).unwrap().label = label;
impl From<UI> for UIBuilder {
fn from(ui: UI) -> Self {
UIBuilder {
ui: Rc::new(RefCell::new(ui)),
}
}
}
pub fn label<W>(&self, id: &WidgetId<W>) -> &String {
&self.data.widgets.data(&id.id).unwrap().label
}
pub fn add_widget<W: Widget>(&mut self, w: W) -> WidgetId<W> {
self.push(w)
impl UIBuilder {
pub fn add<W: Widget>(&mut self, w: W) -> WidgetRef<W> {
WidgetRef::new(self.clone(), (self.push(w), ()))
}
pub fn push<W: Widget>(&mut self, w: W) -> WidgetId<W> {
let id = self.id();
self.data.widgets.insert(id.id, w);
id
let mut ui = self.ui.borrow_mut();
let id = ui.ids.next();
ui.widgets.insert(id.duplicate(), w);
WidgetId::new(id, TypeId::of::<W>())
}
pub fn set<W: Widget>(&mut self, id: &WidgetId<W>, w: W) {
self.data.widgets.insert(id.id, w);
}
pub fn set_root<Tag>(&mut self, w: impl WidgetLike<Tag>) {
self.root = Some(w.add(self).any());
self.full_redraw = true;
}
pub fn new() -> Self {
Self::default()
}
pub fn get<W: Widget>(&self, id: &impl IdLike<W>) -> Option<&W> {
self.data.widgets.get(id)
}
pub fn get_mut<W: Widget>(&mut self, id: &impl IdLike<W>) -> Option<&mut W> {
self.data.widgets.get_mut(id)
}
pub fn id<W: Widget>(&mut self) -> WidgetId<W> {
WidgetId::new(
self.data.widgets.reserve(),
TypeId::of::<W>(),
self.send.clone(),
false,
)
}
pub fn id_static<W: Widget>(&mut self) -> StaticWidgetId<W> {
let id = self.id();
id.into_static()
}
pub fn add_texture(&mut self, image: DynamicImage) -> TextureHandle {
self.data.textures.add(image)
}
pub fn resize(&mut self, size: impl Into<Vec2>) {
self.data.output_size = size.into();
self.resized = true;
}
pub fn redraw_all(&mut self) {
for (_, inst) in self.data.active.drain() {
for m in self.data.modules.iter_mut() {
m.on_undraw(&inst);
}
}
// free before bc nothing should exist
self.free();
if let Some(root) = &self.root {
self.data.draw(root.id);
}
}
pub fn update(&mut self) {
if self.full_redraw {
self.redraw_all();
self.full_redraw = false;
} else if !self.updates.is_empty() {
self.redraw_updates();
}
if self.resized {
self.resized = false;
self.redraw_size();
}
}
fn redraw_size(&mut self) {
// let mut ctx = PainterCtx::new(&mut self.data);
// let dep = ctx.px_dependent.clone();
// for id in dep {
// ctx.redraw(id);
// }
self.redraw_all();
}
fn redraw_updates(&mut self) {
self.data.redraw(std::mem::take(&mut self.updates));
self.free();
}
/// free any resources that don't have references anymore
fn free(&mut self) {
for id in self.recv.try_iter() {
for m in self.data.modules.iter_mut() {
m.on_remove(&id);
}
self.data.widgets.delete(id);
}
self.data.textures.free();
}
pub fn needs_redraw(&self) -> bool {
self.full_redraw || !self.updates.is_empty()
}
pub fn num_widgets(&self) -> usize {
self.data.widgets.len()
}
pub fn active_widgets(&self) -> usize {
self.data.active.len()
}
pub fn text(&mut self, id: &impl IdLike<TextEdit>) -> TextEditCtx<'_> {
self.updates.insert(id.id());
TextEditCtx {
text: self.data.widgets.get_mut(id).unwrap(),
font_system: &mut self.data.text.font_system,
}
}
pub fn window_region<W>(&self, id: &impl IdLike<W>) -> Option<PixelRegion> {
let region = self.data.active.get(&id.id())?.region;
Some(region.to_px(self.data.output_size))
}
pub fn debug(&self, label: &str) -> impl Iterator<Item = &WidgetInstance> {
self.data.active.iter().filter_map(move |(id, inst)| {
let l = &self.data.widgets.label(id);
if *l == label { Some(inst) } else { None }
})
pub fn finish<WL: WidgetLike>(mut self, base: WL) -> UI {
let base = base.id(&mut self).erase_type();
let mut ui = Rc::into_inner(self.ui).unwrap().into_inner();
ui.base = Some(base);
ui
}
}
impl<W: Widget> Index<&WidgetId<W>> for Ui {
type Output = W;
impl UI {
pub fn build() -> UIBuilder {
Self::empty().into()
}
fn index(&self, id: &WidgetId<W>) -> &Self::Output {
self.get(id).unwrap()
pub fn empty() -> Self {
Self {
ids: IDTracker::new(),
base: None,
widgets: Widgets::new(),
}
}
pub fn to_primitives(&self) -> Primitives {
let mut painter = Painter::new(&self.widgets);
if let Some(base) = &self.base {
painter.draw(base);
}
painter.finish()
}
}
impl<W: Widget> IndexMut<&WidgetId<W>> for Ui {
fn index_mut(&mut self, id: &WidgetId<W>) -> &mut Self::Output {
self.updates.insert(id.id);
self.get_mut(id).unwrap()
impl Widgets {
fn new() -> Self {
Self(HashMap::new())
}
}
impl<W: Widget> Index<StaticWidgetId<W>> for Ui {
type Output = W;
fn index(&self, id: StaticWidgetId<W>) -> &Self::Output {
self.data.widgets.get(&id).unwrap()
pub fn get(&self, id: &WidgetId) -> &dyn Widget {
self.0.get(&id.id).unwrap().as_ref()
}
}
impl<W: Widget> IndexMut<StaticWidgetId<W>> for Ui {
fn index_mut(&mut self, id: StaticWidgetId<W>) -> &mut Self::Output {
self.updates.insert(id.id);
self.data.widgets.get_mut(&id).unwrap()
pub fn get_mut<W: Widget>(&mut self, id: &WidgetId<W>) -> Option<&mut W> {
self.0.get_mut(&id.id).unwrap().as_any_mut().downcast_mut()
}
pub fn insert(&mut self, id: ID, widget: impl Widget) {
self.0.insert(id, Box::new(widget));
}
pub fn insert_any(&mut self, id: ID, widget: Box<dyn Widget>) {
self.0.insert(id, widget);
}
}
impl dyn Widget {
pub fn as_any(&self) -> &dyn Any {
self
}
pub fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl Default for Ui {
fn default() -> Self {
let (send, recv) = channel();
Self {
data: PainterData::default(),
root: Default::default(),
updates: Default::default(),
full_redraw: false,
send,
recv,
resized: false,
}
}
}

View File

@@ -1,117 +0,0 @@
use crate::{
layout::UiNum,
util::{DivOr, impl_op},
};
use std::{hash::Hash, marker::Destruct, ops::*};
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vec2 {
pub x: f32,
pub y: f32,
}
impl Eq for Vec2 {}
impl Hash for Vec2 {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u32(self.x.to_bits());
state.write_u32(self.y.to_bits());
}
}
pub const fn vec2(x: impl const UiNum, y: impl const UiNum) -> Vec2 {
Vec2::new(x.to_f32(), y.to_f32())
}
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 round(self) -> Self {
Self {
x: self.x.round(),
y: self.y.round(),
}
}
pub const fn floor(self) -> Self {
Self {
x: self.x.floor(),
y: self.y.floor(),
}
}
pub const fn ceil(self) -> Self {
Self {
x: self.x.ceil(),
y: self.y.ceil(),
}
}
pub const fn tuple(&self) -> (f32, f32) {
(self.x, self.y)
}
}
impl<T: const UiNum + Copy> const From<T> for Vec2 {
fn from(v: T) -> Self {
Self {
x: v.to_f32(),
y: v.to_f32(),
}
}
}
// 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 const DivOr for Vec2 {
fn div_or(self, rhs: Self, other: Self) -> Self {
Self {
x: self.x.div_or(rhs.x, other.x),
y: self.y.div_or(rhs.y, other.y),
}
}
}
impl Neg for Vec2 {
type Output = Self;
fn neg(mut self) -> Self::Output {
self.x = -self.x;
self.y = -self.y;
self
}
}
impl<T: const UiNum, U: const UiNum> const From<(T, U)> for Vec2
where
(T, U): const Destruct,
{
fn from((x, y): (T, U)) -> Self {
Self {
x: x.to_f32(),
y: y.to_f32(),
}
}
}
impl std::fmt::Debug for Vec2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
impl std::fmt::Display for Vec2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}

View File

@@ -1,137 +1,186 @@
use crate::layout::{Len, Painter, SizeCtx, StaticWidgetId, Ui, WidgetId, WidgetIdFn};
use std::{
any::{Any, TypeId},
marker::PhantomData,
};
use std::{any::Any, marker::PhantomData};
use crate::{
primitive::Painter,
util::{IntoTupleList, TupleList, ID},
UIBuilder,
};
pub trait Widget: Any {
fn draw(&mut self, painter: &mut Painter);
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len;
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len;
pub trait Widget: 'static + Any {
fn draw(&self, painter: &mut Painter);
}
impl Widget for () {
fn draw(&mut self, _: &mut Painter) {}
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
Len::ZERO
}
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
Len::ZERO
impl<W: Widget> Widget for (W,) {
fn draw(&self, painter: &mut Painter) {
self.0.draw(painter);
}
}
pub struct WidgetTag;
pub struct FnTag;
#[derive(Eq, Hash, PartialEq, Debug)]
pub struct WidgetId<W = ()> {
pub(super) ty: TypeId,
pub(super) id: ID,
_pd: PhantomData<W>,
}
pub trait WidgetLike<Tag> {
type Widget: 'static;
fn add(self, ui: &mut Ui) -> WidgetId<Self::Widget>;
fn with_id<W2>(
self,
f: impl FnOnce(&mut Ui, WidgetId<Self::Widget>) -> WidgetId<W2>,
) -> impl WidgetIdFn<W2>
where
Self: Sized,
{
move |ui| {
let id = self.add(ui);
f(ui, id)
// TODO: temp
impl<W> Clone for WidgetId<W> {
fn clone(&self) -> Self {
Self {
ty: self.ty,
id: self.id.duplicate(),
_pd: self._pd,
}
}
fn add_static(self, ui: &mut Ui) -> StaticWidgetId<Self::Widget>
where
Self: Sized,
{
self.add(ui).into_static()
}
fn set_root(self, ui: &mut Ui)
where
Self: Sized,
{
ui.set_root(self);
}
}
/// A function that returns a widget given a UI.
/// Useful for defining trait functions on widgets that create a parent widget so that the children
/// don't need to be IDs yet
pub trait WidgetFn<W: Widget>: FnOnce(&mut Ui) -> W {}
impl<W: Widget, F: FnOnce(&mut Ui) -> W> WidgetFn<W> for F {}
impl<W: Widget, F: FnOnce(&mut Ui) -> W> WidgetLike<FnTag> for F {
type Widget = W;
fn add(self, ui: &mut Ui) -> WidgetId<W> {
self(ui).add(ui)
}
}
impl<W: Widget> WidgetLike<WidgetTag> for W {
type Widget = W;
fn add(self, ui: &mut Ui) -> WidgetId<W> {
ui.add_widget(self)
}
}
pub struct WidgetArr<const LEN: usize, Ws> {
pub arr: [WidgetId; LEN],
_pd: PhantomData<Ws>,
}
impl<const LEN: usize, Ws> WidgetArr<LEN, Ws> {
pub fn new(arr: [WidgetId; LEN]) -> Self {
impl<W> WidgetId<W> {
pub(super) fn new(id: ID, ty: TypeId) -> Self {
Self {
arr,
ty,
id,
_pd: PhantomData,
}
}
pub fn erase_type(self) -> WidgetId<()> {
self.cast_type()
}
fn cast_type<W2>(self) -> WidgetId<W2> {
WidgetId {
ty: self.ty,
id: self.id,
_pd: PhantomData,
}
}
}
pub struct ArrTag;
pub trait WidgetArrLike<const LEN: usize, Tag> {
type Ws;
fn ui(self, ui: &mut Ui) -> WidgetArr<LEN, Self::Ws>;
pub trait WidgetLike {
type Widget;
fn id(self, ui: &mut UIBuilder) -> WidgetId<Self::Widget>;
}
impl<const LEN: usize, Ws> WidgetArrLike<LEN, ArrTag> for WidgetArr<LEN, Ws> {
type Ws = Ws;
fn ui(self, _: &mut Ui) -> WidgetArr<LEN, Ws> {
/// wouldn't be needed if negative trait bounds & disjoint impls existed
pub struct WidgetFn<F: FnOnce(&mut UIBuilder) -> W, W>(pub F);
impl<W: Widget, F: FnOnce(&mut UIBuilder) -> W> WidgetLike for WidgetFn<F, W> {
type Widget = W;
fn id(self, ui: &mut UIBuilder) -> WidgetId<W> {
let w = (self.0)(ui);
ui.add(w).to_id()
}
}
impl<W: Widget> WidgetLike for W {
type Widget = W;
fn id(self, ui: &mut UIBuilder) -> WidgetId<W> {
ui.add(self).to_id()
}
}
impl<W> WidgetLike for WidgetId<W> {
type Widget = W;
fn id(self, _: &mut UIBuilder) -> WidgetId<W> {
self
}
}
impl<W: WidgetLike<WidgetTag>> WidgetArrLike<1, WidgetTag> for W {
type Ws = (W::Widget,);
fn ui(self, ui: &mut Ui) -> WidgetArr<1, (W::Widget,)> {
WidgetArr::new([self.add(ui).any()])
pub struct WidgetArr<Ws: WidgetList> {
pub ui: UIBuilder,
pub arr: Ws::Ids,
}
impl<Ws: WidgetList> WidgetArr<Ws> {
pub fn new(ui: UIBuilder, arr: Ws::Ids) -> Self {
Self { ui, arr }
}
pub fn ids(self) -> Vec<WidgetId> {
self.arr.all()
}
}
// I hate this language it's so bad why do I even use it
macro_rules! impl_widget_arr {
($n:expr;$($W:ident)*) => {
impl_widget_arr!($n;$($W)*;$(${concat($W,Tag)})*);
};
($n:expr;$($W:ident)*;$($Tag:ident)*) => {
impl<$($W: WidgetLike<$Tag>,$Tag,)*> WidgetArrLike<$n, ($($Tag,)*)> for ($($W,)*) {
type Ws = ($($W::Widget,)*);
fn ui(self, ui: &mut Ui) -> WidgetArr<$n, ($($W::Widget,)*)> {
#[allow(non_snake_case)]
let ($($W,)*) = self;
WidgetArr::new(
[$($W.add(ui).cast_type(),)*],
)
}
}
};
pub type WidgetRef<W> = WidgetArr<(W, ())>;
impl<W> WidgetRef<W> {
pub fn handle(&self) -> WidgetId<W> {
self.arr.head().clone()
}
pub fn to_id(self) -> WidgetId<W> {
self.arr.into_head()
}
}
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);
impl<W> WidgetLike for WidgetRef<W> {
type Widget = W;
fn id(self, _: &mut UIBuilder) -> WidgetId<W> {
self.arr.into_head()
}
}
pub trait WidgetArrLike {
const LEN: usize;
type Ws: WidgetList;
fn ui(self, ui: &mut UIBuilder) -> WidgetArr<Self::Ws>;
}
impl<Ws: WidgetList> WidgetArrLike for WidgetArr<Ws> {
const LEN: usize = Ws::LEN;
type Ws = Ws;
fn ui(self, _: &mut UIBuilder) -> WidgetArr<Ws> {
self
}
}
impl<W: WidgetLike> WidgetArrLike for (W, ())
where
Self: TupleList,
{
const LEN: usize = <Self as TupleList>::LEN;
type Ws = (W::Widget, ());
fn ui(self, ui: &mut UIBuilder) -> WidgetArr<(W::Widget, ())> {
WidgetArr::new(ui.clone(), (self.0.id(ui), ()))
}
}
impl<W: WidgetLike, T: WidgetArrLike> WidgetArrLike for (W, T)
where
Self: TupleList,
{
const LEN: usize = <Self as TupleList>::LEN;
type Ws = (W::Widget, T::Ws);
fn ui(self, ui: &mut UIBuilder) -> WidgetArr<(W::Widget, T::Ws)> {
WidgetArr::new(ui.clone(), (self.0.id(ui), self.1.ui(ui).arr))
}
}
pub trait WidgetList: TupleList {
type Ids: IdList;
}
impl<H> WidgetList for (H, ()) {
type Ids = (WidgetId<H>, ());
}
impl<H, T: WidgetList> WidgetList for (H, T) {
type Ids = (WidgetId<H>, T::Ids);
}
pub trait IdList: TupleList {
fn iter(self) -> impl Iterator<Item = WidgetId>;
fn all(self) -> Vec<WidgetId>
where
Self: Sized,
{
self.iter().collect::<Vec<_>>()
}
}
impl<H> IdList for (WidgetId<H>, ()) {
fn iter(self) -> impl Iterator<Item = WidgetId> {
core::iter::once(self.0.erase_type())
}
}
impl<H, T: IdList> IdList for (WidgetId<H>, T) {
fn iter(self) -> impl Iterator<Item = WidgetId> {
core::iter::once(self.0.erase_type()).chain(self.1.iter())
}
}

View File

@@ -1,107 +0,0 @@
use crate::{
layout::{IdLike, Widget},
util::{DynBorrower, HashMap, Id, IdTracker},
};
#[derive(Default)]
pub struct Widgets {
ids: IdTracker,
map: HashMap<Id, WidgetData>,
}
pub struct WidgetData {
pub widget: Box<dyn Widget>,
pub label: String,
/// dynamic borrow checking
pub borrowed: bool,
}
impl Widgets {
pub fn new() -> Self {
Self {
ids: IdTracker::default(),
map: HashMap::default(),
}
}
pub fn get_dyn(&self, id: Id) -> Option<&dyn Widget> {
Some(self.map.get(&id)?.widget.as_ref())
}
pub fn get_dyn_mut(&mut self, id: Id) -> Option<&mut dyn Widget> {
Some(self.map.get_mut(&id)?.widget.as_mut())
}
/// get_dyn but dynamic borrow checking of widgets
/// lets you do recursive (tree) operations, like the painter does
pub fn get_dyn_dynamic(&self, id: Id) -> WidgetWrapper<'_> {
// SAFETY: must guarantee no other mutable references to this widget exist
// done through the borrow variable
#[allow(mutable_transmutes)]
let data = unsafe {
std::mem::transmute::<&WidgetData, &mut WidgetData>(self.map.get(&id).unwrap())
};
if data.borrowed {
panic!("tried to mutably borrow the same widget twice");
}
WidgetWrapper::new(data.widget.as_mut(), &mut data.borrowed)
}
pub fn get<W: Widget>(&self, id: &impl IdLike<W>) -> Option<&W> {
self.get_dyn(id.id())?.as_any().downcast_ref()
}
pub fn get_mut<W: Widget>(&mut self, id: &impl IdLike<W>) -> Option<&mut W> {
self.get_dyn_mut(id.id())?.as_any_mut().downcast_mut()
}
pub fn insert<W: Widget>(&mut self, id: Id, widget: W) {
let mut label = std::any::type_name::<W>().to_string();
if let (Some(first), Some(last)) = (label.find(":"), label.rfind(":")) {
label = label.split_at(first).0.to_string() + "::" + label.split_at(last + 1).1;
}
self.insert_any(id, Box::new(widget), label);
}
pub fn data(&self, id: &Id) -> Option<&WidgetData> {
self.map.get(id)
}
pub fn label(&self, id: &Id) -> &String {
&self.data(id).unwrap().label
}
pub fn data_mut(&mut self, id: &Id) -> Option<&mut WidgetData> {
self.map.get_mut(id)
}
pub fn insert_any(&mut self, id: Id, widget: Box<dyn Widget>, label: String) {
self.map.insert(
id,
WidgetData {
widget,
label,
borrowed: false,
},
);
}
pub fn delete(&mut self, id: Id) {
self.map.remove(&id);
self.ids.free(id);
}
pub fn reserve(&mut self) -> Id {
self.ids.next()
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
}
pub type WidgetWrapper<'a> = DynBorrower<'a, dyn Widget>;

View File

@@ -1,20 +1,17 @@
#![feature(macro_metavar_expr_concat)]
#![feature(const_ops)]
#![feature(const_trait_impl)]
#![feature(const_convert)]
#![feature(map_try_insert)]
#![feature(unboxed_closures)]
#![feature(fn_traits)]
#![feature(const_cmp)]
#![feature(const_destruct)]
#![feature(const_from)]
#![feature(trait_alias)]
#![feature(generic_const_exprs)]
pub mod core;
pub mod layout;
pub mod render;
mod layout;
mod render;
pub mod util;
mod base;
pub mod prelude {
pub use crate::core::*;
pub use crate::layout::*;
pub use crate::render::*;
}
pub use layout::*;
pub use render::*;
pub use base::*;
pub type HashMap<K, V> = std::collections::HashMap<K, V>;

5
src/main.rs Normal file
View File

@@ -0,0 +1,5 @@
mod testing;
fn main() {
testing::main();
}

View File

@@ -1,5 +1,12 @@
use crate::{layout::UiRegion, util::Id};
use wgpu::*;
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,
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
@@ -8,43 +15,20 @@ pub struct WindowUniform {
pub height: f32,
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct PrimitiveInstance {
pub region: UiRegion,
pub binding: u32,
pub idx: u32,
pub mask_idx: MaskIdx,
}
impl PrimitiveInstance {
const ATTRIBS: [VertexAttribute; 7] = vertex_attr_array![
const ATTRIBS: [VertexAttribute; 5] = wgpu::vertex_attr_array![
0 => Float32x2,
1 => Float32x2,
2 => Float32x2,
3 => Float32x2,
4 => Uint32,
5 => Uint32,
6 => Uint32,
];
pub fn desc() -> VertexBufferLayout<'static> {
VertexBufferLayout {
array_stride: std::mem::size_of::<Self>() as BufferAddress,
step_mode: VertexStepMode::Instance,
pub fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &Self::ATTRIBS,
}
}
}
pub type MaskIdx = Id<u32>;
impl MaskIdx {
pub const NONE: Self = Self::preset(u32::MAX);
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Mask {
pub region: UiRegion,
}

View File

@@ -1,9 +1,6 @@
use std::num::NonZero;
use crate::{
layout::Ui,
render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf},
util::HashMap,
render::{data::PrimitiveInstance, util::ArrBuf},
UI,
};
use data::WindowUniform;
use wgpu::{
@@ -13,100 +10,41 @@ use wgpu::{
use winit::dpi::PhysicalSize;
mod data;
mod primitive;
mod texture;
pub mod primitive;
mod util;
pub use data::{Mask, MaskIdx};
pub use primitive::*;
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
pub struct UiRenderer {
uniform_group: BindGroup,
primitive_layout: BindGroupLayout,
rsc_layout: BindGroupLayout,
rsc_group: BindGroup,
pub struct UIRenderNode {
bind_group_layout: BindGroupLayout,
bind_group: BindGroup,
pipeline: RenderPipeline,
layers: HashMap<usize, RenderLayer>,
active: Vec<usize>,
window_buffer: Buffer,
textures: GpuTextures,
masks: ArrBuf<Mask>,
}
struct RenderLayer {
instance: ArrBuf<PrimitiveInstance>,
primitives: PrimitiveBuffers,
primitive_group: BindGroup,
data: ArrBuf<u32>,
}
impl UiRenderer {
impl UIRenderNode {
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &self.uniform_group, &[]);
pass.set_bind_group(2, &self.rsc_group, &[]);
for i in &self.active {
let layer = &self.layers[i];
if layer.instance.len() == 0 {
continue;
}
pass.set_bind_group(1, &layer.primitive_group, &[]);
pass.set_vertex_buffer(0, layer.instance.buffer.slice(..));
pass.draw(0..4, 0..layer.instance.len() as u32);
pass.set_bind_group(0, &self.bind_group, &[]);
if self.instance.len() != 0 {
pass.set_vertex_buffer(0, self.instance.buffer.slice(..));
pass.draw(0..4, 0..self.instance.len() as u32);
}
}
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &mut Ui) {
self.active.clear();
for (i, ulayer) in ui.data.layers.iter_mut() {
self.active.push(i);
let primitives = &mut ulayer.primitives;
for change in primitives.apply_free() {
if let Some(inst) = ui.data.active.get_mut(&change.id) {
for h in &mut inst.primitives {
if h.inst_idx == change.old {
h.inst_idx = change.new;
break;
}
}
}
}
let rlayer = self.layers.entry(i).or_insert_with(|| {
let primitives = PrimitiveBuffers::new(device);
let primitive_group =
Self::primitive_group(device, &self.primitive_layout, primitives.buffers());
RenderLayer {
instance: ArrBuf::new(
device,
BufferUsages::VERTEX | BufferUsages::COPY_DST,
"instance",
),
primitives,
primitive_group,
}
});
if primitives.updated {
rlayer
.instance
.update(device, queue, primitives.instances());
rlayer.primitives.update(device, queue, primitives.data());
rlayer.primitive_group = Self::primitive_group(
device,
&self.primitive_layout,
rlayer.primitives.buffers(),
)
}
}
if self.textures.update(&mut ui.data.textures) {
self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures, &self.masks)
}
if ui.data.masks.changed {
ui.data.masks.changed = false;
self.masks.update(device, queue, &ui.data.masks[..]);
}
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &UI) {
let primitives = ui.to_primitives();
self.instance.update(device, queue, &primitives.instances);
self.data.update(device, queue, &primitives.data);
self.bind_group = Self::bind_group(
device,
&self.bind_group_layout,
&self.window_buffer,
&self.data.buffer,
)
}
pub fn resize(&mut self, size: &PhysicalSize<u32>, queue: &Queue) {
@@ -117,12 +55,7 @@ impl UiRenderer {
queue.write_buffer(&self.window_buffer, 0, bytemuck::cast_slice(slice));
}
pub fn new(
device: &Device,
queue: &Queue,
config: &SurfaceConfiguration,
limits: UiLimits,
) -> Self {
pub fn new(device: &Device, config: &SurfaceConfiguration) -> Self {
let shader = device.create_shader_module(ShaderModuleDescriptor {
label: Some("UI Shape Shader"),
source: ShaderSource::Wgsl(SHAPE_SHADER.into()),
@@ -130,31 +63,36 @@ impl UiRenderer {
let window_uniform = WindowUniform::default();
let window_buffer = device.create_buffer_init(&BufferInitDescriptor {
label: Some("window"),
label: Some("Camera Buffer"),
contents: bytemuck::cast_slice(&[window_uniform]),
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
});
let uniform_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
label: Some("window"),
});
let instance = ArrBuf::new(
device,
BufferUsages::VERTEX | BufferUsages::COPY_DST,
"instance",
);
let data = ArrBuf::new(
device,
BufferUsages::STORAGE | BufferUsages::COPY_DST,
"data",
);
let uniform_group = Self::bind_group_0(device, &uniform_layout, &window_buffer);
let primitive_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &core::array::from_fn::<_, { PrimitiveBuffers::LEN }, _>(|i| {
let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[
BindGroupLayoutEntry {
binding: i as u32,
binding: 0,
visibility: ShaderStages::VERTEX,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Storage { read_only: true },
@@ -162,24 +100,16 @@ impl UiRenderer {
min_binding_size: None,
},
count: None,
}
}),
label: Some("primitive"),
},
],
label: Some("camera_bind_group_layout"),
});
let tex_manager = GpuTextures::new(device, queue);
let masks = ArrBuf::new(
device,
BufferUsages::STORAGE | BufferUsages::COPY_DST,
"ui masks",
);
let rsc_layout = Self::rsc_layout(device, &limits);
let rsc_group = Self::rsc_group(device, &rsc_layout, &tex_manager, &masks);
let bind_group = Self::bind_group(device, &bind_group_layout, &window_buffer, &data.buffer);
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
label: Some("UI Shape Pipeline Layout"),
bind_group_layouts: &[&uniform_layout, &primitive_layout, &rsc_layout],
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
@@ -221,133 +151,34 @@ impl UiRenderer {
});
Self {
uniform_group,
primitive_layout,
rsc_layout,
rsc_group,
bind_group_layout,
bind_group,
pipeline,
window_buffer,
layers: HashMap::default(),
active: Vec::new(),
textures: tex_manager,
masks,
instance,
data,
}
}
fn bind_group_0(
pub fn bind_group(
device: &Device,
layout: &BindGroupLayout,
window_buffer: &Buffer,
) -> BindGroup {
device.create_bind_group(&BindGroupDescriptor {
layout,
entries: &[BindGroupEntry {
binding: 0,
resource: window_buffer.as_entire_binding(),
}],
label: Some("ui window"),
})
}
fn primitive_group(
device: &Device,
layout: &BindGroupLayout,
buffers: [(u32, &Buffer); PrimitiveBuffers::LEN],
) -> BindGroup {
device.create_bind_group(&BindGroupDescriptor {
layout,
entries: &buffers.map(|(binding, buf)| BindGroupEntry {
binding,
resource: buf.as_entire_binding(),
}),
label: Some("ui primitives"),
})
}
fn rsc_layout(device: &Device, limits: &UiLimits) -> BindGroupLayout {
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: false },
view_dimension: TextureViewDimension::D2,
multisampled: false,
},
count: Some(NonZero::new(limits.max_textures).unwrap()),
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Sampler(SamplerBindingType::NonFiltering),
count: Some(NonZero::new(limits.max_samplers).unwrap()),
},
BindGroupLayoutEntry {
binding: 2,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
label: Some("ui rsc"),
})
}
fn rsc_group(
device: &Device,
layout: &BindGroupLayout,
tex_manager: &GpuTextures,
masks: &ArrBuf<Mask>,
data: &Buffer,
) -> BindGroup {
device.create_bind_group(&BindGroupDescriptor {
layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureViewArray(&tex_manager.views()),
resource: window_buffer.as_entire_binding(),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::SamplerArray(&tex_manager.samplers()),
},
BindGroupEntry {
binding: 2,
resource: masks.buffer.as_entire_binding(),
resource: data.as_entire_binding(),
},
],
label: Some("ui rsc"),
label: Some("ui_bind_group"),
})
}
pub fn view_count(&self) -> usize {
self.textures.view_count()
}
}
pub struct UiLimits {
max_textures: u32,
max_samplers: u32,
}
impl Default for UiLimits {
fn default() -> Self {
Self {
max_textures: 100000,
max_samplers: 1000,
}
}
}
impl UiLimits {
pub fn max_binding_array_elements_per_shader_stage(&self) -> u32 {
self.max_textures + self.max_samplers
}
pub fn max_binding_array_sampler_elements_per_shader_stage(&self) -> u32 {
self.max_samplers
}
}

View File

@@ -1,281 +0,0 @@
use std::ops::{Deref, DerefMut};
use crate::{
layout::{Color, UiRegion},
render::{
ArrBuf,
data::{MaskIdx, PrimitiveInstance},
},
util::Id,
};
use bytemuck::Pod;
use wgpu::*;
pub struct Primitives {
instances: Vec<PrimitiveInstance>,
assoc: Vec<Id>,
data: PrimitiveData,
free: Vec<usize>,
pub updated: bool,
}
impl Default for Primitives {
fn default() -> Self {
Self {
instances: Default::default(),
assoc: Default::default(),
data: Default::default(),
free: Vec::new(),
updated: true,
}
}
}
pub trait Primitive: Pod {
const BINDING: u32;
fn vec(data: &mut PrimitiveData) -> &mut PrimitiveVec<Self>;
}
macro_rules! primitives {
($($name:ident: $ty:ty => $binding:expr,)*) => {
#[derive(Default)]
pub struct PrimitiveData {
$($name: PrimitiveVec<$ty>,)*
}
pub struct PrimitiveBuffers {
$($name: ArrBuf<$ty>,)*
}
impl PrimitiveBuffers {
pub fn update(&mut self, device: &Device, queue: &Queue, data: &PrimitiveData) {
$(self.$name.update(device, queue, &data.$name);)*
}
}
impl PrimitiveBuffers {
pub const LEN: usize = primitives!(@count $($name)*);
pub fn buffers(&self) -> [(u32, &Buffer); Self::LEN] {
[
$((<$ty>::BINDING, &self.$name.buffer),)*
]
}
pub fn new(device: &Device) -> Self {
Self {
$($name: ArrBuf::new(
device,
BufferUsages::STORAGE | BufferUsages::COPY_DST,
stringify!($name),
),)*
}
}
}
impl PrimitiveData {
pub fn clear(&mut self) {
$(self.$name.clear();)*
}
pub fn free(&mut self, binding: u32, idx: usize) {
match binding {
$(<$ty>::BINDING => self.$name.free(idx),)*
_ => unreachable!()
}
}
}
$(
unsafe impl bytemuck::Pod for $ty {}
unsafe impl bytemuck::Zeroable for $ty {}
impl Primitive for $ty {
const BINDING: u32 = $binding;
fn vec(data: &mut PrimitiveData) -> &mut PrimitiveVec<Self> {
&mut data.$name
}
}
)*
};
(@count $t1:tt $($t:tt)+) => { 1 + primitives!(@count $($t),+) };
(@count $t:tt) => { 1 };
}
pub struct PrimitiveInst<P> {
pub id: Id,
pub primitive: P,
pub region: UiRegion,
pub mask_idx: MaskIdx,
}
impl Primitives {
pub fn write<P: Primitive>(
&mut self,
layer: usize,
PrimitiveInst {
id,
primitive,
region,
mask_idx,
}: PrimitiveInst<P>,
) -> PrimitiveHandle {
let vec = P::vec(&mut self.data);
let i = vec.add(primitive);
let inst = PrimitiveInstance {
region,
idx: i as u32,
mask_idx,
binding: P::BINDING,
};
let inst_i = if let Some(i) = self.free.pop() {
self.instances[i] = inst;
self.assoc[i] = id;
i
} else {
let i = self.instances.len();
self.instances.push(inst);
self.assoc.push(id);
i
};
PrimitiveHandle::new::<P>(layer, inst_i, i)
}
/// returns (old index, new index)
pub fn apply_free(&mut self) -> impl Iterator<Item = PrimitiveChange> {
self.free.sort_by(|a, b| b.cmp(a));
self.free.drain(..).filter_map(|i| {
self.instances.swap_remove(i);
self.assoc.swap_remove(i);
if i == self.instances.len() {
return None;
}
let id = self.assoc[i];
let old = self.instances.len();
Some(PrimitiveChange { id, old, new: i })
})
}
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
self.data.free(h.binding, h.data_idx);
self.free.push(h.inst_idx);
self.instances[h.inst_idx].mask_idx
}
pub fn data(&self) -> &PrimitiveData {
&self.data
}
pub fn instances(&self) -> &Vec<PrimitiveInstance> {
&self.instances
}
pub fn region_mut(&mut self, h: &PrimitiveHandle) -> &mut UiRegion {
self.updated = true;
&mut self.instances[h.inst_idx].region
}
}
pub struct PrimitiveChange {
pub id: Id,
pub old: usize,
pub new: usize,
}
#[derive(Debug)]
pub struct PrimitiveHandle {
pub layer: usize,
pub inst_idx: usize,
pub data_idx: usize,
pub binding: u32,
}
impl PrimitiveHandle {
fn new<P: Primitive>(layer: usize, inst_idx: usize, data_idx: usize) -> Self {
Self {
layer,
inst_idx,
data_idx,
binding: P::BINDING,
}
}
}
primitives!(
rects: RectPrimitive => 0,
textures: TexturePrimitive => 1,
);
#[repr(C)]
#[derive(Copy, Clone)]
pub struct RectPrimitive {
pub color: Color<u8>,
pub radius: f32,
pub thickness: f32,
pub inner_radius: f32,
}
impl RectPrimitive {
pub fn color(color: Color<u8>) -> Self {
Self {
color,
radius: 0.0,
thickness: 0.0,
inner_radius: 0.0,
}
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct TexturePrimitive {
pub view_idx: u32,
pub sampler_idx: u32,
}
pub struct PrimitiveVec<T> {
vec: Vec<T>,
free: Vec<usize>,
}
impl<T> PrimitiveVec<T> {
pub fn new() -> Self {
Self {
vec: Vec::new(),
free: Vec::new(),
}
}
pub fn add(&mut self, t: T) -> usize {
if let Some(i) = self.free.pop() {
self.vec[i] = t;
i
} else {
let i = self.vec.len();
self.vec.push(t);
i
}
}
pub fn free(&mut self, i: usize) {
self.free.push(i);
}
pub fn clear(&mut self) {
self.free.clear();
self.vec.clear();
}
}
impl<T> Default for PrimitiveVec<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Deref for PrimitiveVec<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.vec
}
}
impl<T> DerefMut for PrimitiveVec<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.vec
}
}

View File

@@ -0,0 +1,47 @@
#![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 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

@@ -0,0 +1,14 @@
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

@@ -0,0 +1,97 @@
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

@@ -0,0 +1,63 @@
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>,
}
pub struct Painter<'a> {
nodes: &'a Widgets,
primitives: Primitives,
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 {
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
}
}

View File

@@ -0,0 +1,84 @@
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,79 +1,36 @@
const RECT: u32 = 0u;
const TEXTURE: u32 = 1u;
@group(0) @binding(0)
var<uniform> window: WindowUniform;
@group(1) @binding(RECT)
var<storage> rects: array<Rect>;
@group(1) @binding(TEXTURE)
var<storage> textures: array<TextureInfo>;
struct Rect {
color: u32,
radius: f32,
thickness: f32,
inner_radius: f32,
}
struct TextureInfo {
view_idx: u32,
sampler_idx: u32,
}
struct Mask {
x: UiSpan,
y: UiSpan,
}
struct UiSpan {
start: UiScalar,
end: UiScalar,
}
struct UiScalar {
rel: f32,
abs: f32,
}
struct UiVec2 {
rel: vec2<f32>,
abs: vec2<f32>,
}
@group(2) @binding(0)
var views: binding_array<texture_2d<f32>>;
@group(2) @binding(1)
var samplers: binding_array<sampler>;
@group(2) @binding(2)
var<storage> masks: array<Mask>;
@group(0) @binding(1)
var<storage> data: array<u32>;
struct WindowUniform {
dim: vec2<f32>,
};
struct InstanceInput {
@location(0) x_start: vec2<f32>,
@location(1) x_end: vec2<f32>,
@location(2) y_start: vec2<f32>,
@location(3) y_end: vec2<f32>,
@location(4) binding: u32,
@location(5) idx: u32,
@location(6) mask_idx: u32,
@location(0) top_left_anchor: vec2<f32>,
@location(1) top_left_offset: vec2<f32>,
@location(2) bottom_right_anchor: vec2<f32>,
@location(3) bottom_right_offset: vec2<f32>,
@location(4) pointer: u32,
}
struct RoundedRect {
color: u32,
radius: f32,
thickness: f32,
inner_radius: f32,
}
struct VertexOutput {
@location(0) top_left: vec2<f32>,
@location(1) bot_right: vec2<f32>,
@location(2) uv: vec2<f32>,
@location(3) binding: u32,
@location(4) idx: u32,
@location(5) mask_idx: u32,
@location(0) pointer: u32,
@location(1) top_left: vec2<f32>,
@location(2) bot_right: vec2<f32>,
@builtin(position) clip_position: vec4<f32>,
};
struct Region {
pos: vec2<f32>,
uv: vec2<f32>,
top_left: vec2<f32>,
bot_right: vec2<f32>,
}
@@ -85,27 +42,19 @@ fn vs_main(
) -> VertexOutput {
var out: VertexOutput;
let top_left_rel = vec2(in.x_start.x, in.y_start.x);
let top_left_abs = vec2(in.x_start.y, in.y_start.y);
let bot_right_rel = vec2(in.x_end.x, in.y_end.x);
let bot_right_abs = vec2(in.x_end.y, in.y_end.y);
let top_left = floor(top_left_rel * window.dim) + floor(top_left_abs);
let bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_abs);
let top_left = in.top_left_anchor * window.dim + in.top_left_offset;
let bot_right = in.bottom_right_anchor * window.dim + in.bottom_right_offset;
let size = bot_right - top_left;
let uv = vec2<f32>(
var pos = top_left + vec2<f32>(
f32(vi % 2u),
f32(vi / 2u)
);
let pos = (top_left + uv * size) / window.dim * 2.0 - 1.0;
) * size;
pos = pos / window.dim * 2.0 - 1.0;
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
out.uv = uv;
out.binding = in.binding;
out.idx = in.idx;
out.pointer = in.pointer;
out.top_left = top_left;
out.bot_right = bot_right;
out.mask_idx = in.mask_idx;
return out;
}
@@ -115,40 +64,24 @@ fn fs_main(
in: VertexOutput
) -> @location(0) vec4<f32> {
let pos = in.clip_position.xy;
let region = Region(pos, in.uv, in.top_left, in.bot_right);
let i = in.idx;
var color: vec4<f32>;
switch in.binding {
case RECT: {
color = draw_rounded_rect(region, rects[i]);
}
case TEXTURE: {
color = draw_texture(region, textures[i]);
}
default: {
color = vec4(1.0, 0.0, 1.0, 1.0);
let ty = data[in.pointer];
let dp = in.pointer + 1u;
let region = Region(pos, in.top_left, in.bot_right);
switch ty {
case 0u: {
return draw_rounded_rect(region, RoundedRect(
data[dp + 0u],
bitcast<f32>(data[dp + 1u]),
bitcast<f32>(data[dp + 2u]),
bitcast<f32>(data[dp + 3u]),
));
}
default: {}
}
if in.mask_idx != 4294967295u {
let mask = masks[in.mask_idx];
let tl = UiVec2(vec2(mask.x.start.rel, mask.y.start.rel), vec2(mask.x.start.abs, mask.y.start.abs));
let br = UiVec2(vec2(mask.x.end.rel, mask.y.end.rel), vec2(mask.x.end.abs, mask.y.end.abs));
let top_left = floor(tl.rel * window.dim) + floor(tl.abs);
let bot_right = floor(br.rel * window.dim) + floor(br.abs);
if pos.x < top_left.x || pos.x > bot_right.x || pos.y < top_left.y || pos.y > bot_right.y {
color *= 0.0;
}
}
return color;
return vec4(1.0, 0.0, 1.0, 1.0);
}
// TODO: this seems really inefficient (per frag indexing)?
fn draw_texture(region: Region, info: TextureInfo) -> vec4<f32> {
return textureSample(views[info.view_idx], samplers[info.sampler_idx], region.uv);
}
fn draw_rounded_rect(region: Region, rect: Rect) -> vec4<f32> {
fn draw_rounded_rect(region: Region, rect: RoundedRect) -> vec4<f32> {
var color = unpack4x8unorm(rect.color);
let edge = 0.5;
@@ -173,6 +106,5 @@ fn distance_from_rect(pixel_pos: vec2<f32>, rect_center: vec2<f32>, rect_corner:
let p = pixel_pos - rect_center;
// vec from inner rect corner to pixel
let q = abs(p) - (rect_corner - radius);
return length(max(q, vec2(0.0))) - radius;
return length(max(q, vec2<f32>(0.0, 0.0))) - radius;
}

View File

@@ -1,129 +0,0 @@
use image::{DynamicImage, EncodableLayout};
use wgpu::{util::DeviceExt, *};
use crate::layout::{TextureUpdate, Textures};
pub struct GpuTextures {
device: Device,
queue: Queue,
views: Vec<TextureView>,
view_count: usize,
samplers: Vec<Sampler>,
null_view: TextureView,
no_views: Vec<TextureView>,
}
impl GpuTextures {
pub fn update(&mut self, textures: &mut Textures) -> bool {
let mut changed = false;
for update in textures.updates() {
changed = true;
match update {
TextureUpdate::Push(image) => self.push(image),
TextureUpdate::Set(i, image) => self.set(i, image),
TextureUpdate::SetFree => self.view_count += 1,
TextureUpdate::Free(i) => self.free(i),
TextureUpdate::PushFree => self.push_free(),
}
}
changed
}
fn set(&mut self, i: u32, image: &DynamicImage) {
self.view_count += 1;
let view = self.create_view(image);
self.views[i as usize] = view;
}
fn free(&mut self, i: u32) {
self.view_count -= 1;
self.views[i as usize] = self.null_view.clone();
}
fn push(&mut self, image: &DynamicImage) {
self.view_count += 1;
let view = self.create_view(image);
self.views.push(view);
}
fn push_free(&mut self) {
self.view_count += 1;
self.views.push(self.null_view.clone());
}
fn create_view(&self, image: &DynamicImage) -> TextureView {
let image = image.to_rgba8();
let (width, height) = image.dimensions();
let texture = self.device.create_texture_with_data(
&self.queue,
&TextureDescriptor {
label: None,
size: Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba8Unorm,
usage: TextureUsages::TEXTURE_BINDING,
view_formats: &[],
},
wgt::TextureDataOrder::MipMajor,
image.as_bytes(),
);
texture.create_view(&TextureViewDescriptor::default())
}
pub fn new(device: &Device, queue: &Queue) -> Self {
let null_view = null_texture_view(device);
Self {
device: device.clone(),
queue: queue.clone(),
views: Vec::new(),
samplers: vec![default_sampler(device)],
no_views: vec![null_view.clone()],
null_view,
view_count: 0,
}
}
pub fn views(&self) -> Vec<&TextureView> {
if self.views.is_empty() {
&self.no_views
} else {
&self.views
}
.iter()
.by_ref()
.collect()
}
pub fn samplers(&self) -> Vec<&Sampler> {
self.samplers.iter().by_ref().collect()
}
pub fn view_count(&self) -> usize {
self.view_count
}
}
pub fn null_texture_view(device: &Device) -> TextureView {
device
.create_texture(&TextureDescriptor {
label: Some("null"),
size: Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba8Unorm,
usage: TextureUsages::TEXTURE_BINDING,
view_formats: &[],
})
.create_view(&TextureViewDescriptor::default())
}
pub fn default_sampler(device: &Device) -> Sampler {
device.create_sampler(&SamplerDescriptor::default())
}

View File

@@ -32,7 +32,7 @@ impl<T: Pod> ArrBuf<T> {
fn init_buf(device: &Device, size: usize, usage: BufferUsages, label: &'static str) -> Buffer {
let mut size = size as u64;
if usage.contains(BufferUsages::STORAGE) {
size = size.max(std::mem::size_of::<T>() as u64);
size = size.max(1);
}
device.create_buffer(&BufferDescriptor {
label: Some(label),

View File

@@ -31,7 +31,6 @@ impl ApplicationHandler for App {
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
let client = self.client.as_mut().unwrap();
client.event(event, event_loop);
self.client.as_mut().unwrap().event(event, event_loop);
}
}

71
src/testing/mod.rs Normal file
View File

@@ -0,0 +1,71 @@
use gui::util::IntoTupleList;
use std::sync::Arc;
use app::App;
use gui::{primitive::Axis, RoundedRect, UIColor, WidgetArrUtil, WidgetUtil, UI};
use render::Renderer;
use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window};
mod app;
mod render;
pub fn main() {
App::run();
}
pub struct Client {
renderer: Renderer,
}
impl Client {
pub fn new(window: Arc<Window>) -> Self {
let mut renderer = Renderer::new(window);
let rect = RoundedRect {
color: UIColor::WHITE,
radius: 10.0,
thickness: 0.0,
inner_radius: 0.0,
};
let mut ui = UI::build();
let blue = ui.add(rect.color(UIColor::BLUE));
let handle = blue.handle();
let mut ui = ui.finish(
(
(
blue,
(
rect.color(UIColor::RED),
(
rect.color(UIColor::ORANGE),
rect.color(UIColor::LIME).pad(10.0),
)
.into_list()
.span(Axis::Y, [1, 1].into()),
rect.color(UIColor::YELLOW),
)
.into_list()
.span(Axis::X, [2, 2, 1])
.pad(10),
)
.into_list()
.span(Axis::X, [1, 3]),
rect.color(UIColor::GREEN),
)
.into_list()
.span(Axis::Y, [3, 1])
.pad(10),
);
ui.widgets.get_mut(&handle).unwrap().color = UIColor::MAGENTA;
renderer.update(&ui);
Self { renderer }
}
pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => self.renderer.draw(),
WindowEvent::Resized(size) => self.renderer.resize(&size),
_ => (),
}
}
}

View File

@@ -1,51 +1,47 @@
use gui::{UIRenderNode, UI};
use pollster::FutureExt;
use std::sync::Arc;
use iris::{
layout::Ui,
render::{UiLimits, UiRenderer},
};
use wgpu::{util::StagingBelt, *};
use wgpu::util::StagingBelt;
use winit::{dpi::PhysicalSize, window::Window};
pub const CLEAR_COLOR: Color = Color::BLACK;
pub const CLEAR_COLOR: wgpu::Color = wgpu::Color::BLACK;
pub struct Renderer {
window: Arc<Window>,
surface: Surface<'static>,
device: Device,
queue: Queue,
config: SurfaceConfiguration,
encoder: CommandEncoder,
surface: wgpu::Surface<'static>,
device: wgpu::Device,
queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration,
encoder: wgpu::CommandEncoder,
staging_belt: StagingBelt,
pub ui: UiRenderer,
ui_node: UIRenderNode,
}
impl Renderer {
pub fn update(&mut self, updates: &mut Ui) {
self.ui.update(&self.device, &self.queue, updates);
pub fn update(&mut self, ui: &UI) {
self.ui_node.update(&self.device, &self.queue, ui);
}
pub fn draw(&mut self) {
let output = self.surface.get_current_texture().unwrap();
let view = output
.texture
.create_view(&TextureViewDescriptor::default());
.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = std::mem::replace(&mut self.encoder, Self::create_encoder(&self.device));
{
let render_pass = &mut encoder.begin_render_pass(&RenderPassDescriptor {
color_attachments: &[Some(RenderPassColorAttachment {
let render_pass = &mut encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: Operations {
load: LoadOp::Clear(CLEAR_COLOR),
store: StoreOp::Store,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(CLEAR_COLOR),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
..Default::default()
});
self.ui.draw(render_pass);
self.ui_node.draw(render_pass);
}
self.queue.submit(std::iter::once(encoder.finish()));
@@ -58,11 +54,11 @@ impl Renderer {
self.config.width = size.width;
self.config.height = size.height;
self.surface.configure(&self.device, &self.config);
self.ui.resize(size, &self.queue);
self.ui_node.resize(size, &self.queue);
}
fn create_encoder(device: &Device) -> CommandEncoder {
device.create_command_encoder(&CommandEncoderDescriptor {
fn create_encoder(device: &wgpu::Device) -> wgpu::CommandEncoder {
device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
})
}
@@ -70,8 +66,8 @@ impl Renderer {
pub fn new(window: Arc<Window>) -> Self {
let size = window.inner_size();
let instance = Instance::new(&InstanceDescriptor {
backends: Backends::PRIMARY,
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::PRIMARY,
..Default::default()
});
@@ -80,28 +76,18 @@ impl Renderer {
.expect("Could not create window surface!");
let adapter = instance
.request_adapter(&RequestAdapterOptions {
power_preference: PowerPreference::default(),
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.block_on()
.expect("Could not get adapter!");
let ui_limits = UiLimits::default();
let (device, queue) = adapter
.request_device(&DeviceDescriptor {
required_features: Features::TEXTURE_BINDING_ARRAY
| Features::PARTIALLY_BOUND_BINDING_ARRAY
| Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
required_limits: Limits {
max_binding_array_elements_per_shader_stage: ui_limits
.max_binding_array_elements_per_shader_stage(),
max_binding_array_sampler_elements_per_shader_stage: ui_limits
.max_binding_array_sampler_elements_per_shader_stage(),
..Default::default()
},
.request_device(&wgpu::DeviceDescriptor {
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
..Default::default()
})
.block_on()
@@ -115,12 +101,12 @@ impl Renderer {
.find(|f| f.is_srgb())
.unwrap_or(surface_caps.formats[0]);
let config = SurfaceConfiguration {
usage: TextureUsages::RENDER_ATTACHMENT,
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width,
height: size.height,
present_mode: PresentMode::AutoVsync,
present_mode: wgpu::PresentMode::AutoVsync,
alpha_mode: surface_caps.alpha_modes[0],
desired_maximum_frame_latency: 2,
view_formats: vec![],
@@ -131,7 +117,7 @@ impl Renderer {
let staging_belt = StagingBelt::new(4096 * 4);
let encoder = Self::create_encoder(&device);
let shape_pipeline = UiRenderer::new(&device, &queue, &config, ui_limits);
let shape_pipeline = UIRenderNode::new(&device, &config);
Self {
surface,
@@ -140,12 +126,7 @@ impl Renderer {
config,
encoder,
staging_belt,
ui: shape_pipeline,
window,
ui_node: shape_pipeline,
}
}
pub fn window(&self) -> &Window {
self.window.as_ref()
}
}

View File

@@ -1,109 +0,0 @@
use std::ops::Deref;
use crate::util::{Id, IdNum, IdTracker};
pub struct Arena<T, I> {
data: Vec<T>,
tracker: IdTracker<I>,
}
impl<T, I: IdNum> Arena<T, I> {
pub fn new() -> Self {
Self {
data: Vec::new(),
tracker: IdTracker::default(),
}
}
pub fn push(&mut self, value: T) -> Id<I> {
let id = self.tracker.next();
let i = id.idx();
if i == self.data.len() {
self.data.push(value);
} else {
self.data[i] = value;
}
id
}
pub fn remove(&mut self, id: Id<I>) -> T
where
T: Copy,
{
let i = id.idx();
self.tracker.free(id);
self.data[i]
}
}
impl<T, I: IdNum> Default for Arena<T, I> {
fn default() -> Self {
Self::new()
}
}
pub struct TrackedArena<T, I> {
inner: Arena<T, I>,
refs: Vec<u32>,
pub changed: bool,
}
impl<T, I: IdNum> TrackedArena<T, I> {
pub fn new() -> Self {
Self {
inner: Arena::default(),
refs: Vec::new(),
changed: true,
}
}
pub fn push(&mut self, value: T) -> Id<I> {
self.changed = true;
let id = self.inner.push(value);
let i = id.idx();
if i == self.refs.len() {
self.refs.push(0);
}
id
}
pub fn push_ref(&mut self, i: Id<I>) {
self.refs[i.idx()] += 1;
}
pub fn remove(&mut self, id: Id<I>) -> T
where
T: Copy,
{
let i = id.idx();
self.refs[i] -= 1;
if self.refs[i] == 0 {
self.changed = true;
self.inner.remove(id)
} else {
self[i]
}
}
}
impl<T, I: IdNum> Default for TrackedArena<T, I> {
fn default() -> Self {
Self::new()
}
}
impl<T, I> Deref for TrackedArena<T, I> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.inner.data
}
}
impl<T, I> Deref for Arena<T, I> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.data
}
}

View File

@@ -1,35 +0,0 @@
use std::ops::{Deref, DerefMut};
pub struct DynBorrower<'a, T: ?Sized> {
data: &'a mut T,
borrowed: &'a mut bool,
}
impl<'a, T: ?Sized> DynBorrower<'a, T> {
pub fn new(data: &'a mut T, borrowed: &'a mut bool) -> Self {
if *borrowed {
panic!("tried to mutably borrow the same thing twice");
}
Self { data, borrowed }
}
}
impl<T: ?Sized> Drop for DynBorrower<'_, T> {
fn drop(&mut self) {
*self.borrowed = false;
}
}
impl<T: ?Sized> Deref for DynBorrower<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.data
}
}
impl<T: ?Sized> DerefMut for DynBorrower<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.data
}
}

View File

@@ -1,30 +0,0 @@
use std::ops::{Deref, DerefMut};
pub struct MutDetect<T> {
inner: T,
pub changed: bool,
}
impl<T> Deref for MutDetect<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> DerefMut for MutDetect<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.changed = true;
&mut self.inner
}
}
impl<T> From<T> for MutDetect<T> {
fn from(inner: T) -> Self {
MutDetect {
inner,
changed: true,
}
}
}

View File

@@ -1,84 +1,44 @@
#[repr(C)]
#[derive(Eq, Hash, PartialEq, Debug, Clone, Copy, bytemuck::Zeroable)]
pub struct Id<I = u64>(I);
/// intentionally does not implement copy or clone
/// which should make it harder to misuse;
/// the idea is to generally try to guarantee all IDs
/// point to something valid, although duplicate
/// gets around this if needed
#[derive(Eq, Hash, PartialEq, Debug)]
pub struct ID(usize);
unsafe impl<I: Copy + bytemuck::Zeroable + 'static> bytemuck::Pod for Id<I> {}
pub struct IdTracker<I = u64> {
free: Vec<Id<I>>,
cur: Id<I>,
#[derive(Default)]
pub struct IDTracker {
free: Vec<ID>,
cur: usize,
}
impl<I: IdNum> IdTracker<I> {
impl IDTracker {
pub fn new() -> Self {
Self::default()
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Id<I> {
pub fn next(&mut self) -> ID {
if let Some(id) = self.free.pop() {
return id;
}
let next = self.cur.next();
std::mem::replace(&mut self.cur, next)
let id = ID(self.cur);
self.cur += 1;
id
}
#[allow(dead_code)]
pub fn free(&mut self, id: Id<I>) {
pub fn free(&mut self, id: ID) {
self.free.push(id);
}
}
impl<I: IdNum> Id<I> {
pub(crate) fn raw(id: I) -> Self {
Self(id)
}
pub fn idx(&self) -> usize {
self.0.idx()
}
pub fn next(&self) -> Id<I> {
Self(self.0.next())
}
pub const fn preset(value: I) -> Self {
Self(value)
}
}
impl<I: IdNum> Default for IdTracker<I> {
fn default() -> Self {
Self {
free: Vec::new(),
cur: Id(I::first()),
}
}
}
pub trait IdNum {
fn first() -> Self;
fn next(&self) -> Self;
fn idx(&self) -> usize;
}
impl IdNum for u64 {
fn first() -> Self {
0
}
fn next(&self) -> Self {
self + 1
}
fn idx(&self) -> usize {
*self as usize
}
}
impl IdNum for u32 {
fn first() -> Self {
0
}
fn next(&self) -> Self {
self + 1
}
fn idx(&self) -> usize {
*self as usize
impl ID {
/// this must be used carefully to make sure
/// all IDs are still valid references;
/// named weirdly to indicate this.
/// generally should not be used in "user" code
pub fn duplicate(&self) -> Self {
Self(self.0)
}
}

View File

@@ -1,87 +0,0 @@
use std::ops::*;
pub const trait LerpUtil {
fn lerp(self, from: Self, to: Self) -> Self;
fn lerp_inv(self, from: Self, to: Self) -> Self;
}
pub const trait DivOr {
fn div_or(self, rhs: Self, other: Self) -> Self;
}
impl const DivOr for f32 {
fn div_or(self, rhs: Self, other: Self) -> Self {
let res = self / rhs;
if res.is_nan() { other } else { res }
}
}
impl<T: const Add<Output = T> + const Sub<Output = T> + const Mul<Output = T> + const DivOr + Copy> const
LerpUtil for T
{
/// linear interpolation
/// from * (1.0 - self) + to * self
fn lerp(self, from: Self, to: Self) -> Self {
from + (to - from) * self
}
/// inverse of lerp
fn lerp_inv(self, from: Self, to: Self) -> Self {
(self - from).div_or(to - from, from)
}
}
macro_rules! impl_op {
($T:ident $op:ident $fn:ident $opa:ident $fna:ident; $($field:ident)*) => {
#[allow(non_snake_case)]
mod ${concat($T, _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 const $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 const $op<$T> for f32 {
type Output = $T;
fn $fn(self, rhs: $T) -> Self::Output {
$T {
$($field: self.$fn(rhs.$field),)*
}
}
}
impl const $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,16 +1,5 @@
mod arena;
mod borrow;
mod change;
mod id;
mod math;
mod refcount;
mod tuple;
pub(crate) use arena::*;
pub(crate) use borrow::*;
pub use change::*;
pub(crate) use id::*;
pub(crate) use math::*;
pub(crate) use refcount::*;
pub type HashMap<K, V> = fxhash::FxHashMap<K, V>;
pub type HashSet<K> = fxhash::FxHashSet<K>;
pub use id::*;
pub use tuple::*;

View File

@@ -1,27 +0,0 @@
use std::sync::{
Arc,
atomic::{AtomicU32, Ordering},
};
#[derive(Debug)]
pub struct RefCounter(Arc<AtomicU32>);
impl RefCounter {
pub fn new() -> Self {
Self(Arc::new(0.into()))
}
pub fn refs(&self) -> u32 {
self.0.load(Ordering::Acquire)
}
pub fn drop(&mut self) -> bool {
let refs = self.0.fetch_sub(1, Ordering::Release);
refs == 0
}
}
impl Clone for RefCounter {
fn clone(&self) -> Self {
self.0.fetch_add(1, Ordering::Release);
Self(self.0.clone())
}
}

70
src/util/tuple.rs Normal file
View File

@@ -0,0 +1,70 @@
pub trait TupleList {
const LEN: usize;
type Head;
type Tail;
fn into_head(self) -> Self::Head;
fn head(&self) -> &Self::Head;
fn split(self) -> (Self::Head, Self::Tail);
}
impl TupleList for () {
const LEN: usize = 0;
type Head = ();
type Tail = ();
fn head(&self) -> &Self::Head {
&()
}
fn into_head(self) -> Self::Head {}
fn split(self) -> (Self::Head, Self::Tail) {
((), ())
}
}
impl<T, Rest: TupleList> TupleList for (T, Rest) {
const LEN: usize = Rest::LEN + 1;
type Head = T;
type Tail = Rest;
fn head(&self) -> &Self::Head {
&self.0
}
fn into_head(self) -> Self::Head {
self.0
}
fn split(self) -> (Self::Head, Self::Tail) {
(self.0, self.1)
}
}
pub trait IntoTupleList {
type List;
fn into_list(self) -> Self::List;
}
impl IntoTupleList for () {
type List = ();
fn into_list(self) -> Self::List {}
}
macro_rules! impl_tuple {
($H:tt $($T:tt)*) => {
impl<$H, $($T,)*> IntoTupleList for ($H, $($T,)*) {
type List = ($H, <($($T,)*) as IntoTupleList>::List);
fn into_list(self) -> Self::List {
#[allow(non_snake_case)]
let ($H, $($T,)*) = self;
($H, ($($T,)*).into_list())
}
}
};
}
impl_tuple!(A);
impl_tuple!(A B);
impl_tuple!(A B C);
impl_tuple!(A B C D);
impl_tuple!(A B C D E);
impl_tuple!(A B C D E F);
impl_tuple!(A B C D E F G);
impl_tuple!(A B C D E F G H);
impl_tuple!(A B C D E F G H I);
impl_tuple!(A B C D E F G H I J);
impl_tuple!(A B C D E F G H I J K);
impl_tuple!(A B C D E F G H I J K L);