From 966b6a2ac299a5d8d2f887641fcc8c42c5fc6be4 Mon Sep 17 00:00:00 2001 From: shadowcat Date: Thu, 11 Dec 2025 16:23:14 -0500 Subject: [PATCH] proper widgetid + slot vec instead of map --- core/src/event.rs | 23 +- core/src/module.rs | 7 +- core/src/painter.rs | 82 +++---- core/src/render/primitive.rs | 9 +- core/src/ui.rs | 38 +--- core/src/util/mod.rs | 2 + core/src/util/slot.rs | 69 ++++++ core/src/widget/handle.rs | 22 +- core/src/widget/mod.rs | 10 + core/src/widget/widgets.rs | 78 ++++--- fm:w | 400 +++++++++++++++++++++++++++++++++++ src/widget/sense.rs | 18 +- 12 files changed, 606 insertions(+), 152 deletions(-) create mode 100644 core/src/util/slot.rs create mode 100644 fm:w diff --git a/core/src/event.rs b/core/src/event.rs index 95f7db5..5d9b3e8 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -1,7 +1,4 @@ -use crate::{ - Ui, UiModule, WidgetView, - util::{HashMap, Id}, -}; +use crate::{Ui, UiModule, WidgetId, WidgetView, util::HashMap}; use std::{hash::Hash, rc::Rc}; pub trait Event: Sized { @@ -41,23 +38,23 @@ impl Event for E { } pub trait EventModule: UiModule + Default { - fn register(&mut self, id: Id, event: E, f: impl EventFn); + fn register(&mut self, id: WidgetId, event: E, f: impl EventFn); fn run<'a>( &self, - id: &Id, + id: WidgetId, event: E, ) -> Option) + use<'a, Self, E, Ctx>>; } -type EventFnMap = HashMap>>>; +type EventFnMap = HashMap>>>; pub struct DefaultEventModule { map: HashMap::Data>>, } impl UiModule for DefaultEventModule { - fn on_remove(&mut self, id: &Id) { + fn on_remove(&mut self, id: WidgetId) { for map in self.map.values_mut() { - map.remove(id); + map.remove(&id); } } } @@ -66,7 +63,7 @@ pub trait HashableEvent: Event + Hash + Eq + 'static {} impl HashableEvent for E {} impl EventModule for DefaultEventModule { - fn register(&mut self, id: Id, event: E, f: impl EventFn::Data>) { + fn register(&mut self, id: WidgetId, event: E, f: impl EventFn::Data>) { self.map .entry(event) .or_default() @@ -77,11 +74,11 @@ impl EventModule for DefaultEventModule< fn run<'a>( &self, - id: &Id, + id: WidgetId, event: E, ) -> Option) + use<'a, E, Ctx>> { if let Some(map) = self.map.get(&event) - && let Some(fs) = map.get(id) + && let Some(fs) = map.get(&id) { let fs = fs.clone(); Some(move |ctx: EventCtx::Data>| { @@ -138,7 +135,7 @@ impl Ui { .data .modules .get_mut::>() - .run(&id.id(), event) + .run(id.id(), event) { f(EventCtx { ui: self, diff --git a/core/src/module.rs b/core/src/module.rs index fe8f7a8..41570b4 100644 --- a/core/src/module.rs +++ b/core/src/module.rs @@ -1,15 +1,12 @@ use std::any::{Any, TypeId}; -use crate::{ - ActiveData, - util::{HashMap, Id}, -}; +use crate::{ActiveData, WidgetId, util::HashMap}; #[allow(unused_variables)] pub trait UiModule: Any { fn on_draw(&mut self, inst: &ActiveData) {} fn on_undraw(&mut self, inst: &ActiveData) {} - fn on_remove(&mut self, id: &Id) {} + fn on_remove(&mut self, id: WidgetId) {} fn on_move(&mut self, inst: &ActiveData) {} } diff --git a/core/src/painter.rs b/core/src/painter.rs index 5a7709c..4645eea 100644 --- a/core/src/painter.rs +++ b/core/src/painter.rs @@ -1,8 +1,8 @@ use crate::{ Axis, Len, Modules, PrimitiveLayers, RenderedText, Size, TextAttrs, TextBuffer, TextData, - TextureHandle, Textures, UiRegion, UiVec2, WidgetHandle, Widgets, + TextureHandle, Textures, UiRegion, UiVec2, WidgetHandle, WidgetId, Widgets, render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst}, - util::{HashMap, HashSet, Id, TrackedArena, Vec2}, + util::{HashMap, HashSet, TrackedArena, Vec2}, }; /// makes your surfaces look pretty @@ -12,46 +12,46 @@ pub struct Painter<'a, 'c> { mask: MaskIdx, textures: Vec, primitives: Vec, - children: Vec, - children_width: HashMap, - children_height: HashMap, + children: Vec, + children_width: HashMap, + children_height: HashMap, pub layer: usize, - id: Id, + id: WidgetId, } /// context for a painter; lets you draw and redraw widgets struct PainterCtx<'a> { pub widgets: &'a Widgets, - pub active: &'a mut HashMap, + pub active: &'a mut HashMap, pub layers: &'a mut PrimitiveLayers, pub textures: &'a mut Textures, pub masks: &'a mut TrackedArena, pub text: &'a mut TextData, pub output_size: Vec2, pub modules: &'a mut Modules, - pub cache_width: HashMap, - pub cache_height: HashMap, - pub needs_redraw: &'a mut HashSet, - draw_started: HashSet, + pub cache_width: HashMap, + pub cache_height: HashMap, + pub needs_redraw: &'a mut HashSet, + draw_started: HashSet, } /// 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))>, + x: Option<(WidgetId, (UiVec2, Len))>, + y: Option<(WidgetId, (UiVec2, Len))>, } /// important non rendering data for retained drawing #[derive(Debug)] pub struct ActiveData { - pub id: Id, + pub id: WidgetId, pub region: UiRegion, - pub parent: Option, + pub parent: Option, pub textures: Vec, pub primitives: Vec, - pub children: Vec, + pub children: Vec, pub resize: ResizeRef, pub mask: MaskIdx, pub layer: usize, @@ -61,13 +61,13 @@ pub struct ActiveData { #[derive(Default)] pub struct PainterData { pub widgets: Widgets, - pub active: HashMap, + pub active: HashMap, pub layers: PrimitiveLayers, pub textures: Textures, pub text: TextData, pub output_size: Vec2, pub modules: Modules, - pub px_dependent: HashSet, + pub px_dependent: HashSet, pub masks: TrackedArena, } @@ -75,7 +75,7 @@ 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) { + pub fn redraw(&mut self, id: WidgetId) { self.needs_redraw.remove(&id); if self.draw_started.contains(&id) { return; @@ -168,11 +168,11 @@ impl<'a> PainterCtx<'a> { fn draw_inner( &mut self, layer: usize, - id: Id, + id: WidgetId, region: UiRegion, - parent: Option, + parent: Option, mask: MaskIdx, - old_children: Option>, + old_children: Option>, ) { // I have no idea if these checks work lol // the idea is u can't redraw stuff u already drew, @@ -272,7 +272,7 @@ impl<'a> PainterCtx<'a> { self.active.insert(id, instance); } - fn mov(&mut self, id: Id, from: UiRegion, to: UiRegion) { + fn mov(&mut self, id: WidgetId, from: UiRegion, to: UiRegion) { let active = self.active.get_mut(&id).unwrap(); for h in &active.primitives { let region = self.layers[h.layer].region_mut(h); @@ -291,7 +291,7 @@ impl<'a> PainterCtx<'a> { } /// NOTE: instance textures are cleared and self.textures freed - fn remove(&mut self, id: Id) -> Option { + fn remove(&mut self, id: WidgetId) -> Option { let mut inst = self.active.remove(&id); if let Some(inst) = &mut inst { for h in &inst.primitives { @@ -309,7 +309,7 @@ impl<'a> PainterCtx<'a> { inst } - fn remove_rec(&mut self, id: Id) -> Option { + fn remove_rec(&mut self, id: WidgetId) -> Option { let inst = self.remove(id); if let Some(inst) = &inst { for c in &inst.children { @@ -321,7 +321,7 @@ impl<'a> PainterCtx<'a> { } impl PainterData { - fn ctx<'a>(&'a mut self, needs_redraw: &'a mut HashSet) -> PainterCtx<'a> { + fn ctx<'a>(&'a mut self, needs_redraw: &'a mut HashSet) -> PainterCtx<'a> { PainterCtx { widgets: &self.widgets, active: &mut self.active, @@ -338,7 +338,7 @@ impl PainterData { } } - pub fn draw(&mut self, id: Id) { + pub fn draw(&mut self, id: WidgetId) { let mut need_redraw = HashSet::default(); let mut ctx = self.ctx(&mut need_redraw); ctx.draw_started.clear(); @@ -346,7 +346,7 @@ impl PainterData { ctx.draw_inner(0, id, UiRegion::FULL, None, MaskIdx::NONE, None); } - pub fn redraw(&mut self, ids: &mut HashSet) { + pub fn redraw(&mut self, ids: &mut HashSet) { let mut ctx = self.ctx(ids); while let Some(&id) = ctx.needs_redraw.iter().next() { ctx.redraw(id); @@ -475,10 +475,10 @@ impl<'a, 'c> Painter<'a, 'c> { } pub fn label(&self) -> &str { - &self.ctx.widgets.data(&self.id).unwrap().label + &self.ctx.widgets.data(self.id).unwrap().label } - pub fn id(&self) -> &Id { + pub fn id(&self) -> &WidgetId { &self.id } } @@ -486,28 +486,28 @@ impl<'a, 'c> Painter<'a, 'c> { pub struct SizeCtx<'a> { pub text: &'a mut TextData, pub textures: &'a mut Textures, - source: Id, + source: WidgetId, widgets: &'a Widgets, - cache_width: &'a mut HashMap, - cache_height: &'a mut HashMap, - checked_width: &'a mut HashMap, - checked_height: &'a mut HashMap, + cache_width: &'a mut HashMap, + cache_height: &'a mut HashMap, + checked_width: &'a mut HashMap, + checked_height: &'a mut HashMap, /// TODO: should this be pub? rn used for sized pub outer: UiVec2, output_size: Vec2, - id: Id, + id: WidgetId, } impl SizeCtx<'_> { - pub fn id(&self) -> &Id { + pub fn id(&self) -> &WidgetId { &self.id } - pub fn source(&self) -> &Id { + pub fn source(&self) -> &WidgetId { &self.source } - fn width_inner(&mut self, id: Id) -> Len { + fn width_inner(&mut self, id: WidgetId) -> 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 @@ -532,7 +532,7 @@ impl SizeCtx<'_> { } // TODO: should be refactored to share code w width_inner - fn height_inner(&mut self, id: Id) -> Len { + fn height_inner(&mut self, id: WidgetId) -> Len { // if let Some(&(outer, len)) = self.cache_height.get(&id) // && outer == self.outer // { @@ -584,7 +584,7 @@ impl SizeCtx<'_> { self.text.draw(buffer, attrs, self.textures) } - pub fn label(&self, id: &Id) -> &String { + pub fn label(&self, id: WidgetId) -> &String { self.widgets.label(id) } } diff --git a/core/src/render/primitive.rs b/core/src/render/primitive.rs index a38e6a5..f8f1069 100644 --- a/core/src/render/primitive.rs +++ b/core/src/render/primitive.rs @@ -1,19 +1,18 @@ use std::ops::{Deref, DerefMut}; use crate::{ - Color, UiRegion, + Color, UiRegion, WidgetId, render::{ ArrBuf, data::{MaskIdx, PrimitiveInstance}, }, - util::Id, }; use bytemuck::Pod; use wgpu::*; pub struct Primitives { instances: Vec, - assoc: Vec, + assoc: Vec, data: PrimitiveData, free: Vec, pub updated: bool, @@ -99,7 +98,7 @@ macro_rules! primitives { } pub struct PrimitiveInst

{ - pub id: Id, + pub id: WidgetId, pub primitive: P, pub region: UiRegion, pub mask_idx: MaskIdx, @@ -175,7 +174,7 @@ impl Primitives { } pub struct PrimitiveChange { - pub id: Id, + pub id: WidgetId, pub old: usize, pub new: usize, } diff --git a/core/src/ui.rs b/core/src/ui.rs index f9e1db7..3c3ee43 100644 --- a/core/src/ui.rs +++ b/core/src/ui.rs @@ -1,11 +1,9 @@ use crate::{ ActiveData, Event, EventFn, EventModule, IdLike, PainterData, PixelRegion, TextureHandle, - Widget, WidgetHandle, WidgetLike, - util::{Id, Vec2}, + Widget, WidgetHandle, WidgetId, WidgetLike, util::Vec2, }; use image::DynamicImage; use std::{ - any::Any, ops::{Index, IndexMut}, sync::mpsc::{Receiver, Sender, channel}, }; @@ -14,8 +12,8 @@ pub struct Ui { // TODO: make this at least pub(super) pub data: PainterData, root: Option, - recv: Receiver, - pub(super) send: Sender, + recv: Receiver, + pub(super) send: Sender, full_redraw: bool, resized: bool, } @@ -27,17 +25,15 @@ impl Ui { /// useful for debugging pub fn set_label(&mut self, id: &WidgetHandle, label: String) { - self.data.widgets.data_mut(&id.id()).unwrap().label = label; + self.data.widgets.data_mut(id.id()).unwrap().label = label; } pub fn label(&self, id: &WidgetHandle) -> &String { - &self.data.widgets.data(&id.id()).unwrap().label + &self.data.widgets.data(id.id()).unwrap().label } pub fn add_widget(&mut self, w: W) -> WidgetHandle { - let id = self.new_id(); - self.data.widgets.insert(id.id(), w); - id + WidgetHandle::new(self.data.widgets.add(w), self.send.clone()) } pub fn set_root(&mut self, w: impl WidgetLike) { @@ -63,10 +59,6 @@ impl Ui { self.data.widgets.get_mut(id) } - fn new_id(&mut self) -> WidgetHandle { - WidgetHandle::new(self.data.widgets.reserve(), self.send.clone()) - } - pub fn add_texture(&mut self, image: DynamicImage) -> TextureHandle { self.data.textures.add(image) } @@ -125,7 +117,7 @@ impl Ui { fn free(&mut self) { for id in self.recv.try_iter() { for m in self.data.modules.iter_mut() { - m.on_remove(&id); + m.on_remove(id); } self.data.widgets.delete(id); } @@ -162,9 +154,9 @@ impl Ui { } pub fn debug(&self, label: &str) -> impl Iterator { - self.data.active.iter().filter_map(move |(id, inst)| { - let l = &self.data.widgets.label(id); - if *l == label { Some(inst) } else { None } + self.data.active.iter().filter_map(move |(&id, inst)| { + let l = self.data.widgets.label(id); + if l == label { Some(inst) } else { None } }) } } @@ -189,16 +181,6 @@ where } } -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(); diff --git a/core/src/util/mod.rs b/core/src/util/mod.rs index f2220c3..014c79c 100644 --- a/core/src/util/mod.rs +++ b/core/src/util/mod.rs @@ -1,6 +1,7 @@ mod arena; mod borrow; mod change; +mod slot; mod id; mod math; mod refcount; @@ -9,6 +10,7 @@ mod vec2; pub use arena::*; pub use borrow::*; pub use change::*; +pub use slot::*; pub use id::*; pub use math::*; pub use refcount::*; diff --git a/core/src/util/slot.rs b/core/src/util/slot.rs new file mode 100644 index 0000000..94fd2a1 --- /dev/null +++ b/core/src/util/slot.rs @@ -0,0 +1,69 @@ +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct SlotId { + idx: u32, + genr: u32, +} + +pub struct SlotVec { + data: Vec<(u32, Option)>, + free: Vec, +} + +impl SlotVec { + pub fn new() -> Self { + Self { + data: Default::default(), + free: Default::default(), + } + } + + pub fn add(&mut self, x: T) -> SlotId { + if let Some(idx) = self.free.pop() { + let (genr, data) = &mut self.data[idx as usize]; + *data = Some(x); + SlotId { idx, genr: *genr } + } else { + let idx = self.data.len() as u32; + let genr = 0; + self.data.push((genr, Some(x))); + SlotId { idx, genr } + } + } + + pub fn free(&mut self, id: SlotId) { + let (genr, data) = &mut self.data[id.idx as usize]; + *genr += 1; + *data = None; + self.free.push(id.idx); + } + + pub fn get(&self, id: SlotId) -> Option<&T> { + let slot = &self.data[id.idx as usize]; + if slot.0 != id.genr { + return None; + } + slot.1.as_ref() + } + + pub fn get_mut(&mut self, id: SlotId) -> Option<&mut T> { + let slot = &mut self.data[id.idx as usize]; + if slot.0 != id.genr { + return None; + } + slot.1.as_mut() + } + + pub fn len(&self) -> usize { + self.data.len() - self.free.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl Default for SlotVec { + fn default() -> Self { + Self::new() + } +} diff --git a/core/src/widget/handle.rs b/core/src/widget/handle.rs index 832ffff..535d291 100644 --- a/core/src/widget/handle.rs +++ b/core/src/widget/handle.rs @@ -2,9 +2,11 @@ use std::{marker::Unsize, mem::MaybeUninit, ops::CoerceUnsized, sync::mpsc::Send use crate::{ Ui, Widget, - util::{Id, RefCounter}, + util::{RefCounter, SlotId}, }; +pub type WidgetId = SlotId; + /// 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. @@ -14,14 +16,14 @@ use crate::{ /// /// TODO: ergonomic clones when they get put in rust-analyzer & don't cause ICEs? pub struct WidgetHandle { - pub(super) id: Id, + pub(super) id: WidgetId, counter: RefCounter, - send: Sender, + send: Sender, ty: *const W, } pub struct WidgetView { - pub(super) id: Id, + pub(super) id: WidgetId, #[allow(unused)] ty: *const W, } @@ -35,7 +37,7 @@ impl> WidgetHandle { } impl WidgetHandle { - pub(crate) fn new(id: Id, send: Sender) -> Self { + pub(crate) fn new(id: WidgetId, send: Sender) -> Self { Self { id, counter: RefCounter::new(), @@ -49,7 +51,7 @@ impl WidgetHandle { unsafe { std::mem::transmute(self) } } - pub fn id(&self) -> Id { + pub fn id(&self) -> WidgetId { self.id } @@ -69,7 +71,7 @@ impl WidgetHandle { } impl WidgetView { - pub fn id(&self) -> Id { + pub fn id(&self) -> WidgetId { self.id } } @@ -87,19 +89,19 @@ impl WidgetHandle> WidgetIdFn for F {} pub trait IdLike { type Widget: Widget + ?Sized + 'static; - fn id(&self) -> Id; + fn id(&self) -> WidgetId; } impl IdLike for WidgetHandle { type Widget = W; - fn id(&self) -> Id { + fn id(&self) -> WidgetId { self.id } } impl IdLike for WidgetView { type Widget = W; - fn id(&self) -> Id { + fn id(&self) -> WidgetId { self.id } } diff --git a/core/src/widget/mod.rs b/core/src/widget/mod.rs index 0fcb03b..7b4daf1 100644 --- a/core/src/widget/mod.rs +++ b/core/src/widget/mod.rs @@ -27,6 +27,16 @@ impl Widget for () { } } +impl dyn Widget { + pub fn as_any(&self) -> &dyn Any { + self + } + + pub fn as_any_mut(&mut self) -> &mut dyn Any { + 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 diff --git a/core/src/widget/widgets.rs b/core/src/widget/widgets.rs index f8fae2c..55dfb03 100644 --- a/core/src/widget/widgets.rs +++ b/core/src/widget/widgets.rs @@ -1,13 +1,12 @@ use crate::{ - IdLike, Widget, - util::{DynBorrower, HashMap, HashSet, Id, IdTracker}, + IdLike, Widget, WidgetId, + util::{DynBorrower, HashSet, SlotVec}, }; #[derive(Default)] pub struct Widgets { - pub updates: HashSet, - ids: IdTracker, - map: HashMap, + pub updates: HashSet, + vec: SlotVec, } pub struct WidgetData { @@ -22,23 +21,23 @@ impl Widgets { !self.updates.is_empty() } - pub fn get_dyn(&self, id: Id) -> Option<&dyn Widget> { - Some(self.map.get(&id)?.widget.as_ref()) + pub fn get_dyn(&self, id: WidgetId) -> Option<&dyn Widget> { + Some(self.vec.get(id)?.widget.as_ref()) } - pub fn get_dyn_mut(&mut self, id: Id) -> Option<&mut dyn Widget> { + pub fn get_dyn_mut(&mut self, id: WidgetId) -> Option<&mut dyn Widget> { self.updates.insert(id); - Some(self.map.get_mut(&id)?.widget.as_mut()) + Some(self.vec.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<'_> { + pub fn get_dyn_dynamic(&self, id: WidgetId) -> 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()) + std::mem::transmute::<&WidgetData, &mut WidgetData>(self.vec.get(id).unwrap()) }; if data.borrowed { panic!("tried to mutably borrow the same widget twice"); @@ -46,60 +45,57 @@ impl Widgets { WidgetWrapper::new(data.widget.as_mut(), &mut data.borrowed) } - pub fn get(&self, id: &I) -> Option<&I::Widget> where I::Widget: Sized { + pub fn get(&self, id: &I) -> Option<&I::Widget> + where + I::Widget: Sized, + { self.get_dyn(id.id())?.as_any().downcast_ref() } - pub fn get_mut(&mut self, id: &I) -> Option<&mut I::Widget> where I::Widget: Sized { + pub fn get_mut(&mut self, id: &I) -> Option<&mut I::Widget> + where + I::Widget: Sized, + { self.get_dyn_mut(id.id())?.as_any_mut().downcast_mut() } - pub fn insert(&mut self, id: Id, widget: W) { + pub fn add(&mut self, widget: W) -> WidgetId { let mut label = std::any::type_name::().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); + self.insert_any(Box::new(widget), label) } - pub fn data(&self, id: &Id) -> Option<&WidgetData> { - self.map.get(id) + pub fn data(&self, id: WidgetId) -> Option<&WidgetData> { + self.vec.get(id) } - pub fn label(&self, id: &Id) -> &String { + pub fn label(&self, id: WidgetId) -> &String { &self.data(id).unwrap().label } - pub fn data_mut(&mut self, id: &Id) -> Option<&mut WidgetData> { - self.map.get_mut(id) + pub fn data_mut(&mut self, id: WidgetId) -> Option<&mut WidgetData> { + self.vec.get_mut(id) } - pub fn insert_any(&mut self, id: Id, widget: Box, label: String) { - self.map.insert( - id, - WidgetData { - widget, - label, - borrowed: false, - }, - ); + pub fn insert_any(&mut self, widget: Box, label: String) -> WidgetId { + self.vec.add(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 delete(&mut self, id: WidgetId) { + self.vec.free(id); + // not sure if there's any point in this + // self.updates.remove(&id); } + #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize { - self.map.len() - } - - pub fn is_empty(&self) -> bool { - self.map.is_empty() + self.vec.len() } } diff --git a/fm:w b/fm:w new file mode 100644 index 0000000..8292549 --- /dev/null +++ b/fm:w @@ -0,0 +1,400 @@ +use crate::prelude::*; +use iris_core::util::{HashMap}; +use std::{ + ops::{BitOr, Deref, DerefMut}, + rc::Rc, +}; + + + +#[derive(Clone, Copy, PartialEq)] +pub enum Button { + Left, + Right, + Middle, +} + +#[derive(Clone, Copy, PartialEq)] +pub enum CursorSense { + PressStart(Button), + Pressing(Button), + PressEnd(Button), + HoverStart, + Hovering, + HoverEnd, + Scroll, +} + +pub struct CursorSenses(Vec); + +impl CursorSense { + pub fn click() -> Self { + Self::PressStart(Button::Left) + } + pub fn click_or_drag() -> CursorSenses { + Self::click() | Self::Pressing(Button::Left) + } + pub fn unclick() -> Self { + Self::PressEnd(Button::Left) + } + pub fn is_dragging(&self) -> bool { + matches!(self, CursorSense::Pressing(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 { + pub senses: CursorSenses, + pub f: Rc>, +} + +pub type SensorMap = HashMap>; +pub type SenseShape = UiRegion; +pub struct SensorGroup { + pub hover: ActivationState, + pub sensors: Vec>, +} + +#[derive(Clone)] +pub struct CursorData { + pub cursor: Vec2, + pub size: Vec2, + pub scroll_delta: Vec2, + /// the (first) sense that triggered this event + /// the senses are checked in order + pub sense: CursorSense, +} + +pub struct CursorModule { + map: SensorMap, + active: HashMap>, +} + +impl UiModule for CursorModule { + fn on_draw(&mut self, inst: &ActiveData) { + 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: &ActiveData) { + 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: &ActiveData) { + if let Some(map) = self.active.get_mut(&inst.layer) + && let Some(region) = map.get_mut(&inst.id) + { + *region = inst.region; + } + } +} + +impl CursorModule { + 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 SensorUi { + fn run_sensors(&mut self, ctx: &mut Ctx, cursor: &CursorState, window_size: Vec2); +} + +impl SensorUi for Ui { + fn run_sensors( + &mut self, + ctx: &mut Ctx, + cursor: &CursorState, + window_size: Vec2, + ) { + CursorModule::::run(self, ctx, cursor, window_size); + } +} + +impl CursorModule { + pub fn run(ui: &mut Ui, ctx: &mut Ctx, cursor: &CursorState, window_size: Vec2) { + let layers = std::mem::take(&mut ui.data.layers); + let mut module = std::mem::take(ui.data.modules.get_mut::()); + + 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 let Some(sense) = 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, + sense, + }; + (sensor.f)(EventCtx { + ui, + state: ctx, + data, + }); + } + } + } + if sensed { + break; + } + } + + let ui_mod = ui.data.modules.get_mut::(); + std::mem::swap(ui_mod, &mut module); + ui_mod.merge(module); + ui.data.layers = layers; + } +} + +pub fn should_run( + senses: &CursorSenses, + cursor: &CursorState, + hover: ActivationState, +) -> Option { + 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 Some(*sense); + } + } + None +} + +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 = CursorModule; + type Data = CursorData; +} + +impl Event for CursorSense { + type Module = CursorModule; + type Data = CursorData; +} + +impl::Data> + Into, Ctx: 'static> + EventModule for CursorModule +{ + fn register(&mut self, id: Id, senses: E, f: impl EventFn::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::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: EventCtx| { + for f in &fs { + f(EventCtx { + state: ctx.state, + ui: ctx.ui, + data: ctx.data.clone(), + }); + } + }) + } else { + None + } + } +} + +impl Default for SensorGroup { + fn default() -> Self { + Self { + hover: Default::default(), + sensors: Default::default(), + } + } +} + +impl Deref for CursorSenses { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for CursorSenses { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From 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 for CursorSenses { + type Output = Self; + + fn bitor(mut self, rhs: CursorSense) -> Self::Output { + self.0.push(rhs); + self + } +} + +impl Default for CursorModule { + fn default() -> Self { + Self { + map: Default::default(), + active: Default::default(), + } + } +} diff --git a/src/widget/sense.rs b/src/widget/sense.rs index ee2d8af..f39f64f 100644 --- a/src/widget/sense.rs +++ b/src/widget/sense.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use iris_core::util::{HashMap, Id}; +use iris_core::util::HashMap; use std::{ ops::{BitOr, Deref, DerefMut}, rc::Rc, @@ -97,7 +97,7 @@ pub struct Sensor { pub f: Rc>, } -pub type SensorMap = HashMap>; +pub type SensorMap = HashMap>; pub type SenseShape = UiRegion; pub struct SensorGroup { pub hover: ActivationState, @@ -116,7 +116,7 @@ pub struct CursorData { pub struct CursorModule { map: SensorMap, - active: HashMap>, + active: HashMap>, } impl UiModule for CursorModule { @@ -135,10 +135,10 @@ impl UiModule for CursorModule { } } - fn on_remove(&mut self, id: &Id) { - self.map.remove(id); + fn on_remove(&mut self, id: WidgetId) { + self.map.remove(&id); for layer in self.active.values_mut() { - layer.remove(id); + layer.remove(&id); } } @@ -301,7 +301,7 @@ impl Event for CursorSense { impl::Data> + Into, Ctx: 'static> EventModule for CursorModule { - fn register(&mut self, id: Id, senses: E, f: impl EventFn::Data>) { + fn register(&mut self, id: WidgetId, senses: E, f: impl EventFn::Data>) { // TODO: does not add to active if currently active self.map.entry(id).or_default().sensors.push(Sensor { senses: senses.into(), @@ -311,11 +311,11 @@ impl::Data> + Into, Ctx: ' fn run<'a>( &self, - id: &Id, + id: WidgetId, event: E, ) -> Option::Data>) + use<'a, E, Ctx>> { let senses = event.into(); - if let Some(group) = self.map.get(id) { + if let Some(group) = self.map.get(&id) { let fs: Vec<_> = group .sensors .iter()