remove typed stuff / just specify rsc if needed

This commit is contained in:
2026-01-03 18:06:05 -05:00
parent 59901b6580
commit f2ac6f195f
14 changed files with 126 additions and 164 deletions

View File

@@ -2,13 +2,13 @@ use crate::{HasEvents, HasUi, Widget, WidgetRef};
pub struct EventCtx<'a, Rsc: HasEvents, Data> { pub struct EventCtx<'a, Rsc: HasEvents, Data> {
pub state: &'a mut Rsc::State, pub state: &'a mut Rsc::State,
pub data: &'a mut Data, pub data: Data,
} }
pub struct EventIdCtx<'a, Rsc: HasEvents, Data, W: ?Sized> { pub struct EventIdCtx<'a, Rsc: HasEvents, Data, W: ?Sized> {
pub widget: WidgetRef<W>, pub widget: WidgetRef<W>,
pub state: &'a mut Rsc::State, pub state: &'a mut Rsc::State,
pub data: &'a mut Data, pub data: Data,
} }
impl<Rsc: HasEvents + HasUi, Data, W: Widget> EventIdCtx<'_, Rsc, Data, W> { impl<Rsc: HasEvents + HasUi, Data, W: Widget> EventIdCtx<'_, Rsc, Data, W> {

View File

@@ -28,7 +28,7 @@ impl<Rsc: HasEvents + 'static> EventManager<Rsc> {
&mut self, &mut self,
id: WidgetRef<W>, id: WidgetRef<W>,
event: E, event: E,
f: impl for<'a> WidgetEventFn<Rsc, <E::Event as Event>::Data<'a>, W>, f: impl WidgetEventFn<Rsc, <E::Event as Event>::Data, W>,
) { ) {
self.get_type::<E>().register(id, event, f); self.get_type::<E>().register(id, event, f);
self.widget_to_types self.widget_to_types
@@ -74,7 +74,7 @@ pub trait EventManagerLike<State> {
fn undraw(&mut self, data: &ActiveData); fn undraw(&mut self, data: &ActiveData);
} }
type EventData<Rsc, E> = (E, Rc<dyn for<'a> EventFn<Rsc, <E as Event>::Data<'a>>>); type EventData<Rsc, E> = (E, Rc<dyn EventFn<Rsc, <E as Event>::Data>>);
pub struct TypeEventManager<Rsc: HasEvents, E: Event> { pub struct TypeEventManager<Rsc: HasEvents, E: Event> {
// TODO: reduce visiblity!! // TODO: reduce visiblity!!
pub active: HashMap<LayerId, HashMap<WidgetId, E::State>>, pub active: HashMap<LayerId, HashMap<WidgetId, E::State>>,
@@ -116,18 +116,20 @@ impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> {
&mut self, &mut self,
widget: WidgetRef<W>, widget: WidgetRef<W>,
event: impl EventLike<Event = E>, event: impl EventLike<Event = E>,
f: impl for<'a> WidgetEventFn<Rsc, E::Data<'a>, W>, f: impl WidgetEventFn<Rsc, E::Data, W>,
) { ) {
let event = event.into_event(); let event = event.into_event();
self.map.entry(widget.id()).or_default().push(( self.map.entry(widget.id()).or_default().push((
event, event,
Rc::new(move |ctx, rsc| { Rc::new(move |ctx, rsc| {
let mut test = EventIdCtx { f(
widget, EventIdCtx {
state: ctx.state, widget,
data: ctx.data, state: ctx.state,
}; data: ctx.data,
f(&mut test, rsc); },
rsc,
);
}), }),
)); ));
} }
@@ -135,15 +137,15 @@ impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> {
pub fn run_fn<'a>( pub fn run_fn<'a>(
&mut self, &mut self,
id: impl IdLike, id: impl IdLike,
) -> impl for<'b> FnOnce(EventCtx<'_, Rsc, E::Data<'b>>, &mut Rsc) + 'a { ) -> impl FnOnce(EventCtx<'_, Rsc, E::Data>, &mut Rsc) + 'a {
let fs = self.map.get(&id.id()).cloned().unwrap_or_default(); let fs = self.map.get(&id.id()).cloned().unwrap_or_default();
move |ctx, rsc| { move |ctx, rsc| {
for (e, f) in fs { for (e, f) in fs {
if e.should_run(ctx.data) { if let Some(data) = e.should_run(&ctx.data) {
f( f(
&mut EventCtx { EventCtx {
state: ctx.state, state: ctx.state,
data: ctx.data, data,
}, },
rsc, rsc,
) )

View File

@@ -7,11 +7,11 @@ pub use manager::*;
pub use rsc::*; pub use rsc::*;
pub trait Event: Sized + 'static + Clone { pub trait Event: Sized + 'static + Clone {
type Data<'a> = (); type Data: Clone = ();
type State: Default = (); type State: Default = ();
#[allow(unused_variables)] #[allow(unused_variables)]
fn should_run(&self, data: &mut Self::Data<'_>) -> bool { fn should_run(&self, data: &Self::Data) -> Option<Self::Data> {
true Some(data.clone())
} }
} }
@@ -28,17 +28,17 @@ impl<E: Event> EventLike for E {
} }
} }
pub trait EventFn<Rsc: HasEvents, Data>: Fn(&mut EventCtx<Rsc, Data>, &mut Rsc) + 'static {} pub trait EventFn<Rsc: HasEvents, Data>: Fn(EventCtx<Rsc, Data>, &mut Rsc) + 'static {}
impl<Rsc: HasEvents, F: Fn(&mut EventCtx<Rsc, Data>, &mut Rsc) + 'static, Data> EventFn<Rsc, Data> impl<Rsc: HasEvents, F: Fn(EventCtx<Rsc, Data>, &mut Rsc) + 'static, Data> EventFn<Rsc, Data>
for F for F
{ {
} }
pub trait WidgetEventFn<Rsc: HasEvents, Data, W: ?Sized>: pub trait WidgetEventFn<Rsc: HasEvents, Data, W: ?Sized>:
Fn(&mut EventIdCtx<Rsc, Data, W>, &mut Rsc) + 'static Fn(EventIdCtx<Rsc, Data, W>, &mut Rsc) + 'static
{ {
} }
impl<Rsc: HasEvents, F: Fn(&mut EventIdCtx<Rsc, Data, W>, &mut Rsc) + 'static, Data, W: ?Sized> impl<Rsc: HasEvents, F: Fn(EventIdCtx<Rsc, Data, W>, &mut Rsc) + 'static, Data, W: ?Sized>
WidgetEventFn<Rsc, Data, W> for F WidgetEventFn<Rsc, Data, W> for F
{ {
} }

View File

@@ -14,7 +14,7 @@ pub trait HasEvents: Sized + HasUi + HasState {
&mut self, &mut self,
id: WidgetRef<W>, id: WidgetRef<W>,
event: E, event: E,
f: impl for<'a> WidgetEventFn<Self, <E::Event as Event>::Data<'a>, W>, f: impl WidgetEventFn<Self, <E::Event as Event>::Data, W>,
) { ) {
self.events_mut().register(id, event, f); self.events_mut().register(id, event, f);
} }
@@ -24,7 +24,7 @@ pub trait RunEvents: HasEvents {
fn run_event<E: EventLike>( fn run_event<E: EventLike>(
&mut self, &mut self,
id: impl IdLike, id: impl IdLike,
data: &mut <E::Event as Event>::Data<'_>, data: <E::Event as Event>::Data,
state: &mut Self::State, state: &mut Self::State,
) { ) {
let f = self.events_mut().get_type::<E>().run_fn(id); let f = self.events_mut().get_type::<E>().run_fn(id);

View File

@@ -171,3 +171,4 @@ fn null_ptr<W: ?Sized>() -> *const W {
} }
unsafe impl<W: ?Sized> Send for WidgetRef<W> {} unsafe impl<W: ?Sized> Send for WidgetRef<W> {}
unsafe impl<W: ?Sized> Sync for WidgetRef<W> {}

View File

@@ -2,7 +2,8 @@ use cosmic_text::Family;
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
use winit::event::WindowEvent; use winit::event::WindowEvent;
iris::state_prelude!(DefaultRsc<Client>); use iris::prelude::*;
type ClientRsc = DefaultRsc<Client>;
fn main() { fn main() {
DefaultApp::<Client>::run(); DefaultApp::<Client>::run();
@@ -119,6 +120,7 @@ impl DefaultAppState for Client {
texts(rsc).push(msg_box); texts(rsc).push(msg_box);
}) })
.add(rsc); .add(rsc);
let text_edit_scroll = ( let text_edit_scroll = (
msg_area.height(rest(1)), msg_area.height(rest(1)),
( (
@@ -126,8 +128,8 @@ impl DefaultAppState for Client {
( (
add_text.width(rest(1)), add_text.width(rest(1)),
Rect::new(Color::GREEN) Rect::new(Color::GREEN)
.on(CursorSense::click(), move |ctx, rsc| { .on(CursorSense::click(), move |ctx, rsc: &mut ClientRsc| {
rsc.run_event::<Submit>(add_text, &mut (), ctx.state); rsc.run_event::<Submit>(add_text, (), ctx.state);
}) })
.sized((40, 40)), .sized((40, 40)),
) )

View File

@@ -1,7 +1,6 @@
use iris::prelude::*;
use std::time::Duration; use std::time::Duration;
iris::state_prelude!(DefaultRsc<State>);
fn main() { fn main() {
DefaultApp::<State>::run(); DefaultApp::<State>::run();
} }
@@ -14,17 +13,15 @@ struct State {
impl DefaultAppState for State { impl DefaultAppState for State {
fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, _: Proxy<Self::Event>) -> Self { fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, _: Proxy<Self::Event>) -> Self {
let rect = rect(Color::RED).add(rsc); let rect = rect(Color::RED).add(rsc);
rect.on(CursorSense::click(), move |_, rsc| { rect.task_on(CursorSense::click(), async move |mut ctx| {
rsc.tasks.spawn(async move |ctx| { tokio::time::sleep(Duration::from_secs(1)).await;
tokio::time::sleep(Duration::from_secs(1)).await; ctx.task.update(move |_, rsc| {
ctx.update(move |_, rsc| { let rect = rect(rsc);
let rect = rect(rsc); if rect.color == Color::RED {
if rect.color == Color::RED { rect.color = Color::GREEN;
rect.color = Color::GREEN; } else {
} else { rect.color = Color::RED;
rect.color = Color::RED; }
}
});
}); });
}) })
.set_root(rsc); .set_root(rsc);

View File

@@ -127,6 +127,12 @@ impl<State: 'static> HasEvents for DefaultRsc<State> {
} }
} }
impl<State: 'static> HasTasks for DefaultRsc<State> {
fn tasks_mut(&mut self) -> &mut Tasks<Self> {
&mut self.tasks
}
}
pub struct DefaultApp<State: DefaultAppState> { pub struct DefaultApp<State: DefaultAppState> {
rsc: DefaultRsc<State>, rsc: DefaultRsc<State>,
state: State, state: State,
@@ -169,7 +175,7 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
if input_changed { if input_changed {
let window_size = ui_state.window_size(); let window_size = ui_state.window_size();
self.rsc self.rsc
.run_sensors(&mut self.state, &cursor_state, window_size); .run_sensors(&mut self.state, cursor_state, window_size);
} }
let ui = &mut self.rsc.ui; let ui = &mut self.rsc.ui;
let ui_state = self.state.default_state_mut(); let ui_state = self.state.default_state_mut();
@@ -200,13 +206,13 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
ui_state.window.set_ime_allowed(false); ui_state.window.set_ime_allowed(false);
} }
TextInputResult::Submit => { TextInputResult::Submit => {
self.rsc.run_event::<Submit>(sel, &mut (), &mut self.state); self.rsc.run_event::<Submit>(sel, (), &mut self.state);
} }
TextInputResult::Paste => { TextInputResult::Paste => {
if let Ok(t) = ui_state.clipboard.get_text() { if let Ok(t) = ui_state.clipboard.get_text() {
text.insert(&t); text.insert(&t);
} }
self.rsc.run_event::<Edited>(sel, &mut (), &mut self.state); self.rsc.run_event::<Edited>(sel, (), &mut self.state);
} }
TextInputResult::Copy(text) => { TextInputResult::Copy(text) => {
if let Err(err) = ui_state.clipboard.set_text(text) { if let Err(err) = ui_state.clipboard.set_text(text) {
@@ -214,7 +220,7 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
} }
} }
TextInputResult::Used => { TextInputResult::Used => {
self.rsc.run_event::<Edited>(sel, &mut (), &mut self.state); self.rsc.run_event::<Edited>(sel, (), &mut self.state);
} }
TextInputResult::Unused => {} TextInputResult::Unused => {}
} }

View File

@@ -26,14 +26,15 @@ pub enum CursorSense {
pub struct CursorSenses(Vec<CursorSense>); pub struct CursorSenses(Vec<CursorSense>);
impl Event for CursorSenses { impl Event for CursorSenses {
type Data<'a> = CursorData<'a>; type Data = CursorData;
type State = SensorState; type State = SensorState;
fn should_run(&self, data: &mut Self::Data<'_>) -> bool { fn should_run(&self, data: &Self::Data) -> Option<Self::Data> {
if let Some(sense) = should_run(self, data.cursor, data.hover) { if let Some(sense) = should_run(self, &data.cursor, data.hover) {
let mut data = data.clone();
data.sense = sense; data.sense = sense;
true Some(data)
} else { } else {
false None
} }
} }
} }
@@ -127,23 +128,24 @@ pub struct SensorState {
pub hover: ActivationState, pub hover: ActivationState,
} }
pub struct CursorData<'a> { #[derive(Clone)]
pub struct CursorData {
/// where this widget was hit /// where this widget was hit
pub pos: Vec2, pub pos: Vec2,
pub size: Vec2, pub size: Vec2,
pub scroll_delta: Vec2, pub scroll_delta: Vec2,
pub hover: ActivationState, pub hover: ActivationState,
pub cursor: &'a CursorState, pub cursor: CursorState,
/// the first sense that triggered this /// the first sense that triggered this
pub sense: CursorSense, pub sense: CursorSense,
} }
pub trait SensorUi<Rsc: HasEvents> { pub trait SensorUi<Rsc: HasEvents> {
fn run_sensors(&mut self, state: &mut Rsc::State, cursor: &CursorState, window_size: Vec2); fn run_sensors(&mut self, state: &mut Rsc::State, cursor: CursorState, window_size: Vec2);
} }
impl<Rsc: HasEvents> SensorUi<Rsc> for Rsc { impl<Rsc: HasEvents> SensorUi<Rsc> for Rsc {
fn run_sensors(&mut self, state: &mut Rsc::State, cursor: &CursorState, window_size: Vec2) { fn run_sensors(&mut self, state: &mut Rsc::State, cursor: CursorState, window_size: Vec2) {
let layers = std::mem::take(&mut self.ui_mut().layers); let layers = std::mem::take(&mut self.ui_mut().layers);
let mut active = std::mem::take(&mut self.events_mut().get_type::<CursorSense>().active); let mut active = std::mem::take(&mut self.events_mut().get_type::<CursorSense>().active);
for layer in layers.indices().rev() { for layer in layers.indices().rev() {
@@ -158,7 +160,9 @@ impl<Rsc: HasEvents> SensorUi<Rsc> for Rsc {
} }
sensed = true; sensed = true;
let mut data = CursorData { let cursor = cursor.clone();
let data = CursorData {
pos: cursor.pos - region.top_left, pos: cursor.pos - region.top_left,
size: region.bot_right - region.top_left, size: region.bot_right - region.top_left,
scroll_delta: cursor.scroll_delta, scroll_delta: cursor.scroll_delta,
@@ -168,7 +172,7 @@ impl<Rsc: HasEvents> SensorUi<Rsc> for Rsc {
// might wanna set up Event to have a prepare stage // might wanna set up Event to have a prepare stage
sense: CursorSense::Hovering, sense: CursorSense::Hovering,
}; };
self.run_event::<CursorSense>(*id, &mut data, state); self.run_event::<CursorSense>(*id, data, state);
} }
if sensed { if sensed {
break; break;

View File

@@ -62,17 +62,14 @@ impl<Rsc: HasState> Tasks<Rsc> {
) )
} }
pub fn spawn<F: for<'a> AsyncFnOnce(&'a mut TaskCtx<Rsc>) + 'static + std::marker::Send>( pub fn spawn<F: AsyncFnOnce(TaskCtx<Rsc>) + 'static + std::marker::Send>(&mut self, task: F)
&mut self, where
task: F, F::CallOnceFuture: Send,
) where
for<'a> <F as AsyncFnOnce<(&'a mut TaskCtx<Rsc>,)>>::CallOnceFuture: Send,
{ {
let send = self.msg_send.clone(); let send = self.msg_send.clone();
let window = self.window.clone(); let window = self.window.clone();
let _ = self.start.send(Box::pin(async move { let _ = self.start.send(Box::pin(async move {
let mut ctx = TaskCtx::new(send); task(TaskCtx::new(send)).await;
task(&mut ctx).await;
window.request_redraw(); window.request_redraw();
})); }));
} }

View File

@@ -1,54 +1,60 @@
use iris_core::*; use iris_core::*;
use iris_macro::*; use iris_macro::*;
use std::sync::Arc;
use crate::default::{TaskCtx, Tasks}; use crate::default::{TaskCtx, Tasks};
pub mod eventable { pub trait Eventable<Rsc: HasEvents, Tag>: WidgetLike<Rsc, Tag> {
use super::*; fn on<E: EventLike>(
widget_trait! { self,
pub trait Eventable<Rsc: HasEvents>; event: E,
fn on<E: EventLike>( f: impl WidgetEventFn<Rsc, <E::Event as Event>::Data, Self::Widget>,
self, ) -> impl WidgetIdFn<Rsc, Self::Widget> {
event: E, move |rsc| {
f: impl for<'a> WidgetEventFn<Rsc, <E::Event as Event>::Data<'a>, WL::Widget>, let id = self.add(rsc);
) -> impl WidgetIdFn<Rsc, WL::Widget> { rsc.register_event(id, event.into_event(), move |ctx, rsc| {
move |rsc| { f(
let id = self.add(rsc); EventIdCtx {
rsc.register_event(id, event.into_event(), move |ctx, rsc| {
f(&mut EventIdCtx {
widget: id, widget: id,
state: ctx.state, state: ctx.state,
data: ctx.data, data: ctx.data,
}, rsc); },
}); rsc,
id );
} });
id
} }
} }
}
impl<WL: WidgetLike<Rsc, Tag>, Rsc: HasEvents, Tag> Eventable<Rsc, Tag> for WL {}
// widget_trait! { widget_trait! {
// pub trait TaskEventable<Rsc: HasEvents + HasTasks>; pub trait TaskEventable<Rsc: HasEvents + HasTasks>;
// fn task_on<E: EventLike>( fn task_on<E: EventLike, F: AsyncWidgetEventFn<Rsc, <E::Event as Event>::Data, WL::Widget>>(
// self, self,
// event: E, event: E,
// f: impl for<'a> AsyncWidgetEventFn<Rsc, <E::Event as Event>::Data<'a>, WL::Widget>, f: F,
// ) -> impl WidgetIdFn<Rsc, WL::Widget> { ) -> impl WidgetIdFn<Rsc, WL::Widget>
// move |rsc| { where <E::Event as Event>::Data: Send,
// let id = self.add(rsc); for<'a> F::CallRefFuture<'a>: Send,
// rsc.register_event(id, event.into_event(), move |ctx, rsc| { {
// rsc.tasks_mut().spawn(async move |task| { let f = Arc::new(f);
// f(&mut AsyncEventIdCtx { move |rsc| {
// widget: id, let id = self.add(rsc);
// state: ctx.state, rsc.register_event(id, event.into_event(), move |ctx, rsc| {
// data: ctx.data, let data = ctx.data;
// task, let f = f.clone();
// }, rsc).await; rsc.tasks_mut().spawn(async move |task| {
// }); f(AsyncEventIdCtx {
// }); widget: id,
// id data,
// } task,
// } }).await;
// } });
});
id
}
}
} }
pub trait HasTasks: Sized + HasState + HasEvents { pub trait HasTasks: Sized + HasState + HasEvents {
@@ -56,21 +62,20 @@ pub trait HasTasks: Sized + HasState + HasEvents {
} }
pub trait AsyncWidgetEventFn<Rsc: HasEvents, Data, W: ?Sized>: pub trait AsyncWidgetEventFn<Rsc: HasEvents, Data, W: ?Sized>:
AsyncFn(&mut AsyncEventIdCtx<Rsc, Data, W>, &mut Rsc) + 'static AsyncFn(AsyncEventIdCtx<Rsc, Data, W>) + Send + Sync + 'static
{ {
} }
impl< impl<
Rsc: HasEvents, Rsc: HasEvents,
F: AsyncFn(&mut AsyncEventIdCtx<Rsc, Data, W>, &mut Rsc) + 'static, F: AsyncFn(AsyncEventIdCtx<Rsc, Data, W>) + Send + Sync + 'static,
Data, Data,
W: ?Sized, W: ?Sized,
> AsyncWidgetEventFn<Rsc, Data, W> for F > AsyncWidgetEventFn<Rsc, Data, W> for F
{ {
} }
pub struct AsyncEventIdCtx<'a, Rsc: HasEvents, Data, W: ?Sized> { pub struct AsyncEventIdCtx<Rsc: HasEvents, Data, W: ?Sized> {
pub widget: WidgetRef<W>, pub widget: WidgetRef<W>,
pub state: &'a mut Rsc::State, pub data: Data,
pub data: &'a mut Data, pub task: TaskCtx<Rsc>,
pub task: &'a mut TaskCtx<Rsc>,
} }

View File

@@ -8,25 +8,11 @@
pub mod default; pub mod default;
pub mod event; pub mod event;
pub mod typed;
pub mod widget; pub mod widget;
pub use iris_core as core; pub use iris_core as core;
pub use iris_macro as macros; pub use iris_macro as macros;
#[macro_export]
macro_rules! state_prelude {
($vis:vis $state:ty) => {
iris::event_state!($vis $state);
$vis use iris::{
default::*,
core::{len_fns::*, util::Vec2, *},
macros::*,
widget::*,
};
};
}
pub mod prelude { pub mod prelude {
use super::*; use super::*;
pub use default::*; pub use default::*;
@@ -36,5 +22,5 @@ pub mod prelude {
pub use widget::*; pub use widget::*;
pub use iris_core::util::Vec2; pub use iris_core::util::Vec2;
pub use typed::*; pub use len_fns::*;
} }

View File

@@ -1,37 +0,0 @@
#[macro_export]
macro_rules! event_state {
($vis:vis $rsc:ty) => {
mod local_event_trait {
use super::*;
#[allow(unused_imports)]
use $crate::prelude::*;
pub trait EventableCtx<WL: WidgetLike<$rsc, Tag>, Tag> {
fn on<E: EventLike>(
self,
event: E,
f: impl for<'a> WidgetEventFn<
$rsc,
<E::Event as Event>::Data<'a>,
WL::Widget
>,
) -> impl WidgetIdFn<$rsc, WL::Widget>;
}
impl<WL: WidgetLike<$rsc, Tag>, Tag> EventableCtx<WL, Tag> for WL {
fn on<E: EventLike>(
self,
event: E,
f: impl for<'a> WidgetEventFn<
$rsc,
<E::Event as Event>::Data<'a>,
WL::Widget
>,
) -> impl WidgetIdFn<$rsc, WL::Widget> {
eventable::Eventable::on(self, event, f)
}
}
}
$vis type EventManager = $crate::prelude::EventManager<<$rsc as HasState>::State>;
$vis use local_event_trait::*;
};
}
pub use event_state;

View File

@@ -84,7 +84,6 @@ widget_trait! {
} }
fn scrollable(self) -> impl WidgetIdFn<Rsc, Scroll> where Rsc: HasEvents { fn scrollable(self) -> impl WidgetIdFn<Rsc, Scroll> where Rsc: HasEvents {
use eventable::Eventable;
move |state| { move |state| {
Scroll::new(self.add_strong(state), Axis::Y) Scroll::new(self.add_strong(state), Axis::Y)
.on(CursorSense::Scroll, |ctx, rsc| { .on(CursorSense::Scroll, |ctx, rsc| {
@@ -132,9 +131,9 @@ widget_trait! {
} }
} }
pub trait CoreWidgetArr<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag> { pub trait CoreWidgetArr<Rsc, const LEN: usize, Wa: WidgetArrLike<Rsc, LEN, Tag>, Tag> {
fn span(self, dir: Dir) -> SpanBuilder<State, LEN, Wa, Tag>; fn span(self, dir: Dir) -> SpanBuilder<Rsc, LEN, Wa, Tag>;
fn stack(self) -> StackBuilder<State, LEN, Wa, Tag>; fn stack(self) -> StackBuilder<Rsc, LEN, Wa, Tag>;
} }
impl<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag> impl<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag>