diff --git a/:w b/:w new file mode 100644 index 0000000..50d518c --- /dev/null +++ b/:w @@ -0,0 +1,107 @@ +use crate::{HashMap, Ui, UiRegion, Vec2, WidgetId, util::Id}; + +pub trait SenseCtx: 'static { + fn cursor_state(&self) -> CursorState; + fn window_size(&self) -> Vec2; + fn active(&mut self, trigger: &SenseTrigger) -> bool; +} + +#[derive(Clone, Copy, PartialEq)] +pub enum Sense { + PressStart, + Pressing, + PressEnd, + HoverStart, + Hovering, + HoverEnd, + NotHovering, +} + +pub struct CursorState { + pos: Vec2, + exists: bool, + primary: ActivationState, +} + +#[derive(Debug, Clone, Copy, Default)] +pub enum ActivationState { + Start, + On, + End, + #[default] + Off, +} + +pub struct Sensor { + pub sense: Sense, + pub f: Box>, +} + +impl Clone for SensorGroup { + fn clone(&self) -> Self { + Self { + shape: self.shape.clone(), + hover: self.hover.clone(), + f: self.f.box_clone(), + } + } +} +pub type SensorMap = HashMap>>; +pub type ActiveSensors = Vec>; +pub trait SenseFn_ = FnMut(&mut Ui, &mut Ctx) + 'static; +pub type SenseShape = UiRegion; +#[derive(Clone)] +pub struct SenseTrigger { + pub shape: SenseShape, + pub sense: Sense, +} +pub struct SensorGroup { + pub shape: SenseShape, + pub hover: ActivationState, + pub sensors: Vec>, +} +pub trait SenseFn: SenseFn_ { + fn box_clone(&self) -> Box>; +} +impl + Clone, Ctx> SenseFn for F { + fn box_clone(&self) -> Box> { + Box::new(self.clone()) + } +} + +impl Ui { + pub fn add_sensor(&mut self, id: &WidgetId, f: Sensor) { + self.sensor_map + .entry(id.id.duplicate()) + .or_default() + .push(f); + } + + pub fn run_sensors(&mut self, ctx: &mut Ctx, cursor: &CursorState, window_size: Vec2) + where + Ctx: SenseCtx + 'static, + { + let mut groups = std::mem::take(&mut self.active_sensors_swap); + groups.clone_from(&self.active_sensors); + for (hover, sensors) in groups.drain(..).rev() { + for mut sensor in sensors { + if should_run(sensor.trigger, cursor, hover, window_size) { + (sensor.f)(self, ctx); + } + } + } + self.active_sensors_swap = groups; + } +} + +pub fn should_run( + trigger: SenseTrigger, + cursor: &CursorState, + hover: ActivationState, + window_size: Vec2, +) -> bool { + let region = trigger.shape.to_screen(window_size); + if !cursor.exists || !region.contains(cursor.pos) { + return trigger.sense == Sense::NotHovering; + } +} diff --git a/src/core/sense.rs b/src/core/sense.rs index 2d695d5..ff1c28f 100644 --- a/src/core/sense.rs +++ b/src/core/sense.rs @@ -1,28 +1,32 @@ -use crate::{Sense, SenseFn, SenseTrigger, Sensor, Ui, Widget, WidgetIdFn, WidgetLike}; - -pub trait SenseCtx: 'static { - fn active(&mut self, trigger: &SenseTrigger) -> bool; -} +use crate::{Sense, SenseFn, Sensor, Ui, Widget, WidgetId, WidgetIdFn, WidgetLike}; pub trait Sensable { - fn sense( + fn on( self, sense: Sense, // trait copied here bc rust analyzer skill issue - f: impl Fn(&mut Ui, &mut Ctx) + 'static + Clone, + f: impl FnMut(&mut Ui, &mut Ctx) + 'static + Clone, ) -> impl WidgetIdFn; - fn sense_and_edit( + fn id_on( self, sense: Sense, // trait copied here bc rust analyzer skill issue - f: impl Fn(&mut W, &mut Ctx) + 'static + Clone, + f: impl FnMut(&WidgetId, &mut Ui, &mut Ctx) + 'static + Clone, + ) -> impl WidgetIdFn + where + W: Widget; + fn edit_on( + self, + sense: Sense, + // trait copied here bc rust analyzer skill issue + f: impl FnMut(&mut W, &mut Ctx) + 'static + Clone, ) -> impl WidgetIdFn where W: Widget; } impl, Ctx, Tag> Sensable for W { - fn sense(self, sense: Sense, f: impl SenseFn + Clone) -> impl WidgetIdFn { + fn on(self, sense: Sense, f: impl SenseFn + Clone) -> impl WidgetIdFn { move |ui| { let id = self.add(ui); ui.add_sensor( @@ -35,18 +39,29 @@ impl, Ctx, Tag> Sensable for W { id } } - fn sense_and_edit( + fn id_on( self, sense: Sense, // trait copied here bc rust analyzer skill issue - f: impl Fn(&mut W::Widget, &mut Ctx) + 'static + Clone, + mut f: impl FnMut(&WidgetId, &mut Ui, &mut Ctx) + 'static + Clone, ) -> impl WidgetIdFn where W::Widget: Widget, { self.with_id(move |ui, id| { let id2 = id.clone(); - ui.add(id.sense(sense, move |ui, ctx| f(&mut ui[&id2], ctx))) + ui.add(id.on(sense, move |ui, ctx| f(&id2, ui, ctx))) }) } + fn edit_on( + self, + sense: Sense, + // trait copied here bc rust analyzer skill issue + mut f: impl FnMut(&mut W::Widget, &mut Ctx) + 'static + Clone, + ) -> impl WidgetIdFn + where + W::Widget: Widget, + { + self.id_on(sense, move |id, ui, ctx| f(&mut ui[id], ctx)) + } } diff --git a/src/layout/painter.rs b/src/layout/painter.rs index d8900fe..a60078b 100644 --- a/src/layout/painter.rs +++ b/src/layout/painter.rs @@ -1,5 +1,5 @@ use crate::{ - ActiveSensor, ActiveSensors, SenseTrigger, SensorMap, UiRegion, WidgetId, Widgets, + ActiveSensors, SensorMap, UiRegion, WidgetId, Widgets, primitive::{PrimitiveData, PrimitiveInstance, Primitives}, }; @@ -7,7 +7,7 @@ pub struct Painter<'a, Ctx: 'static> { nodes: &'a Widgets, ctx: &'a mut Ctx, sensors_map: &'a mut SensorMap, - active_sensors: &'a mut ActiveSensors, + active_sensors: &'a mut ActiveSensors, primitives: Primitives, pub region: UiRegion, } @@ -17,7 +17,7 @@ impl<'a, Ctx> Painter<'a, Ctx> { nodes: &'a Widgets, ctx: &'a mut Ctx, sensors_map: &'a mut SensorMap, - active_sensors: &'a mut ActiveSensors, + active_sensors: &'a mut ActiveSensors, ) -> Self { Self { nodes, @@ -44,19 +44,8 @@ impl<'a, Ctx> Painter<'a, Ctx> { where Ctx: 'static, { - if let Some(sensors) = self.sensors_map.get(&id.id) { - self.active_sensors.push( - sensors - .iter() - .map(|sensor| ActiveSensor { - trigger: SenseTrigger { - shape: self.region, - sense: sensor.sense, - }, - f: sensor.f.box_clone(), - }) - .collect(), - ); + if self.sensors_map.get(&id.id).is_some() { + self.active_sensors.push((self.region, id.id.duplicate())); } self.nodes.get_dyn(id).draw(self); } diff --git a/src/layout/region.rs b/src/layout/region.rs index 7d9ec56..dd6672c 100644 --- a/src/layout/region.rs +++ b/src/layout/region.rs @@ -174,9 +174,7 @@ impl UiRegion { } pub fn shifted(mut self, offset: impl Into) -> Self { - println!("before {:?}", self); self.shift(offset); - println!("after {:?}", self); self } diff --git a/src/layout/sense.rs b/src/layout/sense.rs index 2ac26ab..de0eee8 100644 --- a/src/layout/sense.rs +++ b/src/layout/sense.rs @@ -1,11 +1,29 @@ -use crate::{HashMap, Ui, UiRegion, util::Id}; +use crate::{HashMap, Ui, UiRegion, Vec2, WidgetId, util::Id}; #[derive(Clone, Copy, PartialEq)] pub enum Sense { - Press, - Held, - Hover, - NoHover, + PressStart, + Pressing, + PressEnd, + HoverStart, + Hovering, + HoverEnd, + NotHovering, +} + +pub struct CursorState { + pub pos: Vec2, + pub exists: bool, + pub pressed: bool, +} + +#[derive(Debug, Clone, Copy, Default, PartialEq)] +pub enum ActivationState { + Start, + On, + End, + #[default] + Off, } pub struct Sensor { @@ -13,20 +31,8 @@ pub struct Sensor { pub f: Box>, } -pub struct ActiveSensor { - pub trigger: SenseTrigger, - pub f: Box>, -} -impl Clone for ActiveSensor { - fn clone(&self) -> Self { - Self { - trigger: self.trigger.clone(), - f: self.f.box_clone(), - } - } -} -pub type SensorMap = HashMap>>; -pub type ActiveSensors = Vec>>; +pub type SensorMap = HashMap>; +pub type ActiveSensors = Vec<(SenseShape, Id)>; pub trait SenseFn_ = FnMut(&mut Ui, &mut Ctx) + 'static; pub type SenseShape = UiRegion; #[derive(Clone)] @@ -34,6 +40,11 @@ pub struct SenseTrigger { pub shape: SenseShape, pub sense: Sense, } +pub struct SensorGroup { + pub hover: ActivationState, + pub cursor: ActivationState, + pub sensors: Vec>, +} pub trait SenseFn: SenseFn_ { fn box_clone(&self) -> Box>; } @@ -42,3 +53,110 @@ impl + Clone, Ctx> SenseFn for F { Box::new(self.clone()) } } + +impl Ui { + pub fn add_sensor(&mut self, id: &WidgetId, f: Sensor) { + self.sensor_map + .entry(id.id.duplicate()) + .or_default() + .sensors + .push(f); + } + + pub fn run_sensors(&mut self, ctx: &mut Ctx, cursor: &CursorState, window_size: Vec2) + where + Ctx: 'static, + { + let mut 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() { + let group = &mut map.get_mut(id).unwrap(); + let region = shape.to_screen(window_size); + let in_shape = cursor.exists && region.contains(cursor.pos); + group.hover.update(in_shape); + group.cursor.update(cursor.pressed && in_shape); + + for sensor in &mut group.sensors { + if should_run(sensor.sense, group.cursor, group.hover) { + (sensor.f.box_clone())(self, ctx); + } + } + } + self.sensor_map = map; + self.active_sensors = active; + } +} + +pub fn should_run(sense: Sense, cursor: ActivationState, hover: ActivationState) -> bool { + match sense { + Sense::PressStart => cursor.is_start(), + Sense::Pressing => cursor.is_on(), + Sense::PressEnd => cursor.is_end(), + Sense::HoverStart => hover.is_start(), + Sense::Hovering => hover.is_on(), + Sense::HoverEnd => hover.is_end(), + Sense::NotHovering => hover.is_off(), + } +} + +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, + }, + } + } +} + +impl Default for SensorGroup { + fn default() -> Self { + Self { + hover: Default::default(), + cursor: Default::default(), + sensors: Default::default(), + } + } +} +impl Clone for SensorGroup { + fn clone(&self) -> Self { + Self { + hover: self.hover, + cursor: self.cursor, + sensors: self.sensors.clone(), + } + } +} +impl Clone for Sensor { + fn clone(&self) -> Self { + Self { + sense: self.sense, + f: self.f.box_clone(), + } + } +} diff --git a/src/layout/ui.rs b/src/layout/ui.rs index 0ff8b11..64f66cb 100644 --- a/src/layout/ui.rs +++ b/src/layout/ui.rs @@ -1,5 +1,5 @@ use crate::{ - ActiveSensors, HashMap, Painter, SenseCtx, Sensor, SensorMap, Widget, WidgetId, WidgetLike, + ActiveSensors, HashMap, Painter, SensorMap, Widget, WidgetId, WidgetLike, primitive::Primitives, util::{IDTracker, Id}, }; @@ -13,8 +13,8 @@ pub struct Ui { base: Option, widgets: Widgets, updates: Vec, - active_sensors: ActiveSensors, - sensor_map: SensorMap, + pub(super) active_sensors: ActiveSensors, + pub(super) sensor_map: SensorMap, primitives: Primitives, full_redraw: bool, } @@ -85,26 +85,6 @@ impl Ui { self.primitives = painter.finish(); } - pub fn add_sensor(&mut self, id: &WidgetId, f: Sensor) { - self.sensor_map - .entry(id.id.duplicate()) - .or_default() - .push(f); - } - - pub fn run_sensors(&mut self, ctx: &mut Ctx) - where - Ctx: SenseCtx + 'static, - { - for sensors in self.active_sensors.clone().iter().rev() { - for sensor in sensors { - if ctx.active(&sensor.trigger) { - (sensor.f.box_clone())(self, ctx); - } - } - } - } - pub fn update(&mut self, ctx: &mut Ctx) -> Option<&Primitives> where Ctx: 'static, diff --git a/src/testing/input.rs b/src/testing/input.rs index 5d80c04..0971d1f 100644 --- a/src/testing/input.rs +++ b/src/testing/input.rs @@ -1,61 +1,44 @@ -use gui::{Sense, SenseCtx, SenseTrigger, Vec2}; +use gui::{CursorState, Vec2}; use winit::event::WindowEvent; use crate::testing::Client; #[derive(Default)] pub struct Input { - size: Vec2, mouse_pos: Vec2, mouse_pressed: bool, - mouse_just_pressed: bool, - mouse_just_released: bool, - mouse_in: bool, + mouse_exists: bool, } impl Input { pub fn event(&mut self, event: &WindowEvent) { - self.mouse_just_pressed = false; - self.mouse_just_released = false; match event { - WindowEvent::Resized(size) => { - self.size = Vec2::new(size.width as f32, size.height as f32); - } WindowEvent::CursorMoved { position, .. } => { self.mouse_pos = Vec2::new(position.x as f32, position.y as f32); - self.mouse_in = true; + self.mouse_exists = true; } WindowEvent::MouseInput { state, button, .. } => match button { winit::event::MouseButton::Left => { - if state.is_pressed() { - self.mouse_just_pressed = !self.mouse_pressed; - self.mouse_pressed = true; - } else { - self.mouse_just_released = self.mouse_pressed; - self.mouse_pressed = false; - } + self.mouse_pressed = state.is_pressed(); } _ => (), }, _ => (), } } - fn active(&mut self, trigger: &SenseTrigger) -> bool { - let region = trigger.shape.to_screen(self.size); - if !self.mouse_in || !region.contains(self.mouse_pos) { - return trigger.sense == Sense::NoHover; - } - match trigger.sense { - Sense::Press => self.mouse_just_pressed, - Sense::Held => self.mouse_pressed, - Sense::Hover => true, - Sense::NoHover => false, - } - } } -impl SenseCtx for Client { - fn active(&mut self, trigger: &SenseTrigger) -> bool { - self.input.active(trigger) +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 { + CursorState { + pos: self.input.mouse_pos, + exists: self.input.mouse_exists, + pressed: self.input.mouse_pressed, + } } } diff --git a/src/testing/mod.rs b/src/testing/mod.rs index 8569a45..b331146 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -84,17 +84,18 @@ impl Client { let main = main.clone(); let to = to.clone().erase_type(); Rect::new(color) - .sense(Sense::Press, move |ui, _| { + .id_on(Sense::PressStart, move |id, ui, _| { ui[&main].inner = to.clone(); + ui[id].color = color.add_rgb(-0.2); }) - .sense_and_edit(Sense::Hover, move |r, _| { + .edit_on(Sense::HoverStart, move |r, _| { r.color = color.add_rgb(0.1); }) - .sense_and_edit(Sense::NoHover, move |r, _| { - r.color = color; + .edit_on(Sense::PressEnd, move |r, _| { + r.color = color.add_rgb(0.1); }) - .sense_and_edit(Sense::Held, move |r, _| { - r.color = color.add_rgb(-0.1); + .edit_on(Sense::HoverEnd, move |r, _| { + r.color = color; }) } @@ -135,7 +136,9 @@ impl Client { pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop, ui: &mut Ui) { self.input.event(&event); - ui.run_sensors(self); + let cursor_state = self.cursor_state(); + let window_size = self.window_size(); + ui.run_sensors(self, &cursor_state, window_size); match event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::RedrawRequested => { diff --git a/src/util/id.rs b/src/util/id.rs index cdb8b9f..e76bb65 100644 --- a/src/util/id.rs +++ b/src/util/id.rs @@ -13,10 +13,6 @@ pub struct IDTracker { } impl IDTracker { - pub fn new() -> Self { - Self::default() - } - #[allow(clippy::should_implement_trait)] pub fn next(&mut self) -> Id { if let Some(id) = self.free.pop() {