diff --git a/src/base/rect.rs b/src/base/rect.rs index e7fb090..a719af4 100644 --- a/src/base/rect.rs +++ b/src/base/rect.rs @@ -1,15 +1,23 @@ -use crate::{Painter, UIColor, Widget, primitive::RoundedRectData}; +use crate::{Painter, UiColor, Widget, primitive::RoundedRectData}; #[derive(Clone, Copy)] pub struct Rect { - pub color: UIColor, + pub color: UiColor, pub radius: f32, pub thickness: f32, pub inner_radius: f32, } impl Rect { - pub fn color(mut self, color: UIColor) -> Self { + 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 } diff --git a/src/base/span.rs b/src/base/span.rs index 2500f07..f596eb0 100644 --- a/src/base/span.rs +++ b/src/base/span.rs @@ -46,6 +46,13 @@ pub struct SpanLenSums { } impl Span { + pub fn empty(dir: Dir) -> Self { + Self { + children: Vec::new(), + dir, + } + } + pub fn sums(&self) -> SpanLenSums { self.lengths().fold(SpanLenSums::default(), |mut s, l| { match l { diff --git a/src/base/trait_fns.rs b/src/base/trait_fns.rs index a1cde88..e27fa05 100644 --- a/src/base/trait_fns.rs +++ b/src/base/trait_fns.rs @@ -1,12 +1,12 @@ use super::*; -use crate::{UIRegion, Vec2, WidgetArrLike, WidgetFn, WidgetLike}; +use crate::{UIRegion, Ui, Vec2, WidgetArrLike, WidgetFn, WidgetLike}; -pub trait WidgetUtil { +pub trait BaseWidget { fn pad(self, padding: impl Into) -> impl WidgetLike; fn center(self, size: impl Into) -> impl WidgetLike; } -impl WidgetUtil for W { +impl BaseWidget for W { fn pad(self, padding: impl Into) -> impl WidgetLike { WidgetFn(|ui| Regioned { region: padding.into().region(), @@ -22,12 +22,20 @@ impl WidgetUtil for W { } } -pub trait WidgetArrUtil { - fn span(self, dir: Dir, lengths: [impl Into; LEN]) -> impl WidgetLike; +pub trait BaseWidgetArr { + fn span( + self, + dir: Dir, + lengths: [impl Into; LEN], + ) -> WidgetFn Span, Span>; } -impl> WidgetArrUtil for Wa { - fn span(self, dir: Dir, lengths: [impl Into; LEN]) -> impl WidgetLike { +impl> BaseWidgetArr for Wa { + fn span( + self, + dir: Dir, + lengths: [impl Into; LEN], + ) -> WidgetFn Span, Span> { let lengths = lengths.map(Into::into); WidgetFn(move |ui| Span { dir, diff --git a/src/layout/color.rs b/src/layout/color.rs index 7be0934..674892c 100644 --- a/src/layout/color.rs +++ b/src/layout/color.rs @@ -3,10 +3,10 @@ #[repr(C)] #[derive(Clone, Copy, bytemuck::Zeroable)] pub struct Color { - r: T, - g: T, - b: T, - a: T, + pub r: T, + pub g: T, + pub b: T, + pub a: T, } impl Color { diff --git a/src/layout/mod.rs b/src/layout/mod.rs index ec38dda..fab6dcc 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -12,4 +12,4 @@ pub use vec2::*; pub use widget::*; pub use painter::*; -pub type UIColor = Color; +pub type UiColor = Color; diff --git a/src/layout/painter.rs b/src/layout/painter.rs index 219fe2c..24d9c2c 100644 --- a/src/layout/painter.rs +++ b/src/layout/painter.rs @@ -29,7 +29,7 @@ impl<'a> Painter<'a> { .extend_from_slice(bytemuck::cast_slice::<_, u32>(&[data])); } pub fn draw(&mut self, node: &WidgetId) { - self.nodes.get(node).draw(self); + self.nodes.get_dyn(node).draw(self); } pub fn draw_within(&mut self, node: &WidgetId, region: UIRegion) { let old = self.region; diff --git a/src/layout/ui.rs b/src/layout/ui.rs index 163e361..1987298 100644 --- a/src/layout/ui.rs +++ b/src/layout/ui.rs @@ -1,88 +1,115 @@ use crate::{ - HashMap, Painter, Widget, WidgetId, WidgetLike, WidgetRef, + HashMap, Painter, Widget, WidgetId, WidgetLike, primitive::Primitives, util::{ID, IDTracker}, }; use std::{ any::{Any, TypeId}, - cell::RefCell, - rc::Rc, + ops::{Index, IndexMut}, }; -pub struct UI { +#[derive(Default)] +pub struct Ui { ids: IDTracker, base: Option, - pub widgets: Widgets, + widgets: Widgets, + updates: Vec, + primitives: Primitives, + full_redraw: bool, } +#[derive(Default)] pub struct Widgets(HashMap>); -#[derive(Clone)] -pub struct UIBuilder { - ui: Rc>, -} - -impl From for UIBuilder { - fn from(ui: UI) -> Self { - UIBuilder { - ui: Rc::new(RefCell::new(ui)), - } - } -} - -impl UIBuilder { - pub fn add(&mut self, w: impl WidgetLike) -> WidgetRef { - WidgetRef::new([w.add(self).erase_type()]) +impl Ui { + pub fn add(&mut self, w: impl WidgetLike) -> WidgetId { + w.add(self) } - pub fn add_widget(&mut self, w: W) -> WidgetRef { - WidgetRef::new([self.push(w)]) + pub fn add_widget(&mut self, w: W) -> WidgetId { + self.push(w) } - pub fn push(&mut self, w: W) -> WidgetId { - let mut ui = self.ui.borrow_mut(); - let id = ui.ids.next(); - ui.widgets.insert(id.duplicate(), w); + pub fn push(&mut self, w: W) -> WidgetId { + let id = self.ids.next(); + self.widgets.insert(id.duplicate(), w); WidgetId::new(id, TypeId::of::()) } - pub fn finish(mut self, base: W) -> UI { - let base = base.add(&mut self).erase_type(); - let mut ui = Rc::into_inner(self.ui).unwrap().into_inner(); - ui.base = Some(base); - ui - } -} - -impl UI { - pub fn build() -> UIBuilder { - Self::empty().into() + pub fn set(&mut self, i: &WidgetId, w: W) { + self.widgets.insert(i.id.duplicate(), w); } - pub fn empty() -> Self { - Self { - ids: IDTracker::new(), - base: None, - widgets: Widgets::new(), - } + pub fn set_base(&mut self, w: impl WidgetLike) { + self.base = Some(w.add(self).erase_type()); + self.full_redraw = true; } - pub fn to_primitives(&self) -> Primitives { + pub fn new() -> Self { + Self::default() + } + + pub fn get(&self, id: &WidgetId) -> Option<&W> { + self.widgets.get(id) + } + + pub fn get_mut(&mut self, id: &WidgetId) -> Option<&mut W> { + self.widgets.get_mut(id) + } + + pub fn id(&mut self) -> WidgetId { + WidgetId::new(self.ids.next(), TypeId::of::()) + } + + pub fn redraw_all(&mut self) { let mut painter = Painter::new(&self.widgets); if let Some(base) = &self.base { painter.draw(base); } - painter.finish() + self.primitives = painter.finish(); + } + + pub fn update(&mut self) -> Option<&Primitives> { + if self.full_redraw { + self.redraw_all(); + self.full_redraw = false; + return Some(&self.primitives); + } + if self.updates.is_empty() { + return None; + } + self.redraw_all(); + self.updates.drain(..); + Some(&self.primitives) + } + + pub fn needs_redraw(&self) -> bool { + self.full_redraw || !self.updates.is_empty() + } +} + +impl Index<&WidgetId> for Ui { + type Output = W; + + fn index(&self, id: &WidgetId) -> &Self::Output { + self.get(id).unwrap() + } +} + +impl IndexMut<&WidgetId> for Ui { + fn index_mut(&mut self, id: &WidgetId) -> &mut Self::Output { + self.updates.push(id.clone().erase_type()); + self.get_mut(id).unwrap() } } impl Widgets { - fn new() -> Self { - Self(HashMap::new()) + pub fn get_dyn(&self, id: &WidgetId) -> &dyn Widget { + self.0.get(&id.id).unwrap().as_ref() } - pub fn get(&self, id: &WidgetId) -> &dyn Widget { - self.0.get(&id.id).unwrap().as_ref() + pub fn get(&self, id: &WidgetId) -> Option<&W> { + self.0.get(&id.id).unwrap().as_any().downcast_ref() } pub fn get_mut(&mut self, id: &WidgetId) -> Option<&mut W> { @@ -99,6 +126,10 @@ impl Widgets { } impl dyn Widget { + pub fn as_any(&self) -> &dyn Any { + self + } + pub fn as_any_mut(&mut self) -> &mut dyn Any { self } diff --git a/src/layout/widget.rs b/src/layout/widget.rs index 1b38fce..4b8e0fc 100644 --- a/src/layout/widget.rs +++ b/src/layout/widget.rs @@ -3,20 +3,19 @@ use std::{ marker::PhantomData, }; -use crate::{Painter, util::ID, UIBuilder}; +use crate::{Painter, Ui, util::ID}; pub trait Widget: 'static + Any { fn draw(&self, painter: &mut Painter); } -impl Widget for (W,) { - fn draw(&self, painter: &mut Painter) { - self.0.draw(painter); - } -} - pub struct AnyWidget; +/// An identifier for a widget. +/// Can index a UI to get the associated widget. +/// +/// 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. #[derive(Eq, Hash, PartialEq, Debug)] pub struct WidgetId { pub(super) ty: TypeId, @@ -54,42 +53,83 @@ impl WidgetId { pub trait WidgetLike { type Widget; - fn add(self, ui: &mut UIBuilder) -> WidgetId; + fn add(self, ui: &mut Ui) -> WidgetId; } -/// wouldn't be needed if negative trait bounds & disjoint impls existed -pub struct WidgetFn W, W>(pub F); +/// 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 struct WidgetFn W, W>(pub F); +pub struct WidgetIdFn WidgetId, W>(pub F); -impl W> WidgetLike for WidgetFn { +pub trait _WidgetFn { + fn call(ui: &mut Ui) -> W; +} + +pub trait Idable { + type Widget: Widget; + fn set(self, ui: &mut Ui, id: &WidgetId); +} + +pub trait WidgetFns { + fn id(self, id: &WidgetId) -> impl WidgetLike; +} + +impl WidgetFns for I { + fn id(self, id: &WidgetId) -> impl WidgetLike { + WidgetIdFn(|ui| { + self.set(ui, id); + id.clone() + }) + } +} + +impl Idable for W { type Widget = W; - fn add(self, ui: &mut UIBuilder) -> WidgetId { + + fn set(self, ui: &mut Ui, id: &WidgetId) { + ui.set(id, self); + } +} + +impl FnOnce(&'a mut Ui) -> W, W: Widget> Idable for WidgetFn { + type Widget = W; + + fn set(self, ui: &mut Ui, id: &WidgetId) { + let w = self.0(ui); + ui.set(id, w); + } +} + +impl W> WidgetLike for WidgetFn { + type Widget = W; + fn add(self, ui: &mut Ui) -> WidgetId { let w = (self.0)(ui); - ui.add_widget(w).to_id() + ui.add(w) + } +} + +impl WidgetId> WidgetLike for WidgetIdFn { + type Widget = W; + fn add(self, ui: &mut Ui) -> WidgetId { + (self.0)(ui) } } impl WidgetLike for W { type Widget = W; - fn add(self, ui: &mut UIBuilder) -> WidgetId { - ui.add_widget(self).to_id() + fn add(self, ui: &mut Ui) -> WidgetId { + ui.add_widget(self) } } impl WidgetLike for WidgetId { type Widget = W; - fn add(self, _: &mut UIBuilder) -> WidgetId { + fn add(self, _: &mut Ui) -> WidgetId { self } } -impl WidgetLike for WidgetArr<1, (W,)> { - type Widget = W; - fn add(self, _: &mut UIBuilder) -> WidgetId { - let [id] = self.arr; - id.cast_type() - } -} - pub struct WidgetArr { pub arr: [WidgetId; LEN], _pd: PhantomData, @@ -104,37 +144,31 @@ impl WidgetArr { } } -pub type WidgetRef = WidgetArr<1, (W,)>; - -impl WidgetRef { - pub fn handle(&self) -> WidgetId { - let [id] = &self.arr; - id.clone().cast_type() - } - pub fn to_id(self) -> WidgetId { - let [id] = self.arr; - id.cast_type() - } -} - pub trait WidgetArrLike { type Ws; - fn ui(self, ui: &mut UIBuilder) -> WidgetArr; + fn ui(self, ui: &mut Ui) -> WidgetArr; } impl WidgetArrLike for WidgetArr { type Ws = Ws; - fn ui(self, _: &mut UIBuilder) -> WidgetArr { + fn ui(self, _: &mut Ui) -> WidgetArr { self } } +impl WidgetArrLike<1> for W { + type Ws = (W::Widget,); + fn ui(self, ui: &mut Ui) -> WidgetArr<1, (W::Widget,)> { + WidgetArr::new([self.add(ui).erase_type()]) + } +} + // I hate this language it's so bad why do I even use it macro_rules! impl_widget_arr { ($n:expr;$($T:tt)*) => { impl<$($T: WidgetLike,)*> WidgetArrLike<$n> for ($($T,)*) { type Ws = ($($T::Widget,)*); - fn ui(self, ui: &mut UIBuilder) -> WidgetArr<$n, ($($T::Widget,)*)> { + fn ui(self, ui: &mut Ui) -> WidgetArr<$n, ($($T::Widget,)*)> { #[allow(non_snake_case)] let ($($T,)*) = self; WidgetArr::new( diff --git a/src/render/mod.rs b/src/render/mod.rs index ba1533b..0dc36fc 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,4 +1,4 @@ -use crate::{UI, primitive::PrimitiveInstance, render::util::ArrBuf}; +use crate::{Ui, primitive::PrimitiveInstance, render::util::ArrBuf}; use data::WindowUniform; use wgpu::{ util::{BufferInitDescriptor, DeviceExt}, @@ -32,16 +32,17 @@ impl UIRenderNode { } } - 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 update(&mut self, device: &Device, queue: &Queue, ui: &mut Ui) { + if let Some(primitives) = ui.update() { + 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, queue: &Queue) { diff --git a/src/testing/mod.rs b/src/testing/mod.rs index b4978d7..d1cdd8c 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use app::App; -use gui::{Dir, Rect, UI, UIColor, WidgetArrUtil, WidgetUtil, fixed, ratio, rel}; +use gui::*; use render::Renderer; use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window}; @@ -14,44 +14,46 @@ pub fn main() { pub struct Client { renderer: Renderer, + ui: Ui, + test: WidgetId, } impl Client { pub fn new(window: Arc) -> Self { - let mut renderer = Renderer::new(window); + let renderer = Renderer::new(window); let rect = Rect { - color: UIColor::WHITE, + color: UiColor::WHITE, radius: 20.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( + let mut ui = Ui::new(); + let test = ui.id(); + ui.set_base( ( ( - blue, + rect.color(UiColor::BLUE), ( - rect.color(UIColor::RED).center((100.0, 100.0)), + rect.color(UiColor::RED).center((100.0, 100.0)), ( - rect.color(UIColor::ORANGE), - rect.color(UIColor::LIME).pad(10.0), + rect.color(UiColor::ORANGE), + rect.color(UiColor::LIME).pad(10.0), ) .span(Dir::RIGHT, [1, 1]), - rect.color(UIColor::YELLOW), + rect.color(UiColor::YELLOW), ) .span(Dir::RIGHT, [2, 2, 1]) .pad(10), ) .span(Dir::RIGHT, [1, 3]), + Span::empty(Dir::RIGHT).id(&test), ( - rect.color(UIColor::GREEN), - rect.color(UIColor::ORANGE), - rect.color(UIColor::CYAN), - rect.color(UIColor::BLUE), - rect.color(UIColor::MAGENTA), - rect.color(UIColor::RED), + rect.color(UiColor::GREEN), + rect.color(UiColor::ORANGE), + rect.color(UiColor::CYAN), + rect.color(UiColor::BLUE), + rect.color(UiColor::MAGENTA), + rect.color(UiColor::RED), ) .span( Dir::LEFT, @@ -65,19 +67,27 @@ impl Client { ], ), ) - .span(Dir::DOWN, [3, 1]) + .span(Dir::DOWN, [3, 1, 1]) .pad(10), ); - ui.widgets.get_mut(&handle).unwrap().color = UIColor::MAGENTA; - renderer.update(&ui); - Self { renderer } + Self { renderer, ui, test } } pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) { match event { WindowEvent::CloseRequested => event_loop.exit(), - WindowEvent::RedrawRequested => self.renderer.draw(), + WindowEvent::RedrawRequested => { + self.renderer.update(&mut self.ui); + self.renderer.draw() + } WindowEvent::Resized(size) => self.renderer.resize(&size), + WindowEvent::KeyboardInput { event, .. } => { + if event.state.is_pressed() { + let child = self.ui.add(Rect::new(Color::YELLOW)).erase_type(); + self.ui[&self.test].children.push((child, fixed(20.0))); + self.renderer.window().request_redraw(); + } + } _ => (), } } diff --git a/src/testing/render/mod.rs b/src/testing/render/mod.rs index 0a1b0e6..a353d28 100644 --- a/src/testing/render/mod.rs +++ b/src/testing/render/mod.rs @@ -1,4 +1,4 @@ -use gui::{UIRenderNode, UI}; +use gui::{UIRenderNode, Ui}; use pollster::FutureExt; use std::sync::Arc; use wgpu::util::StagingBelt; @@ -7,6 +7,7 @@ use winit::{dpi::PhysicalSize, window::Window}; pub const CLEAR_COLOR: wgpu::Color = wgpu::Color::BLACK; pub struct Renderer { + window: Arc, surface: wgpu::Surface<'static>, device: wgpu::Device, queue: wgpu::Queue, @@ -17,7 +18,7 @@ pub struct Renderer { } impl Renderer { - pub fn update(&mut self, ui: &UI) { + pub fn update(&mut self, ui: &mut Ui) { self.ui_node.update(&self.device, &self.queue, ui); } @@ -127,6 +128,11 @@ impl Renderer { encoder, staging_belt, ui_node: shape_pipeline, + window, } } + + pub fn window(&self) -> &Window { + self.window.as_ref() + } }