diff --git a/TODO b/TODO index acb681a..0870f6d 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,6 @@ images settings (sampler) -abstract sensors to work with any event, maybe associate data as well? +consider unsafe cell really weird limitation: I don't think you can currently remove an element from a parent and put it in a child of the same parent diff --git a/src/core/event.rs b/src/core/event.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/core/sense.rs b/src/core/sense.rs index 9fa0289..455b42f 100644 --- a/src/core/sense.rs +++ b/src/core/sense.rs @@ -1,6 +1,9 @@ use crate::prelude::*; -use std::ops::{BitOr, Deref, DerefMut}; +use std::{ + ops::{BitOr, Deref, DerefMut}, + rc::Rc, +}; use crate::{ layout::{UiModule, UiRegion, Vec2}, @@ -11,12 +14,14 @@ pub trait CursorCtx { fn cursor_state(&self) -> &CursorState; } +#[derive(PartialEq)] pub enum Button { Left, Right, Middle, } +#[derive(PartialEq)] pub enum Sense { PressStart(Button), Pressing(Button), @@ -78,7 +83,7 @@ pub enum ActivationState { pub struct Sensor { pub senses: Senses, - pub f: Box>, + pub f: Rc>, } pub type SensorMap = HashMap>; @@ -88,13 +93,14 @@ pub struct SensorGroup { pub sensors: Vec>, } +#[derive(Clone)] pub struct SenseData { pub cursor: Vec2, pub size: Vec2, } -pub trait SenseFn: FnMut(&mut Ctx, SenseData) + 'static {} -impl SenseFn for F {} +pub trait SenseFn: Fn(&mut Ctx, SenseData) + 'static {} +impl SenseFn for F {} pub struct SensorModule { map: SensorMap, @@ -242,24 +248,48 @@ impl Event for Senses { type Data = SenseData; } -impl EventModule for SensorModule { - fn register(&mut self, id: Id, senses: Senses, f: impl EventFn) { - // TODO: does not add to active if currently active - self.map.entry(id).or_default().sensors.push(Sensor { - senses, - f: Box::new(f), - }); - } -} - impl Event for Sense { type Module = SensorModule; type Data = SenseData; } -impl EventModule for SensorModule { - fn register(&mut self, id: Id, sense: Sense, f: impl EventFn) { - self.register(id, Senses::from(sense), f); +impl>::Data> + Into, Ctx: 'static> + EventModule for SensorModule +{ + 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>( + &mut self, + id: &Id, + event: E, + ) -> Option> { + let senses = event.into(); + if let Some(group) = self.map.get_mut(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: &mut Ctx, data: SenseData| { + for f in &fs { + f(ctx, data.clone()); + } + }) + } else { + None + } } } diff --git a/src/core/text_edit.rs b/src/core/text_edit.rs index ad59e3d..8e06e44 100644 --- a/src/core/text_edit.rs +++ b/src/core/text_edit.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use cosmic_text::{Attrs, Cursor, Family, FontSystem, Metrics, Motion, Shaping}; +use cosmic_text::{Affinity, Attrs, Cursor, Family, FontSystem, Metrics, Motion, Shaping}; use unicode_segmentation::UnicodeSegmentation; use winit::{ event::KeyEvent, @@ -133,6 +133,11 @@ impl<'a> TextEditCtx<'a> { self.text .buf .set_text(self.font_system, "", &Attrs::new(), Shaping::Advanced); + if let Some(cursor) = &mut self.text.cursor { + cursor.line = 0; + cursor.index = 0; + cursor.affinity = Affinity::default(); + } text } @@ -219,13 +224,19 @@ impl<'a> TextEditCtx<'a> { self.text.cursor = None; } - pub fn apply_event(&mut self, event: &KeyEvent) -> TextInputResult { + pub fn apply_event(&mut self, event: &KeyEvent, modifiers: &Modifiers) -> TextInputResult { match &event.logical_key { Key::Named(named) => match named { NamedKey::Backspace => self.backspace(), NamedKey::Delete => self.delete(), NamedKey::Space => self.insert(" "), - NamedKey::Enter => self.insert("\n"), + NamedKey::Enter => { + if modifiers.shift { + self.newline(); + } else { + return TextInputResult::Submit; + } + } NamedKey::ArrowRight => self.motion(Motion::Right), NamedKey::ArrowLeft => self.motion(Motion::Left), NamedKey::ArrowUp => self.motion(Motion::Up), @@ -243,10 +254,24 @@ impl<'a> TextEditCtx<'a> { } } +#[derive(Default)] +pub struct Modifiers { + pub shift: bool, + pub control: bool, +} + +impl Modifiers { + pub fn clear(&mut self) { + self.shift = false; + self.control = false; + } +} + pub enum TextInputResult { Used, Unused, Unfocus, + Submit, } impl TextInputResult { diff --git a/src/layout/event.rs b/src/layout/event.rs index 706c85b..ad043f7 100644 --- a/src/layout/event.rs +++ b/src/layout/event.rs @@ -1,6 +1,8 @@ +use std::{hash::Hash, rc::Rc}; + use crate::{ layout::{IdFnTag, Ui, UiModule, Widget, WidgetId, WidgetIdFn, WidgetLike}, - util::Id, + util::{HashMap, Id}, }; pub trait UiCtx { @@ -14,16 +16,12 @@ impl UiCtx for Ui { } pub trait Event: Sized { - type Module: UiModule + EventModule + Default; - type Data; + type Module: EventModule; + type Data: Clone; } -pub trait EventModule> { - fn register(&mut self, id: Id, event: E, f: impl EventFn); -} - -pub trait EventFn: FnMut(&mut Ctx, Data) + 'static {} -impl EventFn for F {} +pub trait EventFn: Fn(&mut Ctx, Data) + 'static {} +impl EventFn for F {} pub trait Eventable { fn on, Ctx>( @@ -35,7 +33,7 @@ pub trait Eventable { fn id_on, Ctx>( self, event: E, - f: impl FnMut(&WidgetId, &mut Ctx, E::Data) + 'static, + f: impl Fn(&WidgetId, &mut Ctx, E::Data) + 'static, ) -> impl WidgetIdFn + Eventable where W: Widget; @@ -43,7 +41,7 @@ pub trait Eventable { fn edit_on>( self, event: E, - f: impl FnMut(&mut W, E::Data) + 'static, + f: impl Fn(&mut W, E::Data) + 'static, ) -> impl WidgetIdFn + Eventable where W: Widget; @@ -67,7 +65,7 @@ impl, Tag> Eventable for W { fn id_on, Ctx>( self, event: E, - mut f: impl FnMut(&WidgetId, &mut Ctx, E::Data) + 'static, + mut f: impl Fn(&WidgetId, &mut Ctx, E::Data) + 'static, ) -> impl WidgetIdFn where W::Widget: Widget, @@ -81,7 +79,7 @@ impl, Tag> Eventable for W { fn edit_on>( self, event: E, - mut f: impl FnMut(&mut W::Widget, E::Data) + 'static, + mut f: impl Fn(&mut W::Widget, E::Data) + 'static, ) -> impl WidgetIdFn where W::Widget: Widget, @@ -89,3 +87,116 @@ impl, Tag> Eventable for W { self.id_on(event, move |id, ui, pos| f(&mut ui[id], pos)) } } + +pub trait DefaultEvent: Hash + Eq + 'static { + type Data: Clone; +} + +impl Event for E { + type Module = DefaultEventModule; + type Data = E::Data; +} + +pub trait EventModule, Ctx>: UiModule + Default { + fn register(&mut self, id: Id, event: E, f: impl EventFn); + fn run<'a>( + &mut self, + id: &Id, + event: E, + ) -> Option>; +} + +type EventFnMap = HashMap>>>; +pub struct DefaultEventModule, Ctx> { + map: HashMap>::Data>>, +} + +impl + 'static, Ctx: 'static> UiModule for DefaultEventModule { + fn on_remove(&mut self, id: &Id) { + for map in self.map.values_mut() { + map.remove(id); + } + } +} + +pub trait HashableEvent: Event + Hash + Eq + 'static {} +impl + Hash + Eq + 'static, Ctx> HashableEvent for E {} + +impl, Ctx: 'static> EventModule for DefaultEventModule { + fn register(&mut self, id: Id, event: E, f: impl EventFn>::Data>) { + self.map + .entry(event) + .or_default() + .entry(id) + .or_default() + .push(Rc::new(f)); + } + + fn run<'a>( + &mut self, + id: &Id, + event: E, + ) -> Option> { + if let Some(map) = self.map.get_mut(&event) + && let Some(fs) = map.get(id) + { + let fs = fs.clone(); + Some(move |ctx: &mut Ctx, data: E::Data| { + for f in &fs { + f(ctx, data.clone()) + } + }) + } else { + None + } + } +} + +impl, Ctx: 'static> DefaultEventModule { + pub fn run_all(&mut self, ctx: &mut Ctx, event: E, data: E::Data) + where + E::Data: Clone, + { + if let Some(map) = self.map.get_mut(&event) { + for fs in map.values_mut() { + for f in fs { + f(ctx, data.clone()) + } + } + } + } + + pub fn run(&mut self, ctx: &mut Ctx, id: WidgetId, event: E, data: E::Data) + where + E::Data: Clone, + { + if let Some(map) = self.map.get_mut(&event) + && let Some(fs) = map.get_mut(&id.id) + { + for f in fs { + f(ctx, data.clone()) + } + } + } +} + +impl + 'static, Ctx: 'static> Default for DefaultEventModule { + fn default() -> Self { + Self { + map: Default::default(), + } + } +} + +impl Ui { + pub fn run_event, Ctx: UiCtx, W>( + ctx: &mut Ctx, + id: &WidgetId, + event: E, + data: E::Data, + ) { + if let Some(f) = ctx.ui().modules.get_mut::().run(&id.id, event) { + f(ctx, data); + } + } +} diff --git a/src/testing/input.rs b/src/testing/input.rs index 5998afc..77b99df 100644 --- a/src/testing/input.rs +++ b/src/testing/input.rs @@ -1,11 +1,18 @@ -use ui::{core::CursorState, layout::Vec2}; -use winit::event::{MouseButton, WindowEvent}; +use ui::{ + core::{CursorState, Modifiers}, + layout::Vec2, +}; +use winit::{ + event::{MouseButton, WindowEvent}, + keyboard::{Key, NamedKey}, +}; use crate::testing::Client; #[derive(Default)] pub struct Input { cursor: CursorState, + pub modifiers: Modifiers, } impl Input { @@ -27,6 +34,21 @@ impl Input { } WindowEvent::CursorLeft { .. } => { self.cursor.exists = false; + self.modifiers.clear(); + } + WindowEvent::KeyboardInput { event, .. } => { + if let Key::Named(named) = event.logical_key { + let pressed = event.state.is_pressed(); + match named { + NamedKey::Control => { + self.modifiers.control = pressed; + } + NamedKey::Shift => { + self.modifiers.shift = pressed; + } + _ => (), + } + } } _ => return false, } diff --git a/src/testing/mod.rs b/src/testing/mod.rs index 35762b8..6e3159e 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -24,6 +24,13 @@ pub struct Client { focus: Option>, } +#[derive(Eq, PartialEq, Hash)] +struct Submit; + +impl DefaultEvent for Submit { + type Data = (); +} + impl Client { pub fn new(window: Arc) -> Self { let renderer = Renderer::new(window); @@ -119,7 +126,7 @@ impl Client { .span(Dir::DOWN, sized()) .add_static(&mut ui); - let texts = Span::empty(Dir::DOWN).add(&mut ui); + let texts = Span::empty(Dir::DOWN).add_static(&mut ui); let add_text = text_edit("add") .text_align(Align::Left) .font_size(30) @@ -127,23 +134,26 @@ impl Client { client.ui.text(id).select(ctx.cursor, ctx.size); client.focus = Some(id.clone()); }) + .id_on(Submit, move |id, client: &mut Client, _| { + let content = client.ui.text(id).take(); + let text = text_edit(content) + .font_size(30) + .id_on(Sense::click(), |id, client: &mut Client, ctx| { + client.ui.text(id).select(ctx.cursor, ctx.size); + client.focus = Some(id.clone()); + }) + .pad(10) + .add(&mut client.ui); + client.ui[texts].children.push((text.any(), sized())); + }) .add(&mut ui); let text_edit_scroll = ( - (Rect::new(Color::SKY), texts.clone()).stack(), + (Rect::new(Color::SKY), texts).stack(), ( add_text.clone(), Rect::new(Color::GREEN) .on(Sense::click(), move |client: &mut Client, _| { - let content = client.ui.text(&add_text).take(); - let text = text_edit(content) - .font_size(30) - .id_on(Sense::click(), |id, client: &mut Client, ctx| { - client.ui.text(id).select(ctx.cursor, ctx.size); - client.focus = Some(id.clone()); - }) - .pad(10) - .add(&mut client.ui); - client.ui[&texts].children.push((text.any(), sized())); + Ui::run_event(client, &add_text, Submit, ()); }) .size(40), ) @@ -228,9 +238,16 @@ impl Client { WindowEvent::KeyboardInput { event, .. } => { if let Some(sel) = &self.focus && event.state.is_pressed() - && self.ui.text(sel).apply_event(&event).unfocus() { - self.focus = None; + match self.ui.text(sel).apply_event(&event, &self.input.modifiers) { + TextInputResult::Unfocus => { + self.focus = None; + } + TextInputResult::Submit => { + Ui::run_event(self, &sel.clone(), Submit, ()); + } + _ => (), + } } } _ => (),