diff --git a/src/core/sense.rs b/src/core/sense.rs index c598b04..d1789f9 100644 --- a/src/core/sense.rs +++ b/src/core/sense.rs @@ -1,34 +1,57 @@ -use std::marker::PhantomData; +use crate::{ + Painter, Sense, SenseFn, SenseShape, SenseTrigger, Ui, Widget, WidgetFn, WidgetId, WidgetLike, +}; -use crate::{Painter, Widget, WidgetFn, WidgetId, WidgetLike}; - -pub struct Sensor, Ctx> { +pub struct Sensor { inner: WidgetId, - f: F, - _pd: PhantomData, + sense: Sense, + f: Box>, } -impl, Ctx: 'static> Widget for Sensor { +impl Widget for Sensor { fn draw(&self, painter: &mut Painter) { - (self.f)(painter.ctx_mut()); - painter.draw(&self.inner); + painter.sense( + SenseTrigger { + shape: painter.region, + sense: self.sense, + }, + self.f.box_clone(), + ); + painter.draw(self.inner.as_any()); } } -pub trait SenseFn = Fn(&mut Ctx) + 'static; - -pub trait Sensable { - fn sense>(self, f: F) -> impl WidgetFn, Ctx>; +pub trait SenseCtx: 'static { + fn active(&mut self, trigger: &SenseTrigger) -> bool; } -impl, Ctx: 'static, Tag> Sensable for W { - fn sense>(self, f: F) -> impl WidgetFn, Ctx> { - |ui| { - let inner = self.add(ui).erase_type(); +pub trait WidgetSenseFn, Ctx> = Fn(&WidgetId, &mut Ui, &mut Ctx) + 'static; + +pub trait Sensable, Ctx: 'static, Tag> { + // copied here so LSP can at least get the UI and id + fn sense, &mut Ui, &mut Ctx) + 'static + Clone>( + self, + sense: Sense, + f: F, + ) -> impl WidgetFn, Ctx>; +} + +impl, Ctx: SenseCtx, Tag> Sensable for W +where + W::Widget: Widget, +{ + fn sense + Clone>( + self, + sense: Sense, + f: F, + ) -> impl WidgetFn, Ctx> { + move |ui| { + let inner_arg = self.add(ui); + let inner = inner_arg.clone().erase_type(); Sensor { inner, - f, - _pd: PhantomData, + sense, + f: Box::new(move |ui, ctx| (f)(&inner_arg, ui, ctx)), } } } diff --git a/src/layout/painter.rs b/src/layout/painter.rs index 38bf6ab..e92e05f 100644 --- a/src/layout/painter.rs +++ b/src/layout/painter.rs @@ -1,20 +1,22 @@ use crate::{ - UiRegion, WidgetId, Widgets, + SenseFn, SenseTrigger, Sensors, UiRegion, WidgetId, Widgets, primitive::{PrimitiveData, PrimitiveInstance, Primitives}, }; pub struct Painter<'a, Ctx> { nodes: &'a Widgets, ctx: &'a mut Ctx, + sensors: &'a mut Sensors, primitives: Primitives, pub region: UiRegion, } impl<'a, Ctx> Painter<'a, Ctx> { - pub fn new(nodes: &'a Widgets, ctx: &'a mut Ctx) -> Self { + pub fn new(nodes: &'a Widgets, ctx: &'a mut Ctx, sensors: &'a mut Sensors) -> Self { Self { nodes, ctx, + sensors, primitives: Primitives::default(), region: UiRegion::full(), } @@ -31,17 +33,27 @@ impl<'a, Ctx> Painter<'a, Ctx> { .extend_from_slice(bytemuck::cast_slice::<_, u32>(&[data])); } - pub fn draw(&mut self, id: &WidgetId) where Ctx: 'static { + pub fn draw(&mut self, id: &WidgetId) + where + Ctx: 'static, + { self.nodes.get_dyn(id).draw(self); } - pub fn draw_within(&mut self, node: &WidgetId, region: UiRegion) where Ctx: 'static { + pub fn draw_within(&mut self, node: &WidgetId, region: UiRegion) + where + Ctx: 'static, + { let old = self.region; self.region.select(®ion); self.draw(node); self.region = old; } + pub fn sense(&mut self, trigger: SenseTrigger, f: Box>) { + self.sensors.push((trigger, f)); + } + pub fn finish(self) -> Primitives { self.primitives } diff --git a/src/layout/region.rs b/src/layout/region.rs index bc61169..bc0e275 100644 --- a/src/layout/region.rs +++ b/src/layout/region.rs @@ -149,6 +149,26 @@ impl UiRegion { self.bot_right.flip(); std::mem::swap(&mut self.top_left, &mut self.bot_right); } + + pub fn to_screen(&self, size: Vec2) -> ScreenRect { + ScreenRect { + top_left: self.top_left.anchor * size + self.top_left.offset, + bot_right: self.bot_right.anchor * size + self.bot_right.offset, + } + } +} + +pub struct ScreenRect { + top_left: Vec2, + bot_right: Vec2, +} +impl ScreenRect { + 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 struct UIRegionAxisView<'a> { diff --git a/src/layout/ui.rs b/src/layout/ui.rs index 1dc1a82..250e7d7 100644 --- a/src/layout/ui.rs +++ b/src/layout/ui.rs @@ -1,5 +1,5 @@ use crate::{ - HashMap, Painter, Widget, WidgetId, WidgetLike, + HashMap, Painter, SenseCtx, UiRegion, Widget, WidgetId, WidgetLike, primitive::Primitives, util::{IDTracker, Id}, }; @@ -13,6 +13,7 @@ pub struct Ui { base: Option, widgets: Widgets, updates: Vec, + sensors: Sensors, primitives: Primitives, full_redraw: bool, } @@ -20,6 +21,27 @@ pub struct Ui { #[derive(Default)] pub struct Widgets(HashMap>>); +#[derive(Clone, Copy)] +pub enum Sense { + Click, + Hover, +} +pub type Sensors = Vec<(SenseTrigger, Box>)>; +pub trait SenseFn_ = Fn(&mut Ui, &mut Ctx) + 'static; +pub type SenseShape = UiRegion; +pub struct SenseTrigger { + pub shape: SenseShape, + pub sense: Sense, +} +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, Tag>( &mut self, @@ -67,13 +89,27 @@ impl Ui { where Ctx: 'static, { - let mut painter = Painter::new(&self.widgets, ctx); + self.sensors.clear(); + let mut painter = Painter::new(&self.widgets, ctx, &mut self.sensors); if let Some(base) = &self.base { painter.draw(base); } self.primitives = painter.finish(); } + pub fn run_sensors(&mut self, ctx: &mut Ctx) + where + Ctx: SenseCtx, + { + for (t, f) in self.sensors.iter().rev() { + if ctx.active(t) { + let f = f.as_ref().box_clone(); + f(self, ctx); + return; + } + } + } + pub fn update(&mut self, ctx: &mut Ctx) -> Option<&Primitives> where Ctx: 'static, @@ -94,8 +130,6 @@ impl Ui { pub fn needs_redraw(&self) -> bool { self.full_redraw || !self.updates.is_empty() } - - pub fn set_mouse_pos(&mut self) {} } impl, Ctx> Index<&WidgetId> for Ui { @@ -157,7 +191,8 @@ impl Default for Ui { widgets: Widgets::new(), updates: Default::default(), primitives: Default::default(), - full_redraw: Default::default(), + full_redraw: false, + sensors: Default::default(), } } } diff --git a/src/layout/widget.rs b/src/layout/widget.rs index f8d3df4..81ccc90 100644 --- a/src/layout/widget.rs +++ b/src/layout/widget.rs @@ -16,6 +16,7 @@ pub struct AnyWidget; /// /// 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. +#[repr(C)] #[derive(Eq, Hash, PartialEq, Debug)] pub struct WidgetId { pub(super) ty: TypeId, @@ -38,10 +39,16 @@ impl WidgetId { _pd: PhantomData, } } + pub fn erase_type(self) -> WidgetId { self.cast_type() } + pub fn as_any(&self) -> &WidgetId { + // safety: self is repr(C) and generic only used for phantom data + unsafe { std::mem::transmute(self) } + } + fn cast_type(self) -> WidgetId { WidgetId { ty: self.ty, diff --git a/src/testing/app.rs b/src/testing/app.rs index 5d097d1..a738825 100644 --- a/src/testing/app.rs +++ b/src/testing/app.rs @@ -1,3 +1,4 @@ +use gui::Ui; use winit::{ application::ApplicationHandler, event::WindowEvent, @@ -9,7 +10,7 @@ use super::Client; #[derive(Default)] pub struct App { - client: Option, + client: Option<(Client, Ui)>, } impl App { @@ -25,12 +26,14 @@ impl ApplicationHandler for App { let window = event_loop .create_window(Window::default_attributes()) .unwrap(); - let client = Client::new(window.into()); - self.client = Some(client); + let (ui, ids) = Client::create_ui(); + let client = Client::new(window.into(), ids); + self.client = Some((client, ui)); } } fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { - self.client.as_mut().unwrap().event(event, event_loop); + let (client, ui) = self.client.as_mut().unwrap(); + client.event(event, event_loop, ui); } } diff --git a/src/testing/input.rs b/src/testing/input.rs new file mode 100644 index 0000000..da40e31 --- /dev/null +++ b/src/testing/input.rs @@ -0,0 +1,58 @@ +use gui::{SenseCtx, SenseTrigger, 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, +} + +impl Input { + pub fn event(&mut self, event: &WindowEvent) { + self.mouse_just_pressed = 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) + } + WindowEvent::MouseInput { state, button, .. } => match button { + winit::event::MouseButton::Left => { + if state.is_pressed() { + if !self.mouse_pressed { + self.mouse_just_pressed = true; + } else { + self.mouse_just_pressed = false; + } + self.mouse_pressed = true; + } else { + self.mouse_pressed = false; + } + } + _ => (), + }, + _ => (), + } + } + fn active(&mut self, trigger: &SenseTrigger) -> bool { + let region = trigger.shape.to_screen(self.size); + if !region.contains(self.mouse_pos) { + return false; + } + match trigger.sense { + gui::Sense::Click => self.mouse_just_pressed, + gui::Sense::Hover => true, + } + } +} + +impl SenseCtx for Client { + fn active(&mut self, trigger: &SenseTrigger) -> bool { + self.input.active(trigger) + } +} diff --git a/src/testing/mod.rs b/src/testing/mod.rs index a530518..759d832 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -5,39 +5,44 @@ use gui::*; use render::Renderer; use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window}; +use crate::testing::input::Input; + mod app; +mod input; mod render; pub fn main() { App::run(); } -struct Data { - x: u32, -} - pub struct Client { renderer: Renderer, - ui: Ui, + input: Input, + ui: UiIds, +} + +pub struct UiIds { test: WidgetId, } impl Client { - pub fn new(window: Arc) -> Self { - let renderer = Renderer::new(window); + pub fn create_ui() -> (Ui, UiIds) { + let mut ui = Ui::new(); + let test = ui.id(); let rect = Rect { color: UiColor::WHITE, radius: 20.0, thickness: 0.0, inner_radius: 0.0, }; - let mut ui = Ui::new(); - let test = ui.id(); ui.set_base( ( ( rect.color(UiColor::BLUE) - .sense(|d: &mut Data| println!("{}", d.x)), + .sense(Sense::Click, |id, ui, client| { + println!("hello!"); + ui[id].color.a -= 1; + }), ( rect.color(UiColor::RED).center((100.0, 100.0)), ( @@ -75,26 +80,33 @@ impl Client { .span(Dir::DOWN, [3, 1, 1]) .pad(10), ); - Self { renderer, ui, test } + (ui, UiIds { test }) } - pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) { + pub fn new(window: Arc, ui: UiIds) -> Self { + let renderer = Renderer::new(window); + Self { + renderer, + ui, + input: Input::default(), + } + } + + pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop, ui: &mut Ui) { + self.input.event(&event); + ui.run_sensors(self); match event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::RedrawRequested => { - let primitives = self.ui.update(&mut Data { x: 39 }); + let primitives = ui.update(self); self.renderer.update(primitives); 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(); - } - } _ => (), } + if ui.needs_redraw() { + self.renderer.window().request_redraw(); + } } }