15 Commits

Author SHA1 Message Date
7b54aaf3c4 readme 2026-01-29 16:39:19 -05:00
17c436d944 stuff 2026-01-22 23:33:46 -05:00
a592318a6f impl idlike for widgetview 2026-01-20 18:11:21 -05:00
796bc41752 didn't actually remove state on widget remove (mem leak) 2026-01-19 21:39:13 -05:00
7bafb04a34 widget state 2026-01-19 20:39:58 -05:00
06dd015092 finished moving out render_state 2026-01-19 18:00:24 -05:00
79813db3ba work 2026-01-12 18:40:27 -05:00
a9c76e4326 lol bounds 2026-01-05 17:50:18 -05:00
32e45e9238 griefed last commit 2026-01-05 17:20:11 -05:00
2fadfe4b82 app event stuff bruh 2026-01-05 17:16:56 -05:00
d11107f965 widget view 2026-01-05 17:01:09 -05:00
07de7c8722 update deps 2026-01-04 14:45:18 -05:00
f2ac6f195f remove typed stuff / just specify rsc if needed 2026-01-03 18:06:05 -05:00
59901b6580 tasks initial impl (still working on task_on trait method) 2026-01-03 16:26:23 -05:00
5da1e9e767 separate state from rsc 2026-01-01 22:18:08 -05:00
53 changed files with 1852 additions and 1489 deletions

709
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,5 @@
[package] [package]
name = "iris" name = "iris"
default-run = "test"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
@@ -16,6 +15,10 @@ arboard = { workspace = true, features = ["wayland-data-control"] }
pollster = { workspace = true } pollster = { workspace = true }
wgpu = { workspace = true } wgpu = { workspace = true }
image = { workspace = true } image = { workspace = true }
tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread"] }
[dev-dependencies]
tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread", "time"] }
[workspace] [workspace]
members = ["core", "macro"] members = ["core", "macro"]
@@ -27,12 +30,13 @@ edition = "2024"
[workspace.dependencies] [workspace.dependencies]
pollster = "0.4.0" pollster = "0.4.0"
winit = "0.30.12" winit = "0.30.12"
wgpu = "27.0.1" wgpu = "28.0.0"
bytemuck = "1.23.1" bytemuck = "1.23.1"
image = "0.25.6" image = "0.25.6"
cosmic-text = "0.15.0" cosmic-text = "0.16.0"
unicode-segmentation = "1.12.0" unicode-segmentation = "1.12.0"
fxhash = "0.2.1" fxhash = "0.2.1"
arboard = "3.6.1" arboard = "3.6.1"
iris-core = { path = "core" } iris-core = { path = "core" }
iris-macro = { path = "macro" } iris-macro = { path = "macro" }
tokio = "1.49.0"

View File

@@ -1,24 +1,22 @@
use crate::{HasUi, StateLike, WidgetIdFn, WidgetLike, WidgetRef}; use crate::{UiRsc, WidgetIdFn, WidgetLike, WeakWidget};
pub trait WidgetAttr<State, W: ?Sized> { pub trait WidgetAttr<Rsc, W: ?Sized> {
type Input; type Input;
fn run(state: &mut State, id: WidgetRef<W>, input: Self::Input); fn run(rsc: &mut Rsc, id: WeakWidget<W>, input: Self::Input);
} }
pub trait Attrable<State, W: ?Sized, Tag> { pub trait Attrable<Rsc, W: ?Sized, Tag> {
fn attr<A: WidgetAttr<State, W>>(self, input: A::Input) -> impl WidgetIdFn<State, W>; fn attr<A: WidgetAttr<Rsc, W>>(self, input: A::Input) -> impl WidgetIdFn<Rsc, W>;
} }
impl<State: HasUi + StateLike<State>, WL: WidgetLike<State, Tag>, Tag> impl<Rsc: UiRsc, WL: WidgetLike<Rsc, Tag>, Tag> Attrable<Rsc, WL::Widget, Tag> for WL {
Attrable<State, WL::Widget, Tag> for WL fn attr<A: WidgetAttr<Rsc, WL::Widget>>(
{
fn attr<A: WidgetAttr<State, WL::Widget>>(
self, self,
input: A::Input, input: A::Input,
) -> impl WidgetIdFn<State, WL::Widget> { ) -> impl WidgetIdFn<Rsc, WL::Widget> {
|state| { |rsc| {
let id = self.add(state); let id = self.add(rsc);
A::run(state, id, input); A::run(rsc, id, input);
id id
} }
} }

View File

@@ -1,116 +1,18 @@
use crate::{HasEvents, HasState, HasUi, StateLike, Ui, Widget, WidgetRef}; use crate::{HasEvents, WeakWidget, Widget};
use std::ops::{Deref, DerefMut};
pub struct EventCtx<'a, State, Data> { pub struct EventCtx<'a, Rsc: HasEvents, Data> {
pub state: &'a mut State, pub state: &'a mut Rsc::State,
pub data: &'a mut Data, pub data: Data,
} }
pub struct EventIdCtx<'a, State, Data, W: ?Sized> { pub struct EventIdCtx<'a, Rsc: HasEvents, Data, W: ?Sized> {
pub widget: WidgetRef<W>, pub widget: WeakWidget<W>,
pub state: &'a mut State, pub state: &'a mut Rsc::State,
pub data: &'a mut Data, pub data: Data,
} }
impl<State: HasUi, Data, W: ?Sized> Deref for EventIdCtx<'_, State, Data, W> { impl<Rsc: HasEvents, Data, W: Widget> EventIdCtx<'_, Rsc, Data, W> {
type Target = State; pub fn widget<'a>(&self, rsc: &'a mut Rsc) -> &'a mut W {
&mut rsc.ui_mut().widgets[self.widget]
fn deref(&self) -> &Self::Target {
self.state
} }
} }
impl<State: HasUi, Data, W: ?Sized> DerefMut for EventIdCtx<'_, State, Data, W> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.state
}
}
impl<State: HasUi, Data, W: Widget> EventIdCtx<'_, State, Data, W> {
pub fn widget(&mut self) -> &mut W {
&mut self.state.get_mut()[self.widget]
}
}
impl<State: HasUi, Data, W: Widget> HasUi for EventIdCtx<'_, State, Data, W> {
fn get(&self) -> &Ui {
self.state.ui()
}
fn get_mut(&mut self) -> &mut Ui {
self.state.ui_mut()
}
}
impl<State: HasUi, Data, W: Widget> HasState for EventIdCtx<'_, State, Data, W> {
type State = State;
}
impl<State: HasEvents<State = State>, Data, W: Widget> HasEvents
for EventIdCtx<'_, State, Data, W>
{
fn get(&self) -> &super::EventManager<Self::State> {
self.state.events()
}
fn get_mut(&mut self) -> &mut super::EventManager<Self::State> {
self.state.events_mut()
}
}
impl<State, Data, W: Widget> StateLike<State> for EventIdCtx<'_, State, Data, W> {
fn as_state(&mut self) -> &mut State {
self.state
}
}
// fn test() {
// use crate::*;
// struct ClientRsc;
// impl<State, Data> HasUi for EventCtx<'_, State, Data> {
// fn get(&self) -> &Ui {
// todo!()
// }
//
// fn get_mut(&mut self) -> &mut Ui {
// todo!()
// }
// }
// fn on(_: impl for<'a> EventFn<ClientRsc, &'a mut i32>) {}
//
// pub trait WidgetLike<State: HasUi, Tag>: Sized {
// type Widget: Widget + ?Sized + std::marker::Unsize<dyn Widget>;
//
// fn add(self, state: &mut State) -> WidgetHandle<Self::Widget>;
//
// fn with_id<W2>(
// self,
// f: impl FnOnce(&mut State, WidgetHandle<Self::Widget>) -> WidgetHandle<W2>,
// ) -> impl WidgetIdFn<State, W2> {
// move |state| {
// let id = self.add(state);
// f(state, id)
// }
// }
//
// fn set_root(self, state: &mut State) {
// state.get_mut().root = Some(self.add(state));
// }
//
// fn handles(self, state: &mut State) -> WidgetHandles<Self::Widget> {
// self.add(state).handles()
// }
// }
//
// pub struct WidgetTag;
// impl<State: HasUi, W: Widget> WidgetLike<State, WidgetTag> for W {
// type Widget = W;
// fn add(self, state: &mut State) -> WidgetHandle<W> {
// state.get_mut().add_widget(self)
// }
// }
//
// on(move |ctx| {
// ().add(ctx);
// });
// }

View File

@@ -1,16 +1,16 @@
use crate::{ use crate::{
ActiveData, Event, EventCtx, EventFn, EventIdCtx, EventLike, IdLike, LayerId, Widget, ActiveData, Event, EventCtx, EventFn, EventIdCtx, EventLike, HasEvents, IdLike, LayerId,
WidgetEventFn, WidgetId, WidgetRef, WeakWidget, WidgetEventFn, WidgetId,
util::{HashMap, HashSet, TypeMap}, util::{HashMap, HashSet, TypeMap},
}; };
use std::{any::TypeId, rc::Rc}; use std::{any::TypeId, rc::Rc};
pub struct EventManager<State> { pub struct EventManager<Rsc> {
widget_to_types: HashMap<WidgetId, HashSet<TypeId>>, widget_to_types: HashMap<WidgetId, HashSet<TypeId>>,
types: TypeMap<dyn EventManagerLike<State>>, types: TypeMap<dyn EventManagerLike<Rsc>>,
} }
impl<State> Default for EventManager<State> { impl<Rsc> Default for EventManager<Rsc> {
fn default() -> Self { fn default() -> Self {
Self { Self {
widget_to_types: Default::default(), widget_to_types: Default::default(),
@@ -19,26 +19,27 @@ impl<State> Default for EventManager<State> {
} }
} }
impl<State: 'static> EventManager<State> { impl<Rsc: HasEvents + 'static> EventManager<Rsc> {
pub fn get_type<E: EventLike>(&mut self) -> &mut TypeEventManager<State, E::Event> { pub fn get_type<E: EventLike>(&mut self) -> &mut TypeEventManager<Rsc, E::Event> {
self.types.type_or_default() self.types.type_or_default()
} }
pub fn register<W: Widget + ?Sized, E: EventLike>( pub fn register<I: IdLike + 'static, E: EventLike>(
&mut self, &mut self,
id: WidgetRef<W>, id: I,
event: E, event: E,
f: impl for<'a> WidgetEventFn<State, <E::Event as Event>::Data<'a>, W>, f: impl for<'a> WidgetEventFn<Rsc, <E::Event as Event>::Data<'a>, I::Widget>,
) { ) {
let i = id.id();
self.get_type::<E>().register(id, event, f); self.get_type::<E>().register(id, event, f);
self.widget_to_types self.widget_to_types
.entry(id.id()) .entry(i)
.or_default() .or_default()
.insert(Self::type_key::<E>()); .insert(Self::type_key::<E>());
} }
pub fn type_key<E: EventLike>() -> TypeId { pub fn type_key<E: EventLike>() -> TypeId {
TypeId::of::<TypeEventManager<State, E::Event>>() TypeId::of::<TypeEventManager<Rsc, E::Event>>()
} }
} }
@@ -48,7 +49,7 @@ pub trait EventsLike {
fn undraw(&mut self, active: &ActiveData); fn undraw(&mut self, active: &ActiveData);
} }
impl<State: 'static> EventsLike for EventManager<State> { impl<Rsc: HasEvents + 'static> EventsLike for EventManager<Rsc> {
fn remove(&mut self, id: WidgetId) { fn remove(&mut self, id: WidgetId) {
for t in self.widget_to_types.get(&id).into_flat_iter() { for t in self.widget_to_types.get(&id).into_flat_iter() {
self.types.get_mut(t).unwrap().remove(id); self.types.get_mut(t).unwrap().remove(id);
@@ -74,14 +75,14 @@ pub trait EventManagerLike<State> {
fn undraw(&mut self, data: &ActiveData); fn undraw(&mut self, data: &ActiveData);
} }
type EventData<State, E> = (E, Rc<dyn for<'a> EventFn<State, <E as Event>::Data<'a>>>); type EventData<Rsc, E> = (E, Rc<dyn for<'a> EventFn<Rsc, <E as Event>::Data<'a>>>);
pub struct TypeEventManager<State, 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>>,
map: HashMap<WidgetId, Vec<EventData<State, E>>>, map: HashMap<WidgetId, Vec<EventData<Rsc, E>>>,
} }
impl<State, E: Event> EventManagerLike<State> for TypeEventManager<State, E> { impl<Rsc: HasEvents, E: Event> EventManagerLike<Rsc> for TypeEventManager<Rsc, E> {
fn remove(&mut self, id: WidgetId) { fn remove(&mut self, id: WidgetId) {
self.map.remove(&id); self.map.remove(&id);
for layer in self.active.values_mut() { for layer in self.active.values_mut() {
@@ -102,7 +103,7 @@ impl<State, E: Event> EventManagerLike<State> for TypeEventManager<State, E> {
} }
} }
impl<State, E: Event> Default for TypeEventManager<State, E> { impl<Rsc: HasEvents, E: Event> Default for TypeEventManager<Rsc, E> {
fn default() -> Self { fn default() -> Self {
Self { Self {
active: Default::default(), active: Default::default(),
@@ -111,23 +112,25 @@ impl<State, E: Event> Default for TypeEventManager<State, E> {
} }
} }
impl<State: 'static, E: Event> TypeEventManager<State, E> { impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> {
fn register<W: Widget + ?Sized>( fn register<I: IdLike + 'static>(
&mut self, &mut self,
widget: WidgetRef<W>, widget: I,
event: impl EventLike<Event = E>, event: impl EventLike<Event = E>,
f: impl for<'a> WidgetEventFn<State, E::Data<'a>, W>, f: impl for<'a> WidgetEventFn<Rsc, E::Data<'a>, I::Widget>,
) { ) {
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| { Rc::new(move |ctx, rsc| {
let mut test = EventIdCtx { f(
widget, EventIdCtx {
state: ctx.state, widget: WeakWidget::new(widget.id()),
data: ctx.data, state: ctx.state,
}; data: ctx.data,
f(&mut test); },
rsc,
);
}), }),
)); ));
} }
@@ -135,15 +138,18 @@ impl<State: 'static, E: Event> TypeEventManager<State, 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<'_, State, E::Data<'b>>) + 'a { ) -> impl for<'b> FnOnce(EventCtx<'_, Rsc, E::Data<'b>>, &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| { 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(&mut EventCtx { f(
state: ctx.state, EventCtx {
data: ctx.data, state: ctx.state,
}) data,
},
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<'a>: 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<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
true Some(data.clone())
} }
} }
@@ -28,14 +28,17 @@ impl<E: Event> EventLike for E {
} }
} }
pub trait EventFn<State, Data>: Fn(&mut EventCtx<State, Data>) + 'static {} pub trait EventFn<Rsc: HasEvents, Data>: Fn(EventCtx<Rsc, Data>, &mut Rsc) + 'static {}
impl<State, F: Fn(&mut EventCtx<State, Data>) + 'static, Data> EventFn<State, Data> for F {} impl<Rsc: HasEvents, F: Fn(EventCtx<Rsc, Data>, &mut Rsc) + 'static, Data> EventFn<Rsc, Data>
for F
{
}
pub trait WidgetEventFn<State, Data, W: ?Sized>: pub trait WidgetEventFn<Rsc: HasEvents, Data, W: ?Sized>:
Fn(&mut EventIdCtx<State, Data, W>) + 'static Fn(EventIdCtx<Rsc, Data, W>, &mut Rsc) + 'static
{ {
} }
impl<State, F: Fn(&mut EventIdCtx<State, Data, W>) + 'static, Data, W: ?Sized> impl<Rsc: HasEvents, F: Fn(EventIdCtx<Rsc, Data, W>, &mut Rsc) + 'static, Data, W: ?Sized>
WidgetEventFn<State, Data, W> for F WidgetEventFn<Rsc, Data, W> for F
{ {
} }

View File

@@ -1,41 +1,34 @@
use crate::{ use crate::{
Event, EventCtx, EventLike, EventManager, HasUi, IdLike, Widget, WidgetEventFn, WidgetRef, Event, EventCtx, EventLike, EventManager, IdLike, UiRsc, WeakWidget, Widget, WidgetEventFn,
}; };
pub trait HasState { pub trait HasState: 'static {
type State: HasUi; type State;
} }
pub trait HasEvents: Sized + HasState + HasUi { pub trait HasEvents: Sized + UiRsc + HasState {
fn get(&self) -> &EventManager<Self::State>; fn events(&self) -> &EventManager<Self>;
fn get_mut(&mut self) -> &mut EventManager<Self::State>; fn events_mut(&mut self) -> &mut EventManager<Self>;
fn events(&self) -> &EventManager<Self::State> {
HasEvents::get(self)
}
fn events_mut(&mut self) -> &mut EventManager<Self::State> {
HasEvents::get_mut(self)
}
fn register_event<W: Widget + ?Sized, E: EventLike>( fn register_event<W: Widget + ?Sized, E: EventLike>(
&mut self, &mut self,
id: WidgetRef<W>, id: WeakWidget<W>,
event: E, event: E,
f: impl for<'a> WidgetEventFn<Self::State, <E::Event as Event>::Data<'a>, W>, f: impl for<'a> WidgetEventFn<Self, <E::Event as Event>::Data<'a>, W>,
) where ) {
Self::State: 'static,
{
self.events_mut().register(id, event, f); self.events_mut().register(id, event, f);
} }
} }
pub trait RunEvents: HasEvents + HasState<State = Self> + 'static { 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,
) { ) {
let f = self.events_mut().get_type::<E>().run_fn(id); let f = self.events_mut().get_type::<E>().run_fn(id);
f(EventCtx { state: self, data }) f(EventCtx { state, data }, self)
} }
} }
impl<T: HasEvents + HasState<State = Self> + 'static> RunEvents for T {} impl<T: HasEvents> RunEvents for T {}

View File

@@ -3,7 +3,7 @@ use cosmic_text::{
Attrs, AttrsList, Buffer, CacheKey, Color, Family, FontSystem, Metrics, Placement, SwashCache, Attrs, AttrsList, Buffer, CacheKey, Color, Family, FontSystem, Metrics, Placement, SwashCache,
SwashContent, SwashContent,
}; };
use image::{GenericImageView, RgbaImage}; use image::{DynamicImage, GenericImageView, RgbaImage};
use std::simd::{Simd, num::SimdUint}; use std::simd::{Simd, num::SimdUint};
/// TODO: properly wrap this /// TODO: properly wrap this
@@ -185,3 +185,7 @@ pub struct RenderedText {
pub top_left_offset: Vec2, pub top_left_offset: Vec2,
pub size: Vec2, pub size: Vec2,
} }
pub trait HasTextures {
fn add_texture(&mut self, image: DynamicImage) -> TextureHandle;
}

View File

@@ -1,7 +1,7 @@
use std::num::NonZero; use std::num::NonZero;
use crate::{ use crate::{
Ui, UiData, UiRenderState,
render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf}, render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf},
util::HashMap, util::HashMap,
}; };
@@ -59,12 +59,18 @@ impl UiRenderNode {
} }
} }
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &mut Ui) { pub fn update(
&mut self,
device: &Device,
queue: &Queue,
ui: &mut UiData,
ui_render: &mut UiRenderState,
) {
self.active.clear(); self.active.clear();
for (i, primitives) in ui.layers.iter_mut() { for (i, primitives) in ui_render.layers.iter_mut() {
self.active.push(i); self.active.push(i);
for change in primitives.apply_free() { for change in primitives.apply_free() {
if let Some(inst) = ui.active.get_mut(&change.id) { if let Some(inst) = ui_render.active.get_mut(&change.id) {
for h in &mut inst.primitives { for h in &mut inst.primitives {
if h.layer == i && h.inst_idx == change.old { if h.layer == i && h.inst_idx == change.old {
h.inst_idx = change.new; h.inst_idx = change.new;
@@ -183,7 +189,7 @@ impl UiRenderNode {
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
label: Some("UI Shape Pipeline Layout"), label: Some("UI Shape Pipeline Layout"),
bind_group_layouts: &[&uniform_layout, &primitive_layout, &rsc_layout], bind_group_layouts: &[&uniform_layout, &primitive_layout, &rsc_layout],
push_constant_ranges: &[], immediate_size: 0,
}); });
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
label: Some("UI Shape Pipeline"), label: Some("UI Shape Pipeline"),
@@ -219,7 +225,7 @@ impl UiRenderNode {
mask: !0, mask: !0,
alpha_to_coverage_enabled: false, alpha_to_coverage_enabled: false,
}, },
multiview: None, multiview_mask: None,
cache: None, cache: None,
}); });

View File

@@ -1,232 +0,0 @@
use crate::{
ActiveData, Axis, EventsLike, Painter, SizeCtx, Ui, UiRegion, UiVec2, WidgetId,
render::MaskIdx,
util::{HashSet, forget_ref},
};
use std::ops::{Deref, DerefMut};
/// state maintained between widgets during painting
pub struct DrawState<'a> {
pub(super) ui: &'a mut Ui,
pub(super) events: &'a mut dyn EventsLike,
draw_started: HashSet<WidgetId>,
}
impl<'a> DrawState<'a> {
pub fn new(ui: &'a mut Ui, events: &'a mut dyn EventsLike) -> Self {
Self {
ui,
events,
draw_started: Default::default(),
}
}
pub fn redraw_updates(&mut self) {
while let Some(&id) = self.widgets.needs_redraw.iter().next() {
self.redraw(id);
}
self.ui.free(self.events);
}
/// redraws a widget that's currently active (drawn)
pub fn redraw(&mut self, id: WidgetId) {
self.widgets.needs_redraw.remove(&id);
self.draw_started.remove(&id);
// check if parent depends on the desired size of this, if so then redraw it first
for axis in [Axis::X, Axis::Y] {
if let Some(&(outer, old)) = self.cache.size.axis_dyn(axis).get(&id)
&& let Some(current) = self.active.get(&id)
&& let Some(pid) = current.parent
{
self.cache.size.axis_dyn(axis).remove(&id);
let new = self.size_ctx(id, outer).len_axis(id, axis);
self.cache.size.axis_dyn(axis).insert(id, (outer, new));
if new != old {
self.redraw(pid);
}
}
}
if self.draw_started.contains(&id) {
return;
}
let Some(active) = self.remove(id, false) else {
return;
};
self.draw_inner(
active.layer,
id,
active.region,
active.parent,
active.mask,
Some(active.children),
);
}
pub(super) fn size_ctx<'b>(&'b mut self, source: WidgetId, outer: UiVec2) -> SizeCtx<'b> {
SizeCtx {
source,
cache: &mut self.ui.cache,
text: &mut self.ui.text,
textures: &mut self.ui.textures,
widgets: &self.ui.widgets,
outer,
output_size: self.ui.output_size,
id: source,
}
}
pub fn redraw_all(&mut self) {
// free all resources & cache
for (_, active) in self.ui.active.drain() {
self.events.undraw(&active);
}
self.ui.cache.clear();
self.ui.free(self.events);
self.layers.clear();
self.widgets.needs_redraw.clear();
if let Some(id) = &self.ui.root {
self.draw_inner(0, id.id(), UiRegion::FULL, None, MaskIdx::NONE, None);
}
}
pub(super) fn draw_inner(
&mut self,
layer: usize,
id: WidgetId,
region: UiRegion,
parent: Option<WidgetId>,
mask: MaskIdx,
old_children: Option<Vec<WidgetId>>,
) {
let mut old_children = old_children.unwrap_or_default();
if let Some(active) = self.ui.active.get_mut(&id)
&& !self.ui.widgets.needs_redraw.contains(&id)
{
// check to see if we can skip drawing first
if active.region == region {
return;
} else if active.region.size() == region.size() {
// TODO: epsilon?
let from = active.region;
self.mov(id, from, region);
return;
}
// if not, then maintain resize and track old children to remove unneeded
let active = self.remove(id, false).unwrap();
old_children = active.children;
}
// draw widget
self.draw_started.insert(id);
let mut painter = Painter {
state: self,
region,
mask,
layer,
id,
textures: Vec::new(),
primitives: Vec::new(),
children: Vec::new(),
};
let mut widget = painter.state.widgets.get_dyn_dynamic(id);
widget.draw(&mut painter);
drop(widget);
let Painter {
state: _,
region,
mask,
textures,
primitives,
children,
layer,
id,
} = painter;
// add to active
let active = ActiveData {
id,
region,
parent,
textures,
primitives,
children,
mask,
layer,
};
// remove old children that weren't kept
for c in &old_children {
if !active.children.contains(c) {
self.remove_rec(*c);
}
}
// update modules
self.events.draw(&active);
self.active.insert(id, active);
}
fn mov(&mut self, id: WidgetId, from: UiRegion, to: UiRegion) {
let active = self.ui.active.get_mut(&id).unwrap();
for h in &active.primitives {
let region = self.ui.layers[h.layer].region_mut(h);
*region = region.outside(&from).within(&to);
}
active.region = active.region.outside(&from).within(&to);
// SAFETY: children cannot be recursive
let children = unsafe { forget_ref(&active.children) };
for child in children {
self.mov(*child, from, to);
}
}
/// NOTE: instance textures are cleared and self.textures freed
fn remove(&mut self, id: WidgetId, undraw: bool) -> Option<ActiveData> {
let mut active = self.active.remove(&id);
if let Some(active) = &mut active {
for h in &active.primitives {
let mask = self.layers.free(h);
if mask != MaskIdx::NONE {
self.masks.remove(mask);
}
}
active.textures.clear();
self.textures.free();
if undraw {
self.events.undraw(active);
}
}
active
}
fn remove_rec(&mut self, id: WidgetId) -> Option<ActiveData> {
self.cache.remove(id);
let inst = self.remove(id, true);
if let Some(inst) = &inst {
for c in &inst.children {
self.remove_rec(*c);
}
}
inst
}
}
impl Deref for DrawState<'_> {
type Target = Ui;
fn deref(&self) -> &Self::Target {
self.ui
}
}
impl DerefMut for DrawState<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.ui
}
}

View File

@@ -1,217 +1,47 @@
use crate::{ use crate::{Mask, TextData, Textures, WeakWidget, WidgetId, Widgets, util::TrackedArena};
EventsLike, IdLike, Mask, PixelRegion, PrimitiveLayers, TextData, TextureHandle, Textures,
Widget, WidgetHandle, WidgetId, Widgets,
ui::draw_state::DrawState,
util::{HashMap, TrackedArena, Vec2},
};
use image::DynamicImage;
use std::{
ops::{Index, IndexMut},
sync::mpsc::{Receiver, channel},
};
mod active; mod active;
mod cache; mod cache;
mod draw_state;
mod painter; mod painter;
mod render_state;
mod size; mod size;
mod state;
pub use active::*; pub use active::*;
use cache::*;
pub use painter::Painter; pub use painter::Painter;
pub use render_state::*;
pub use size::*; pub use size::*;
pub struct Ui { #[derive(Default)]
// TODO: edit visibilities pub struct UiData {
pub widgets: Widgets, pub widgets: Widgets,
// retained painter state
pub active: HashMap<WidgetId, ActiveData>,
pub layers: PrimitiveLayers,
pub textures: Textures, pub textures: Textures,
pub text: TextData, pub text: TextData,
output_size: Vec2,
pub masks: TrackedArena<Mask, u32>, pub masks: TrackedArena<Mask, u32>,
pub cache: Cache,
pub root: Option<WidgetHandle>,
old_root: Option<WidgetId>,
recv: Receiver<WidgetId>,
resized: bool,
} }
pub trait HasUi: Sized { pub trait UiRsc {
fn get(&self) -> &Ui; fn ui(&self) -> &UiData;
fn get_mut(&mut self) -> &mut Ui; fn ui_mut(&mut self) -> &mut UiData;
fn ui(&self) -> &Ui {
self.get()
}
fn ui_mut(&mut self) -> &mut Ui {
self.get_mut()
}
}
impl HasUi for Ui { #[allow(unused_variables)]
fn get(&self) -> &Ui { fn on_add(&mut self, id: WeakWidget) {}
self #[allow(unused_variables)]
} fn on_remove(&mut self, id: WidgetId) {}
#[allow(unused_variables)]
fn on_draw(&mut self, active: &ActiveData) {}
#[allow(unused_variables)]
fn on_undraw(&mut self, active: &ActiveData) {}
fn get_mut(&mut self) -> &mut Ui { fn widgets(&self) -> &Widgets {
self &self.ui().widgets
} }
} fn widgets_mut(&mut self) -> &mut Widgets {
&mut self.ui_mut().widgets
impl Ui {
/// useful for debugging
pub fn set_label(&mut self, id: impl IdLike, label: String) {
self.widgets.data_mut(id.id()).unwrap().label = label;
} }
fn free(&mut self) {
pub fn label(&self, id: impl IdLike) -> &String { while let Some(id) = self.widgets_mut().free_next() {
&self.widgets.data(id.id()).unwrap().label self.on_remove(id);
}
pub fn new() -> Self {
Self::default()
}
pub fn get<I: IdLike>(&self, id: &I) -> Option<&I::Widget>
where
I::Widget: Sized + Widget,
{
self.widgets.get(id)
}
pub fn get_mut<I: IdLike>(&mut self, id: &I) -> Option<&mut I::Widget>
where
I::Widget: Sized + Widget,
{
self.widgets.get_mut(id)
}
pub fn add_texture(&mut self, image: DynamicImage) -> TextureHandle {
self.textures.add(image)
}
pub fn resize(&mut self, size: impl Into<Vec2>) {
self.output_size = size.into();
self.resized = true;
}
pub fn update(&mut self, events: &mut dyn EventsLike) {
if !self.widgets.waiting.is_empty() {
let len = self.widgets.waiting.len();
let all: Vec<_> = self
.widgets
.waiting
.iter()
.map(|&w| format!("'{}' ({w:?})", self.label(w)))
.collect();
panic!(
"{len} widget(s) were never upgraded\n\
this is likely a memory leak; consider upgrading to strong if you plan on using it later\n\
weak widgets: {all:#?}"
);
}
if self.root_changed() {
DrawState::new(self, events).redraw_all();
self.old_root = self.root.as_ref().map(|r| r.id());
} else if self.widgets.has_updates() {
DrawState::new(self, events).redraw_updates();
}
if self.resized {
self.resized = false;
DrawState::new(self, events).redraw_all();
}
}
/// free any resources that don't have references anymore
fn free(&mut self, events: &mut dyn EventsLike) {
for id in self.recv.try_iter() {
events.remove(id);
self.widgets.delete(id);
}
self.textures.free();
}
pub fn root_changed(&self) -> bool {
self.root.as_ref().map(|r| r.id()) != self.old_root
}
pub fn needs_redraw(&self) -> bool {
self.root_changed() || self.widgets.has_updates()
}
pub fn num_widgets(&self) -> usize {
self.widgets.len()
}
pub fn active_widgets(&self) -> usize {
self.active.len()
}
pub fn debug_layers(&self) {
for ((idx, depth), primitives) in self.layers.iter_depth() {
let indent = " ".repeat(depth * 2);
let len = primitives.instances().len();
print!("{indent}{idx}: {len} primitives");
if len >= 1 {
print!(" ({})", primitives.instances()[0].binding);
}
println!();
}
}
pub fn window_region(&self, id: &impl IdLike) -> Option<PixelRegion> {
let region = self.active.get(&id.id())?.region;
Some(region.to_px(self.output_size))
}
pub fn debug(&self, label: &str) -> impl Iterator<Item = &ActiveData> {
self.active.iter().filter_map(move |(&id, inst)| {
let l = self.widgets.label(id);
if l == label { Some(inst) } else { None }
})
}
}
impl<I: IdLike> Index<I> for Ui
where
I::Widget: Sized + Widget,
{
type Output = I::Widget;
fn index(&self, id: I) -> &Self::Output {
self.get(&id).unwrap()
}
}
impl<I: IdLike> IndexMut<I> for Ui
where
I::Widget: Sized + Widget,
{
fn index_mut(&mut self, id: I) -> &mut Self::Output {
self.get_mut(&id).unwrap()
}
}
impl Default for Ui {
fn default() -> Self {
let (send, recv) = channel();
Self {
widgets: Widgets::new(send),
active: Default::default(),
layers: Default::default(),
masks: Default::default(),
text: Default::default(),
textures: Default::default(),
cache: Default::default(),
output_size: Vec2::ZERO,
root: None,
old_root: None,
recv,
resized: false,
} }
self.ui_mut().textures.free();
} }
} }

View File

@@ -1,14 +1,14 @@
use crate::{ use crate::{
Axis, Len, RenderedText, Size, SizeCtx, TextAttrs, TextBuffer, TextData, TextureHandle, Axis, Len, RenderedText, Size, SizeCtx, StrongWidget, TextAttrs, TextBuffer, TextData,
UiRegion, Widget, WidgetHandle, WidgetId, TextureHandle, UiRegion, UiRenderState, UiRsc, Widget, WidgetId,
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst}, render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
ui::draw_state::DrawState,
util::Vec2, util::Vec2,
}; };
/// makes your surfaces look pretty /// makes your surfaces look pretty
pub struct Painter<'a, 'b> { pub struct Painter<'a> {
pub(super) state: &'a mut DrawState<'b>, pub(super) state: &'a mut UiRenderState,
pub(super) rsc: &'a mut dyn UiRsc,
pub(super) region: UiRegion, pub(super) region: UiRegion,
pub(super) mask: MaskIdx, pub(super) mask: MaskIdx,
@@ -19,7 +19,7 @@ pub struct Painter<'a, 'b> {
pub(super) id: WidgetId, pub(super) id: WidgetId,
} }
impl<'a, 'c> Painter<'a, 'c> { impl<'a> Painter<'a> {
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) { fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
let h = self.state.layers.write( let h = self.state.layers.write(
self.layer, self.layer,
@@ -32,7 +32,7 @@ impl<'a, 'c> Painter<'a, 'c> {
); );
if self.mask != MaskIdx::NONE { if self.mask != MaskIdx::NONE {
// TODO: I have no clue if this works at all :joy: // TODO: I have no clue if this works at all :joy:
self.state.masks.push_ref(self.mask); self.rsc.ui_mut().masks.push_ref(self.mask);
} }
self.primitives.push(h); self.primitives.push(h);
} }
@@ -48,24 +48,31 @@ impl<'a, 'c> Painter<'a, 'c> {
pub fn set_mask(&mut self, region: UiRegion) { pub fn set_mask(&mut self, region: UiRegion) {
assert!(self.mask == MaskIdx::NONE); assert!(self.mask == MaskIdx::NONE);
self.mask = self.state.masks.push(Mask { region }); self.mask = self.rsc.ui_mut().masks.push(Mask { region });
} }
/// Draws a widget within this widget's region. /// Draws a widget within this widget's region.
pub fn widget<W: ?Sized>(&mut self, id: &WidgetHandle<W>) { pub fn widget<W: ?Sized>(&mut self, id: &StrongWidget<W>) {
self.widget_at(id, self.region); self.widget_at(id, self.region);
} }
/// Draws a widget somewhere within this one. /// Draws a widget somewhere within this one.
/// Useful for drawing child widgets in select areas. /// Useful for drawing child widgets in select areas.
pub fn widget_within<W: ?Sized>(&mut self, id: &WidgetHandle<W>, region: UiRegion) { pub fn widget_within<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) {
self.widget_at(id, region.within(&self.region)); self.widget_at(id, region.within(&self.region));
} }
fn widget_at<W: ?Sized>(&mut self, id: &WidgetHandle<W>, region: UiRegion) { fn widget_at<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) {
self.children.push(id.id()); self.children.push(id.id());
self.state self.state.draw_inner(
.draw_inner(self.layer, id.id(), region, Some(self.id), self.mask, None); self.layer,
id.id(),
region,
Some(self.id),
self.mask,
None,
self.rsc,
);
} }
pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) { pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) {
@@ -85,21 +92,19 @@ impl<'a, 'c> Painter<'a, 'c> {
/// returns (handle, offset from top left) /// returns (handle, offset from top left)
pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText { pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText {
self.state let ui = self.rsc.ui_mut();
.ui ui.text.draw(buffer, attrs, &mut ui.textures)
.text
.draw(buffer, attrs, &mut self.state.ui.textures)
} }
pub fn region(&self) -> UiRegion { pub fn region(&self) -> UiRegion {
self.region self.region
} }
pub fn size<W: ?Sized + Widget>(&mut self, id: &WidgetHandle<W>) -> Size { pub fn size<W: ?Sized + Widget>(&mut self, id: &StrongWidget<W>) -> Size {
self.size_ctx().size(id) self.size_ctx().size(id)
} }
pub fn len_axis<W: ?Sized + Widget>(&mut self, id: &WidgetHandle<W>, axis: Axis) -> Len { pub fn len_axis<W: ?Sized + Widget>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Len {
match axis { match axis {
Axis::X => self.size_ctx().width(id), Axis::X => self.size_ctx().width(id),
Axis::Y => self.size_ctx().height(id), Axis::Y => self.size_ctx().height(id),
@@ -115,7 +120,7 @@ impl<'a, 'c> Painter<'a, 'c> {
} }
pub fn text_data(&mut self) -> &mut TextData { pub fn text_data(&mut self) -> &mut TextData {
&mut self.state.text &mut self.rsc.ui_mut().text
} }
pub fn child_layer(&mut self) { pub fn child_layer(&mut self) {
@@ -127,7 +132,7 @@ impl<'a, 'c> Painter<'a, 'c> {
} }
pub fn label(&self) -> &str { pub fn label(&self) -> &str {
&self.state.widgets.data(self.id).unwrap().label &self.rsc.widgets().data(self.id).unwrap().label
} }
pub fn id(&self) -> &WidgetId { pub fn id(&self) -> &WidgetId {
@@ -135,6 +140,6 @@ impl<'a, 'c> Painter<'a, 'c> {
} }
pub fn size_ctx(&mut self) -> SizeCtx<'_> { pub fn size_ctx(&mut self) -> SizeCtx<'_> {
self.state.size_ctx(self.id, self.region.size()) self.state.size_ctx(self.id, self.region.size(), self.rsc)
} }
} }

319
core/src/ui/render_state.rs Normal file
View File

@@ -0,0 +1,319 @@
use crate::{
ActiveData, Axis, IdLike, MaskIdx, Painter, PixelRegion, PrimitiveLayers, SizeCtx,
StrongWidget, UiRegion, UiRsc, UiVec2, WidgetId, Widgets,
ui::cache::Cache,
util::{HashMap, HashSet, Vec2, forget_ref},
};
pub struct UiRenderState {
pub active: HashMap<WidgetId, ActiveData>,
pub layers: PrimitiveLayers,
pub(super) output_size: Vec2,
pub cache: Cache,
old_root: Option<WidgetId>,
resized: bool,
draw_started: HashSet<WidgetId>,
}
impl UiRenderState {
pub fn new() -> Self {
Self {
active: Default::default(),
layers: Default::default(),
cache: Default::default(),
output_size: Vec2::ZERO,
old_root: None,
resized: false,
draw_started: Default::default(),
}
}
pub fn resize(&mut self, size: impl Into<Vec2>) {
self.output_size = size.into();
self.resized = true;
}
pub fn update<'a>(&mut self, root: impl Into<Option<&'a StrongWidget>>, rsc: &mut dyn UiRsc) {
// safety mechanism for memory leaks; might wanna return a result instead so user can
// decide whether to panic or not
if !rsc.widgets().waiting.is_empty() {
let widgets = rsc.widgets();
let len = widgets.waiting.len();
let all: Vec<_> = widgets
.waiting
.iter()
.map(|&w| format!("'{}' ({w:?})", widgets.label(w)))
.collect();
panic!(
"{len} widget(s) were never upgraded\n\
this is likely a memory leak; consider upgrading to strong if you plan on using it later\n\
weak widgets: {all:#?}"
);
}
let root = root.into();
if self.root_changed(root) || self.resized {
self.redraw_all(root, rsc);
self.old_root = root.map(|r| r.id());
self.resized = false;
} else if rsc.widgets().has_updates() {
self.redraw_updates(rsc);
}
}
fn redraw_all(&mut self, root: Option<&StrongWidget>, rsc: &mut dyn UiRsc) {
self.clear(rsc);
// free all resources & cache
if let Some(id) = root {
self.draw_inner(0, id.id(), UiRegion::FULL, None, MaskIdx::NONE, None, rsc);
}
}
// TODO: should prolly make a DrawInfo struct or smth for everything other than rsc
#[allow(clippy::too_many_arguments)]
pub(super) fn draw_inner(
&mut self,
layer: usize,
id: WidgetId,
region: UiRegion,
parent: Option<WidgetId>,
mask: MaskIdx,
old_children: Option<Vec<WidgetId>>,
rsc: &mut dyn UiRsc,
) {
let mut old_children = old_children.unwrap_or_default();
if let Some(active) = self.active.get_mut(&id)
&& !rsc.widgets().needs_redraw.contains(&id)
{
// check to see if we can skip drawing first
if active.region == region {
return;
} else if active.region.size() == region.size() {
// TODO: epsilon?
let from = active.region;
self.mov(id, from, region);
return;
}
// if not, then maintain resize and track old children to remove unneeded
let active = self.remove(id, false, rsc).unwrap();
old_children = active.children;
}
// draw widget
self.draw_started.insert(id);
let mut painter = Painter {
state: self,
region,
mask,
layer,
id,
textures: Vec::new(),
primitives: Vec::new(),
children: Vec::new(),
rsc,
};
let mut widget = painter.rsc.widgets().get_dyn_dynamic(id);
widget.draw(&mut painter);
drop(widget);
let Painter {
state: _,
rsc: _,
region,
mask,
textures,
primitives,
children,
layer,
id,
} = painter;
// add to active
let active = ActiveData {
id,
region,
parent,
textures,
primitives,
children,
mask,
layer,
};
// remove old children that weren't kept
for c in &old_children {
if !active.children.contains(c) {
self.remove_rec(*c, rsc);
}
}
rsc.on_draw(&active);
self.active.insert(id, active);
}
fn mov(&mut self, id: WidgetId, from: UiRegion, to: UiRegion) {
let active = self.active.get_mut(&id).unwrap();
for h in &active.primitives {
let region = self.layers[h.layer].region_mut(h);
*region = region.outside(&from).within(&to);
}
active.region = active.region.outside(&from).within(&to);
// SAFETY: children cannot be recursive
let children = unsafe { forget_ref(&active.children) };
for child in children {
self.mov(*child, from, to);
}
}
/// NOTE: instance textures are cleared and self.textures freed
fn remove(&mut self, id: WidgetId, undraw: bool, rsc: &mut dyn UiRsc) -> Option<ActiveData> {
let mut active = self.active.remove(&id);
if let Some(active) = &mut active {
for h in &active.primitives {
let mask = self.layers.free(h);
if mask != MaskIdx::NONE {
rsc.ui_mut().masks.remove(mask);
}
}
active.textures.clear();
rsc.ui_mut().textures.free();
if undraw {
rsc.on_undraw(active);
}
}
active
}
fn remove_rec(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) -> Option<ActiveData> {
self.cache.remove(id);
let inst = self.remove(id, true, rsc);
if let Some(inst) = &inst {
for c in &inst.children {
self.remove_rec(*c, rsc);
}
}
inst
}
fn clear(&mut self, rsc: &mut dyn UiRsc) {
for (_, active) in self.active.drain() {
rsc.on_undraw(&active);
}
self.cache.clear();
self.layers.clear();
rsc.widgets_mut().needs_redraw.clear();
rsc.free();
}
pub fn redraw_updates(&mut self, rsc: &mut dyn UiRsc) {
while let Some(&id) = rsc.widgets().needs_redraw.iter().next() {
self.redraw(id, rsc);
}
rsc.free();
}
pub fn root_changed<'a>(&self, root: impl Into<Option<&'a StrongWidget>>) -> bool {
root.into().map(|r| r.id()) != self.old_root
}
pub fn needs_redraw<'a>(
&self,
root: impl Into<Option<&'a StrongWidget>>,
widgets: &Widgets,
) -> bool {
self.root_changed(root) || widgets.has_updates()
}
pub fn active_widgets(&self) -> usize {
self.active.len()
}
pub fn debug(&self, widgets: &Widgets, label: &str) -> impl Iterator<Item = &ActiveData> {
self.active.iter().filter_map(move |(&id, inst)| {
let l = widgets.label(id);
if l == label { Some(inst) } else { None }
})
}
pub fn debug_layers(&self) {
for ((idx, depth), primitives) in self.layers.iter_depth() {
let indent = " ".repeat(depth * 2);
let len = primitives.instances().len();
print!("{indent}{idx}: {len} primitives");
if len >= 1 {
print!(" ({})", primitives.instances()[0].binding);
}
println!();
}
}
pub fn window_region(&self, id: &impl IdLike) -> Option<PixelRegion> {
let region = self.active.get(&id.id())?.region;
Some(region.to_px(self.output_size))
}
/// redraws a widget that's currently active (drawn)
pub fn redraw(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) {
rsc.widgets_mut().needs_redraw.remove(&id);
self.draw_started.remove(&id);
// check if parent depends on the desired size of this, if so then redraw it first
for axis in [Axis::X, Axis::Y] {
if let Some(&(outer, old)) = self.cache.size.axis_dyn(axis).get(&id)
&& let Some(current) = self.active.get(&id)
&& let Some(pid) = current.parent
{
self.cache.size.axis_dyn(axis).remove(&id);
let new = self.size_ctx(id, outer, rsc).len_axis(id, axis);
self.cache.size.axis_dyn(axis).insert(id, (outer, new));
if new != old {
self.redraw(pid, rsc);
}
}
}
if self.draw_started.contains(&id) {
return;
}
let Some(active) = self.remove(id, false, rsc) else {
return;
};
self.draw_inner(
active.layer,
id,
active.region,
active.parent,
active.mask,
Some(active.children),
rsc,
);
}
pub(super) fn size_ctx<'b>(
&'b mut self,
source: WidgetId,
outer: UiVec2,
rsc: &'b mut dyn UiRsc,
) -> SizeCtx<'b> {
let ui = rsc.ui_mut();
SizeCtx {
source,
cache: &mut self.cache,
text: &mut ui.text,
textures: &mut ui.textures,
widgets: &ui.widgets,
outer,
output_size: self.output_size,
id: source,
}
}
}
impl Default for UiRenderState {
fn default() -> Self {
Self::new()
}
}

View File

@@ -1,3 +0,0 @@
// pub struct DynState {
//
// }

View File

@@ -1,7 +1,7 @@
use std::{marker::Unsize, ops::CoerceUnsized, sync::mpsc::Sender}; use std::{marker::Unsize, ops::CoerceUnsized, sync::mpsc::Sender};
use crate::{ use crate::{
HasUi, Widget, UiRsc, Widget,
util::{RefCounter, SlotId}, util::{RefCounter, SlotId},
}; };
@@ -12,7 +12,7 @@ pub type WidgetId = SlotId;
/// a signal is sent to the owning UI to clean up the resources. /// a signal is sent to the owning UI to clean up the resources.
/// ///
/// TODO: ergonomic clones when they get put in rust-analyzer & don't cause ICEs? /// TODO: ergonomic clones when they get put in rust-analyzer & don't cause ICEs?
pub struct WidgetHandle<W: ?Sized = dyn Widget> { pub struct StrongWidget<W: ?Sized = dyn Widget> {
pub(super) id: WidgetId, pub(super) id: WidgetId,
counter: RefCounter, counter: RefCounter,
send: Sender<WidgetId>, send: Sender<WidgetId>,
@@ -21,24 +21,19 @@ pub struct WidgetHandle<W: ?Sized = dyn Widget> {
/// A weak handle to a widget. /// A weak handle to a widget.
/// Will not keep it alive, but can still be used for indexing like WidgetHandle. /// Will not keep it alive, but can still be used for indexing like WidgetHandle.
pub struct WidgetRef<W: ?Sized = dyn Widget> { pub struct WeakWidget<W: ?Sized = dyn Widget> {
pub(super) id: WidgetId, pub(super) id: WidgetId,
#[allow(unused)] #[allow(unused)]
ty: *const W, ty: *const W,
} }
pub struct WidgetHandles<W: ?Sized = dyn Widget> { impl<W: Widget + ?Sized + Unsize<dyn Widget>> StrongWidget<W> {
pub h: WidgetHandle<W>, pub fn any(self) -> StrongWidget<dyn Widget> {
pub r: WidgetRef<W>,
}
impl<W: Widget + ?Sized + Unsize<dyn Widget>> WidgetHandle<W> {
pub fn any(self) -> WidgetHandle<dyn Widget> {
self self
} }
} }
impl<W: ?Sized> WidgetHandle<W> { impl<W: ?Sized> StrongWidget<W> {
pub(crate) fn new(id: WidgetId, send: Sender<WidgetId>) -> Self { pub(crate) fn new(id: WidgetId, send: Sender<WidgetId>) -> Self {
Self { Self {
id, id,
@@ -56,18 +51,13 @@ impl<W: ?Sized> WidgetHandle<W> {
self.counter.refs() self.counter.refs()
} }
pub fn weak(&self) -> WidgetRef<W> { pub fn weak(&self) -> WeakWidget<W> {
let Self { ty, id, .. } = *self; let Self { ty, id, .. } = *self;
WidgetRef { ty, id } WeakWidget { ty, id }
}
pub fn handles(self) -> WidgetHandles<W> {
let r = self.weak();
WidgetHandles { h: self, r }
} }
} }
impl<W: ?Sized> WidgetRef<W> { impl<W: ?Sized> WeakWidget<W> {
pub(crate) fn new(id: WidgetId) -> Self { pub(crate) fn new(id: WidgetId) -> Self {
Self { id, ty: null_ptr() } Self { id, ty: null_ptr() }
} }
@@ -77,12 +67,12 @@ impl<W: ?Sized> WidgetRef<W> {
} }
#[track_caller] #[track_caller]
pub fn upgrade(self, ui: &mut impl HasUi) -> WidgetHandle<W> { pub fn upgrade(self, ui: &mut impl UiRsc) -> StrongWidget<W> {
ui.ui_mut().widgets.upgrade(self) ui.widgets_mut().upgrade(self)
} }
} }
impl<W: ?Sized> Drop for WidgetHandle<W> { impl<W: ?Sized> Drop for StrongWidget<W> {
fn drop(&mut self) { fn drop(&mut self) {
if self.counter.drop() { if self.counter.drop() {
let _ = self.send.send(self.id); let _ = self.send.send(self.id);
@@ -90,29 +80,29 @@ impl<W: ?Sized> Drop for WidgetHandle<W> {
} }
} }
pub trait WidgetIdFn<State, W: ?Sized = dyn Widget>: FnOnce(&mut State) -> WidgetRef<W> {} pub trait WidgetIdFn<Rsc, W: ?Sized = dyn Widget>: FnOnce(&mut Rsc) -> WeakWidget<W> {}
impl<State, W: ?Sized, F: FnOnce(&mut State) -> WidgetRef<W>> WidgetIdFn<State, W> for F {} impl<Rsc, W: ?Sized, F: FnOnce(&mut Rsc) -> WeakWidget<W>> WidgetIdFn<Rsc, W> for F {}
pub trait IdLike { pub trait IdLike {
type Widget: ?Sized; type Widget: ?Sized;
fn id(&self) -> WidgetId; fn id(&self) -> WidgetId;
} }
impl<W: ?Sized> IdLike for &WidgetHandle<W> { impl<W: ?Sized> IdLike for &StrongWidget<W> {
type Widget = W; type Widget = W;
fn id(&self) -> WidgetId { fn id(&self) -> WidgetId {
self.id self.id
} }
} }
impl<W: ?Sized> IdLike for WidgetHandle<W> { impl<W: ?Sized> IdLike for StrongWidget<W> {
type Widget = W; type Widget = W;
fn id(&self) -> WidgetId { fn id(&self) -> WidgetId {
self.id self.id
} }
} }
impl<W: ?Sized> IdLike for WidgetRef<W> { impl<W: ?Sized> IdLike for WeakWidget<W> {
type Widget = W; type Widget = W;
fn id(&self) -> WidgetId { fn id(&self) -> WidgetId {
self.id self.id
@@ -126,38 +116,38 @@ impl IdLike for WidgetId {
} }
} }
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WidgetHandle<U>> for WidgetHandle<T> {} impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<StrongWidget<U>> for StrongWidget<T> {}
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WidgetRef<U>> for WidgetRef<T> {} impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WeakWidget<U>> for WeakWidget<T> {}
impl<W: ?Sized> Clone for WidgetRef<W> { impl<W: ?Sized> Clone for WeakWidget<W> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
*self *self
} }
} }
impl<W: ?Sized> Copy for WidgetRef<W> {} impl<W: ?Sized> Copy for WeakWidget<W> {}
impl<W: ?Sized> PartialEq for WidgetRef<W> { impl<W: ?Sized> PartialEq for WeakWidget<W> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.id == other.id self.id == other.id
} }
} }
impl<W> PartialEq for WidgetHandle<W> { impl<W> PartialEq for StrongWidget<W> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.id == other.id self.id == other.id
} }
} }
impl<W> std::fmt::Debug for WidgetHandle<W> { impl<W> std::fmt::Debug for StrongWidget<W> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.id.fmt(f) self.id.fmt(f)
} }
} }
impl<'a, W: Widget + 'a, State: HasUi> FnOnce<(&'a mut State,)> for WidgetRef<W> { impl<'a, W: Widget + 'a, State: UiRsc> FnOnce<(&'a mut State,)> for WeakWidget<W> {
type Output = &'a mut W; type Output = &'a mut W;
extern "rust-call" fn call_once(self, args: (&'a mut State,)) -> Self::Output { extern "rust-call" fn call_once(self, args: (&'a mut State,)) -> Self::Output {
&mut args.0.ui_mut()[self] &mut args.0.widgets_mut()[self]
} }
} }
@@ -169,3 +159,6 @@ fn null_ptr<W: ?Sized>() -> *const W {
unsafe { std::mem::transmute_copy(&[0usize; 1]) } unsafe { std::mem::transmute_copy(&[0usize; 1]) }
} }
} }
unsafe impl<W: ?Sized> Send for WeakWidget<W> {}
unsafe impl<W: ?Sized> Sync for WeakWidget<W> {}

View File

@@ -1,55 +1,44 @@
use crate::{HasUi, Ui}; use crate::UiRsc;
use super::*; use super::*;
use std::marker::Unsize; use std::marker::Unsize;
pub trait StateLike<State> { pub trait WidgetLike<Rsc: UiRsc, Tag>: Sized {
fn as_state(&mut self) -> &mut State;
}
impl StateLike<Ui> for Ui {
fn as_state(&mut self) -> &mut Ui {
self
}
}
pub trait WidgetLike<State: HasUi + StateLike<State>, Tag>: Sized {
type Widget: Widget + ?Sized + Unsize<dyn Widget>; type Widget: Widget + ?Sized + Unsize<dyn Widget>;
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<Self::Widget>; fn add(self, rsc: &mut Rsc) -> WeakWidget<Self::Widget>;
fn add_strong(self, state: &mut impl StateLike<State>) -> WidgetHandle<Self::Widget> { fn add_strong(self, rsc: &mut Rsc) -> StrongWidget<Self::Widget> {
self.add(state).upgrade(state.as_state().ui_mut()) self.add(rsc).upgrade(rsc)
} }
fn with_id<W2>( fn with_id<W2>(
self, self,
f: impl FnOnce(&mut State, WidgetRef<Self::Widget>) -> WidgetRef<W2>, f: impl FnOnce(&mut Rsc, WeakWidget<Self::Widget>) -> WeakWidget<W2>,
) -> impl WidgetIdFn<State, W2> { ) -> impl WidgetIdFn<Rsc, W2> {
move |state| { move |state| {
let id = self.add(state); let id = self.add(state);
f(state, id) f(state, id)
} }
} }
fn set_root(self, state: &mut impl StateLike<State>) { fn set_root(self, rsc: &mut Rsc, root: &mut impl HasRoot) {
let id = self.add(state); let id = self.add_strong(rsc);
let ui = state.as_state().ui_mut(); root.set_root(id);
ui.root = Some(id.upgrade(ui));
}
fn handles(self, state: &mut impl StateLike<State>) -> WidgetHandles<Self::Widget> {
self.add(state).upgrade(state.as_state().ui_mut()).handles()
} }
} }
pub trait WidgetArrLike<State, const LEN: usize, Tag> { pub trait HasRoot {
fn set_root(&mut self, root: StrongWidget);
}
pub trait WidgetArrLike<Rsc, const LEN: usize, Tag> {
#[track_caller] #[track_caller]
fn add(self, state: &mut impl StateLike<State>) -> WidgetArr<LEN>; fn add(self, state: &mut Rsc) -> WidgetArr<LEN>;
} }
impl<State, const LEN: usize> WidgetArrLike<State, LEN, ArrTag> for WidgetArr<LEN> { impl<Rsc, const LEN: usize> WidgetArrLike<Rsc, LEN, ArrTag> for WidgetArr<LEN> {
fn add(self, _: &mut impl StateLike<State>) -> WidgetArr<LEN> { fn add(self, _: &mut Rsc) -> WidgetArr<LEN> {
self self
} }
} }
@@ -60,12 +49,12 @@ macro_rules! impl_widget_arr {
impl_widget_arr!($n;$($W)*;$(${concat($W,Tag)})*); impl_widget_arr!($n;$($W)*;$(${concat($W,Tag)})*);
}; };
($n:expr;$($W:ident)*;$($Tag:ident)*) => { ($n:expr;$($W:ident)*;$($Tag:ident)*) => {
impl<State: HasUi + StateLike<State>, $($W: WidgetLike<State, $Tag>,$Tag,)*> WidgetArrLike<State, $n, ($($Tag,)*)> for ($($W,)*) { impl<Rsc: UiRsc, $($W: WidgetLike<Rsc, $Tag>,$Tag,)*> WidgetArrLike<Rsc, $n, ($($Tag,)*)> for ($($W,)*) {
fn add(self, state: &mut impl StateLike<State>) -> WidgetArr<$n> { fn add(self, rsc: &mut Rsc) -> WidgetArr<$n> {
#[allow(non_snake_case)] #[allow(non_snake_case)]
let ($($W,)*) = self; let ($($W,)*) = self;
WidgetArr::new( WidgetArr::new(
[$($W.add(state).upgrade(state.as_state().ui_mut()),)*], [$($W.add(rsc).upgrade(rsc),)*],
) )
} }
} }

View File

@@ -5,12 +5,14 @@ mod data;
mod handle; mod handle;
mod like; mod like;
mod tag; mod tag;
mod view;
mod widgets; mod widgets;
pub use data::*; pub use data::*;
pub use handle::*; pub use handle::*;
pub use like::*; pub use like::*;
pub use tag::*; pub use tag::*;
pub use view::*;
pub use widgets::*; pub use widgets::*;
pub trait Widget: Any { pub trait Widget: Any {
@@ -59,27 +61,27 @@ pub trait WidgetFn<State, W: Widget + ?Sized>: FnOnce(&mut State) -> W {}
impl<State, W: Widget + ?Sized, F: FnOnce(&mut State) -> W> WidgetFn<State, W> for F {} impl<State, W: Widget + ?Sized, F: FnOnce(&mut State) -> W> WidgetFn<State, W> for F {}
pub struct WidgetArr<const LEN: usize> { pub struct WidgetArr<const LEN: usize> {
pub arr: [WidgetHandle; LEN], pub arr: [StrongWidget; LEN],
} }
impl<const LEN: usize> WidgetArr<LEN> { impl<const LEN: usize> WidgetArr<LEN> {
pub fn new(arr: [WidgetHandle; LEN]) -> Self { pub fn new(arr: [StrongWidget; LEN]) -> Self {
Self { arr } Self { arr }
} }
} }
pub trait WidgetOption<State> { pub trait WidgetOption<State> {
fn get(self, state: &mut State) -> Option<WidgetHandle>; fn get(self, state: &mut State) -> Option<StrongWidget>;
} }
impl<State> WidgetOption<State> for () { impl<State> WidgetOption<State> for () {
fn get(self, _: &mut State) -> Option<WidgetHandle> { fn get(self, _: &mut State) -> Option<StrongWidget> {
None None
} }
} }
impl<State, F: FnOnce(&mut State) -> Option<WidgetHandle>> WidgetOption<State> for F { impl<State, F: FnOnce(&mut State) -> Option<StrongWidget>> WidgetOption<State> for F {
fn get(self, state: &mut State) -> Option<WidgetHandle> { fn get(self, state: &mut State) -> Option<StrongWidget> {
self(state) self(state)
} }
} }

View File

@@ -1,58 +1,63 @@
use super::*; use super::*;
use crate::HasUi; use crate::UiRsc;
use std::marker::Unsize; use std::marker::Unsize;
pub struct WidgetTag; pub struct WidgetTag;
impl<State: HasUi + StateLike<State>, W: Widget> WidgetLike<State, WidgetTag> for W { impl<Rsc: UiRsc, W: Widget> WidgetLike<Rsc, WidgetTag> for W {
type Widget = W; type Widget = W;
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<W> { fn add(self, rsc: &mut Rsc) -> WeakWidget<W> {
state.as_state().get_mut().widgets.add_weak(self) let w = rsc.ui_mut().widgets.add_weak(self);
rsc.on_add(w);
w
} }
} }
pub struct FnTag; pub struct FnTag;
impl<State: HasUi + StateLike<State>, W: Widget, F: FnOnce(&mut State) -> W> impl<Rsc: UiRsc, W: Widget, F: FnOnce(&mut Rsc) -> W> WidgetLike<Rsc, FnTag> for F {
WidgetLike<State, FnTag> for F
{
type Widget = W; type Widget = W;
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<W> { fn add(self, rsc: &mut Rsc) -> WeakWidget<W> {
self(state.as_state()).add(state) self(rsc).add(rsc)
} }
} }
pub trait WidgetFnTrait<State> { pub trait WidgetFnTrait<Rsc> {
type Widget: Widget; type Widget: Widget;
fn run(self, state: &mut State) -> Self::Widget; fn run(self, rsc: &mut Rsc) -> Self::Widget;
} }
pub struct FnTraitTag; pub struct FnTraitTag;
impl<State: HasUi + StateLike<State>, T: WidgetFnTrait<State>> WidgetLike<State, FnTraitTag> for T { impl<Rsc: UiRsc, T: WidgetFnTrait<Rsc>> WidgetLike<Rsc, FnTraitTag> for T {
type Widget = T::Widget; type Widget = T::Widget;
#[track_caller] #[track_caller]
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<T::Widget> { fn add(self, rsc: &mut Rsc) -> WeakWidget<T::Widget> {
self.run(state.as_state()).add(state) self.run(rsc).add(rsc)
} }
} }
pub struct IdTag; pub struct RefTag;
impl<State: HasUi + StateLike<State>, W: ?Sized + Widget + Unsize<dyn Widget>> impl<Rsc: UiRsc, W: ?Sized + Widget + Unsize<dyn Widget>> WidgetLike<Rsc, RefTag>
WidgetLike<State, IdTag> for WidgetRef<W> for WeakWidget<W>
{ {
type Widget = W; type Widget = W;
fn add(self, _: &mut impl StateLike<State>) -> WidgetRef<W> { fn add(self, _: &mut Rsc) -> WeakWidget<W> {
self self
} }
} }
pub struct IdFnTag; pub struct RefFnTag;
impl< impl<Rsc: UiRsc, W: ?Sized + Widget + Unsize<dyn Widget>, F: FnOnce(&mut Rsc) -> WeakWidget<W>>
State: HasUi + StateLike<State>, WidgetLike<Rsc, RefFnTag> for F
W: ?Sized + Widget + Unsize<dyn Widget>,
F: FnOnce(&mut State) -> WidgetRef<W>,
> WidgetLike<State, IdFnTag> for F
{ {
type Widget = W; type Widget = W;
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<W> { fn add(self, rsc: &mut Rsc) -> WeakWidget<W> {
self(state.as_state()) self(rsc)
}
}
pub struct ViewTag;
impl<Rsc: UiRsc, V: WidgetView> WidgetLike<Rsc, ViewTag> for V {
type Widget = V::Widget;
fn add(self, _: &mut Rsc) -> WeakWidget<Self::Widget> {
self.root()
} }
} }

24
core/src/widget/view.rs Normal file
View File

@@ -0,0 +1,24 @@
use std::marker::Unsize;
use crate::{IdLike, WeakWidget, Widget};
pub trait WidgetView {
type Widget: Widget + ?Sized + Unsize<dyn Widget>;
fn root(&self) -> WeakWidget<Self::Widget>;
}
pub trait HasWidget {
type Widget: Widget + ?Sized + Unsize<dyn Widget>;
}
impl<W: Widget + Unsize<dyn Widget> + ?Sized> HasWidget for WeakWidget<W> {
type Widget = W;
}
impl<WV: WidgetView> IdLike for WV {
type Widget = WV::Widget;
fn id(&self) -> super::WidgetId {
self.root().id
}
}

View File

@@ -1,7 +1,7 @@
use std::sync::mpsc::Sender; use std::sync::mpsc::{Receiver, Sender, channel};
use crate::{ use crate::{
IdLike, Widget, WidgetData, WidgetHandle, WidgetId, WidgetRef, IdLike, StrongWidget, WeakWidget, Widget, WidgetData, WidgetId,
util::{DynBorrower, HashSet, SlotVec, forget_mut, to_mut}, util::{DynBorrower, HashSet, SlotVec, forget_mut, to_mut},
}; };
@@ -9,16 +9,19 @@ pub struct Widgets {
pub needs_redraw: HashSet<WidgetId>, pub needs_redraw: HashSet<WidgetId>,
vec: SlotVec<WidgetData>, vec: SlotVec<WidgetData>,
send: Sender<WidgetId>, send: Sender<WidgetId>,
recv: Receiver<WidgetId>,
pub(crate) waiting: HashSet<WidgetId>, pub(crate) waiting: HashSet<WidgetId>,
} }
impl Widgets { impl Widgets {
pub fn new(send: Sender<WidgetId>) -> Self { pub fn new() -> Self {
let (send, recv) = channel();
Self { Self {
needs_redraw: Default::default(), needs_redraw: Default::default(),
vec: Default::default(), vec: Default::default(),
waiting: Default::default(), waiting: Default::default(),
send, send,
recv,
} }
} }
@@ -61,25 +64,27 @@ impl Widgets {
self.get_dyn_mut(id.id())?.as_any_mut().downcast_mut() self.get_dyn_mut(id.id())?.as_any_mut().downcast_mut()
} }
pub fn add_strong<W: Widget>(&mut self, widget: W) -> WidgetHandle<W> { pub fn add_strong<W: Widget>(&mut self, widget: W) -> StrongWidget<W> {
let id = self.vec.add(WidgetData::new(widget)); let id = self.vec.add(WidgetData::new(widget));
WidgetHandle::new(id, self.send.clone()) StrongWidget::new(id, self.send.clone())
} }
pub fn add_weak<W: Widget>(&mut self, widget: W) -> WidgetRef<W> { pub fn add_weak<W: Widget>(&mut self, widget: W) -> WeakWidget<W> {
let id = self.vec.add(WidgetData::new(widget)); let id = self.vec.add(WidgetData::new(widget));
self.waiting.insert(id); self.waiting.insert(id);
WidgetRef::new(id) WeakWidget::new(id)
} }
#[track_caller] #[track_caller]
pub fn upgrade<W: ?Sized>(&mut self, rf: WidgetRef<W>) -> WidgetHandle<W> { pub fn upgrade<W: ?Sized>(&mut self, rf: WeakWidget<W>) -> StrongWidget<W> {
if !self.waiting.remove(&rf.id()) { if !self.waiting.remove(&rf.id()) {
let label = self.label(rf); let label = self.label(rf);
let id = rf.id(); let id = rf.id();
panic!("widget '{label}' ({id:?}) was already added\ncannot add a widget twice; consider creating two") panic!(
"widget '{label}' ({id:?}) was already added\ncannot add a widget twice; consider creating two"
)
} }
WidgetHandle::new(rf.id(), self.send.clone()) StrongWidget::new(rf.id(), self.send.clone())
} }
pub fn data(&self, id: impl IdLike) -> Option<&WidgetData> { pub fn data(&self, id: impl IdLike) -> Option<&WidgetData> {
@@ -90,14 +95,19 @@ impl Widgets {
&self.data(id.id()).unwrap().label &self.data(id.id()).unwrap().label
} }
/// useful for debugging
pub fn set_label(&mut self, id: impl IdLike, label: String) {
self.data_mut(id.id()).unwrap().label = label;
}
pub fn data_mut(&mut self, id: impl IdLike) -> Option<&mut WidgetData> { pub fn data_mut(&mut self, id: impl IdLike) -> Option<&mut WidgetData> {
self.vec.get_mut(id.id()) self.vec.get_mut(id.id())
} }
pub fn delete(&mut self, id: impl IdLike) { pub fn free_next(&mut self) -> Option<WidgetId> {
self.vec.free(id.id()); let next = self.recv.try_recv().ok()?;
// not sure if there's any point in this self.vec.free(next);
// self.updates.remove(&id); Some(next)
} }
#[allow(clippy::len_without_is_empty)] #[allow(clippy::len_without_is_empty)]
@@ -106,4 +116,30 @@ impl Widgets {
} }
} }
impl Default for Widgets {
fn default() -> Self {
Self::new()
}
}
pub type WidgetWrapper<'a> = DynBorrower<'a, dyn Widget>; pub type WidgetWrapper<'a> = DynBorrower<'a, dyn Widget>;
impl<I: IdLike> std::ops::Index<I> for Widgets
where
I::Widget: Sized + Widget,
{
type Output = I::Widget;
fn index(&self, id: I) -> &Self::Output {
self.get(&id).unwrap()
}
}
impl<I: IdLike> std::ops::IndexMut<I> for Widgets
where
I::Widget: Sized + Widget,
{
fn index_mut(&mut self, id: I) -> &mut Self::Output {
self.get_mut(&id).unwrap()
}
}

View File

@@ -1,20 +1,21 @@
use iris::prelude::*; use iris::prelude::*;
fn main() { fn main() {
App::<State>::run(); DefaultApp::<State>::run();
} }
#[default_ui_state] #[derive(DefaultUiState)]
struct State {} struct State {
ui_state: DefaultUiState,
}
impl DefaultAppState for State { impl DefaultAppState for State {
fn new(ui_state: DefaultUiState, _proxy: Proxy<Self::Event>) -> Self { fn new(
let mut ui = Ui::new(); mut ui_state: DefaultUiState,
rect(Color::RED).set_root(&mut ui); rsc: &mut DefaultRsc<Self>,
Self { _: Proxy<Self::Event>,
ui, ) -> Self {
ui_state, rect(Color::RED).set_root(rsc, &mut ui_state);
events: EventManager::default(), Self { ui_state }
}
} }
} }

View File

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -2,25 +2,25 @@ 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!(ClientRsc); use iris::prelude::*;
type ClientRsc = DefaultRsc<Client>;
fn main() { fn main() {
App::<Client>::run(); DefaultApp::<Client>::run();
} }
#[default_ui_state] #[derive(DefaultUiState)]
pub struct Client { pub struct Client {
info: WidgetRef<Text>, ui_state: DefaultUiState,
info: WeakWidget<Text>,
} }
impl DefaultAppState for Client { impl DefaultAppState for Client {
fn new(ui_state: DefaultUiState, _proxy: Proxy<Self::Event>) -> Self { fn new(
let mut rsc = ClientRsc { mut ui_state: DefaultUiState,
ui: Ui::new(), rsc: &mut DefaultRsc<Self>,
ui_state, _: Proxy<Self::Event>,
events: EventManager::default(), ) -> Self {
};
let rrect = rect(Color::WHITE).radius(20); let rrect = rect(Color::WHITE).radius(20);
let pad_test = ( let pad_test = (
rrect.color(Color::BLUE), rrect.color(Color::BLUE),
@@ -43,7 +43,7 @@ impl DefaultAppState for Client {
.width(rest(3)), .width(rest(3)),
) )
.span(Dir::RIGHT) .span(Dir::RIGHT)
.add(&mut rsc); .add(rsc);
let span_test = ( let span_test = (
rrect.color(Color::GREEN).width(100), rrect.color(Color::GREEN).width(100),
@@ -54,30 +54,30 @@ impl DefaultAppState for Client {
rrect.color(Color::RED).width(100), rrect.color(Color::RED).width(100),
) )
.span(Dir::LEFT) .span(Dir::LEFT)
.add(&mut rsc); .add(rsc);
let span_add = Span::empty(Dir::RIGHT).add(&mut rsc); let span_add = Span::empty(Dir::RIGHT).add(rsc);
let add_button = rect(Color::LIME) let add_button = rect(Color::LIME)
.radius(30) .radius(30)
.on(CursorSense::click(), move |ctx| { .on(CursorSense::click(), move |_, rsc| {
let child = image(include_bytes!("assets/sungals.png")) let child = image(include_bytes!("assets/sungals.png"))
.center() .center()
.add_strong(ctx); .add_strong(rsc);
span_add(ctx).push(child); span_add(rsc).push(child);
}) })
.sized((150, 150)) .sized((150, 150))
.align(Align::BOT_RIGHT); .align(Align::BOT_RIGHT);
let del_button = rect(Color::RED) let del_button = rect(Color::RED)
.radius(30) .radius(30)
.on(CursorSense::click(), move |ctx| { .on(CursorSense::click(), move |_, rsc| {
span_add(ctx).pop(); span_add(rsc).pop();
}) })
.sized((150, 150)) .sized((150, 150))
.align(Align::BOT_LEFT); .align(Align::BOT_LEFT);
let span_add_test = (span_add, add_button, del_button).stack().add(&mut rsc); let span_add_test = (span_add, add_button, del_button).stack().add(rsc);
let btext = |content| wtext(content).size(30); let btext = |content| wtext(content).size(30);
@@ -100,18 +100,18 @@ impl DefaultAppState for Client {
wtext("pretty cool right?").size(50), wtext("pretty cool right?").size(50),
) )
.span(Dir::DOWN) .span(Dir::DOWN)
.add(&mut rsc); .add(rsc);
let texts = Span::empty(Dir::DOWN).gap(10).add(&mut rsc); let texts = Span::empty(Dir::DOWN).gap(10).add(rsc);
let msg_area = texts.scrollable().masked().background(rect(Color::SKY)); let msg_area = texts.scrollable().masked().background(rect(Color::SKY));
let add_text = wtext("add") let add_text = wtext("add")
.editable(EditMode::MultiLine) .editable(EditMode::MultiLine)
.text_align(Align::LEFT) .text_align(Align::LEFT)
.size(30) .size(30)
.attr::<Selectable>(()) .attr::<Selectable>(())
.on(Submit, move |ctx| { .on(Submit, move |ctx, rsc| {
let w = ctx.widget; let w = ctx.widget;
let content = w.edit(ctx).take(); let content = w.edit(rsc).take();
let text = wtext(content) let text = wtext(content)
.editable(EditMode::MultiLine) .editable(EditMode::MultiLine)
.size(30) .size(30)
@@ -120,10 +120,11 @@ impl DefaultAppState for Client {
.attr::<Selectable>(()); .attr::<Selectable>(());
let msg_box = text let msg_box = text
.background(rect(Color::WHITE.darker(0.5))) .background(rect(Color::WHITE.darker(0.5)))
.add_strong(ctx); .add_strong(rsc);
texts(ctx).push(msg_box); texts(rsc).push(msg_box);
}) })
.add(&mut rsc); .add(rsc);
let text_edit_scroll = ( let text_edit_scroll = (
msg_area.height(rest(1)), msg_area.height(rest(1)),
( (
@@ -131,8 +132,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| { .on(CursorSense::click(), move |ctx, rsc: &mut ClientRsc| {
ctx.state.run_event::<Submit>(add_text, &mut ()); rsc.run_event::<Submit>(add_text, (), ctx.state);
}) })
.sized((40, 40)), .sized((40, 40)),
) )
@@ -145,39 +146,39 @@ impl DefaultAppState for Client {
.align(Align::BOT), .align(Align::BOT),
) )
.span(Dir::DOWN) .span(Dir::DOWN)
.add(&mut rsc); .add(rsc);
let main = WidgetPtr::new().add(&mut rsc); let main = WidgetPtr::new().add(rsc);
let vals = Rc::new(RefCell::new((0, Vec::new()))); let vals = Rc::new(RefCell::new((0, Vec::new())));
let mut switch_button = |color, to: WidgetRef, label| { let mut switch_button = |color, to: WeakWidget, label| {
let to = to.upgrade(&mut rsc); let to = to.upgrade(rsc);
let vec = &mut vals.borrow_mut().1; let vec = &mut vals.borrow_mut().1;
let i = vec.len(); let i = vec.len();
if vec.is_empty() { if vec.is_empty() {
vec.push(None); vec.push(None);
rsc.ui[main].set(to); main(rsc).set(to);
} else { } else {
vec.push(Some(to)); vec.push(Some(to));
} }
let vals = vals.clone(); let vals = vals.clone();
let rect = rect(color) let rect = rect(color)
.on(CursorSense::click(), move |ctx| { .on(CursorSense::click(), move |ctx, rsc| {
let (prev, vec) = &mut *vals.borrow_mut(); let (prev, vec) = &mut *vals.borrow_mut();
if let Some(h) = vec[i].take() { if let Some(h) = vec[i].take() {
vec[*prev] = main(ctx).replace(h); vec[*prev] = main(rsc).replace(h);
*prev = i; *prev = i;
} }
ctx.widget().color = color.darker(0.3); ctx.widget(rsc).color = color.darker(0.3);
}) })
.on( .on(
CursorSense::HoverStart | CursorSense::unclick(), CursorSense::HoverStart | CursorSense::unclick(),
move |ctx| { move |ctx, rsc| {
ctx.widget().color = color.brighter(0.2); ctx.widget(rsc).color = color.brighter(0.2);
}, },
) )
.on(CursorSense::HoverEnd, move |ctx| { .on(CursorSense::HoverEnd, move |ctx, rsc| {
ctx.widget().color = color; ctx.widget(rsc).color = color;
}); });
(rect, wtext(label).size(30).text_align(Align::CENTER)).stack() (rect, wtext(label).size(30).text_align(Align::CENTER)).stack()
}; };
@@ -195,30 +196,30 @@ impl DefaultAppState for Client {
) )
.span(Dir::RIGHT); .span(Dir::RIGHT);
let info = wtext("").add(&mut rsc); let info = wtext("").add(rsc);
let info_sect = info.pad(10).align(Align::RIGHT); let info_sect = info.pad(10).align(Align::RIGHT);
((tabs.height(40), main.pad(10)).span(Dir::DOWN), info_sect) ((tabs.height(40), main.pad(10)).span(Dir::DOWN), info_sect)
.stack() .stack()
.set_root(&mut rsc); .set_root(rsc, &mut ui_state);
Self { Self { ui_state, info }
ui: rsc.ui,
ui_state: rsc.ui_state,
events: rsc.events,
info,
}
} }
fn window_event(&mut self, _: WindowEvent) { fn window_event(
&mut self,
_: WindowEvent,
rsc: &mut DefaultRsc<Self>,
render: &mut UiRenderState,
) {
let new = format!( let new = format!(
"widgets: {}\nactive:{}\nviews: {}", "widgets: {}\nactive: {}\nviews: {}",
self.ui.num_widgets(), rsc.widgets().len(),
self.ui.active_widgets(), render.active_widgets(),
self.ui_state.renderer.ui.view_count() self.ui_state.renderer.ui.view_count(),
); );
if new != *self.ui[self.info].content { if new != *rsc.widgets()[self.info].content {
*self.ui[self.info].content = new; *rsc.widgets_mut()[self.info].content = new;
} }
} }
} }

34
examples/task.rs Normal file
View File

@@ -0,0 +1,34 @@
use iris::prelude::*;
use std::time::Duration;
fn main() {
DefaultApp::<State>::run();
}
#[derive(DefaultUiState)]
struct State {
ui_state: DefaultUiState,
}
impl DefaultAppState for State {
fn new(
mut ui_state: DefaultUiState,
rsc: &mut DefaultRsc<Self>,
_: Proxy<Self::Event>,
) -> Self {
let rect = rect(Color::RED).add(rsc);
rect.task_on(CursorSense::click(), async move |mut ctx| {
tokio::time::sleep(Duration::from_secs(1)).await;
ctx.update(move |_, rsc| {
let rect = rect(rsc);
if rect.color == Color::RED {
rect.color = Color::BLUE;
} else {
rect.color = Color::RED;
}
});
})
.set_root(rsc, &mut ui_state);
Self { ui_state }
}
}

53
examples/view.rs Normal file
View File

@@ -0,0 +1,53 @@
use iris::prelude::*;
fn main() {
DefaultApp::<State>::run();
}
#[derive(DefaultUiState)]
struct State {
ui_state: DefaultUiState,
}
type Rsc = DefaultRsc<State>;
#[derive(Clone, Copy, WidgetView)]
struct Test {
#[root]
root: WeakWidget<Rect>,
cur: WeakState<bool>,
}
impl Test {
pub fn new(rsc: &mut Rsc) -> Self {
let root = rect(Color::RED).add(rsc);
let cur = rsc.create_state(root, false);
Self { root, cur }
}
pub fn toggle(&self, rsc: &mut Rsc) {
let cur = &mut rsc[self.cur];
*cur = !*cur;
if *cur {
rsc[self.root].color = Color::BLUE;
} else {
rsc[self.root].color = Color::RED;
}
}
}
impl DefaultAppState for State {
fn new(
mut ui_state: DefaultUiState,
rsc: &mut DefaultRsc<Self>,
_: Proxy<Self::Event>,
) -> Self {
let test = Test::new(rsc);
test.on(CursorSense::click(), move |_, rsc| {
test.toggle(rsc);
})
.set_root(rsc, &mut ui_state);
Self { ui_state }
}
}

View File

@@ -2,8 +2,8 @@ extern crate proc_macro;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{ use syn::{
Attribute, Block, Error, Fields, FieldsNamed, GenericParam, Generics, Ident, ItemStruct, Attribute, Block, Error, GenericParam, Generics, Ident, ItemStruct, ItemTrait, Signature,
ItemTrait, Meta, Signature, Token, Visibility, Token, Type, Visibility,
parse::{Parse, ParseStream, Result}, parse::{Parse, ParseStream, Result},
parse_macro_input, parse_quote, parse_macro_input, parse_quote,
spanned::Spanned, spanned::Spanned,
@@ -89,104 +89,108 @@ pub fn widget_trait(input: TokenStream) -> TokenStream {
quote! { quote! {
#trai #trai
impl #generics #name<State, WL, Tag> for WL { impl #generics #name<Rsc, WL, Tag> for WL {
#(#impls)* #(#impls)*
} }
} }
.into() .into()
} }
#[proc_macro_derive(UiState, attributes(rsc))] #[proc_macro_derive(DefaultUiState, attributes(default_ui_state))]
pub fn derive_ui_state(input: TokenStream) -> TokenStream { pub fn derive_default_ui_state(input: TokenStream) -> TokenStream {
let mut output = proc_macro2::TokenStream::new(); let mut output = proc_macro2::TokenStream::new();
let state: ItemStruct = parse_macro_input!(input); let state: ItemStruct = parse_macro_input!(input);
let sname = state.ident;
let rscname = Ident::new(&(sname.to_string() + "Rsc"), sname.span());
let mut rsc_fields = Vec::new();
for field in state.fields { let mut found_attr = false;
let Some(attr) = field.attrs.iter().find(|a| a.path().is_ident("rsc")) else { let mut state_field = None;
for field in &state.fields {
if !found_attr
&& let Type::Path(path) = &field.ty
&& path.path.is_ident("DefaultUiState")
{
state_field = Some(field);
}
let Some(attr) = field
.attrs
.iter()
.find(|a| a.path().is_ident("default_ui_state"))
else {
continue; continue;
}; };
let Meta::List(list) = &attr.meta else { if found_attr {
output.extend(Error::new(attr.span(), "invalid attr syntax").into_compile_error()); output.extend(
Error::new(
attr.span(),
"cannot have more than one default_ui_state attribute",
)
.into_compile_error(),
);
continue; continue;
}; }
let tname: Ident = match list.parse_args::<Ident>() { found_attr = true;
Ok(ident) => ident, state_field = Some(field);
Err(err) => {
output.extend(err.to_compile_error());
continue;
}
};
let fty = &field.ty;
let fname = &field.ident.unwrap();
rsc_fields.extend(quote! {#fname: #fty,});
output.extend(quote! {
impl #tname for #sname {
fn get(&self) -> &#fty {
&self.#fname
}
fn get_mut(&mut self) -> &mut #fty {
&mut self.#fname
}
}
impl #tname for #rscname {
fn get(&self) -> &#fty {
&self.#fname
}
fn get_mut(&mut self) -> &mut #fty {
&mut self.#fname
}
}
});
} }
let vis = state.vis; let Some(field) = state_field else {
output.extend(
Error::new(state.ident.span(), "no DefaultUiState field found").into_compile_error(),
);
return output.into();
};
let sname = &state.ident;
let fname = field.ident.as_ref().unwrap();
output.extend(quote! { output.extend(quote! {
#vis struct #rscname { impl iris::default::HasDefaultUiState for #sname {
#(#rsc_fields)* fn default_state(&self) -> &iris::default::DefaultUiState {
} &self.#fname
impl HasState for #sname {
type State = #sname;
}
impl HasState for #rscname {
type State = #sname;
}
impl StateLike<#sname> for #sname {
fn as_state(&mut self) -> &mut Self {
self
} }
} fn default_state_mut(&mut self) -> &mut iris::default::DefaultUiState {
&mut self.#fname
impl StateLike<#rscname> for #rscname {
fn as_state(&mut self) -> &mut Self {
self
} }
} }
}); });
output.into() output.into()
} }
#[proc_macro_attribute] #[proc_macro_derive(WidgetView, attributes(root))]
pub fn default_ui_state(_attr: TokenStream, input: TokenStream) -> TokenStream { pub fn derive_widget_view(input: TokenStream) -> TokenStream {
let mut state: ItemStruct = parse_macro_input!(input); let mut output = proc_macro2::TokenStream::new();
let Fields::Named(fields) = &mut state.fields else {
panic!("must be on named fields struct"); let state: ItemStruct = parse_macro_input!(input);
let mut found_attr = false;
let mut state_field = None;
for field in &state.fields {
let Some(attr) = field.attrs.iter().find(|a| a.path().is_ident("root")) else {
continue;
};
if found_attr {
output.extend(
Error::new(attr.span(), "cannot have more than one root widget")
.into_compile_error(),
);
continue;
}
found_attr = true;
state_field = Some(field);
}
let Some(field) = state_field else {
output.extend(
Error::new(state.ident.span(), "no root widget field found (#[root])")
.into_compile_error(),
);
return output.into();
}; };
let name = &state.ident; let sname = &state.ident;
state.attrs.push(parse_quote! {#[derive(UiState)]}); let fname = field.ident.as_ref().unwrap();
let new: FieldsNamed = parse_quote! {{ let fty = &field.ty;
#[rsc(HasUi)] output.extend(quote! {
pub ui: Ui, impl iris::core::WidgetView for #sname {
#[rsc(HasDefaultUiState)] type Widget = <#fty as iris::core::HasWidget>::Widget;
pub ui_state: DefaultUiState, fn root(&self) -> #fty {
#[rsc(HasEvents)] self.#fname
pub events: iris::prelude::EventManager<#name>, }
}}; }
fields.named.extend(new.named); });
quote! {#state}.into() output.into()
} }

View File

@@ -4,12 +4,12 @@ My experimental attempt at a rust ui library (also my first ui library).
It's currently designed around using retained data structures (widgets), rather than diffing generated trees from data like xilem or iced. This is an experiment and I'm not sure if it's a good idea or not. It's currently designed around using retained data structures (widgets), rather than diffing generated trees from data like xilem or iced. This is an experiment and I'm not sure if it's a good idea or not.
There's a `main.rs` that runs a testing window, so you can just `cargo run` to see it working. Examples are in `examples`, eg. `cargo run --example tabs`.
Goals, in general order: Goals, in general order:
1. does what I want it to (text, images, video, animations) 1. does what I want it to (text, images, video, animations)
2. very easy to use ignoring ergonomic ref counting 2. very easy to use ignoring ergonomic ref counting
3. reasonably fast / efficient (a lot faster than electron, save battery life) 3. reasonably fast / efficient (a lot faster than electron, save battery life, try to beat iced and xilem)
## dev details ## dev details

View File

@@ -11,6 +11,13 @@ pub trait AppState {
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop); fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop);
fn event(&mut self, event: Self::Event, event_loop: &ActiveEventLoop); fn event(&mut self, event: Self::Event, event_loop: &ActiveEventLoop);
fn exit(&mut self); fn exit(&mut self);
fn run()
where
Self: Sized,
{
App::<Self>::run();
}
} }
pub struct App<State: AppState> { pub struct App<State: AppState> {

View File

@@ -4,35 +4,45 @@ use winit::dpi::{LogicalPosition, LogicalSize};
pub struct Selector; pub struct Selector;
impl<State: HasEvents, W: Widget + 'static> WidgetAttr<State, W> for Selector impl<Rsc: HasEvents, W: Widget + 'static> WidgetAttr<Rsc, W> for Selector
where where
State::State: HasDefaultUiState, Rsc::State: HasDefaultUiState,
{ {
type Input = WidgetRef<TextEdit>; type Input = WeakWidget<TextEdit>;
fn run(state: &mut State, container: WidgetRef<W>, id: Self::Input) { fn run(rsc: &mut Rsc, container: WeakWidget<W>, id: Self::Input) {
state.register_event(container, CursorSense::click_or_drag(), move |ctx| { rsc.register_event(container, CursorSense::click_or_drag(), move |ctx, rsc| {
let region = ctx.ui().window_region(&id).unwrap(); let region = ctx.data.render.window_region(&id).unwrap();
let id_pos = region.top_left; let id_pos = region.top_left;
let container_pos = ctx.state.ui().window_region(&container).unwrap().top_left; let container_pos = ctx.data.render.window_region(&container).unwrap().top_left;
let pos = ctx.data.pos + container_pos - id_pos; let pos = ctx.data.pos + container_pos - id_pos;
let size = region.size(); let size = region.size();
select(ctx.state, id, pos, size, ctx.data.sense.is_dragging()); select(
rsc,
ctx.data.render,
ctx.state,
id,
pos,
size,
ctx.data.sense.is_dragging(),
);
}); });
} }
} }
pub struct Selectable; pub struct Selectable;
impl<State: HasEvents> WidgetAttr<State, TextEdit> for Selectable impl<Rsc: HasEvents> WidgetAttr<Rsc, TextEdit> for Selectable
where where
State::State: HasDefaultUiState, Rsc::State: HasDefaultUiState,
{ {
type Input = (); type Input = ();
fn run(state: &mut State, id: WidgetRef<TextEdit>, _: Self::Input) { fn run(rsc: &mut Rsc, id: WeakWidget<TextEdit>, _: Self::Input) {
state.register_event(id, CursorSense::click_or_drag(), move |ctx| { rsc.register_event(id, CursorSense::click_or_drag(), move |ctx, rsc| {
select( select(
rsc,
ctx.data.render,
ctx.state, ctx.state,
id, id,
ctx.data.pos, ctx.data.pos,
@@ -44,18 +54,20 @@ where
} }
fn select( fn select(
rsc: &mut impl UiRsc,
render: &UiRenderState,
state: &mut impl HasDefaultUiState, state: &mut impl HasDefaultUiState,
id: WidgetRef<TextEdit>, id: WeakWidget<TextEdit>,
pos: Vec2, pos: Vec2,
size: Vec2, size: Vec2,
dragging: bool, dragging: bool,
) { ) {
let (ui, state) = HasDefaultUiState::ui_with_state(state); let state = state.default_state_mut();
let now = Instant::now(); let now = Instant::now();
let recent = (now - state.last_click) < Duration::from_millis(300); let recent = (now - state.last_click) < Duration::from_millis(300);
state.last_click = now; state.last_click = now;
id.edit(ui).select(pos, size, dragging, recent); id.edit(rsc).select(pos, size, dragging, recent);
if let Some(region) = ui.window_region(&id) { if let Some(region) = render.window_region(&id) {
state.window.set_ime_allowed(true); state.window.set_ime_allowed(true);
state.window.set_ime_cursor_area( state.window.set_ime_cursor_area(
LogicalPosition::<f32>::from(region.top_left.tuple()), LogicalPosition::<f32>::from(region.top_left.tuple()),

View File

@@ -1,7 +1,10 @@
use crate::prelude::*; use crate::prelude::*;
use arboard::Clipboard; use arboard::Clipboard;
use iris_core::util::forget_mut; use std::{
use std::{marker::Sized, sync::Arc, time::Instant}; marker::{PhantomData, Sized},
sync::Arc,
time::Instant,
};
use winit::{ use winit::{
event::{Ime, WindowEvent}, event::{Ime, WindowEvent},
event_loop::{ActiveEventLoop, EventLoopProxy}, event_loop::{ActiveEventLoop, EventLoopProxy},
@@ -14,6 +17,8 @@ mod event;
mod input; mod input;
mod render; mod render;
mod sense; mod sense;
mod state;
mod task;
pub use app::*; pub use app::*;
pub use attr::*; pub use attr::*;
@@ -21,23 +26,33 @@ pub use event::*;
pub use input::*; pub use input::*;
pub use render::*; pub use render::*;
pub use sense::*; pub use sense::*;
pub use state::*;
pub use task::*;
pub type Proxy<Event> = EventLoopProxy<Event>; pub type Proxy<Event> = EventLoopProxy<Event>;
pub struct DefaultUiState { pub struct DefaultUiState {
pub root: Option<StrongWidget>,
pub renderer: UiRenderer, pub renderer: UiRenderer,
pub input: Input, pub input: Input,
pub focus: Option<WidgetRef<TextEdit>>, pub focus: Option<WeakWidget<TextEdit>>,
pub clipboard: Clipboard, pub clipboard: Clipboard,
pub window: Arc<Window>, pub window: Arc<Window>,
pub ime: usize, pub ime: usize,
pub last_click: Instant, pub last_click: Instant,
} }
impl HasRoot for DefaultUiState {
fn set_root(&mut self, root: StrongWidget) {
self.root = Some(root);
}
}
impl DefaultUiState { impl DefaultUiState {
pub fn new(window: impl Into<Arc<Window>>) -> Self { pub fn new(window: impl Into<Arc<Window>>) -> Self {
let window = window.into(); let window = window.into();
Self { Self {
root: None,
renderer: UiRenderer::new(window.clone()), renderer: UiRenderer::new(window.clone()),
window, window,
input: Input::default(), input: Input::default(),
@@ -49,53 +64,162 @@ impl DefaultUiState {
} }
} }
pub trait HasDefaultUiState: Sized + 'static + HasUi { pub trait HasDefaultUiState: Sized + 'static {
fn get(&self) -> &DefaultUiState; fn default_state(&self) -> &DefaultUiState;
fn get_mut(&mut self) -> &mut DefaultUiState; fn default_state_mut(&mut self) -> &mut DefaultUiState;
fn ui_state(&self) -> &DefaultUiState {
HasDefaultUiState::get(self)
}
fn ui_state_mut(&mut self) -> &mut DefaultUiState {
HasDefaultUiState::get_mut(self)
}
fn ui_with_state(&mut self) -> (&mut Ui, &mut DefaultUiState) {
// as long as you're not doing anything actually unhinged this should always work safely
(
unsafe { forget_mut(self.ui_mut()) },
HasDefaultUiState::get_mut(self),
)
}
} }
pub trait DefaultAppState: RunEvents + HasDefaultUiState { pub trait DefaultAppState: HasDefaultUiState {
type Event: 'static = (); type Event = ();
fn new(ui_state: DefaultUiState, proxy: Proxy<Self::Event>) -> Self; fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, proxy: Proxy<Self::Event>)
-> Self;
#[allow(unused_variables)] #[allow(unused_variables)]
fn event(&mut self, event: Self::Event) {} fn event(
&mut self,
event: Self::Event,
rsc: &mut DefaultRsc<Self>,
render: &mut UiRenderState,
) {
}
#[allow(unused_variables)] #[allow(unused_variables)]
fn exit(&mut self) {} fn exit(&mut self, rsc: &mut DefaultRsc<Self>, render: &mut UiRenderState) {}
#[allow(unused_variables)] #[allow(unused_variables)]
fn window_event(&mut self, event: WindowEvent) {} fn window_event(
&mut self,
event: WindowEvent,
rsc: &mut DefaultRsc<Self>,
render: &mut UiRenderState,
) {
}
fn window_attributes() -> WindowAttributes { fn window_attributes() -> WindowAttributes {
Default::default() Default::default()
} }
} }
impl<State: DefaultAppState> AppState for State { pub struct DefaultRsc<State: 'static> {
pub ui: UiData,
pub events: EventManager<Self>,
pub tasks: Tasks<Self>,
pub state: WidgetState,
_state: PhantomData<State>,
}
impl<State> DefaultRsc<State> {
fn init(window: Arc<Window>) -> (Self, TaskMsgReceiver<Self>) {
let (tasks, recv) = Tasks::init(window);
(
Self {
ui: Default::default(),
events: Default::default(),
tasks,
state: Default::default(),
_state: Default::default(),
},
recv,
)
}
pub fn create_state<T: 'static>(&mut self, id: impl IdLike, data: T) -> WeakState<T> {
self.state.add(id.id(), data)
}
}
impl<State> UiRsc for DefaultRsc<State> {
fn ui(&self) -> &UiData {
&self.ui
}
fn ui_mut(&mut self) -> &mut UiData {
&mut self.ui
}
fn on_draw(&mut self, active: &ActiveData) {
self.events.draw(active);
}
fn on_undraw(&mut self, active: &ActiveData) {
self.events.undraw(active);
}
fn on_remove(&mut self, id: WidgetId) {
self.events.remove(id);
self.state.remove(id);
}
}
impl<State: 'static> HasState for DefaultRsc<State> {
type State = State;
}
impl<State: 'static> HasEvents for DefaultRsc<State> {
fn events(&self) -> &EventManager<Self> {
&self.events
}
fn events_mut(&mut self) -> &mut EventManager<Self> {
&mut self.events
}
}
impl<State: 'static> HasTasks for DefaultRsc<State> {
fn tasks_mut(&mut self) -> &mut Tasks<Self> {
&mut self.tasks
}
}
impl<State: 'static> HasWidgetState for DefaultRsc<State> {
fn widget_state(&self) -> &WidgetState {
&self.state
}
fn widget_state_mut(&mut self) -> &mut WidgetState {
&mut self.state
}
}
pub struct DefaultApp<State: DefaultAppState> {
rsc: DefaultRsc<State>,
render: UiRenderState,
state: State,
task_recv: TaskMsgReceiver<DefaultRsc<State>>,
}
impl<State: DefaultAppState> AppState for DefaultApp<State> {
type Event = State::Event; type Event = State::Event;
fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> Self { fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> Self {
let window = event_loop.create_window(Self::window_attributes()).unwrap(); let window = event_loop
Self::new(DefaultUiState::new(window), proxy) .create_window(State::window_attributes())
.unwrap();
let default_state = DefaultUiState::new(window);
let (mut rsc, task_recv) = DefaultRsc::init(default_state.window.clone());
let state = State::new(default_state, &mut rsc, proxy);
let render = UiRenderState::new();
Self {
rsc,
state,
render,
task_recv,
}
} }
fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) { fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) {
self.event(event); self.state.event(event, &mut self.rsc, &mut self.render);
} }
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) { fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
let events = unsafe { forget_mut(self.events_mut()) }; let Self {
let ui_state = HasDefaultUiState::get_mut(self); rsc,
render,
state,
task_recv,
} = self;
for update in task_recv.try_iter() {
update(state, rsc);
}
let ui_state = state.default_state_mut();
let input_changed = ui_state.input.event(&event); let input_changed = ui_state.input.event(&event);
let cursor_state = ui_state.cursor_state().clone(); let cursor_state = ui_state.cursor_state().clone();
let old = ui_state.focus; let old = ui_state.focus;
@@ -104,43 +228,43 @@ impl<State: DefaultAppState> AppState for State {
} }
if input_changed { if input_changed {
let window_size = ui_state.window_size(); let window_size = ui_state.window_size();
self.run_sensors(&cursor_state, window_size); render.run_sensors(rsc, state, cursor_state, window_size);
} }
let (mut ui, mut ui_state) = self.ui_with_state(); let ui_state = state.default_state_mut();
if old != ui_state.focus if old != ui_state.focus
&& let Some(old) = old && let Some(old) = old
{ {
old.edit(ui).deselect(); old.edit(rsc).deselect();
} }
match &event { match &event {
WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
ui.update(events); render.update(&ui_state.root, rsc);
ui_state.renderer.update(ui); ui_state.renderer.update(&mut rsc.ui, render);
ui_state.renderer.draw(); ui_state.renderer.draw();
} }
WindowEvent::Resized(size) => { WindowEvent::Resized(size) => {
ui.resize((size.width, size.height)); render.resize((size.width, size.height));
ui_state.renderer.resize(size) ui_state.renderer.resize(size)
} }
WindowEvent::KeyboardInput { event, .. } => { WindowEvent::KeyboardInput { event, .. } => {
if let Some(sel) = ui_state.focus if let Some(sel) = ui_state.focus
&& event.state.is_pressed() && event.state.is_pressed()
{ {
let mut text = sel.edit(ui); let mut text = sel.edit(rsc);
match text.apply_event(event, &ui_state.input.modifiers) { match text.apply_event(event, &ui_state.input.modifiers) {
TextInputResult::Unfocus => { TextInputResult::Unfocus => {
ui_state.focus = None; ui_state.focus = None;
ui_state.window.set_ime_allowed(false); ui_state.window.set_ime_allowed(false);
} }
TextInputResult::Submit => { TextInputResult::Submit => {
self.run_event::<Submit>(sel, &mut ()); rsc.run_event::<Submit>(sel, (), 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.run_event::<Edited>(sel, &mut ()); rsc.run_event::<Edited>(sel, (), 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) {
@@ -148,7 +272,7 @@ impl<State: DefaultAppState> AppState for State {
} }
} }
TextInputResult::Used => { TextInputResult::Used => {
self.run_event::<Edited>(sel, &mut ()); rsc.run_event::<Edited>(sel, (), state);
} }
TextInputResult::Unused => {} TextInputResult::Unused => {}
} }
@@ -156,7 +280,7 @@ impl<State: DefaultAppState> AppState for State {
} }
WindowEvent::Ime(ime) => { WindowEvent::Ime(ime) => {
if let Some(sel) = ui_state.focus { if let Some(sel) = ui_state.focus {
let mut text = sel.edit(ui); let mut text = sel.edit(rsc);
match ime { match ime {
Ime::Enabled | Ime::Disabled => (), Ime::Enabled | Ime::Disabled => (),
Ime::Preedit(content, _pos) => { Ime::Preedit(content, _pos) => {
@@ -172,15 +296,59 @@ impl<State: DefaultAppState> AppState for State {
} }
_ => (), _ => (),
} }
self.window_event(event); state.window_event(event, rsc, render);
(ui, ui_state) = self.ui_with_state(); let ui_state = self.state.default_state_mut();
if ui.needs_redraw() { if render.needs_redraw(&ui_state.root, rsc.widgets()) {
ui_state.renderer.window().request_redraw(); ui_state.renderer.window().request_redraw();
} }
ui_state.input.end_frame(); ui_state.input.end_frame();
} }
fn exit(&mut self) { fn exit(&mut self) {
self.exit(); self.state.exit(&mut self.rsc, &mut self.render);
}
}
pub trait RscIdx<Rsc> {
type Output;
fn get(self, rsc: &Rsc) -> &Self::Output;
fn get_mut(self, rsc: &mut Rsc) -> &mut Self::Output;
}
impl<State: 'static, I: RscIdx<DefaultRsc<State>>> std::ops::Index<I> for DefaultRsc<State> {
type Output = I::Output;
fn index(&self, index: I) -> &Self::Output {
index.get(self)
}
}
impl<State: 'static, I: RscIdx<DefaultRsc<State>>> std::ops::IndexMut<I> for DefaultRsc<State> {
fn index_mut(&mut self, index: I) -> &mut Self::Output {
index.get_mut(self)
}
}
impl<W: Widget, Rsc: UiRsc> RscIdx<Rsc> for WeakWidget<W> {
type Output = W;
fn get(self, rsc: &Rsc) -> &Self::Output {
&rsc.ui().widgets[self]
}
fn get_mut(self, rsc: &mut Rsc) -> &mut Self::Output {
&mut rsc.ui_mut().widgets[self]
}
}
impl<T: 'static, Rsc: HasWidgetState> RscIdx<Rsc> for WeakState<T> {
type Output = T;
fn get(self, rsc: &Rsc) -> &Self::Output {
rsc.widget_state().get(self)
}
fn get_mut(self, rsc: &mut Rsc) -> &mut Self::Output {
rsc.widget_state_mut().get_mut(self)
} }
} }

View File

@@ -1,7 +1,7 @@
use iris_core::{Ui, UiLimits, UiRenderNode}; use iris_core::{UiData, UiLimits, UiRenderNode, UiRenderState};
use pollster::FutureExt; use pollster::FutureExt;
use std::sync::Arc; use std::sync::Arc;
use wgpu::{util::StagingBelt, *}; use wgpu::*;
use winit::{dpi::PhysicalSize, window::Window}; use winit::{dpi::PhysicalSize, window::Window};
pub const CLEAR_COLOR: Color = Color::BLACK; pub const CLEAR_COLOR: Color = Color::BLACK;
@@ -13,13 +13,12 @@ pub struct UiRenderer {
queue: Queue, queue: Queue,
config: SurfaceConfiguration, config: SurfaceConfiguration,
encoder: CommandEncoder, encoder: CommandEncoder,
staging_belt: StagingBelt,
pub ui: UiRenderNode, pub ui: UiRenderNode,
} }
impl UiRenderer { impl UiRenderer {
pub fn update(&mut self, ui: &mut Ui) { pub fn update(&mut self, ui: &mut UiData, render: &mut UiRenderState) {
self.ui.update(&self.device, &self.queue, ui); self.ui.update(&self.device, &self.queue, ui, render);
} }
pub fn draw(&mut self) { pub fn draw(&mut self) {
@@ -46,9 +45,7 @@ impl UiRenderer {
} }
self.queue.submit(std::iter::once(encoder.finish())); self.queue.submit(std::iter::once(encoder.finish()));
self.staging_belt.finish();
output.present(); output.present();
self.staging_belt.recall();
} }
pub fn resize(&mut self, size: &PhysicalSize<u32>) { pub fn resize(&mut self, size: &PhysicalSize<u32>) {
@@ -126,10 +123,9 @@ impl UiRenderer {
surface.configure(&device, &config); surface.configure(&device, &config);
let staging_belt = StagingBelt::new(4096 * 4);
let encoder = Self::create_encoder(&device); let encoder = Self::create_encoder(&device);
let shape_pipeline = UiRenderNode::new(&device, &queue, &config, ui_limits); let ui = UiRenderNode::new(&device, &queue, &config, ui_limits);
Self { Self {
surface, surface,
@@ -137,8 +133,7 @@ impl UiRenderer {
queue, queue,
config, config,
encoder, encoder,
staging_belt, ui,
ui: shape_pipeline,
window, window,
} }
} }

View File

@@ -28,12 +28,13 @@ pub struct CursorSenses(Vec<CursorSense>);
impl Event for CursorSenses { impl Event for CursorSenses {
type Data<'a> = CursorData<'a>; type Data<'a> = CursorData<'a>;
type State = SensorState; type State = SensorState;
fn should_run(&self, data: &mut Self::Data<'_>) -> bool { fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
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
} }
} }
} }
@@ -115,7 +116,7 @@ pub enum ActivationState {
/// or basically have some way to have custom senses /// or basically have some way to have custom senses
/// that depend on active widget positions /// that depend on active widget positions
/// but I'm not sure how or if worth it /// but I'm not sure how or if worth it
pub struct Sensor<Ctx, Data> { pub struct Sensor<Ctx: HasEvents, Data> {
pub senses: CursorSenses, pub senses: CursorSenses,
pub f: Rc<dyn EventFn<Ctx, Data>>, pub f: Rc<dyn EventFn<Ctx, Data>>,
} }
@@ -127,29 +128,46 @@ pub struct SensorState {
pub hover: ActivationState, pub hover: ActivationState,
} }
#[derive(Clone)]
pub struct CursorData<'a> { pub struct CursorData<'a> {
/// 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 render: &'a UiRenderState,
} }
pub trait SensorUi<State> { pub trait SensorUi {
fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2); fn run_sensors<Rsc: HasEvents>(
&self,
rsc: &mut Rsc,
state: &mut Rsc::State,
cursor: CursorState,
window_size: Vec2,
);
} }
impl<State: RunEvents> SensorUi<State> for State { impl SensorUi for UiRenderState {
fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2) { fn run_sensors<Rsc: HasEvents>(
let layers = std::mem::take(&mut self.ui_mut().layers); &self,
let mut active = std::mem::take(&mut self.events_mut().get_type::<CursorSense>().active); rsc: &mut Rsc,
for layer in layers.indices().rev() { state: &mut Rsc::State,
cursor: CursorState,
window_size: Vec2,
) {
// in order to remove this take, need to store active list in UiRenderState somehow
// this would probably be done through a generic parameter that adds yet another rsc /
// state like thing, but local to render state, and is passed to UiRsc events so you can
// update it there?
let mut active = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().active);
for layer in self.layers.indices().rev() {
let mut sensed = false; let mut sensed = false;
for (id, sensor) in active.get_mut(&layer).into_flat_iter() { for (id, sensor) in active.get_mut(&layer).into_flat_iter() {
let shape = self.ui().active.get(id).unwrap().region; let shape = self.active.get(id).unwrap().region;
let region = shape.to_px(window_size); let region = shape.to_px(window_size);
let in_shape = cursor.exists && region.contains(cursor.pos); let in_shape = cursor.exists && region.contains(cursor.pos);
sensor.hover.update(in_shape); sensor.hover.update(in_shape);
@@ -158,7 +176,9 @@ impl<State: RunEvents> SensorUi<State> for State {
} }
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,
@@ -167,15 +187,15 @@ impl<State: RunEvents> SensorUi<State> for State {
// this does not have any meaning; // this does not have any meaning;
// 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,
render: self,
}; };
self.run_event::<CursorSense>(*id, &mut data); rsc.run_event::<CursorSense>(*id, data, state);
} }
if sensed { if sensed {
break; break;
} }
} }
self.events_mut().get_type::<CursorSense>().active = active; rsc.events_mut().get_type::<CursorSense>().active = active;
self.ui_mut().layers = layers;
} }
} }

75
src/default/state.rs Normal file
View File

@@ -0,0 +1,75 @@
use iris_core::{
WidgetId,
util::{HashMap, HashSet},
};
use std::{
any::{Any, TypeId},
marker::PhantomData,
};
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
struct Key {
id: WidgetId,
ty: TypeId,
i: usize,
}
#[derive(Default)]
pub struct WidgetState {
widgets: HashMap<WidgetId, HashSet<(TypeId, usize)>>,
counts: HashMap<(WidgetId, TypeId), usize>,
map: HashMap<Key, Box<dyn Any>>,
}
impl WidgetState {
pub fn new() -> Self {
Self::default()
}
pub fn add<T: 'static>(&mut self, id: WidgetId, data: T) -> WeakState<T> {
let ty = TypeId::of::<T>();
let count = self.counts.entry((id, ty)).or_default();
let i = *count;
let key = Key { ty, i, id };
self.map.insert(key, Box::new(data));
self.widgets.entry(id).or_default().insert((ty, i));
*count += 1;
WeakState {
key,
_pd: PhantomData,
}
}
pub fn remove(&mut self, id: WidgetId) {
for &(ty, i) in self.widgets.get(&id).into_iter().flatten() {
self.map.remove(&Key { id, ty, i });
}
}
pub fn get<T: 'static>(&self, state: WeakState<T>) -> &T {
self.map.get(&state.key).unwrap().downcast_ref().unwrap()
}
pub fn get_mut<T: 'static>(&mut self, state: WeakState<T>) -> &mut T {
self.map
.get_mut(&state.key)
.unwrap()
.downcast_mut()
.unwrap()
}
}
#[derive(Clone, Copy)]
pub struct WeakState<T> {
key: Key,
_pd: PhantomData<T>,
}
pub trait HasWidgetState {
fn widget_state(&self) -> &WidgetState;
fn widget_state_mut(&mut self) -> &mut WidgetState;
}
impl<'a, T: 'static> FnOnce<(&'a mut WidgetState,)> for WeakState<T> {
type Output = &'a mut T;
extern "rust-call" fn call_once(self, (state,): (&'a mut WidgetState,)) -> Self::Output {
state.get_mut(self)
}
}

82
src/default/task.rs Normal file
View File

@@ -0,0 +1,82 @@
use iris_core::HasState;
use std::{
pin::Pin,
sync::{
Arc,
mpsc::{Receiver as SyncReceiver, Sender as SyncSender, channel as sync_channel},
},
};
use tokio::{
runtime::Runtime,
sync::mpsc::{
UnboundedReceiver as AsyncReceiver, UnboundedSender as AsyncSender,
unbounded_channel as async_channel,
},
};
use winit::window::Window;
pub type TaskMsgSender<Rsc> = SyncSender<Box<dyn TaskUpdate<Rsc>>>;
pub type TaskMsgReceiver<Rsc> = SyncReceiver<Box<dyn TaskUpdate<Rsc>>>;
pub trait TaskUpdate<Rsc: HasState>: FnOnce(&mut Rsc::State, &mut Rsc) + Send {}
impl<F: FnOnce(&mut Rsc::State, &mut Rsc) + Send, Rsc: HasState> TaskUpdate<Rsc> for F {}
pub struct Tasks<Rsc: HasState> {
start: AsyncSender<BoxTask>,
window: Arc<Window>,
msg_send: SyncSender<Box<dyn TaskUpdate<Rsc>>>,
}
pub struct TaskCtx<Rsc: HasState> {
send: TaskMsgSender<Rsc>,
}
impl<Rsc: HasState> TaskCtx<Rsc> {
pub fn update(&mut self, f: impl TaskUpdate<Rsc> + 'static) {
let _ = self.send.send(Box::new(f));
}
}
impl<Rsc: HasState + 'static> TaskCtx<Rsc> {
fn new(send: TaskMsgSender<Rsc>) -> Self {
Self { send }
}
}
type BoxTask = Pin<Box<dyn Future<Output = ()> + Send>>;
impl<Rsc: HasState> Tasks<Rsc> {
pub fn init(window: Arc<Window>) -> (Self, TaskMsgReceiver<Rsc>) {
let (start, start_recv) = async_channel();
let (msgs, msgs_recv) = sync_channel();
std::thread::spawn(|| {
let rt = Runtime::new().unwrap();
rt.block_on(listen(start_recv))
});
(
Self {
start,
msg_send: msgs,
window,
},
msgs_recv,
)
}
pub fn spawn<F: AsyncFnOnce(TaskCtx<Rsc>) + 'static + std::marker::Send>(&mut self, task: F)
where
F::CallOnceFuture: Send,
{
let send = self.msg_send.clone();
let window = self.window.clone();
let _ = self.start.send(Box::pin(async move {
task(TaskCtx::new(send)).await;
window.request_redraw();
}));
}
}
async fn listen(mut recv: AsyncReceiver<BoxTask>) {
while let Some(task) = recv.recv().await {
tokio::spawn(task);
}
}

View File

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

View File

@@ -4,28 +4,15 @@
#![feature(associated_type_defaults)] #![feature(associated_type_defaults)]
#![feature(unsize)] #![feature(unsize)]
#![feature(option_into_flat_iter)] #![feature(option_into_flat_iter)]
#![feature(async_fn_traits)]
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::*;
@@ -35,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 $state:ty) => {
mod local_event_trait {
use super::*;
#[allow(unused_imports)]
use $crate::prelude::*;
pub trait EventableCtx<WL: WidgetLike<$state, Tag>, Tag> {
fn on<E: EventLike>(
self,
event: E,
f: impl for<'a> WidgetEventFn<
<$state as HasState>::State,
<E::Event as Event>::Data<'a>,
WL::Widget
>,
) -> impl WidgetIdFn<$state, WL::Widget>;
}
impl<WL: WidgetLike<$state, Tag>, Tag> EventableCtx<WL, Tag> for WL {
fn on<E: EventLike>(
self,
event: E,
f: impl for<'a> WidgetEventFn<
<$state as HasState>::State,
<E::Event as Event>::Data<'a>,
WL::Widget
>,
) -> impl WidgetIdFn<$state, WL::Widget> {
eventable::Eventable::on(self, event, f)
}
}
}
$vis type EventManager = $crate::prelude::EventManager<<$state as HasState>::State>;
$vis use local_event_trait::*;
};
}
pub use event_state;

View File

@@ -19,10 +19,10 @@ impl Widget for Image {
} }
} }
pub fn image<State: HasUi>(image: impl LoadableImage) -> impl WidgetFn<State, Image> { pub fn image<State: UiRsc>(image: impl LoadableImage) -> impl WidgetFn<State, Image> {
let image = image.get_image().expect("Failed to load image"); let image = image.get_image().expect("Failed to load image");
move |state| Image { move |state| Image {
handle: state.get_mut().add_texture(image), handle: state.ui_mut().textures.add(image),
} }
} }

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Masked { pub struct Masked {
pub inner: WidgetHandle, pub inner: StrongWidget,
} }
impl Widget for Masked { impl Widget for Masked {

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Aligned { pub struct Aligned {
pub inner: WidgetHandle, pub inner: StrongWidget,
pub align: Align, pub align: Align,
} }

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct LayerOffset { pub struct LayerOffset {
pub inner: WidgetHandle, pub inner: StrongWidget,
pub offset: usize, pub offset: usize,
} }

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct MaxSize { pub struct MaxSize {
pub inner: WidgetHandle, pub inner: StrongWidget,
pub x: Option<Len>, pub x: Option<Len>,
pub y: Option<Len>, pub y: Option<Len>,
} }

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Offset { pub struct Offset {
pub inner: WidgetHandle, pub inner: StrongWidget,
pub amt: UiVec2, pub amt: UiVec2,
} }

View File

@@ -2,7 +2,7 @@ use crate::prelude::*;
pub struct Pad { pub struct Pad {
pub padding: Padding, pub padding: Padding,
pub inner: WidgetHandle, pub inner: StrongWidget,
} }
impl Widget for Pad { impl Widget for Pad {

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Scroll { pub struct Scroll {
inner: WidgetHandle, inner: StrongWidget,
axis: Axis, axis: Axis,
amt: f32, amt: f32,
snap_end: bool, snap_end: bool,
@@ -41,7 +41,7 @@ impl Widget for Scroll {
} }
impl Scroll { impl Scroll {
pub fn new(inner: WidgetHandle, axis: Axis) -> Self { pub fn new(inner: StrongWidget, axis: Axis) -> Self {
Self { Self {
inner, inner,
axis, axis,

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Sized { pub struct Sized {
pub inner: WidgetHandle, pub inner: StrongWidget,
pub x: Option<Len>, pub x: Option<Len>,
pub y: Option<Len>, pub y: Option<Len>,
} }

View File

@@ -2,7 +2,7 @@ use crate::prelude::*;
use std::marker::PhantomData; use std::marker::PhantomData;
pub struct Span { pub struct Span {
pub children: Vec<WidgetHandle>, pub children: Vec<StrongWidget>,
pub dir: Dir, pub dir: Dir,
pub gap: f32, pub gap: f32,
} }
@@ -62,11 +62,11 @@ impl Span {
self self
} }
pub fn push(&mut self, w: WidgetHandle) { pub fn push(&mut self, w: StrongWidget) {
self.children.push(w); self.children.push(w);
} }
pub fn pop(&mut self) -> Option<WidgetHandle> { pub fn pop(&mut self) -> Option<StrongWidget> {
self.children.pop() self.children.pop()
} }
@@ -159,15 +159,15 @@ pub struct SpanBuilder<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Ta
_pd: PhantomData<(State, Tag)>, _pd: PhantomData<(State, Tag)>,
} }
impl<State: StateLike<State>, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag> impl<Rsc, const LEN: usize, Wa: WidgetArrLike<Rsc, LEN, Tag>, Tag> WidgetFnTrait<Rsc>
WidgetFnTrait<State> for SpanBuilder<State, LEN, Wa, Tag> for SpanBuilder<Rsc, LEN, Wa, Tag>
{ {
type Widget = Span; type Widget = Span;
#[track_caller] #[track_caller]
fn run(self, state: &mut State) -> Self::Widget { fn run(self, rsc: &mut Rsc) -> Self::Widget {
Span { Span {
children: self.children.add(state).arr.into_iter().collect(), children: self.children.add(rsc).arr.into_iter().collect(),
dir: self.dir, dir: self.dir,
gap: self.gap, gap: self.gap,
} }
@@ -193,7 +193,7 @@ impl<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag>
} }
impl std::ops::Deref for Span { impl std::ops::Deref for Span {
type Target = Vec<WidgetHandle>; type Target = Vec<StrongWidget>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.children &self.children

View File

@@ -3,7 +3,7 @@ use std::marker::PhantomData;
use crate::prelude::*; use crate::prelude::*;
pub struct Stack { pub struct Stack {
pub children: Vec<WidgetHandle>, pub children: Vec<StrongWidget>,
pub size: StackSize, pub size: StackSize,
} }
@@ -48,14 +48,15 @@ pub struct StackBuilder<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, T
_pd: PhantomData<(State, Tag)>, _pd: PhantomData<(State, Tag)>,
} }
impl<State: StateLike<State>, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag> FnOnce<(&mut State,)> impl<Rsc, const LEN: usize, Wa: WidgetArrLike<Rsc, LEN, Tag>, Tag> WidgetFnTrait<Rsc>
for StackBuilder<State, LEN, Wa, Tag> for StackBuilder<Rsc, LEN, Wa, Tag>
{ {
type Output = Stack; type Widget = Stack;
extern "rust-call" fn call_once(self, args: (&mut State,)) -> Self::Output { #[track_caller]
fn run(self, rsc: &mut Rsc) -> Self::Widget {
Stack { Stack {
children: self.children.add(args.0).arr.into_iter().collect(), children: self.children.add(rsc).arr.into_iter().collect(),
size: self.size, size: self.size,
} }
} }

View File

@@ -2,7 +2,7 @@ use crate::prelude::*;
use std::marker::{Sized, Unsize}; use std::marker::{Sized, Unsize};
pub struct WidgetPtr { pub struct WidgetPtr {
pub inner: Option<WidgetHandle>, pub inner: Option<StrongWidget>,
} }
impl Widget for WidgetPtr { impl Widget for WidgetPtr {
@@ -38,14 +38,14 @@ impl WidgetPtr {
inner: Default::default(), inner: Default::default(),
} }
} }
pub fn set<W: ?Sized + Unsize<dyn Widget>>(&mut self, to: WidgetHandle<W>) { pub fn set<W: ?Sized + Unsize<dyn Widget>>(&mut self, to: StrongWidget<W>) {
self.inner = Some(to) self.inner = Some(to)
} }
pub fn replace<W: ?Sized + Unsize<dyn Widget>>( pub fn replace<W: ?Sized + Unsize<dyn Widget>>(
&mut self, &mut self,
to: WidgetHandle<W>, to: StrongWidget<W>,
) -> Option<WidgetHandle> { ) -> Option<StrongWidget> {
self.inner.replace(to) self.inner.replace(to)
} }
} }

View File

@@ -51,15 +51,15 @@ impl<State, O, H: WidgetOption<State>> TextBuilder<State, O, H> {
} }
} }
impl<State: HasUi + StateLike<State>, O> TextBuilder<State, O> { impl<Rsc: UiRsc, O> TextBuilder<Rsc, O> {
pub fn hint<W: WidgetLike<State, Tag>, Tag>( pub fn hint<W: WidgetLike<Rsc, Tag>, Tag>(
self, self,
hint: W, hint: W,
) -> TextBuilder<State, O, impl WidgetOption<State>> { ) -> TextBuilder<Rsc, O, impl WidgetOption<Rsc>> {
TextBuilder { TextBuilder {
content: self.content, content: self.content,
attrs: self.attrs, attrs: self.attrs,
hint: move |ui: &mut State| Some(hint.add_strong(ui).any()), hint: move |rsc: &mut Rsc| Some(hint.add_strong(rsc).any()),
output: self.output, output: self.output,
state: PhantomData, state: PhantomData,
} }
@@ -75,19 +75,19 @@ pub trait TextBuilderOutput<State>: Sized {
} }
pub struct TextOutput; pub struct TextOutput;
impl<State: HasUi> TextBuilderOutput<State> for TextOutput { impl<Rsc: UiRsc> TextBuilderOutput<Rsc> for TextOutput {
type Output = Text; type Output = Text;
fn run<H: WidgetOption<State>>( fn run<H: WidgetOption<Rsc>>(
state: &mut State, state: &mut Rsc,
builder: TextBuilder<State, Self, H>, builder: TextBuilder<Rsc, Self, H>,
) -> Self::Output { ) -> Self::Output {
let mut buf = TextBuffer::new_empty(Metrics::new( let mut buf = TextBuffer::new_empty(Metrics::new(
builder.attrs.font_size, builder.attrs.font_size,
builder.attrs.line_height, builder.attrs.line_height,
)); ));
let hint = builder.hint.get(state); let hint = builder.hint.get(state);
let font_system = &mut state.get_mut().text.font_system; let font_system = &mut state.ui_mut().text.font_system;
buf.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None); buf.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None);
let mut text = Text { let mut text = Text {
content: builder.content.into(), content: builder.content.into(),
@@ -103,7 +103,7 @@ pub struct TextEditOutput {
mode: EditMode, mode: EditMode,
} }
impl<State: HasUi> TextBuilderOutput<State> for TextEditOutput { impl<State: UiRsc> TextBuilderOutput<State> for TextEditOutput {
type Output = TextEdit; type Output = TextEdit;
fn run<H: WidgetOption<State>>( fn run<H: WidgetOption<State>>(
@@ -118,7 +118,7 @@ impl<State: HasUi> TextBuilderOutput<State> for TextEditOutput {
TextView::new(buf, builder.attrs, builder.hint.get(state)), TextView::new(buf, builder.attrs, builder.hint.get(state)),
builder.output.mode, builder.output.mode,
); );
let font_system = &mut state.get_mut().text.font_system; let font_system = &mut state.ui_mut().text.font_system;
text.buf text.buf
.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None); .set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None);
builder.attrs.apply(font_system, &mut text.buf, None); builder.attrs.apply(font_system, &mut text.buf, None);

View File

@@ -617,11 +617,11 @@ impl DerefMut for TextEdit {
} }
pub trait TextEditable { pub trait TextEditable {
fn edit<'a>(&self, ui: &'a mut impl HasUi) -> TextEditCtx<'a>; fn edit<'a>(&self, ui: &'a mut impl UiRsc) -> TextEditCtx<'a>;
} }
impl<I: IdLike<Widget = TextEdit>> TextEditable for I { impl<I: IdLike<Widget = TextEdit>> TextEditable for I {
fn edit<'a>(&self, ui: &'a mut impl HasUi) -> TextEditCtx<'a> { fn edit<'a>(&self, ui: &'a mut impl UiRsc) -> TextEditCtx<'a> {
let ui = ui.ui_mut(); let ui = ui.ui_mut();
TextEditCtx { TextEditCtx {
text: ui.widgets.get_mut(self).unwrap(), text: ui.widgets.get_mut(self).unwrap(),

View File

@@ -22,11 +22,11 @@ pub struct TextView {
// cache // cache
tex: Option<RenderedText>, tex: Option<RenderedText>,
width: Option<f32>, width: Option<f32>,
pub hint: Option<WidgetHandle>, pub hint: Option<StrongWidget>,
} }
impl TextView { impl TextView {
pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option<WidgetHandle>) -> Self { pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option<StrongWidget>) -> Self {
Self { Self {
attrs: attrs.into(), attrs: attrs.into(),
buf: buf.into(), buf: buf.into(),

View File

@@ -3,35 +3,35 @@ use crate::prelude::*;
// these methods should "not require any context" (require unit) because they're in core // these methods should "not require any context" (require unit) because they're in core
widget_trait! { widget_trait! {
pub trait CoreWidget<State: HasUi + StateLike<State> + 'static>; pub trait CoreWidget<Rsc: UiRsc + 'static>;
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<State, Pad> { fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Rsc, Pad> {
|state| Pad { |state| Pad {
padding: padding.into(), padding: padding.into(),
inner: self.add_strong(state), inner: self.add_strong(state),
} }
} }
fn align(self, align: impl Into<Align>) -> impl WidgetFn<State, Aligned> { fn align(self, align: impl Into<Align>) -> impl WidgetFn<Rsc, Aligned> {
move |state| Aligned { move |state| Aligned {
inner: self.add_strong(state), inner: self.add_strong(state),
align: align.into(), align: align.into(),
} }
} }
fn center(self) -> impl WidgetFn<State, Aligned> { fn center(self) -> impl WidgetFn<Rsc, Aligned> {
self.align(Align::CENTER) self.align(Align::CENTER)
} }
fn label(self, label: impl Into<String>) -> impl WidgetIdFn<State, WL::Widget> { fn label(self, label: impl Into<String>) -> impl WidgetIdFn<Rsc, WL::Widget> {
|state| { |state| {
let id = self.add(state); let id = self.add(state);
state.get_mut().set_label(id, label.into()); state.ui_mut().widgets.set_label(id, label.into());
id id
} }
} }
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<State, Sized> { fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Rsc, Sized> {
let size = size.into(); let size = size.into();
move |state| Sized { move |state| Sized {
inner: self.add_strong(state), inner: self.add_strong(state),
@@ -40,7 +40,7 @@ widget_trait! {
} }
} }
fn max_width(self, len: impl Into<Len>) -> impl WidgetFn<State, MaxSize> { fn max_width(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, MaxSize> {
let len = len.into(); let len = len.into();
move |state| MaxSize { move |state| MaxSize {
inner: self.add_strong(state), inner: self.add_strong(state),
@@ -49,7 +49,7 @@ widget_trait! {
} }
} }
fn max_height(self, len: impl Into<Len>) -> impl WidgetFn<State, MaxSize> { fn max_height(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, MaxSize> {
let len = len.into(); let len = len.into();
move |state| MaxSize { move |state| MaxSize {
inner: self.add_strong(state), inner: self.add_strong(state),
@@ -58,7 +58,7 @@ widget_trait! {
} }
} }
fn width(self, len: impl Into<Len>) -> impl WidgetFn<State, Sized> { fn width(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, Sized> {
let len = len.into(); let len = len.into();
move |state| Sized { move |state| Sized {
inner: self.add_strong(state), inner: self.add_strong(state),
@@ -67,7 +67,7 @@ widget_trait! {
} }
} }
fn height(self, len: impl Into<Len>) -> impl WidgetFn<State, Sized> { fn height(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, Sized> {
let len = len.into(); let len = len.into();
move |state| Sized { move |state| Sized {
inner: self.add_strong(state), inner: self.add_strong(state),
@@ -76,65 +76,64 @@ widget_trait! {
} }
} }
fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<State, Offset> { fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<Rsc, Offset> {
move |state| Offset { move |state| Offset {
inner: self.add_strong(state), inner: self.add_strong(state),
amt: amt.into(), amt: amt.into(),
} }
} }
fn scrollable(self) -> impl WidgetIdFn<State, Scroll> where State: HasEvents { fn scrollable(self) -> impl WidgetIdFn<Rsc, Scroll> where Rsc: HasEvents {
use 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: &mut EventIdCtx<'_, State::State, CursorData<'_>, Scroll>| { .on(CursorSense::Scroll, |ctx, rsc| {
let delta = ctx.data.scroll_delta.y * 50.0; let delta = ctx.data.scroll_delta.y * 50.0;
ctx.widget().scroll(delta); ctx.widget(rsc).scroll(delta);
}) })
.add(state) .add(state)
} }
} }
fn masked(self) -> impl WidgetFn<State, Masked> { fn masked(self) -> impl WidgetFn<Rsc, Masked> {
move |state| Masked { move |state| Masked {
inner: self.add_strong(state), inner: self.add_strong(state),
} }
} }
fn background<T>(self, w: impl WidgetLike<State, T>) -> impl WidgetFn<State, Stack> { fn background<T>(self, w: impl WidgetLike<Rsc, T>) -> impl WidgetFn<Rsc, Stack> {
move |state| Stack { move |state| Stack {
children: vec![w.add_strong(state), self.add_strong(state)], children: vec![w.add_strong(state), self.add_strong(state)],
size: StackSize::Child(1), size: StackSize::Child(1),
} }
} }
fn foreground<T>(self, w: impl WidgetLike<State, T>) -> impl WidgetFn<State, Stack> { fn foreground<T>(self, w: impl WidgetLike<Rsc, T>) -> impl WidgetFn<Rsc, Stack> {
move |state| Stack { move |state| Stack {
children: vec![self.add_strong(state), w.add_strong(state)], children: vec![self.add_strong(state), w.add_strong(state)],
size: StackSize::Child(0), size: StackSize::Child(0),
} }
} }
fn layer_offset(self, offset: usize) -> impl WidgetFn<State, LayerOffset> { fn layer_offset(self, offset: usize) -> impl WidgetFn<Rsc, LayerOffset> {
move |state| LayerOffset { move |state| LayerOffset {
inner: self.add_strong(state), inner: self.add_strong(state),
offset, offset,
} }
} }
fn to_any(self) -> impl WidgetIdFn<State> { fn to_any(self) -> impl WidgetIdFn<Rsc> {
|state| self.add(state) |state| self.add(state)
} }
fn set_ptr(self, ptr: WidgetRef<WidgetPtr>, state: &mut State) { fn set_ptr(self, ptr: WeakWidget<WidgetPtr>, state: &mut Rsc) {
let id = self.add_strong(state); let id = self.add_strong(state);
state.get_mut()[ptr].inner = Some(id); state.ui_mut().widgets[ptr].inner = Some(id);
} }
} }
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>