redo event fn signature & add event_ctx macro
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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 EventFn<Ctx, E::Data>,
|
||||
) -> impl WidgetIdFn<W::Widget> {
|
||||
f: impl WidgetEventFn<Ctx, E::Data, W>,
|
||||
) -> impl WidgetIdFn<W> + Eventable<W, IdFnTag>;
|
||||
}
|
||||
|
||||
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);
|
||||
ui.register_event(&id, event, f);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
139
src/winit/mod.rs
139
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<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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user