Compare commits

1 Commits

Author SHA1 Message Date
62aa02847a redo event fn signature & add event_ctx macro 2025-12-06 20:48:10 -05:00
10 changed files with 270 additions and 274 deletions

View File

@@ -1,20 +1,14 @@
use iris::{prelude::*, winit::*};
fn main() {
UiApp::<State>::run();
DefaultApp::<State>::run();
}
struct State {
ui: DefaultUi,
}
struct State;
impl DefaultUiState for State {
fn new(mut ui: DefaultUi, _proxy: Proxy<Self::Event>) -> 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::Event>) -> Self {
rect(Color::RED).set_root(ui);
Self
}
}

View File

@@ -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)

View File

@@ -7,16 +7,17 @@ use len_fns::*;
use winit::event::WindowEvent;
fn main() {
UiApp::<Client>::run();
DefaultApp::<Client>::run();
}
pub struct Client {
ui: DefaultUi,
info: WidgetId<Text>,
}
impl DefaultUiState for Client {
fn new(mut ui: DefaultUi, _proxy: Proxy<Self::Event>) -> Self {
event_ctx!(Client);
impl DefaultAppState for Client {
fn new(ui: &mut Ui, _state: &UiState, _proxy: Proxy<Self::Event>) -> 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::<Selectable>(())
.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::<Selectable>(());
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();
}
}

View File

@@ -166,20 +166,21 @@ impl<Ctx> CursorModule<Ctx> {
}
}
pub trait SensorCtx: UiCtx {
fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2);
}
impl<Ctx: UiCtx + 'static> SensorCtx for Ctx {
fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2) {
CursorModule::<Ctx>::run(self, cursor, window_size);
impl Ui {
pub fn run_sensors<Ctx: 'static>(
&mut self,
ctx: &mut Ctx,
cursor: &CursorState,
window_size: Vec2,
) {
CursorModule::<Ctx>::run(self, ctx, cursor, window_size);
}
}
impl<Ctx: UiCtx + 'static> CursorModule<Ctx> {
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::<Self>());
impl<Ctx: 'static> CursorModule<Ctx> {
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::<Self>());
for i in layers.indices().rev() {
let Some(list) = module.active.get_mut(&i) else {
@@ -204,7 +205,7 @@ impl<Ctx: UiCtx + 'static> CursorModule<Ctx> {
scroll_delta: cursor.scroll_delta,
sense,
};
(sensor.f)(ctx, data);
(sensor.f)(EventCtx { ui, state: ctx, data });
}
}
}
@@ -213,10 +214,10 @@ impl<Ctx: UiCtx + 'static> CursorModule<Ctx> {
}
}
let ui_mod = ctx.ui().data.modules.get_mut::<Self>();
let ui_mod = ui.data.modules.get_mut::<Self>();
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<E: Event<Data = <CursorSenses as Event>::Data> + Into<CursorSenses>, Ctx: '
});
}
fn run<'a>(&self, id: &Id, event: E) -> Option<impl Fn(&mut Ctx, E::Data) + use<'a, E, Ctx>> {
fn run<'a>(
&self,
id: &Id,
event: E,
) -> Option<impl Fn(EventCtx<Ctx, <E as Event>::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<E: Event<Data = <CursorSenses as Event>::Data> + Into<CursorSenses>, Ctx: '
}
})
.collect();
Some(move |ctx: &mut Ctx, data: CursorData| {
Some(move |ctx: EventCtx<Ctx, CursorData>| {
for f in &fs {
f(ctx, data.clone());
f(EventCtx {
state: ctx.state,
ui: ctx.ui,
data: ctx.data.clone(),
});
}
})
} else {

View File

@@ -100,10 +100,13 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
}
fn scroll(self) -> impl WidgetIdFn<Scroll> {
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)
}

View File

@@ -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<Ctx: 'static>: EventModule<Self, Ctx>;
type Data: Clone;
}
pub trait EventFn<Ctx, Data>: Fn(&mut Ctx, Data) + 'static {}
impl<F: Fn(&mut Ctx, Data) + 'static, Ctx, Data> EventFn<Ctx, Data> for F {}
pub trait Eventable<W, Tag> {
fn on<E: Event, Ctx: 'static>(
self,
event: E,
f: impl EventFn<Ctx, E::Data>,
) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag>;
fn id_on<E: Event, Ctx: 'static>(
self,
event: E,
f: impl Fn(&WidgetId<W>, &mut Ctx, E::Data) + 'static,
) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag>
where
W: Widget;
fn edit_on<E: Event>(
self,
event: E,
f: impl Fn(&mut W, E::Data) + 'static,
) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag>
where
W: Widget;
pub struct EventCtx<'a, Ctx, Data> {
pub ui: &'a mut Ui,
pub state: &'a mut Ctx,
pub data: Data,
}
impl<W: WidgetLike<Tag>, Tag> Eventable<W::Widget, Tag> for W {
fn on<E: Event, Ctx: 'static>(
self,
event: E,
f: impl EventFn<Ctx, E::Data>,
) -> impl WidgetIdFn<W::Widget> {
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<W>,
pub ui: &'a mut Ui,
pub state: &'a mut Ctx,
pub data: Data,
}
pub trait EventFn<Ctx, Data>: Fn(EventCtx<Ctx, Data>) + 'static {}
impl<F: Fn(EventCtx<Ctx, Data>) + 'static, Ctx, Data> EventFn<Ctx, Data> for F {}
pub trait WidgetEventFn<Ctx, Data, W>: Fn(EventIdCtx<Ctx, Data, W>) + 'static {}
impl<F: Fn(EventIdCtx<Ctx, Data, W>) + 'static, Ctx, Data, W> WidgetEventFn<Ctx, Data, W> 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<W, Tag, Ctx: 'static> {
fn on<E: Event>(
self,
event: E,
f: impl WidgetEventFn<Ctx, E::Data, W>,
) -> impl WidgetIdFn<W> + EventableCtx<W, IdFnTag, Ctx>;
}
impl<WL: WidgetLike<Tag>, Tag> EventableCtx<WL::Widget, Tag, $ty> for WL {
fn on<E: Event>(
self,
event: E,
f: impl WidgetEventFn<$ty, E::Data, WL::Widget>,
) -> impl WidgetIdFn<WL::Widget> + EventableCtx<WL::Widget, IdFnTag, $ty> {
eventable::Eventable::on(self, event, f)
}
}
}
use local_event_trait::*;
};
}
pub use event_ctx;
pub mod eventable {
use super::*;
pub trait Eventable<W, Tag> {
fn on<E: Event, Ctx: 'static>(
self,
event: E,
f: impl WidgetEventFn<Ctx, E::Data, W>,
) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag>;
}
fn id_on<E: Event, Ctx: 'static>(
self,
event: E,
f: impl Fn(&WidgetId<W::Widget>, &mut Ctx, E::Data) + 'static,
) -> impl WidgetIdFn<W::Widget>
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<E: Event>(
self,
event: E,
f: impl Fn(&mut W::Widget, E::Data) + 'static,
) -> impl WidgetIdFn<W::Widget>
where
W::Widget: Widget,
{
self.id_on(event, move |id, ui: &mut Ui, pos| f(&mut ui[id], pos))
impl<WL: WidgetLike<Tag>, Tag> Eventable<WL::Widget, Tag> for WL {
fn on<E: Event, Ctx: 'static>(
self,
event: E,
f: impl WidgetEventFn<Ctx, E::Data, WL::Widget>,
) -> impl WidgetIdFn<WL::Widget> {
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<E: Event, Ctx>: UiModule + Default {
&self,
id: &Id,
event: E,
) -> Option<impl Fn(&mut Ctx, E::Data) + use<'a, Self, E, Ctx>>;
) -> Option<impl Fn(EventCtx<Ctx, E::Data>) + use<'a, Self, E, Ctx>>;
}
type EventFnMap<Ctx, Data> = HashMap<Id, Vec<Rc<dyn EventFn<Ctx, Data>>>>;
@@ -132,14 +139,22 @@ impl<E: HashableEvent, Ctx: 'static> EventModule<E, Ctx> for DefaultEventModule<
.push(Rc::new(f));
}
fn run<'a>(&self, id: &Id, event: E) -> Option<impl Fn(&mut Ctx, E::Data) + use<'a, E, Ctx>> {
fn run<'a>(
&self,
id: &Id,
event: E,
) -> Option<impl Fn(EventCtx<Ctx, E::Data>) + 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<Ctx, <E as Event>::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<E: HashableEvent, Ctx: 'static> EventModule<E, Ctx> for DefaultEventModule<
}
impl<E: HashableEvent, Ctx: 'static> DefaultEventModule<E, Ctx> {
pub fn run_all(&self, ctx: &mut Ctx, event: E, data: E::Data)
pub fn run_all(&self, event: E, ctx: EventCtx<Ctx, E::Data>)
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<E: Event + 'static, Ctx: 'static> Default for DefaultEventModule<E, Ctx> {
}
impl Ui {
pub fn run_event<E: Event, Ctx: UiCtx + 'static, W>(
pub fn run_event<E: Event, Ctx: 'static, W>(
&mut self,
ctx: &mut Ctx,
id: &WidgetId<W>,
event: E,
data: E::Data,
) {
if let Some(f) = ctx
.ui()
if let Some(f) = self
.data
.modules
.get_mut::<E::Module<Ctx>>()
.run(&id.id, event)
{
f(ctx, data);
f(EventCtx {
ui: self,
state: ctx,
data,
});
}
}
}
pub trait EventCtx: UiCtx {
fn run_event<E: Event + Clone, W>(&mut self, id: &WidgetId<W>, event: E, data: E::Data);
}
impl<Ctx: UiCtx + 'static> EventCtx for Ctx {
fn run_event<E: Event + Clone, W>(&mut self, id: &WidgetId<W>, event: E, data: E::Data) {
Ui::run_event(self, id, event.clone(), data.clone());
}
}

View File

@@ -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::Event>) -> 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<State: UiState> {
pub struct App<State: AppState> {
state: Option<State>,
proxy: EventLoopProxy<State::Event>,
}
impl<State: UiState> UiApp<State> {
impl<State: AppState> App<State> {
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> { state: None, proxy })
.run_app(&mut App::<State> { state: None, proxy })
.unwrap();
}
}
impl<State: UiState> ApplicationHandler<State::Event> for UiApp<State> {
impl<State: AppState> ApplicationHandler<State::Event> for App<State> {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.state.is_none() {
let state = State::new(event_loop, self.proxy.clone());

View File

@@ -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<W: 'static> WidgetAttr<W> 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<TextEdit> for Selectable {
fn run(ui: &mut Ui, id: &WidgetId<TextEdit>, _: 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<TextEdit>, ui: &mut DefaultUi, data: CursorData) {
fn select(ui: &mut Ui, id: WidgetId<TextEdit>, 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::<f32>::from(region.top_left.tuple()),
LogicalSize::<f32>::from(region.size().tuple()),
);
}
ui.focus = Some(id);
state.focus = Some(id);
}

View File

@@ -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()

View File

@@ -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<Event> = EventLoopProxy<Event>;
pub type DefaultApp<Data> = App<DefaultState<Data>>;
pub struct DefaultUi {
pub struct DefaultState<AppState> {
ui: Ui,
ui_state: UiState,
app_state: AppState,
}
pub struct UiState {
pub renderer: UiRenderer,
pub input: Input,
pub ui: Ui,
pub focus: Option<WidgetId<TextEdit>>,
pub clipboard: Clipboard,
pub window: Arc<Window>,
@@ -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::Event>) -> Self;
fn ui(&mut self) -> &mut DefaultUi;
fn new(ui: &mut Ui, state: &UiState, proxy: Proxy<Self::Event>) -> 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<State: DefaultUiState> UiState for State {
impl<State: DefaultAppState> AppState for DefaultState<State> {
type Event = State::Event;
fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> 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<State: DefaultUiState> 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<State: DefaultUiState> 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<T: DefaultUiState> 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);
}
}