diff --git a/src/layout/painter.rs b/src/layout/painter.rs index f920e79..4e643e8 100644 --- a/src/layout/painter.rs +++ b/src/layout/painter.rs @@ -2,52 +2,67 @@ use image::GenericImageView; use crate::{ layout::{ - ActiveSensors, SensorMap, TextAttrs, TextData, TextureHandle, Textures, UiPos, UiRegion, - Vec2, WidgetId, Widgets, + Active, SensorMap, TextAttrs, TextData, TextureHandle, Textures, UiRegion, Vec2, WidgetId, + WidgetInstance, Widgets, }, - render::{Primitive, Primitives}, + render::{Primitive, PrimitiveHandle, Primitives}, + util::Id, }; +struct State { + region: UiRegion, + children: Vec, + parent: Option, + // TODO: there's probably a better way but idc at this point + id: Option, +} + pub struct Painter<'a, Ctx: 'static> { - nodes: &'a Widgets, + widgets: &'a Widgets, ctx: &'a mut Ctx, sensors_map: &'a SensorMap, - active_sensors: &'a mut ActiveSensors, - primitives: &'a mut Primitives, + pub(super) active: &'a mut Active, + pub(super) primitives: &'a mut Primitives, textures: &'a mut Textures, text: &'a mut TextData, - region: UiRegion, screen_size: Vec2, + /// state of what's currently being drawn + state: State, } impl<'a, Ctx> Painter<'a, Ctx> { #[allow(clippy::too_many_arguments)] - pub(crate) fn new( + pub(super) fn new( nodes: &'a Widgets, primitives: &'a mut Primitives, ctx: &'a mut Ctx, sensors_map: &'a SensorMap, - active_sensors: &'a mut ActiveSensors, + active: &'a mut Active, text: &'a mut TextData, textures: &'a mut Textures, screen_size: Vec2, ) -> Self { Self { - nodes, + widgets: nodes, ctx, - active_sensors, + active, sensors_map, primitives, text, - region: UiRegion::full(), textures, screen_size, + state: State { + region: UiRegion::full(), + children: Vec::new(), + parent: None, + id: None, + }, } } /// Writes a primitive to be rendered - pub fn write(&mut self, data: P) { - self.primitives.write(data, self.region); + pub fn write(&mut self, data: P) -> PrimitiveHandle

{ + self.primitives.write(data, self.state.region) } /// Draws a widget within this widget's region. @@ -55,7 +70,7 @@ impl<'a, Ctx> Painter<'a, Ctx> { where Ctx: 'static, { - self.draw_at(id, self.region); + self.draw_at(id, self.state.region); } /// Draws a widget somewhere within this one. @@ -64,7 +79,7 @@ impl<'a, Ctx> Painter<'a, Ctx> { where Ctx: 'static, { - self.draw_at(id, region.within(&self.region)); + self.draw_at(id, region.within(&self.state.region)); } /// Draws a widget in an arbitrary region. @@ -72,15 +87,48 @@ impl<'a, Ctx> Painter<'a, Ctx> { where Ctx: 'static, { - if self.sensors_map.get(&id.id).is_some() { - self.active_sensors.push((region, id.id.duplicate())); + self.draw_raw_at(&id.id, region); + } + + fn draw_raw_at(&mut self, id: &Id, region: UiRegion) + where + Ctx: 'static, + { + if self.active.widgets.contains_key(id) { + panic!("widget drawn twice!"); } + self.state.children.push(id.duplicate()); + // &mut self is passed to avoid copying all of the "static" pointers in self // so need to save non static data here - let old = self.region; - self.region = region; - self.nodes.get_dyn(id).draw(self); - self.region = old; + let child_state = State { + region, + children: Vec::new(), + id: Some(id.duplicate()), + parent: self.state.id.as_ref().map(|i| i.duplicate()), + }; + // save state + let self_state = std::mem::replace(&mut self.state, child_state); + + // draw widgets + let start_i = self.primitives.cur_pos(); + self.widgets.get_dyn(id).draw(self); + let end_i = self.primitives.cur_pos(); + + // restore state + let child_state = std::mem::replace(&mut self.state, self_state); + + // add to active + self.active.add( + id, + WidgetInstance { + region, + primitives: (start_i..end_i).into(), + children: child_state.children, + parent: child_state.parent, + }, + self.sensors_map, + ); } pub fn draw_texture(&mut self, handle: &TextureHandle) { @@ -89,16 +137,16 @@ impl<'a, Ctx> Painter<'a, Ctx> { } pub fn draw_texture_at(&mut self, handle: &TextureHandle, region: UiRegion) { - let old = self.region; - self.region = region; + let old = self.state.region; + self.state.region = region; self.draw_texture(handle); - self.region = old; + self.state.region = old; } pub fn draw_text(&mut self, content: &str, attrs: &TextAttrs) { let handle = self.text.draw(content, attrs, self.textures); let dims: Vec2 = self.textures[&handle].dimensions().into(); - let mut region = self.region.center().expand(dims); + let mut region = self.state.region.center().expand(dims); // TODO: I feel like this shouldn't be needed // what this does is makes sure the text doesn't get squeezed into one pixel less // I'm unsure exactly why it happens or if this will ever expand it too much @@ -107,10 +155,42 @@ impl<'a, Ctx> Painter<'a, Ctx> { } pub fn region(&self) -> UiRegion { - self.region + self.state.region } pub fn ctx(&mut self) -> &mut Ctx { self.ctx } + + pub(crate) fn redraw(&mut self, id: &Id) { + if !self.active.widgets.contains_key(id) { + return; + } + let instance = self.free(id); + self.state.id = instance.parent; + self.primitives.prepare(instance.primitives.into()); + self.draw_raw_at(id, instance.region); + let delta = self.primitives.apply(instance.primitives.into()); + if let Some(parent) = self.state.id.take() { + self.shift_end(parent, delta); + } + } + + fn shift_end(&mut self, id: Id, delta: isize) { + let instance = self.active.widgets.get_mut(&id).unwrap(); + let end = &mut instance.primitives.end; + *end = end.strict_add_signed(delta); + if let Some(parent) = &instance.parent { + let parent = parent.duplicate(); + self.shift_end(parent, delta); + } + } + + fn free(&mut self, id: &Id) -> WidgetInstance { + let instance = self.active.remove(id).unwrap(); + for child in &instance.children { + self.free(child); + } + instance + } } diff --git a/src/layout/sense.rs b/src/layout/sense.rs index 7fc8659..2999cfb 100644 --- a/src/layout/sense.rs +++ b/src/layout/sense.rs @@ -1,6 +1,6 @@ use crate::{ - HashMap, layout::{Ui, UiRegion, Vec2, WidgetId}, + util::HashMap, util::Id, }; @@ -36,7 +36,7 @@ pub struct Sensor { } pub type SensorMap = HashMap>; -pub type ActiveSensors = Vec<(SenseShape, Id)>; +pub type ActiveSensors = HashMap; pub type SenseShape = UiRegion; #[derive(Clone)] pub struct SenseTrigger { @@ -74,9 +74,9 @@ impl Ui { where Ctx: 'static, { - let mut active = std::mem::take(&mut self.active_sensors); + let active = std::mem::take(&mut self.active.sensors); let mut map = std::mem::take(&mut self.sensor_map); - for (shape, id) in active.iter_mut().rev() { + for (id, shape) in active.iter() { let group = &mut map.get_mut(id).unwrap(); let region = shape.to_screen(window_size); let in_shape = cursor.exists && region.contains(cursor.pos); @@ -90,7 +90,7 @@ impl Ui { } } self.sensor_map = map; - self.active_sensors = active; + self.active.sensors = active; } } diff --git a/src/layout/text.rs b/src/layout/text.rs index a63abb6..a9678cb 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -2,8 +2,8 @@ use cosmic_text::{Attrs, Buffer, FontSystem, Metrics, Shaping, SwashCache}; use image::{Rgba, RgbaImage}; use crate::{ - HashMap, layout::{TextureHandle, Textures, UiColor}, + util::HashMap, }; pub(crate) struct TextData { diff --git a/src/layout/ui.rs b/src/layout/ui.rs index 6784347..09513ca 100644 --- a/src/layout/ui.rs +++ b/src/layout/ui.rs @@ -1,17 +1,17 @@ use image::DynamicImage; use crate::{ - HashMap, layout::{ - ActiveSensors, Painter, SensorMap, TextData, TextureHandle, Textures, Vec2, Widget, - WidgetId, WidgetLike, + ActiveSensors, Painter, SensorMap, TextData, TextureHandle, Textures, UiRegion, Vec2, + Widget, WidgetId, WidgetLike, }, render::Primitives, - util::{Id, IdTracker}, + util::{HashMap, Id, IdTracker}, }; use std::{ any::{Any, TypeId}, ops::{Index, IndexMut}, + range::Range, sync::mpsc::{Receiver, Sender, channel}, }; @@ -28,7 +28,7 @@ pub struct Ui { pub(crate) text: TextData, full_redraw: bool, - pub(super) active_sensors: ActiveSensors, + pub(super) active: Active, pub(super) sensor_map: SensorMap, } @@ -96,7 +96,7 @@ impl Ui { where Ctx: 'static, { - self.active_sensors.clear(); + self.active.clear(); self.primitives.clear(); self.free(); let mut painter = Painter::new( @@ -104,7 +104,7 @@ impl Ui { &mut self.primitives, ctx, &self.sensor_map, - &mut self.active_sensors, + &mut self.active, &mut self.text, &mut self.textures, self.size, @@ -112,6 +112,7 @@ impl Ui { if let Some(base) = &self.base { painter.draw(base); } + self.primitives.replace(); } pub fn update(&mut self, ctx: &mut Ctx) @@ -122,9 +123,26 @@ impl Ui { self.redraw_all(ctx); self.full_redraw = false; } else if !self.updates.is_empty() { - // TODO: partial updates - self.redraw_all(ctx); - self.updates.drain(..); + self.redraw_updates(ctx); + } + } + + fn redraw_updates(&mut self, ctx: &mut Ctx) + where + Ctx: 'static, + { + let mut painter = Painter::new( + &self.widgets, + &mut self.primitives, + ctx, + &self.sensor_map, + &mut self.active, + &mut self.text, + &mut self.textures, + self.size, + ); + for id in self.updates.drain(..) { + painter.redraw(&id.id); } } @@ -168,8 +186,8 @@ impl Widgets { } } - pub fn get_dyn(&self, id: &WidgetId) -> &dyn Widget { - self.map.get(&id.id).unwrap().as_ref() + pub fn get_dyn(&self, id: &Id) -> &dyn Widget { + self.map.get(id).unwrap().as_ref() } pub fn get>(&self, id: &WidgetId) -> Option<&W> { @@ -231,7 +249,7 @@ impl Default for Ui { textures: Textures::new(), text: TextData::default(), full_redraw: false, - active_sensors: Default::default(), + active: Default::default(), sensor_map: Default::default(), send, recv, @@ -239,3 +257,37 @@ impl Default for Ui { } } } + +pub struct WidgetInstance { + pub region: UiRegion, + pub primitives: Range, + pub children: Vec, + pub parent: Option, +} +pub type ActiveWidgets = HashMap; + +#[derive(Default)] +pub(super) struct Active { + pub sensors: ActiveSensors, + pub widgets: ActiveWidgets, +} + +impl Active { + pub fn clear(&mut self) { + self.sensors.clear(); + self.widgets.clear(); + } + + pub fn remove(&mut self, id: &Id) -> Option { + let instance = self.widgets.remove(id); + self.sensors.remove(id); + instance + } + + pub fn add(&mut self, id: &Id, instance: WidgetInstance, sensors_map: &SensorMap) { + if sensors_map.get(id).is_some() { + self.sensors.insert(id.duplicate(), instance.region); + } + self.widgets.insert(id.duplicate(), instance); + } +} diff --git a/src/lib.rs b/src/lib.rs index d5754c0..3c089ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,8 +2,8 @@ #![feature(const_ops)] #![feature(const_trait_impl)] #![feature(const_from)] -#![feature(trait_alias)] -#![feature(negative_impls)] +#![feature(map_try_insert)] +#![feature(new_range_api)] pub mod core; pub mod layout; @@ -15,6 +15,3 @@ pub mod prelude { pub use crate::layout::*; pub use crate::render::*; } - -pub type HashMap = std::collections::HashMap; -pub type HashSet = std::collections::HashSet; diff --git a/src/render/primitive.rs b/src/render/primitive.rs index 8d5dc6d..1ed0835 100644 --- a/src/render/primitive.rs +++ b/src/render/primitive.rs @@ -1,3 +1,8 @@ +use std::{ + marker::PhantomData, + ops::{Deref, DerefMut, Range}, +}; + use crate::{ layout::{Color, TextureHandle, UiRegion}, render::{ArrBuf, data::PrimitiveInstance}, @@ -7,33 +12,37 @@ use wgpu::*; pub struct Primitives { pub(super) instances: Vec, + pub(super) run: Vec, pub(super) data: PrimitiveData, // ensure drawn textures don't get freed pub(crate) drawn_textures: Vec, pub updated: bool, + run_start: usize, } impl Default for Primitives { fn default() -> Self { Self { instances: Default::default(), + run: Default::default(), data: Default::default(), drawn_textures: Default::default(), updated: true, + run_start: 0, } } } pub trait Primitive: Pod { const BINDING: u32; - fn vec(data: &mut PrimitiveData) -> &mut Vec; + fn vec(data: &mut PrimitiveData) -> &mut PrimitiveVec; } macro_rules! primitives { ($($name:ident: $ty:ty => $binding:expr,)*) => { #[derive(Default)] pub struct PrimitiveData { - $($name: Vec<$ty>,)* + $($name: PrimitiveVec<$ty>,)* } pub struct PrimitiveBuffers { @@ -68,6 +77,12 @@ macro_rules! primitives { 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!() + } + } } $( @@ -75,7 +90,7 @@ macro_rules! primitives { unsafe impl bytemuck::Zeroable for $ty {} impl Primitive for $ty { const BINDING: u32 = $binding; - fn vec(data: &mut PrimitiveData) -> &mut Vec { + fn vec(data: &mut PrimitiveData) -> &mut PrimitiveVec { &mut data.$name } } @@ -86,23 +101,72 @@ macro_rules! primitives { } impl Primitives { - pub fn write(&mut self, data: P, region: UiRegion) { + pub fn write(&mut self, data: P, region: UiRegion) -> PrimitiveHandle

{ let vec = P::vec(&mut self.data); - let i = vec.len() as u32; - self.instances.push(PrimitiveInstance { + let i = vec.add(data); + self.run.push(PrimitiveInstance { region, - idx: i, + idx: i as u32, binding: P::BINDING, }); - vec.push(data); + PrimitiveHandle::new(i) } pub(crate) fn clear(&mut self) { self.updated = true; self.instances.clear(); + self.run.clear(); self.data.clear(); self.drawn_textures.clear(); } + + pub fn set(&mut self, handle: &PrimitiveHandle

, data: P) { + P::vec(&mut self.data)[handle.idx] = data; + } + + pub fn prepare(&mut self, span: Range) { + self.run_start = span.start; + for instance in &self.instances[span] { + self.data.free(instance.binding, instance.idx as usize); + } + } + + pub fn apply(&mut self, span: Range) -> isize { + let delta = self.run.len() as isize - span.len() as isize; + self.instances.splice(span, self.run.drain(..)); + delta + } + + pub fn replace(&mut self) { + std::mem::swap(&mut self.run, &mut self.instances); + self.run.clear(); + } + + pub fn cur_pos(&self) -> usize { + self.run_start + self.run.len() + } +} + +/// primitives but only exposes set for redrawing +pub struct PrimitiveEditor<'a>(&'a mut Primitives); +impl PrimitiveEditor<'_> { + pub fn set(&mut self, handle: &PrimitiveHandle

, data: P) { + self.0.set(handle, data); + } +} + +pub struct PrimitiveHandle { + idx: usize, + _pd: PhantomData

, +} + +impl PrimitiveHandle

{ + fn new(idx: usize) -> Self { + Self { + idx, + _pd: PhantomData, + } + } } primitives!( @@ -125,3 +189,54 @@ pub struct TexturePrimitive { pub view_idx: u32, pub sampler_idx: u32, } + +pub struct PrimitiveVec { + vec: Vec, + free: Vec, +} + +impl PrimitiveVec { + 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 Default for PrimitiveVec { + fn default() -> Self { + Self::new() + } +} + +impl Deref for PrimitiveVec { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.vec + } +} + +impl DerefMut for PrimitiveVec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.vec + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs index 13decb4..5cf9d68 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -6,3 +6,5 @@ pub use id::*; pub use math::*; pub use refcount::*; +pub type HashMap = std::collections::HashMap; +pub type HashSet = std::collections::HashSet;