event system!!!

This commit is contained in:
2025-09-25 12:37:06 -04:00
parent 4deeabe611
commit 21f15fb9c5
7 changed files with 255 additions and 50 deletions

2
TODO
View File

@@ -1,6 +1,6 @@
images images
settings (sampler) settings (sampler)
abstract sensors to work with any event, maybe associate data as well? consider unsafe cell
really weird limitation: 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 I don't think you can currently remove an element from a parent and put it in a child of the same parent

0
src/core/event.rs Normal file
View File

View File

@@ -1,6 +1,9 @@
use crate::prelude::*; use crate::prelude::*;
use std::ops::{BitOr, Deref, DerefMut}; use std::{
ops::{BitOr, Deref, DerefMut},
rc::Rc,
};
use crate::{ use crate::{
layout::{UiModule, UiRegion, Vec2}, layout::{UiModule, UiRegion, Vec2},
@@ -11,12 +14,14 @@ pub trait CursorCtx {
fn cursor_state(&self) -> &CursorState; fn cursor_state(&self) -> &CursorState;
} }
#[derive(PartialEq)]
pub enum Button { pub enum Button {
Left, Left,
Right, Right,
Middle, Middle,
} }
#[derive(PartialEq)]
pub enum Sense { pub enum Sense {
PressStart(Button), PressStart(Button),
Pressing(Button), Pressing(Button),
@@ -78,7 +83,7 @@ pub enum ActivationState {
pub struct Sensor<Ctx> { pub struct Sensor<Ctx> {
pub senses: Senses, pub senses: Senses,
pub f: Box<dyn SenseFn<Ctx>>, pub f: Rc<dyn SenseFn<Ctx>>,
} }
pub type SensorMap<Ctx> = HashMap<Id, SensorGroup<Ctx>>; pub type SensorMap<Ctx> = HashMap<Id, SensorGroup<Ctx>>;
@@ -88,13 +93,14 @@ pub struct SensorGroup<Ctx> {
pub sensors: Vec<Sensor<Ctx>>, pub sensors: Vec<Sensor<Ctx>>,
} }
#[derive(Clone)]
pub struct SenseData { pub struct SenseData {
pub cursor: Vec2, pub cursor: Vec2,
pub size: Vec2, pub size: Vec2,
} }
pub trait SenseFn<Ctx>: FnMut(&mut Ctx, SenseData) + 'static {} pub trait SenseFn<Ctx>: Fn(&mut Ctx, SenseData) + 'static {}
impl<F: FnMut(&mut Ctx, SenseData) + 'static, Ctx> SenseFn<Ctx> for F {} impl<F: Fn(&mut Ctx, SenseData) + 'static, Ctx> SenseFn<Ctx> for F {}
pub struct SensorModule<Ctx> { pub struct SensorModule<Ctx> {
map: SensorMap<Ctx>, map: SensorMap<Ctx>,
@@ -242,24 +248,48 @@ impl<Ctx: 'static> Event<Ctx> for Senses {
type Data = SenseData; type Data = SenseData;
} }
impl<Ctx: 'static> EventModule<Ctx, Senses> for SensorModule<Ctx> {
fn register(&mut self, id: Id, senses: Senses, f: impl EventFn<Ctx, SenseData>) {
// TODO: does not add to active if currently active
self.map.entry(id).or_default().sensors.push(Sensor {
senses,
f: Box::new(f),
});
}
}
impl<Ctx: 'static> Event<Ctx> for Sense { impl<Ctx: 'static> Event<Ctx> for Sense {
type Module = SensorModule<Ctx>; type Module = SensorModule<Ctx>;
type Data = SenseData; type Data = SenseData;
} }
impl<Ctx: 'static> EventModule<Ctx, Sense> for SensorModule<Ctx> { impl<E: Event<Ctx, Data = <Senses as Event<Ctx>>::Data> + Into<Senses>, Ctx: 'static>
fn register(&mut self, id: Id, sense: Sense, f: impl EventFn<Ctx, SenseData>) { EventModule<E, Ctx> for SensorModule<Ctx>
self.register(id, Senses::from(sense), f); {
fn register(&mut self, id: Id, senses: E, f: impl EventFn<Ctx, <E as Event<Ctx>>::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<impl Fn(&mut Ctx, E::Data) + use<'a, E, Ctx>> {
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
}
} }
} }

View File

@@ -1,5 +1,5 @@
use crate::prelude::*; 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 unicode_segmentation::UnicodeSegmentation;
use winit::{ use winit::{
event::KeyEvent, event::KeyEvent,
@@ -133,6 +133,11 @@ impl<'a> TextEditCtx<'a> {
self.text self.text
.buf .buf
.set_text(self.font_system, "", &Attrs::new(), Shaping::Advanced); .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 text
} }
@@ -219,13 +224,19 @@ impl<'a> TextEditCtx<'a> {
self.text.cursor = None; 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 { match &event.logical_key {
Key::Named(named) => match named { Key::Named(named) => match named {
NamedKey::Backspace => self.backspace(), NamedKey::Backspace => self.backspace(),
NamedKey::Delete => self.delete(), NamedKey::Delete => self.delete(),
NamedKey::Space => self.insert(" "), 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::ArrowRight => self.motion(Motion::Right),
NamedKey::ArrowLeft => self.motion(Motion::Left), NamedKey::ArrowLeft => self.motion(Motion::Left),
NamedKey::ArrowUp => self.motion(Motion::Up), 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 { pub enum TextInputResult {
Used, Used,
Unused, Unused,
Unfocus, Unfocus,
Submit,
} }
impl TextInputResult { impl TextInputResult {

View File

@@ -1,6 +1,8 @@
use std::{hash::Hash, rc::Rc};
use crate::{ use crate::{
layout::{IdFnTag, Ui, UiModule, Widget, WidgetId, WidgetIdFn, WidgetLike}, layout::{IdFnTag, Ui, UiModule, Widget, WidgetId, WidgetIdFn, WidgetLike},
util::Id, util::{HashMap, Id},
}; };
pub trait UiCtx { pub trait UiCtx {
@@ -14,16 +16,12 @@ impl UiCtx for Ui {
} }
pub trait Event<Ctx>: Sized { pub trait Event<Ctx>: Sized {
type Module: UiModule + EventModule<Ctx, Self> + Default; type Module: EventModule<Self, Ctx>;
type Data; type Data: Clone;
} }
pub trait EventModule<Ctx, E: Event<Ctx>> { pub trait EventFn<Ctx, Data>: Fn(&mut Ctx, Data) + 'static {}
fn register(&mut self, id: Id, event: E, f: impl EventFn<Ctx, E::Data>); impl<F: Fn(&mut Ctx, Data) + 'static, Ctx, Data> EventFn<Ctx, Data> for F {}
}
pub trait EventFn<Ctx, Data>: FnMut(&mut Ctx, Data) + 'static {}
impl<F: FnMut(&mut Ctx, Data) + 'static, Ctx, Data> EventFn<Ctx, Data> for F {}
pub trait Eventable<W, Tag> { pub trait Eventable<W, Tag> {
fn on<E: Event<Ctx>, Ctx>( fn on<E: Event<Ctx>, Ctx>(
@@ -35,7 +33,7 @@ pub trait Eventable<W, Tag> {
fn id_on<E: Event<Ctx>, Ctx>( fn id_on<E: Event<Ctx>, Ctx>(
self, self,
event: E, event: E,
f: impl FnMut(&WidgetId<W>, &mut Ctx, E::Data) + 'static, f: impl Fn(&WidgetId<W>, &mut Ctx, E::Data) + 'static,
) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag> ) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag>
where where
W: Widget; W: Widget;
@@ -43,7 +41,7 @@ pub trait Eventable<W, Tag> {
fn edit_on<E: Event<Ui>>( fn edit_on<E: Event<Ui>>(
self, self,
event: E, event: E,
f: impl FnMut(&mut W, E::Data) + 'static, f: impl Fn(&mut W, E::Data) + 'static,
) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag> ) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag>
where where
W: Widget; W: Widget;
@@ -67,7 +65,7 @@ impl<W: WidgetLike<Tag>, Tag> Eventable<W::Widget, Tag> for W {
fn id_on<E: Event<Ctx>, Ctx>( fn id_on<E: Event<Ctx>, Ctx>(
self, self,
event: E, event: E,
mut f: impl FnMut(&WidgetId<W::Widget>, &mut Ctx, E::Data) + 'static, mut f: impl Fn(&WidgetId<W::Widget>, &mut Ctx, E::Data) + 'static,
) -> impl WidgetIdFn<W::Widget> ) -> impl WidgetIdFn<W::Widget>
where where
W::Widget: Widget, W::Widget: Widget,
@@ -81,7 +79,7 @@ impl<W: WidgetLike<Tag>, Tag> Eventable<W::Widget, Tag> for W {
fn edit_on<E: Event<Ui>>( fn edit_on<E: Event<Ui>>(
self, self,
event: E, event: E,
mut f: impl FnMut(&mut W::Widget, E::Data) + 'static, mut f: impl Fn(&mut W::Widget, E::Data) + 'static,
) -> impl WidgetIdFn<W::Widget> ) -> impl WidgetIdFn<W::Widget>
where where
W::Widget: Widget, W::Widget: Widget,
@@ -89,3 +87,116 @@ impl<W: WidgetLike<Tag>, Tag> Eventable<W::Widget, Tag> for W {
self.id_on(event, move |id, ui, pos| f(&mut ui[id], pos)) self.id_on(event, move |id, ui, pos| f(&mut ui[id], pos))
} }
} }
pub trait DefaultEvent: Hash + Eq + 'static {
type Data: Clone;
}
impl<E: DefaultEvent, Ctx: 'static> Event<Ctx> for E {
type Module = DefaultEventModule<E, Ctx>;
type Data = E::Data;
}
pub trait EventModule<E: Event<Ctx>, Ctx>: UiModule + Default {
fn register(&mut self, id: Id, event: E, f: impl EventFn<Ctx, E::Data>);
fn run<'a>(
&mut self,
id: &Id,
event: E,
) -> Option<impl Fn(&mut Ctx, E::Data) + use<'a, Self, E, Ctx>>;
}
type EventFnMap<Ctx, Data> = HashMap<Id, Vec<Rc<dyn EventFn<Ctx, Data>>>>;
pub struct DefaultEventModule<E: Event<Ctx>, Ctx> {
map: HashMap<E, EventFnMap<Ctx, <E as Event<Ctx>>::Data>>,
}
impl<E: Event<Ctx> + 'static, Ctx: 'static> UiModule for DefaultEventModule<E, Ctx> {
fn on_remove(&mut self, id: &Id) {
for map in self.map.values_mut() {
map.remove(id);
}
}
}
pub trait HashableEvent<Ctx>: Event<Ctx> + Hash + Eq + 'static {}
impl<E: Event<Ctx> + Hash + Eq + 'static, Ctx> HashableEvent<Ctx> for E {}
impl<E: HashableEvent<Ctx>, Ctx: 'static> EventModule<E, Ctx> for DefaultEventModule<E, Ctx> {
fn register(&mut self, id: Id, event: E, f: impl EventFn<Ctx, <E as Event<Ctx>>::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<impl Fn(&mut Ctx, E::Data) + use<'a, E, Ctx>> {
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<E: HashableEvent<Ctx>, Ctx: 'static> DefaultEventModule<E, Ctx> {
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<W>(&mut self, ctx: &mut Ctx, id: WidgetId<W>, 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<E: Event<Ctx> + 'static, Ctx: 'static> Default for DefaultEventModule<E, Ctx> {
fn default() -> Self {
Self {
map: Default::default(),
}
}
}
impl Ui {
pub fn run_event<E: Event<Ctx>, Ctx: UiCtx, W>(
ctx: &mut Ctx,
id: &WidgetId<W>,
event: E,
data: E::Data,
) {
if let Some(f) = ctx.ui().modules.get_mut::<E::Module>().run(&id.id, event) {
f(ctx, data);
}
}
}

View File

@@ -1,11 +1,18 @@
use ui::{core::CursorState, layout::Vec2}; use ui::{
use winit::event::{MouseButton, WindowEvent}; core::{CursorState, Modifiers},
layout::Vec2,
};
use winit::{
event::{MouseButton, WindowEvent},
keyboard::{Key, NamedKey},
};
use crate::testing::Client; use crate::testing::Client;
#[derive(Default)] #[derive(Default)]
pub struct Input { pub struct Input {
cursor: CursorState, cursor: CursorState,
pub modifiers: Modifiers,
} }
impl Input { impl Input {
@@ -27,6 +34,21 @@ impl Input {
} }
WindowEvent::CursorLeft { .. } => { WindowEvent::CursorLeft { .. } => {
self.cursor.exists = false; 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, _ => return false,
} }

View File

@@ -24,6 +24,13 @@ pub struct Client {
focus: Option<WidgetId<TextEdit>>, focus: Option<WidgetId<TextEdit>>,
} }
#[derive(Eq, PartialEq, Hash)]
struct Submit;
impl DefaultEvent for Submit {
type Data = ();
}
impl Client { impl Client {
pub fn new(window: Arc<Window>) -> Self { pub fn new(window: Arc<Window>) -> Self {
let renderer = Renderer::new(window); let renderer = Renderer::new(window);
@@ -119,7 +126,7 @@ impl Client {
.span(Dir::DOWN, sized()) .span(Dir::DOWN, sized())
.add_static(&mut ui); .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") let add_text = text_edit("add")
.text_align(Align::Left) .text_align(Align::Left)
.font_size(30) .font_size(30)
@@ -127,14 +134,8 @@ impl Client {
client.ui.text(id).select(ctx.cursor, ctx.size); client.ui.text(id).select(ctx.cursor, ctx.size);
client.focus = Some(id.clone()); client.focus = Some(id.clone());
}) })
.add(&mut ui); .id_on(Submit, move |id, client: &mut Client, _| {
let text_edit_scroll = ( let content = client.ui.text(id).take();
(Rect::new(Color::SKY), texts.clone()).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) let text = text_edit(content)
.font_size(30) .font_size(30)
.id_on(Sense::click(), |id, client: &mut Client, ctx| { .id_on(Sense::click(), |id, client: &mut Client, ctx| {
@@ -143,7 +144,16 @@ impl Client {
}) })
.pad(10) .pad(10)
.add(&mut client.ui); .add(&mut client.ui);
client.ui[&texts].children.push((text.any(), sized())); client.ui[texts].children.push((text.any(), sized()));
})
.add(&mut ui);
let text_edit_scroll = (
(Rect::new(Color::SKY), texts).stack(),
(
add_text.clone(),
Rect::new(Color::GREEN)
.on(Sense::click(), move |client: &mut Client, _| {
Ui::run_event(client, &add_text, Submit, ());
}) })
.size(40), .size(40),
) )
@@ -228,10 +238,17 @@ impl Client {
WindowEvent::KeyboardInput { event, .. } => { WindowEvent::KeyboardInput { event, .. } => {
if let Some(sel) = &self.focus if let Some(sel) = &self.focus
&& event.state.is_pressed() && event.state.is_pressed()
&& self.ui.text(sel).apply_event(&event).unfocus()
{ {
match self.ui.text(sel).apply_event(&event, &self.input.modifiers) {
TextInputResult::Unfocus => {
self.focus = None; self.focus = None;
} }
TextInputResult::Submit => {
Ui::run_event(self, &sel.clone(), Submit, ());
}
_ => (),
}
}
} }
_ => (), _ => (),
} }