From 62aa02847a5d159e6545d81fa6478ea572aff1ed Mon Sep 17 00:00:00 2001 From: shadow cat Date: Sat, 6 Dec 2025 20:48:10 -0500 Subject: [PATCH] redo event fn signature & add event_ctx macro --- examples/minimal.rs | 18 ++-- readme.md | 2 +- src/bin/test/main.rs | 88 +++++++++---------- src/core/sense.rs | 43 ++++++---- src/core/trait_fns.rs | 7 +- src/layout/event.rs | 195 ++++++++++++++++++++++-------------------- src/winit/app.rs | 10 +-- src/winit/attr.rs | 38 ++++---- src/winit/input.rs | 4 +- src/winit/mod.rs | 139 ++++++++++++++---------------- 10 files changed, 270 insertions(+), 274 deletions(-) diff --git a/examples/minimal.rs b/examples/minimal.rs index e835165..60af299 100644 --- a/examples/minimal.rs +++ b/examples/minimal.rs @@ -1,20 +1,14 @@ use iris::{prelude::*, winit::*}; fn main() { - UiApp::::run(); + DefaultApp::::run(); } -struct State { - ui: DefaultUi, -} +struct State; -impl DefaultUiState for State { - fn new(mut ui: DefaultUi, _proxy: Proxy) -> Self { - rect(Color::RED).set_root(&mut ui); - Self { ui } - } - - fn ui(&mut self) -> &mut DefaultUi { - &mut self.ui +impl DefaultAppState for State { + fn new(ui: &mut Ui, _state: &UiState, _proxy: Proxy) -> Self { + rect(Color::RED).set_root(ui); + Self } } diff --git a/readme.md b/readme.md index 6278d4f..91b3d16 100644 --- a/readme.md +++ b/readme.md @@ -17,7 +17,7 @@ general ideas trynna use rn / experiment with: - retained mode - specifically designed around wgpu - postfix functions for most things to prevent unreadable indentation (going very well) -- no macros in user code / actual LSP typechecking (variadic generics if you can hear me please save us) +- almost no macros in user code & actual LSP typechecking (variadic generics if you can hear me please save us) - relative anchor + absolute offset coord system (+ "rest" / leftover during widget layout) - single threaded ui & pass context around to make non async usage straightforward (pretty unsure about this) - widgets store outside of the actual rendering so they can be moved around and swapped easily (unsure about this but seems to work good for now) diff --git a/src/bin/test/main.rs b/src/bin/test/main.rs index db46999..2cd7af8 100644 --- a/src/bin/test/main.rs +++ b/src/bin/test/main.rs @@ -7,16 +7,17 @@ use len_fns::*; use winit::event::WindowEvent; fn main() { - UiApp::::run(); + DefaultApp::::run(); } pub struct Client { - ui: DefaultUi, info: WidgetId, } -impl DefaultUiState for Client { - fn new(mut ui: DefaultUi, _proxy: Proxy) -> Self { +event_ctx!(Client); + +impl DefaultAppState for Client { + fn new(ui: &mut Ui, _state: &UiState, _proxy: Proxy) -> Self { let rrect = rect(Color::WHITE).radius(20); let pad_test = ( rrect.color(Color::BLUE), @@ -39,7 +40,7 @@ impl DefaultUiState for Client { .width(rest(3)), ) .span(Dir::RIGHT) - .add(&mut ui); + .add(ui); let span_test = ( rrect.color(Color::GREEN).width(100), @@ -50,14 +51,14 @@ impl DefaultUiState for Client { rrect.color(Color::RED).width(100), ) .span(Dir::LEFT) - .add(&mut ui); + .add(ui); - let span_add = Span::empty(Dir::RIGHT).add(&mut ui); + let span_add = Span::empty(Dir::RIGHT).add(ui); let span_add_ = span_add.clone(); let add_button = rect(Color::LIME) .radius(30) - .on(CursorSense::click(), move |ctx: &mut Client, _| { + .on(CursorSense::click(), move |ctx| { let child = ctx .ui .add(image(include_bytes!("assets/sungals.png")).center()) @@ -70,13 +71,13 @@ impl DefaultUiState for Client { let span_add_ = span_add.clone(); let del_button = rect(Color::RED) .radius(30) - .on(CursorSense::click(), move |ctx: &mut Client, _| { + .on(CursorSense::click(), move |ctx| { ctx.ui[&span_add_].children.pop(); }) .sized((150, 150)) .align(Align::BOT_LEFT); - let span_add_test = (span_add, add_button, del_button).stack().add(&mut ui); + let span_add_test = (span_add, add_button, del_button).stack().add(ui); let btext = |content| wtext(content).size(30); @@ -99,29 +100,27 @@ impl DefaultUiState for Client { wtext("pretty cool right?").size(50), ) .span(Dir::DOWN) - .add(&mut ui); + .add(ui); - let texts = Span::empty(Dir::DOWN).gap(10).add(&mut ui); + let texts = Span::empty(Dir::DOWN).gap(10).add(ui); let msg_area = texts.clone().scroll().masked().background(rect(Color::SKY)); let add_text = wtext("add") .editable(false) .text_align(Align::LEFT) .size(30) .attr::(()) - .id_on(Submit, move |id, client: &mut Client, _| { - let content = client.ui.text(id).take(); + .on(Submit, move |ctx| { + let content = ctx.ui.text(ctx.id).take(); let text = wtext(content) .editable(false) .size(30) .text_align(Align::LEFT) .wrap(true) .attr::(()); - let msg_box = text - .background(rect(Color::WHITE.darker(0.5))) - .add(&mut client.ui); - client.ui[&texts].children.push(msg_box.any()); + let msg_box = text.background(rect(Color::WHITE.darker(0.5))).add(ctx.ui); + ctx.ui[&texts].children.push(msg_box.any()); }) - .add(&mut ui); + .add(ui); let text_edit_scroll = ( msg_area.height(rest(1)), ( @@ -129,8 +128,8 @@ impl DefaultUiState for Client { ( add_text.clone().width(rest(1)), Rect::new(Color::GREEN) - .on(CursorSense::click(), move |client: &mut Client, _| { - client.run_event(&add_text, Submit, ()); + .on(CursorSense::click(), move |ctx| { + ctx.ui.run_event(ctx.state, &add_text, Submit, ()); }) .sized((40, 40)), ) @@ -143,25 +142,25 @@ impl DefaultUiState for Client { .align(Align::BOT), ) .span(Dir::DOWN) - .add(&mut ui); + .add(ui); - let main = pad_test.clone().pad(10).add(&mut ui); + let main = pad_test.clone().pad(10).add(ui); let switch_button = |color, to: WidgetId, label| { let main_ = main.clone(); let rect = rect(color) - .id_on(CursorSense::click(), move |id, ui: &mut Ui, _| { - ui[&main_.clone()].inner = to.clone(); - ui[id].color = color.darker(0.3); + .on(CursorSense::click(), move |ctx| { + ctx.ui[&main_.clone()].inner = to.clone(); + ctx.ui[ctx.id].color = color.darker(0.3); }) - .edit_on( + .on( CursorSense::HoverStart | CursorSense::unclick(), - move |r, _| { - r.color = color.brighter(0.2); + move |ctx| { + ctx.ui[ctx.id].color = color.brighter(0.2); }, ) - .edit_on(CursorSense::HoverEnd, move |r, _| { - r.color = color; + .on(CursorSense::HoverEnd, move |ctx| { + ctx.ui[ctx.id].color = color; }); (rect, wtext(label).size(30).text_align(Align::CENTER)).stack() }; @@ -179,33 +178,28 @@ impl DefaultUiState for Client { ) .span(Dir::RIGHT); - let info = wtext("").add(&mut ui); + let info = wtext("").add(ui); let info_sect = info.clone().pad(10).align(Align::RIGHT); ((tabs.height(40), main).span(Dir::DOWN), info_sect) .stack() - .set_root(&mut ui); + .set_root(ui); - Self { ui, info } + Self { info } } - fn ui(&mut self) -> &mut DefaultUi { - &mut self.ui - } - - fn window_event(&mut self, _: WindowEvent) { + fn window_event(&mut self, _: WindowEvent, ui: &mut Ui, state: &UiState) { let new = format!( "widgets: {}\nactive:{}\nviews: {}", - self.ui.num_widgets(), - self.ui.active_widgets(), - self.ui.renderer.ui.view_count() + ui.num_widgets(), + ui.active_widgets(), + state.renderer.ui.view_count() ); - if new != *self.ui[&self.info].content { - *self.ui[&self.info].content = new; + if new != *ui[&self.info].content { + *ui[&self.info].content = new; } - if self.ui.needs_redraw() { - self.ui.window.request_redraw(); + if ui.needs_redraw() { + state.window.request_redraw(); } - self.ui.input.end_frame(); } } diff --git a/src/core/sense.rs b/src/core/sense.rs index 14f7760..423bb90 100644 --- a/src/core/sense.rs +++ b/src/core/sense.rs @@ -166,20 +166,21 @@ impl CursorModule { } } -pub trait SensorCtx: UiCtx { - fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2); -} - -impl SensorCtx for Ctx { - fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2) { - CursorModule::::run(self, cursor, window_size); +impl Ui { + pub 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(ctx: &mut Ctx, cursor: &CursorState, window_size: Vec2) { - let layers = std::mem::take(&mut ctx.ui().data.layers); - let mut module = std::mem::take(ctx.ui().data.modules.get_mut::()); +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 { @@ -204,7 +205,7 @@ impl CursorModule { scroll_delta: cursor.scroll_delta, sense, }; - (sensor.f)(ctx, data); + (sensor.f)(EventCtx { ui, state: ctx, data }); } } } @@ -213,10 +214,10 @@ impl CursorModule { } } - let ui_mod = ctx.ui().data.modules.get_mut::(); + let ui_mod = ui.data.modules.get_mut::(); std::mem::swap(ui_mod, &mut module); ui_mod.merge(module); - ctx.ui().data.layers = layers; + ui.data.layers = layers; } } @@ -305,7 +306,11 @@ impl::Data> + Into, Ctx: ' }); } - fn run<'a>(&self, id: &Id, event: E) -> Option> { + 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 @@ -319,9 +324,13 @@ impl::Data> + Into, Ctx: ' } }) .collect(); - Some(move |ctx: &mut Ctx, data: CursorData| { + Some(move |ctx: EventCtx| { for f in &fs { - f(ctx, data.clone()); + f(EventCtx { + state: ctx.state, + ui: ctx.ui, + data: ctx.data.clone(), + }); } }) } else { diff --git a/src/core/trait_fns.rs b/src/core/trait_fns.rs index d7ae059..a9b4003 100644 --- a/src/core/trait_fns.rs +++ b/src/core/trait_fns.rs @@ -100,10 +100,13 @@ impl, Tag> CoreWidget for W { } fn scroll(self) -> impl WidgetIdFn { + event_ctx!(()); + move |ui| { Scroll::new(self.add(ui).any(), Axis::Y) - .edit_on(CursorSense::Scroll, |w, data| { - w.scroll(data.scroll_delta.y * 50.0); + .on(CursorSense::Scroll, |ctx| { + let s = &mut ctx.ui[ctx.id]; + s.scroll(ctx.data.scroll_delta.y * 50.0); }) .add(ui) } diff --git a/src/layout/event.rs b/src/layout/event.rs index 3c5d872..a17ed76 100644 --- a/src/layout/event.rs +++ b/src/layout/event.rs @@ -1,90 +1,97 @@ use std::{hash::Hash, rc::Rc}; use crate::{ - layout::{IdFnTag, Ui, UiModule, Widget, WidgetId, WidgetIdFn, WidgetLike}, + layout::{IdFnTag, Ui, UiModule, WidgetId, WidgetIdFn, WidgetLike}, util::{HashMap, Id}, }; -pub trait UiCtx { - fn ui(&mut self) -> &mut Ui; -} - -impl UiCtx for Ui { - fn ui(&mut self) -> &mut Ui { - self - } -} - pub trait Event: Sized { type Module: EventModule; type Data: Clone; } -pub trait EventFn: Fn(&mut Ctx, Data) + 'static {} -impl EventFn for F {} - -pub trait Eventable { - fn on( - self, - event: E, - f: impl EventFn, - ) -> impl WidgetIdFn + Eventable; - - fn id_on( - self, - event: E, - f: impl Fn(&WidgetId, &mut Ctx, E::Data) + 'static, - ) -> impl WidgetIdFn + Eventable - where - W: Widget; - - fn edit_on( - self, - event: E, - f: impl Fn(&mut W, E::Data) + 'static, - ) -> impl WidgetIdFn + Eventable - where - W: Widget; +pub struct EventCtx<'a, Ctx, Data> { + pub ui: &'a mut Ui, + pub state: &'a mut Ctx, + pub data: Data, } -impl, Tag> Eventable for W { - fn on( - self, - event: E, - f: impl EventFn, - ) -> impl WidgetIdFn { - move |ui| { - let id = self.add(ui); - ui.register_event(&id, event, f); - id +pub type ECtx<'a, Ctx, Data, W> = EventIdCtx<'a, Ctx, Data, W>; +pub struct EventIdCtx<'a, Ctx, Data, W> { + pub id: &'a WidgetId, + pub ui: &'a mut Ui, + pub state: &'a mut Ctx, + pub data: Data, +} + +pub trait EventFn: Fn(EventCtx) + 'static {} +impl) + 'static, Ctx, Data> EventFn for F {} + +pub trait WidgetEventFn: Fn(EventIdCtx) + 'static {} +impl) + 'static, Ctx, Data, W> WidgetEventFn for F {} + +#[macro_export] +macro_rules! event_ctx { + ($ty: ty) => { + mod local_event_trait { + use super::*; + #[allow(unused_imports)] + use $crate::prelude::*; + + pub trait EventableCtx { + fn on( + self, + event: E, + f: impl WidgetEventFn, + ) -> impl WidgetIdFn + EventableCtx; + } + + impl, Tag> EventableCtx for WL { + fn on( + self, + event: E, + f: impl WidgetEventFn<$ty, E::Data, WL::Widget>, + ) -> impl WidgetIdFn + EventableCtx { + eventable::Eventable::on(self, event, f) + } + } } + use local_event_trait::*; + }; +} +pub use event_ctx; + +pub mod eventable { + use super::*; + + pub trait Eventable { + fn on( + self, + event: E, + f: impl WidgetEventFn, + ) -> impl WidgetIdFn + Eventable; } - fn id_on( - self, - event: E, - f: impl Fn(&WidgetId, &mut Ctx, E::Data) + 'static, - ) -> impl WidgetIdFn - where - W::Widget: Widget, - { - self.with_id(move |ui, id| { - // needed so that this widget can actually be dropped - let id2 = id.weak(); - id.on(event, move |ctx, pos| f(&id2.strong(), ctx, pos)) - .add(ui) - }) - } - - fn edit_on( - self, - event: E, - f: impl Fn(&mut W::Widget, E::Data) + 'static, - ) -> impl WidgetIdFn - where - W::Widget: Widget, - { - self.id_on(event, move |id, ui: &mut Ui, pos| f(&mut ui[id], pos)) + impl, Tag> Eventable for WL { + fn on( + self, + event: E, + f: impl WidgetEventFn, + ) -> impl WidgetIdFn { + move |ui| { + let id = self.add(ui); + let id_ = id.weak(); + ui.register_event(&id, event, move |ctx| { + f(EventIdCtx { + id: &id_.strong(), + state: ctx.state, + data: ctx.data, + ui: ctx.ui, + }); + }); + id + } + } } } @@ -103,7 +110,7 @@ pub trait EventModule: UiModule + Default { &self, id: &Id, event: E, - ) -> Option>; + ) -> Option) + use<'a, Self, E, Ctx>>; } type EventFnMap = HashMap>>>; @@ -132,14 +139,22 @@ impl EventModule for DefaultEventModule< .push(Rc::new(f)); } - fn run<'a>(&self, id: &Id, event: E) -> Option> { + fn run<'a>( + &self, + id: &Id, + event: E, + ) -> Option) + use<'a, E, Ctx>> { if let Some(map) = self.map.get(&event) && let Some(fs) = map.get(id) { let fs = fs.clone(); - Some(move |ctx: &mut Ctx, data: E::Data| { + Some(move |ctx: EventCtx::Data>| { for f in &fs { - f(ctx, data.clone()) + f(EventCtx { + ui: ctx.ui, + state: ctx.state, + data: ctx.data.clone(), + }) } }) } else { @@ -149,14 +164,18 @@ impl EventModule for DefaultEventModule< } impl DefaultEventModule { - pub fn run_all(&self, ctx: &mut Ctx, event: E, data: E::Data) + pub fn run_all(&self, event: E, ctx: EventCtx) where E::Data: Clone, { if let Some(map) = self.map.get(&event) { for fs in map.values() { for f in fs { - f(ctx, data.clone()) + f(EventCtx { + ui: ctx.ui, + state: ctx.state, + data: ctx.data.clone(), + }) } } } @@ -172,30 +191,24 @@ impl Default for DefaultEventModule { } impl Ui { - pub fn run_event( + pub fn run_event( + &mut self, ctx: &mut Ctx, id: &WidgetId, event: E, data: E::Data, ) { - if let Some(f) = ctx - .ui() + if let Some(f) = self .data .modules .get_mut::>() .run(&id.id, event) { - f(ctx, data); + f(EventCtx { + ui: self, + state: ctx, + data, + }); } } } - -pub trait EventCtx: UiCtx { - fn run_event(&mut self, id: &WidgetId, event: E, data: E::Data); -} - -impl EventCtx for Ctx { - fn run_event(&mut self, id: &WidgetId, event: E, data: E::Data) { - Ui::run_event(self, id, event.clone(), data.clone()); - } -} diff --git a/src/winit/app.rs b/src/winit/app.rs index 33be8af..700a2bc 100644 --- a/src/winit/app.rs +++ b/src/winit/app.rs @@ -5,7 +5,7 @@ use winit::{ window::WindowId, }; -pub trait UiState { +pub trait AppState { type Event: 'static; fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy) -> Self; fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop); @@ -13,22 +13,22 @@ pub trait UiState { fn exit(&mut self); } -pub struct UiApp { +pub struct App { state: Option, proxy: EventLoopProxy, } -impl UiApp { +impl App { pub fn run() { let event_loop = EventLoop::with_user_event().build().unwrap(); let proxy = event_loop.create_proxy(); event_loop - .run_app(&mut UiApp:: { state: None, proxy }) + .run_app(&mut App:: { state: None, proxy }) .unwrap(); } } -impl ApplicationHandler for UiApp { +impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { if self.state.is_none() { let state = State::new(event_loop, self.proxy.clone()); diff --git a/src/winit/attr.rs b/src/winit/attr.rs index 057fcec..e89a9d6 100644 --- a/src/winit/attr.rs +++ b/src/winit/attr.rs @@ -1,4 +1,4 @@ -use crate::{prelude::*, winit::DefaultUi}; +use crate::{prelude::*, winit::UiState}; use std::time::{Duration, Instant}; use winit::dpi::{LogicalPosition, LogicalSize}; @@ -12,13 +12,14 @@ impl WidgetAttr for Selector { ui.register_event( &container.clone(), CursorSense::click_or_drag(), - move |ui: &mut DefaultUi, mut data| { + move |mut ctx| { + let ui = ctx.ui; let region = ui.window_region(&id).unwrap(); let id_pos = region.top_left; let container_pos = ui.window_region(&container).unwrap().top_left; - data.cursor += container_pos - id_pos; - data.size = region.size(); - select(id.clone(), ui, data); + ctx.data.cursor += container_pos - id_pos; + ctx.data.size = region.size(); + select(ui, id.clone(), ctx.state, ctx.data); }, ); } @@ -31,29 +32,24 @@ impl WidgetAttr for Selectable { fn run(ui: &mut Ui, id: &WidgetId, _: Self::Input) { let id = id.clone(); - ui.register_event( - &id.clone(), - CursorSense::click_or_drag(), - move |ui: &mut DefaultUi, data| { - select(id.clone(), ui, data); - }, - ); + ui.register_event(&id.clone(), CursorSense::click_or_drag(), move |ctx| { + select(ctx.ui, id.clone(), ctx.state, ctx.data); + }); } } -fn select(id: WidgetId, ui: &mut DefaultUi, data: CursorData) { +fn select(ui: &mut Ui, id: WidgetId, state: &mut UiState, data: CursorData) { let now = Instant::now(); - let recent = (now - ui.last_click) < Duration::from_millis(300); - ui.last_click = now; - ui.ui - .text(&id) + let recent = (now - state.last_click) < Duration::from_millis(300); + state.last_click = now; + ui.text(&id) .select(data.cursor, data.size, data.sense.is_dragging(), recent); - if let Some(region) = ui.ui.window_region(&id) { - ui.window.set_ime_allowed(true); - ui.window.set_ime_cursor_area( + if let Some(region) = ui.window_region(&id) { + state.window.set_ime_allowed(true); + state.window.set_ime_cursor_area( LogicalPosition::::from(region.top_left.tuple()), LogicalSize::::from(region.size().tuple()), ); } - ui.focus = Some(id); + state.focus = Some(id); } diff --git a/src/winit/input.rs b/src/winit/input.rs index 4039313..811653f 100644 --- a/src/winit/input.rs +++ b/src/winit/input.rs @@ -1,7 +1,7 @@ use crate::{ core::{CursorState, Modifiers}, layout::Vec2, - winit::DefaultUi, + winit::UiState, }; use winit::{ event::{MouseButton, MouseScrollDelta, WindowEvent}, @@ -70,7 +70,7 @@ impl Input { } } -impl DefaultUi { +impl UiState { pub fn window_size(&self) -> Vec2 { let size = self.renderer.window().inner_size(); (size.width, size.height).into() diff --git a/src/winit/mod.rs b/src/winit/mod.rs index e377c87..d3b868c 100644 --- a/src/winit/mod.rs +++ b/src/winit/mod.rs @@ -2,7 +2,6 @@ use crate::prelude::*; use crate::winit::event::{Edited, Submit}; use crate::winit::{input::Input, render::UiRenderer}; use arboard::Clipboard; -use std::ops::{Deref, DerefMut}; use std::sync::Arc; use std::time::Instant; use winit::event::{Ime, WindowEvent}; @@ -18,11 +17,17 @@ pub mod render; pub use app::*; pub type Proxy = EventLoopProxy; +pub type DefaultApp = App>; -pub struct DefaultUi { +pub struct DefaultState { + ui: Ui, + ui_state: UiState, + app_state: AppState, +} + +pub struct UiState { pub renderer: UiRenderer, pub input: Input, - pub ui: Ui, pub focus: Option>, pub clipboard: Clipboard, pub window: Arc, @@ -30,62 +35,71 @@ pub struct DefaultUi { pub last_click: Instant, } -pub trait DefaultUiState: 'static { +pub trait DefaultAppState: 'static { type Event: 'static = (); - fn new(ui: DefaultUi, proxy: Proxy) -> Self; - fn ui(&mut self) -> &mut DefaultUi; + fn new(ui: &mut Ui, state: &UiState, proxy: Proxy) -> Self; #[allow(unused_variables)] - fn event(&mut self, event: Self::Event) {} - fn exit(&mut self) {} + fn event(&mut self, event: Self::Event, ui: &mut Ui, state: &UiState) {} #[allow(unused_variables)] - fn window_event(&mut self, event: WindowEvent) {} + fn exit(&mut self, ui: &mut Ui, state: &UiState) {} + #[allow(unused_variables)] + fn window_event(&mut self, event: WindowEvent, ui: &mut Ui, state: &UiState) {} fn window_attrs() -> WindowAttributes { WindowAttributes::default() } } -impl UiState for State { +impl AppState for DefaultState { type Event = State::Event; fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy) -> Self { let window = Arc::new( event_loop - .create_window(Self::window_attrs()) + .create_window(State::window_attrs()) .expect("failed to create window "), ); - let ui = DefaultUi { + let mut ui = Ui::new(); + let ui_state = UiState { renderer: UiRenderer::new(window.clone()), window, input: Input::default(), - ui: Ui::new(), clipboard: Clipboard::new().unwrap(), ime: 0, last_click: Instant::now(), focus: None, }; - State::new(ui, proxy) + let app_state = State::new(&mut ui, &ui_state, proxy); + Self { + ui, + ui_state, + app_state, + } } fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) { - self.event(event); + self.app_state.event(event, &mut self.ui, &self.ui_state); } fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) { - let ui = self.ui(); - let input_changed = ui.input.event(&event); - let cursor_state = ui.cursor_state().clone(); - let old = ui.focus.clone(); + let Self { + ui, + ui_state: ui_data, + app_state: app_data, + } = self; + + let input_changed = ui_data.input.event(&event); + let cursor_state = ui_data.cursor_state().clone(); + let old = ui_data.focus.clone(); if cursor_state.buttons.left.is_start() { - ui.focus = None; + ui_data.focus = None; } if input_changed { - let window_size = ui.window_size(); - ui.run_sensors(&cursor_state, window_size); - ui.ui.run_sensors(&cursor_state, window_size); - self.run_sensors(&cursor_state, window_size); + let window_size = ui_data.window_size(); + ui.run_sensors(&mut (), &cursor_state, window_size); + ui.run_sensors(ui_data, &cursor_state, window_size); + ui.run_sensors(app_data, &cursor_state, window_size); } - let ui = self.ui(); - if old != ui.focus + if old != ui_data.focus && let Some(old) = old { ui.text(&old).deselect(); @@ -93,55 +107,55 @@ impl UiState for State { match &event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::RedrawRequested => { - ui.ui.update(); - ui.renderer.update(&mut ui.ui); - ui.renderer.draw(); + ui.update(); + ui_data.renderer.update(ui); + ui_data.renderer.draw(); } WindowEvent::Resized(size) => { - ui.ui.resize((size.width, size.height)); - ui.renderer.resize(size) + ui.resize((size.width, size.height)); + ui_data.renderer.resize(size) } WindowEvent::KeyboardInput { event, .. } => { - if let Some(sel) = &ui.focus + if let Some(sel) = &ui_data.focus && event.state.is_pressed() { let sel = &sel.clone(); - let mut text = ui.ui.text(sel); - match text.apply_event(event, &ui.input.modifiers) { + let mut text = ui.text(sel); + match text.apply_event(event, &ui_data.input.modifiers) { TextInputResult::Unfocus => { - ui.focus = None; - ui.window.set_ime_allowed(false); + ui_data.focus = None; + ui_data.window.set_ime_allowed(false); } TextInputResult::Submit => { - self.run_event(sel, Submit, ()); + ui.run_event(app_data, sel, Submit, ()); } TextInputResult::Paste => { - if let Ok(t) = ui.clipboard.get_text() { + if let Ok(t) = ui_data.clipboard.get_text() { text.insert(&t); } - self.run_event(sel, Edited, ()); + ui.run_event(app_data, sel, Edited, ()); } TextInputResult::Copy(text) => { - if let Err(err) = ui.clipboard.set_text(text) { + if let Err(err) = ui_data.clipboard.set_text(text) { eprintln!("failed to copy text to clipboard: {err}") } } TextInputResult::Used => { - self.run_event(sel, Edited, ()); + ui.run_event(app_data, sel, Edited, ()); } TextInputResult::Unused => {} } } } WindowEvent::Ime(ime) => { - if let Some(sel) = &ui.focus { - let mut text = ui.ui.text(sel); + if let Some(sel) = &ui_data.focus { + let mut text = ui.text(sel); match ime { Ime::Enabled | Ime::Disabled => (), Ime::Preedit(content, _pos) => { // TODO: highlight once that's real - text.replace(ui.ime, content); - ui.ime = content.chars().count(); + text.replace(ui_data.ime, content); + ui_data.ime = content.chars().count(); } Ime::Commit(content) => { text.insert(content); @@ -151,41 +165,14 @@ impl UiState for State { } _ => (), } - self.window_event(event); - let ui = self.ui(); + app_data.window_event(event, ui, ui_data); if ui.needs_redraw() { - ui.renderer.window().request_redraw(); + ui_data.renderer.window().request_redraw(); } - ui.input.end_frame(); + ui_data.input.end_frame(); } fn exit(&mut self) { - self.exit(); - } -} - -impl UiCtx for T { - fn ui(&mut self) -> &mut Ui { - &mut self.ui().ui - } -} - -impl UiCtx for DefaultUi { - fn ui(&mut self) -> &mut Ui { - &mut self.ui - } -} - -impl Deref for DefaultUi { - type Target = Ui; - - fn deref(&self) -> &Self::Target { - &self.ui - } -} - -impl DerefMut for DefaultUi { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.ui + self.app_state.exit(&mut self.ui, &self.ui_state); } }