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]
name = "iris"
default-run = "test"
version.workspace = true
edition.workspace = true
@@ -16,6 +15,10 @@ arboard = { workspace = true, features = ["wayland-data-control"] }
pollster = { workspace = true }
wgpu = { 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]
members = ["core", "macro"]
@@ -27,12 +30,13 @@ edition = "2024"
[workspace.dependencies]
pollster = "0.4.0"
winit = "0.30.12"
wgpu = "27.0.1"
wgpu = "28.0.0"
bytemuck = "1.23.1"
image = "0.25.6"
cosmic-text = "0.15.0"
cosmic-text = "0.16.0"
unicode-segmentation = "1.12.0"
fxhash = "0.2.1"
arboard = "3.6.1"
iris-core = { path = "core" }
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;
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> {
fn attr<A: WidgetAttr<State, W>>(self, input: A::Input) -> impl WidgetIdFn<State, W>;
pub trait Attrable<Rsc, W: ?Sized, Tag> {
fn attr<A: WidgetAttr<Rsc, W>>(self, input: A::Input) -> impl WidgetIdFn<Rsc, W>;
}
impl<State: HasUi + StateLike<State>, WL: WidgetLike<State, Tag>, Tag>
Attrable<State, WL::Widget, Tag> for WL
{
fn attr<A: WidgetAttr<State, WL::Widget>>(
impl<Rsc: UiRsc, WL: WidgetLike<Rsc, Tag>, Tag> Attrable<Rsc, WL::Widget, Tag> for WL {
fn attr<A: WidgetAttr<Rsc, WL::Widget>>(
self,
input: A::Input,
) -> impl WidgetIdFn<State, WL::Widget> {
|state| {
let id = self.add(state);
A::run(state, id, input);
) -> impl WidgetIdFn<Rsc, WL::Widget> {
|rsc| {
let id = self.add(rsc);
A::run(rsc, id, input);
id
}
}

View File

@@ -1,116 +1,18 @@
use crate::{HasEvents, HasState, HasUi, StateLike, Ui, Widget, WidgetRef};
use std::ops::{Deref, DerefMut};
use crate::{HasEvents, WeakWidget, Widget};
pub struct EventCtx<'a, State, Data> {
pub state: &'a mut State,
pub data: &'a mut Data,
pub struct EventCtx<'a, Rsc: HasEvents, Data> {
pub state: &'a mut Rsc::State,
pub data: Data,
}
pub struct EventIdCtx<'a, State, Data, W: ?Sized> {
pub widget: WidgetRef<W>,
pub state: &'a mut State,
pub data: &'a mut Data,
pub struct EventIdCtx<'a, Rsc: HasEvents, Data, W: ?Sized> {
pub widget: WeakWidget<W>,
pub state: &'a mut Rsc::State,
pub data: Data,
}
impl<State: HasUi, Data, W: ?Sized> Deref for EventIdCtx<'_, State, Data, W> {
type Target = State;
fn deref(&self) -> &Self::Target {
self.state
impl<Rsc: HasEvents, Data, W: Widget> EventIdCtx<'_, Rsc, Data, W> {
pub fn widget<'a>(&self, rsc: &'a mut Rsc) -> &'a mut W {
&mut rsc.ui_mut().widgets[self.widget]
}
}
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::{
ActiveData, Event, EventCtx, EventFn, EventIdCtx, EventLike, IdLike, LayerId, Widget,
WidgetEventFn, WidgetId, WidgetRef,
ActiveData, Event, EventCtx, EventFn, EventIdCtx, EventLike, HasEvents, IdLike, LayerId,
WeakWidget, WidgetEventFn, WidgetId,
util::{HashMap, HashSet, TypeMap},
};
use std::{any::TypeId, rc::Rc};
pub struct EventManager<State> {
pub struct EventManager<Rsc> {
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 {
Self {
widget_to_types: Default::default(),
@@ -19,26 +19,27 @@ impl<State> Default for EventManager<State> {
}
}
impl<State: 'static> EventManager<State> {
pub fn get_type<E: EventLike>(&mut self) -> &mut TypeEventManager<State, E::Event> {
impl<Rsc: HasEvents + 'static> EventManager<Rsc> {
pub fn get_type<E: EventLike>(&mut self) -> &mut TypeEventManager<Rsc, E::Event> {
self.types.type_or_default()
}
pub fn register<W: Widget + ?Sized, E: EventLike>(
pub fn register<I: IdLike + 'static, E: EventLike>(
&mut self,
id: WidgetRef<W>,
id: I,
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.widget_to_types
.entry(id.id())
.entry(i)
.or_default()
.insert(Self::type_key::<E>());
}
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);
}
impl<State: 'static> EventsLike for EventManager<State> {
impl<Rsc: HasEvents + 'static> EventsLike for EventManager<Rsc> {
fn remove(&mut self, id: WidgetId) {
for t in self.widget_to_types.get(&id).into_flat_iter() {
self.types.get_mut(t).unwrap().remove(id);
@@ -74,14 +75,14 @@ pub trait EventManagerLike<State> {
fn undraw(&mut self, data: &ActiveData);
}
type EventData<State, E> = (E, Rc<dyn for<'a> EventFn<State, <E as Event>::Data<'a>>>);
pub struct TypeEventManager<State, E: Event> {
type EventData<Rsc, E> = (E, Rc<dyn for<'a> EventFn<Rsc, <E as Event>::Data<'a>>>);
pub struct TypeEventManager<Rsc: HasEvents, E: Event> {
// TODO: reduce visiblity!!
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) {
self.map.remove(&id);
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 {
Self {
active: Default::default(),
@@ -111,23 +112,25 @@ impl<State, E: Event> Default for TypeEventManager<State, E> {
}
}
impl<State: 'static, E: Event> TypeEventManager<State, E> {
fn register<W: Widget + ?Sized>(
impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> {
fn register<I: IdLike + 'static>(
&mut self,
widget: WidgetRef<W>,
widget: I,
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();
self.map.entry(widget.id()).or_default().push((
event,
Rc::new(move |ctx| {
let mut test = EventIdCtx {
widget,
Rc::new(move |ctx, rsc| {
f(
EventIdCtx {
widget: WeakWidget::new(widget.id()),
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>(
&mut self,
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();
move |ctx| {
move |ctx, rsc| {
for (e, f) in fs {
if e.should_run(ctx.data) {
f(&mut EventCtx {
if let Some(data) = e.should_run(&ctx.data) {
f(
EventCtx {
state: ctx.state,
data: ctx.data,
})
data,
},
rsc,
)
}
}
}

View File

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

View File

@@ -1,41 +1,34 @@
use crate::{
Event, EventCtx, EventLike, EventManager, HasUi, IdLike, Widget, WidgetEventFn, WidgetRef,
Event, EventCtx, EventLike, EventManager, IdLike, UiRsc, WeakWidget, Widget, WidgetEventFn,
};
pub trait HasState {
type State: HasUi;
pub trait HasState: 'static {
type State;
}
pub trait HasEvents: Sized + HasState + HasUi {
fn get(&self) -> &EventManager<Self::State>;
fn get_mut(&mut self) -> &mut EventManager<Self::State>;
fn events(&self) -> &EventManager<Self::State> {
HasEvents::get(self)
}
fn events_mut(&mut self) -> &mut EventManager<Self::State> {
HasEvents::get_mut(self)
}
pub trait HasEvents: Sized + UiRsc + HasState {
fn events(&self) -> &EventManager<Self>;
fn events_mut(&mut self) -> &mut EventManager<Self>;
fn register_event<W: Widget + ?Sized, E: EventLike>(
&mut self,
id: WidgetRef<W>,
id: WeakWidget<W>,
event: E,
f: impl for<'a> WidgetEventFn<Self::State, <E::Event as Event>::Data<'a>, W>,
) where
Self::State: 'static,
{
f: impl for<'a> WidgetEventFn<Self, <E::Event as Event>::Data<'a>, W>,
) {
self.events_mut().register(id, event, f);
}
}
pub trait RunEvents: HasEvents + HasState<State = Self> + 'static {
pub trait RunEvents: HasEvents {
fn run_event<E: EventLike>(
&mut self,
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);
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,
SwashContent,
};
use image::{GenericImageView, RgbaImage};
use image::{DynamicImage, GenericImageView, RgbaImage};
use std::simd::{Simd, num::SimdUint};
/// TODO: properly wrap this
@@ -185,3 +185,7 @@ pub struct RenderedText {
pub top_left_offset: 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 crate::{
Ui,
UiData, UiRenderState,
render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf},
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();
for (i, primitives) in ui.layers.iter_mut() {
for (i, primitives) in ui_render.layers.iter_mut() {
self.active.push(i);
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 {
if h.layer == i && h.inst_idx == change.old {
h.inst_idx = change.new;
@@ -183,7 +189,7 @@ impl UiRenderNode {
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
label: Some("UI Shape Pipeline Layout"),
bind_group_layouts: &[&uniform_layout, &primitive_layout, &rsc_layout],
push_constant_ranges: &[],
immediate_size: 0,
});
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
label: Some("UI Shape Pipeline"),
@@ -219,7 +225,7 @@ impl UiRenderNode {
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
multiview_mask: 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::{
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},
};
use crate::{Mask, TextData, Textures, WeakWidget, WidgetId, Widgets, util::TrackedArena};
mod active;
mod cache;
mod draw_state;
mod painter;
mod render_state;
mod size;
mod state;
pub use active::*;
use cache::*;
pub use painter::Painter;
pub use render_state::*;
pub use size::*;
pub struct Ui {
// TODO: edit visibilities
#[derive(Default)]
pub struct UiData {
pub widgets: Widgets,
// retained painter state
pub active: HashMap<WidgetId, ActiveData>,
pub layers: PrimitiveLayers,
pub textures: Textures,
pub text: TextData,
output_size: Vec2,
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 {
fn get(&self) -> &Ui;
fn get_mut(&mut self) -> &mut Ui;
fn ui(&self) -> &Ui {
self.get()
}
fn ui_mut(&mut self) -> &mut Ui {
self.get_mut()
}
}
pub trait UiRsc {
fn ui(&self) -> &UiData;
fn ui_mut(&mut self) -> &mut UiData;
impl HasUi for Ui {
fn get(&self) -> &Ui {
self
}
#[allow(unused_variables)]
fn on_add(&mut self, id: WeakWidget) {}
#[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 {
self
}
}
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;
}
pub fn label(&self, id: impl IdLike) -> &String {
&self.widgets.data(id.id()).unwrap().label
}
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,
}
fn widgets(&self) -> &Widgets {
&self.ui().widgets
}
fn widgets_mut(&mut self) -> &mut Widgets {
&mut self.ui_mut().widgets
}
fn free(&mut self) {
while let Some(id) = self.widgets_mut().free_next() {
self.on_remove(id);
}
self.ui_mut().textures.free();
}
}

View File

@@ -1,14 +1,14 @@
use crate::{
Axis, Len, RenderedText, Size, SizeCtx, TextAttrs, TextBuffer, TextData, TextureHandle,
UiRegion, Widget, WidgetHandle, WidgetId,
Axis, Len, RenderedText, Size, SizeCtx, StrongWidget, TextAttrs, TextBuffer, TextData,
TextureHandle, UiRegion, UiRenderState, UiRsc, Widget, WidgetId,
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
ui::draw_state::DrawState,
util::Vec2,
};
/// makes your surfaces look pretty
pub struct Painter<'a, 'b> {
pub(super) state: &'a mut DrawState<'b>,
pub struct Painter<'a> {
pub(super) state: &'a mut UiRenderState,
pub(super) rsc: &'a mut dyn UiRsc,
pub(super) region: UiRegion,
pub(super) mask: MaskIdx,
@@ -19,7 +19,7 @@ pub struct Painter<'a, 'b> {
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) {
let h = self.state.layers.write(
self.layer,
@@ -32,7 +32,7 @@ impl<'a, 'c> Painter<'a, 'c> {
);
if self.mask != MaskIdx::NONE {
// 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);
}
@@ -48,24 +48,31 @@ impl<'a, 'c> Painter<'a, 'c> {
pub fn set_mask(&mut self, region: UiRegion) {
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.
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);
}
/// Draws a widget somewhere within this one.
/// 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));
}
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.state
.draw_inner(self.layer, id.id(), region, Some(self.id), self.mask, None);
self.state.draw_inner(
self.layer,
id.id(),
region,
Some(self.id),
self.mask,
None,
self.rsc,
);
}
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)
pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText {
self.state
.ui
.text
.draw(buffer, attrs, &mut self.state.ui.textures)
let ui = self.rsc.ui_mut();
ui.text.draw(buffer, attrs, &mut ui.textures)
}
pub fn region(&self) -> UiRegion {
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)
}
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 {
Axis::X => self.size_ctx().width(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 {
&mut self.state.text
&mut self.rsc.ui_mut().text
}
pub fn child_layer(&mut self) {
@@ -127,7 +132,7 @@ impl<'a, 'c> Painter<'a, 'c> {
}
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 {
@@ -135,6 +140,6 @@ impl<'a, 'c> Painter<'a, 'c> {
}
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 crate::{
HasUi, Widget,
UiRsc, Widget,
util::{RefCounter, SlotId},
};
@@ -12,7 +12,7 @@ pub type WidgetId = SlotId;
/// 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?
pub struct WidgetHandle<W: ?Sized = dyn Widget> {
pub struct StrongWidget<W: ?Sized = dyn Widget> {
pub(super) id: WidgetId,
counter: RefCounter,
send: Sender<WidgetId>,
@@ -21,24 +21,19 @@ pub struct WidgetHandle<W: ?Sized = dyn Widget> {
/// A weak handle to a widget.
/// 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,
#[allow(unused)]
ty: *const W,
}
pub struct WidgetHandles<W: ?Sized = dyn Widget> {
pub h: WidgetHandle<W>,
pub r: WidgetRef<W>,
}
impl<W: Widget + ?Sized + Unsize<dyn Widget>> WidgetHandle<W> {
pub fn any(self) -> WidgetHandle<dyn Widget> {
impl<W: Widget + ?Sized + Unsize<dyn Widget>> StrongWidget<W> {
pub fn any(self) -> StrongWidget<dyn Widget> {
self
}
}
impl<W: ?Sized> WidgetHandle<W> {
impl<W: ?Sized> StrongWidget<W> {
pub(crate) fn new(id: WidgetId, send: Sender<WidgetId>) -> Self {
Self {
id,
@@ -56,18 +51,13 @@ impl<W: ?Sized> WidgetHandle<W> {
self.counter.refs()
}
pub fn weak(&self) -> WidgetRef<W> {
pub fn weak(&self) -> WeakWidget<W> {
let Self { ty, id, .. } = *self;
WidgetRef { ty, id }
}
pub fn handles(self) -> WidgetHandles<W> {
let r = self.weak();
WidgetHandles { h: self, r }
WeakWidget { ty, id }
}
}
impl<W: ?Sized> WidgetRef<W> {
impl<W: ?Sized> WeakWidget<W> {
pub(crate) fn new(id: WidgetId) -> Self {
Self { id, ty: null_ptr() }
}
@@ -77,12 +67,12 @@ impl<W: ?Sized> WidgetRef<W> {
}
#[track_caller]
pub fn upgrade(self, ui: &mut impl HasUi) -> WidgetHandle<W> {
ui.ui_mut().widgets.upgrade(self)
pub fn upgrade(self, ui: &mut impl UiRsc) -> StrongWidget<W> {
ui.widgets_mut().upgrade(self)
}
}
impl<W: ?Sized> Drop for WidgetHandle<W> {
impl<W: ?Sized> Drop for StrongWidget<W> {
fn drop(&mut self) {
if self.counter.drop() {
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> {}
impl<State, W: ?Sized, F: FnOnce(&mut State) -> WidgetRef<W>> WidgetIdFn<State, W> for F {}
pub trait WidgetIdFn<Rsc, W: ?Sized = dyn Widget>: FnOnce(&mut Rsc) -> WeakWidget<W> {}
impl<Rsc, W: ?Sized, F: FnOnce(&mut Rsc) -> WeakWidget<W>> WidgetIdFn<Rsc, W> for F {}
pub trait IdLike {
type Widget: ?Sized;
fn id(&self) -> WidgetId;
}
impl<W: ?Sized> IdLike for &WidgetHandle<W> {
impl<W: ?Sized> IdLike for &StrongWidget<W> {
type Widget = W;
fn id(&self) -> WidgetId {
self.id
}
}
impl<W: ?Sized> IdLike for WidgetHandle<W> {
impl<W: ?Sized> IdLike for StrongWidget<W> {
type Widget = W;
fn id(&self) -> WidgetId {
self.id
}
}
impl<W: ?Sized> IdLike for WidgetRef<W> {
impl<W: ?Sized> IdLike for WeakWidget<W> {
type Widget = W;
fn id(&self) -> WidgetId {
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<WidgetRef<U>> for WidgetRef<T> {}
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<StrongWidget<U>> for StrongWidget<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 {
*self
}
}
impl<W: ?Sized> Copy for WidgetRef<W> {}
impl<W: ?Sized> PartialEq for WidgetRef<W> {
impl<W: ?Sized> Copy for WeakWidget<W> {}
impl<W: ?Sized> PartialEq for WeakWidget<W> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<W> PartialEq for WidgetHandle<W> {
impl<W> PartialEq for StrongWidget<W> {
fn eq(&self, other: &Self) -> bool {
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 {
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;
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 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 std::marker::Unsize;
pub trait StateLike<State> {
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 {
pub trait WidgetLike<Rsc: UiRsc, Tag>: Sized {
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> {
self.add(state).upgrade(state.as_state().ui_mut())
fn add_strong(self, rsc: &mut Rsc) -> StrongWidget<Self::Widget> {
self.add(rsc).upgrade(rsc)
}
fn with_id<W2>(
self,
f: impl FnOnce(&mut State, WidgetRef<Self::Widget>) -> WidgetRef<W2>,
) -> impl WidgetIdFn<State, W2> {
f: impl FnOnce(&mut Rsc, WeakWidget<Self::Widget>) -> WeakWidget<W2>,
) -> impl WidgetIdFn<Rsc, W2> {
move |state| {
let id = self.add(state);
f(state, id)
}
}
fn set_root(self, state: &mut impl StateLike<State>) {
let id = self.add(state);
let ui = state.as_state().ui_mut();
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()
fn set_root(self, rsc: &mut Rsc, root: &mut impl HasRoot) {
let id = self.add_strong(rsc);
root.set_root(id);
}
}
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]
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> {
fn add(self, _: &mut impl StateLike<State>) -> WidgetArr<LEN> {
impl<Rsc, const LEN: usize> WidgetArrLike<Rsc, LEN, ArrTag> for WidgetArr<LEN> {
fn add(self, _: &mut Rsc) -> WidgetArr<LEN> {
self
}
}
@@ -60,12 +49,12 @@ macro_rules! impl_widget_arr {
impl_widget_arr!($n;$($W)*;$(${concat($W,Tag)})*);
};
($n:expr;$($W:ident)*;$($Tag:ident)*) => {
impl<State: HasUi + StateLike<State>, $($W: WidgetLike<State, $Tag>,$Tag,)*> WidgetArrLike<State, $n, ($($Tag,)*)> for ($($W,)*) {
fn add(self, state: &mut impl StateLike<State>) -> WidgetArr<$n> {
impl<Rsc: UiRsc, $($W: WidgetLike<Rsc, $Tag>,$Tag,)*> WidgetArrLike<Rsc, $n, ($($Tag,)*)> for ($($W,)*) {
fn add(self, rsc: &mut Rsc) -> WidgetArr<$n> {
#[allow(non_snake_case)]
let ($($W,)*) = self;
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 like;
mod tag;
mod view;
mod widgets;
pub use data::*;
pub use handle::*;
pub use like::*;
pub use tag::*;
pub use view::*;
pub use widgets::*;
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 {}
pub struct WidgetArr<const LEN: usize> {
pub arr: [WidgetHandle; LEN],
pub arr: [StrongWidget; LEN],
}
impl<const LEN: usize> WidgetArr<LEN> {
pub fn new(arr: [WidgetHandle; LEN]) -> Self {
pub fn new(arr: [StrongWidget; LEN]) -> Self {
Self { arr }
}
}
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 () {
fn get(self, _: &mut State) -> Option<WidgetHandle> {
fn get(self, _: &mut State) -> Option<StrongWidget> {
None
}
}
impl<State, F: FnOnce(&mut State) -> Option<WidgetHandle>> WidgetOption<State> for F {
fn get(self, state: &mut State) -> Option<WidgetHandle> {
impl<State, F: FnOnce(&mut State) -> Option<StrongWidget>> WidgetOption<State> for F {
fn get(self, state: &mut State) -> Option<StrongWidget> {
self(state)
}
}

View File

@@ -1,58 +1,63 @@
use super::*;
use crate::HasUi;
use crate::UiRsc;
use std::marker::Unsize;
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;
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<W> {
state.as_state().get_mut().widgets.add_weak(self)
fn add(self, rsc: &mut Rsc) -> WeakWidget<W> {
let w = rsc.ui_mut().widgets.add_weak(self);
rsc.on_add(w);
w
}
}
pub struct FnTag;
impl<State: HasUi + StateLike<State>, W: Widget, F: FnOnce(&mut State) -> W>
WidgetLike<State, FnTag> for F
{
impl<Rsc: UiRsc, W: Widget, F: FnOnce(&mut Rsc) -> W> WidgetLike<Rsc, FnTag> for F {
type Widget = W;
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<W> {
self(state.as_state()).add(state)
fn add(self, rsc: &mut Rsc) -> WeakWidget<W> {
self(rsc).add(rsc)
}
}
pub trait WidgetFnTrait<State> {
pub trait WidgetFnTrait<Rsc> {
type Widget: Widget;
fn run(self, state: &mut State) -> Self::Widget;
fn run(self, rsc: &mut Rsc) -> Self::Widget;
}
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;
#[track_caller]
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<T::Widget> {
self.run(state.as_state()).add(state)
fn add(self, rsc: &mut Rsc) -> WeakWidget<T::Widget> {
self.run(rsc).add(rsc)
}
}
pub struct IdTag;
impl<State: HasUi + StateLike<State>, W: ?Sized + Widget + Unsize<dyn Widget>>
WidgetLike<State, IdTag> for WidgetRef<W>
pub struct RefTag;
impl<Rsc: UiRsc, W: ?Sized + Widget + Unsize<dyn Widget>> WidgetLike<Rsc, RefTag>
for WeakWidget<W>
{
type Widget = W;
fn add(self, _: &mut impl StateLike<State>) -> WidgetRef<W> {
fn add(self, _: &mut Rsc) -> WeakWidget<W> {
self
}
}
pub struct IdFnTag;
impl<
State: HasUi + StateLike<State>,
W: ?Sized + Widget + Unsize<dyn Widget>,
F: FnOnce(&mut State) -> WidgetRef<W>,
> WidgetLike<State, IdFnTag> for F
pub struct RefFnTag;
impl<Rsc: UiRsc, W: ?Sized + Widget + Unsize<dyn Widget>, F: FnOnce(&mut Rsc) -> WeakWidget<W>>
WidgetLike<Rsc, RefFnTag> for F
{
type Widget = W;
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<W> {
self(state.as_state())
fn add(self, rsc: &mut Rsc) -> WeakWidget<W> {
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::{
IdLike, Widget, WidgetData, WidgetHandle, WidgetId, WidgetRef,
IdLike, StrongWidget, WeakWidget, Widget, WidgetData, WidgetId,
util::{DynBorrower, HashSet, SlotVec, forget_mut, to_mut},
};
@@ -9,16 +9,19 @@ pub struct Widgets {
pub needs_redraw: HashSet<WidgetId>,
vec: SlotVec<WidgetData>,
send: Sender<WidgetId>,
recv: Receiver<WidgetId>,
pub(crate) waiting: HashSet<WidgetId>,
}
impl Widgets {
pub fn new(send: Sender<WidgetId>) -> Self {
pub fn new() -> Self {
let (send, recv) = channel();
Self {
needs_redraw: Default::default(),
vec: Default::default(),
waiting: Default::default(),
send,
recv,
}
}
@@ -61,25 +64,27 @@ impl Widgets {
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));
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));
self.waiting.insert(id);
WidgetRef::new(id)
WeakWidget::new(id)
}
#[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()) {
let label = self.label(rf);
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> {
@@ -90,14 +95,19 @@ impl Widgets {
&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> {
self.vec.get_mut(id.id())
}
pub fn delete(&mut self, id: impl IdLike) {
self.vec.free(id.id());
// not sure if there's any point in this
// self.updates.remove(&id);
pub fn free_next(&mut self) -> Option<WidgetId> {
let next = self.recv.try_recv().ok()?;
self.vec.free(next);
Some(next)
}
#[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>;
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::*;
fn main() {
App::<State>::run();
DefaultApp::<State>::run();
}
#[default_ui_state]
struct State {}
#[derive(DefaultUiState)]
struct State {
ui_state: DefaultUiState,
}
impl DefaultAppState for State {
fn new(ui_state: DefaultUiState, _proxy: Proxy<Self::Event>) -> Self {
let mut ui = Ui::new();
rect(Color::RED).set_root(&mut ui);
Self {
ui,
ui_state,
events: EventManager::default(),
}
fn new(
mut ui_state: DefaultUiState,
rsc: &mut DefaultRsc<Self>,
_: Proxy<Self::Event>,
) -> Self {
rect(Color::RED).set_root(rsc, &mut ui_state);
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 winit::event::WindowEvent;
iris::state_prelude!(ClientRsc);
use iris::prelude::*;
type ClientRsc = DefaultRsc<Client>;
fn main() {
App::<Client>::run();
DefaultApp::<Client>::run();
}
#[default_ui_state]
#[derive(DefaultUiState)]
pub struct Client {
info: WidgetRef<Text>,
ui_state: DefaultUiState,
info: WeakWidget<Text>,
}
impl DefaultAppState for Client {
fn new(ui_state: DefaultUiState, _proxy: Proxy<Self::Event>) -> Self {
let mut rsc = ClientRsc {
ui: Ui::new(),
ui_state,
events: EventManager::default(),
};
fn new(
mut ui_state: DefaultUiState,
rsc: &mut DefaultRsc<Self>,
_: Proxy<Self::Event>,
) -> Self {
let rrect = rect(Color::WHITE).radius(20);
let pad_test = (
rrect.color(Color::BLUE),
@@ -43,7 +43,7 @@ impl DefaultAppState for Client {
.width(rest(3)),
)
.span(Dir::RIGHT)
.add(&mut rsc);
.add(rsc);
let span_test = (
rrect.color(Color::GREEN).width(100),
@@ -54,30 +54,30 @@ impl DefaultAppState for Client {
rrect.color(Color::RED).width(100),
)
.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)
.radius(30)
.on(CursorSense::click(), move |ctx| {
.on(CursorSense::click(), move |_, rsc| {
let child = image(include_bytes!("assets/sungals.png"))
.center()
.add_strong(ctx);
span_add(ctx).push(child);
.add_strong(rsc);
span_add(rsc).push(child);
})
.sized((150, 150))
.align(Align::BOT_RIGHT);
let del_button = rect(Color::RED)
.radius(30)
.on(CursorSense::click(), move |ctx| {
span_add(ctx).pop();
.on(CursorSense::click(), move |_, rsc| {
span_add(rsc).pop();
})
.sized((150, 150))
.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);
@@ -100,18 +100,18 @@ impl DefaultAppState for Client {
wtext("pretty cool right?").size(50),
)
.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 add_text = wtext("add")
.editable(EditMode::MultiLine)
.text_align(Align::LEFT)
.size(30)
.attr::<Selectable>(())
.on(Submit, move |ctx| {
.on(Submit, move |ctx, rsc| {
let w = ctx.widget;
let content = w.edit(ctx).take();
let content = w.edit(rsc).take();
let text = wtext(content)
.editable(EditMode::MultiLine)
.size(30)
@@ -120,10 +120,11 @@ impl DefaultAppState for Client {
.attr::<Selectable>(());
let msg_box = text
.background(rect(Color::WHITE.darker(0.5)))
.add_strong(ctx);
texts(ctx).push(msg_box);
.add_strong(rsc);
texts(rsc).push(msg_box);
})
.add(&mut rsc);
.add(rsc);
let text_edit_scroll = (
msg_area.height(rest(1)),
(
@@ -131,8 +132,8 @@ impl DefaultAppState for Client {
(
add_text.width(rest(1)),
Rect::new(Color::GREEN)
.on(CursorSense::click(), move |ctx| {
ctx.state.run_event::<Submit>(add_text, &mut ());
.on(CursorSense::click(), move |ctx, rsc: &mut ClientRsc| {
rsc.run_event::<Submit>(add_text, (), ctx.state);
})
.sized((40, 40)),
)
@@ -145,39 +146,39 @@ impl DefaultAppState for Client {
.align(Align::BOT),
)
.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 mut switch_button = |color, to: WidgetRef, label| {
let to = to.upgrade(&mut rsc);
let mut switch_button = |color, to: WeakWidget, label| {
let to = to.upgrade(rsc);
let vec = &mut vals.borrow_mut().1;
let i = vec.len();
if vec.is_empty() {
vec.push(None);
rsc.ui[main].set(to);
main(rsc).set(to);
} else {
vec.push(Some(to));
}
let vals = vals.clone();
let rect = rect(color)
.on(CursorSense::click(), move |ctx| {
.on(CursorSense::click(), move |ctx, rsc| {
let (prev, vec) = &mut *vals.borrow_mut();
if let Some(h) = vec[i].take() {
vec[*prev] = main(ctx).replace(h);
vec[*prev] = main(rsc).replace(h);
*prev = i;
}
ctx.widget().color = color.darker(0.3);
ctx.widget(rsc).color = color.darker(0.3);
})
.on(
CursorSense::HoverStart | CursorSense::unclick(),
move |ctx| {
ctx.widget().color = color.brighter(0.2);
move |ctx, rsc| {
ctx.widget(rsc).color = color.brighter(0.2);
},
)
.on(CursorSense::HoverEnd, move |ctx| {
ctx.widget().color = color;
.on(CursorSense::HoverEnd, move |ctx, rsc| {
ctx.widget(rsc).color = color;
});
(rect, wtext(label).size(30).text_align(Align::CENTER)).stack()
};
@@ -195,30 +196,30 @@ impl DefaultAppState for Client {
)
.span(Dir::RIGHT);
let info = wtext("").add(&mut rsc);
let info = wtext("").add(rsc);
let info_sect = info.pad(10).align(Align::RIGHT);
((tabs.height(40), main.pad(10)).span(Dir::DOWN), info_sect)
.stack()
.set_root(&mut rsc);
.set_root(rsc, &mut ui_state);
Self {
ui: rsc.ui,
ui_state: rsc.ui_state,
events: rsc.events,
info,
}
Self { ui_state, info }
}
fn window_event(&mut self, _: WindowEvent) {
fn window_event(
&mut self,
_: WindowEvent,
rsc: &mut DefaultRsc<Self>,
render: &mut UiRenderState,
) {
let new = format!(
"widgets: {}\nactive: {}\nviews: {}",
self.ui.num_widgets(),
self.ui.active_widgets(),
self.ui_state.renderer.ui.view_count()
rsc.widgets().len(),
render.active_widgets(),
self.ui_state.renderer.ui.view_count(),
);
if new != *self.ui[self.info].content {
*self.ui[self.info].content = new;
if new != *rsc.widgets()[self.info].content {
*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 quote::quote;
use syn::{
Attribute, Block, Error, Fields, FieldsNamed, GenericParam, Generics, Ident, ItemStruct,
ItemTrait, Meta, Signature, Token, Visibility,
Attribute, Block, Error, GenericParam, Generics, Ident, ItemStruct, ItemTrait, Signature,
Token, Type, Visibility,
parse::{Parse, ParseStream, Result},
parse_macro_input, parse_quote,
spanned::Spanned,
@@ -89,104 +89,108 @@ pub fn widget_trait(input: TokenStream) -> TokenStream {
quote! {
#trai
impl #generics #name<State, WL, Tag> for WL {
impl #generics #name<Rsc, WL, Tag> for WL {
#(#impls)*
}
}
.into()
}
#[proc_macro_derive(UiState, attributes(rsc))]
pub fn derive_ui_state(input: TokenStream) -> TokenStream {
#[proc_macro_derive(DefaultUiState, attributes(default_ui_state))]
pub fn derive_default_ui_state(input: TokenStream) -> TokenStream {
let mut output = proc_macro2::TokenStream::new();
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 Some(attr) = field.attrs.iter().find(|a| a.path().is_ident("rsc")) else {
let mut found_attr = false;
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;
};
let Meta::List(list) = &attr.meta else {
output.extend(Error::new(attr.span(), "invalid attr syntax").into_compile_error());
continue;
};
let tname: Ident = match list.parse_args::<Ident>() {
Ok(ident) => ident,
Err(err) => {
output.extend(err.to_compile_error());
if found_attr {
output.extend(
Error::new(
attr.span(),
"cannot have more than one default_ui_state attribute",
)
.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 DefaultUiState field found").into_compile_error(),
);
return output.into();
};
let fty = &field.ty;
let fname = &field.ident.unwrap();
rsc_fields.extend(quote! {#fname: #fty,});
let sname = &state.ident;
let fname = field.ident.as_ref().unwrap();
output.extend(quote! {
impl #tname for #sname {
fn get(&self) -> &#fty {
impl iris::default::HasDefaultUiState for #sname {
fn default_state(&self) -> &iris::default::DefaultUiState {
&self.#fname
}
fn get_mut(&mut self) -> &mut #fty {
fn default_state_mut(&mut self) -> &mut iris::default::DefaultUiState {
&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;
output.extend(quote! {
#vis struct #rscname {
#(#rsc_fields)*
}
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
}
}
impl StateLike<#rscname> for #rscname {
fn as_state(&mut self) -> &mut Self {
self
}
}
});
output.into()
}
#[proc_macro_attribute]
pub fn default_ui_state(_attr: TokenStream, input: TokenStream) -> TokenStream {
let mut state: ItemStruct = parse_macro_input!(input);
let Fields::Named(fields) = &mut state.fields else {
panic!("must be on named fields struct");
#[proc_macro_derive(WidgetView, attributes(root))]
pub fn derive_widget_view(input: TokenStream) -> TokenStream {
let mut output = proc_macro2::TokenStream::new();
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;
};
let name = &state.ident;
state.attrs.push(parse_quote! {#[derive(UiState)]});
let new: FieldsNamed = parse_quote! {{
#[rsc(HasUi)]
pub ui: Ui,
#[rsc(HasDefaultUiState)]
pub ui_state: DefaultUiState,
#[rsc(HasEvents)]
pub events: iris::prelude::EventManager<#name>,
}};
fields.named.extend(new.named);
quote! {#state}.into()
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 sname = &state.ident;
let fname = field.ident.as_ref().unwrap();
let fty = &field.ty;
output.extend(quote! {
impl iris::core::WidgetView for #sname {
type Widget = <#fty as iris::core::HasWidget>::Widget;
fn root(&self) -> #fty {
self.#fname
}
}
});
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.
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:
1. does what I want it to (text, images, video, animations)
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

View File

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

View File

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

View File

@@ -1,7 +1,10 @@
use crate::prelude::*;
use arboard::Clipboard;
use iris_core::util::forget_mut;
use std::{marker::Sized, sync::Arc, time::Instant};
use std::{
marker::{PhantomData, Sized},
sync::Arc,
time::Instant,
};
use winit::{
event::{Ime, WindowEvent},
event_loop::{ActiveEventLoop, EventLoopProxy},
@@ -14,6 +17,8 @@ mod event;
mod input;
mod render;
mod sense;
mod state;
mod task;
pub use app::*;
pub use attr::*;
@@ -21,23 +26,33 @@ pub use event::*;
pub use input::*;
pub use render::*;
pub use sense::*;
pub use state::*;
pub use task::*;
pub type Proxy<Event> = EventLoopProxy<Event>;
pub struct DefaultUiState {
pub root: Option<StrongWidget>,
pub renderer: UiRenderer,
pub input: Input,
pub focus: Option<WidgetRef<TextEdit>>,
pub focus: Option<WeakWidget<TextEdit>>,
pub clipboard: Clipboard,
pub window: Arc<Window>,
pub ime: usize,
pub last_click: Instant,
}
impl HasRoot for DefaultUiState {
fn set_root(&mut self, root: StrongWidget) {
self.root = Some(root);
}
}
impl DefaultUiState {
pub fn new(window: impl Into<Arc<Window>>) -> Self {
let window = window.into();
Self {
root: None,
renderer: UiRenderer::new(window.clone()),
window,
input: Input::default(),
@@ -49,53 +64,162 @@ impl DefaultUiState {
}
}
pub trait HasDefaultUiState: Sized + 'static + HasUi {
fn get(&self) -> &DefaultUiState;
fn get_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 HasDefaultUiState: Sized + 'static {
fn default_state(&self) -> &DefaultUiState;
fn default_state_mut(&mut self) -> &mut DefaultUiState;
}
pub trait DefaultAppState: RunEvents + HasDefaultUiState {
type Event: 'static = ();
fn new(ui_state: DefaultUiState, proxy: Proxy<Self::Event>) -> Self;
pub trait DefaultAppState: HasDefaultUiState {
type Event = ();
fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, proxy: Proxy<Self::Event>)
-> Self;
#[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)]
fn exit(&mut self) {}
fn exit(&mut self, rsc: &mut DefaultRsc<Self>, render: &mut UiRenderState) {}
#[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 {
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;
fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> Self {
let window = event_loop.create_window(Self::window_attributes()).unwrap();
Self::new(DefaultUiState::new(window), proxy)
let window = event_loop
.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) {
self.event(event);
self.state.event(event, &mut self.rsc, &mut self.render);
}
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
let events = unsafe { forget_mut(self.events_mut()) };
let ui_state = HasDefaultUiState::get_mut(self);
let 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 cursor_state = ui_state.cursor_state().clone();
let old = ui_state.focus;
@@ -104,43 +228,43 @@ impl<State: DefaultAppState> AppState for State {
}
if input_changed {
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
&& let Some(old) = old
{
old.edit(ui).deselect();
old.edit(rsc).deselect();
}
match &event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => {
ui.update(events);
ui_state.renderer.update(ui);
render.update(&ui_state.root, rsc);
ui_state.renderer.update(&mut rsc.ui, render);
ui_state.renderer.draw();
}
WindowEvent::Resized(size) => {
ui.resize((size.width, size.height));
render.resize((size.width, size.height));
ui_state.renderer.resize(size)
}
WindowEvent::KeyboardInput { event, .. } => {
if let Some(sel) = ui_state.focus
&& 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) {
TextInputResult::Unfocus => {
ui_state.focus = None;
ui_state.window.set_ime_allowed(false);
}
TextInputResult::Submit => {
self.run_event::<Submit>(sel, &mut ());
rsc.run_event::<Submit>(sel, (), state);
}
TextInputResult::Paste => {
if let Ok(t) = ui_state.clipboard.get_text() {
text.insert(&t);
}
self.run_event::<Edited>(sel, &mut ());
rsc.run_event::<Edited>(sel, (), state);
}
TextInputResult::Copy(text) => {
if let Err(err) = ui_state.clipboard.set_text(text) {
@@ -148,7 +272,7 @@ impl<State: DefaultAppState> AppState for State {
}
}
TextInputResult::Used => {
self.run_event::<Edited>(sel, &mut ());
rsc.run_event::<Edited>(sel, (), state);
}
TextInputResult::Unused => {}
}
@@ -156,7 +280,7 @@ impl<State: DefaultAppState> AppState for State {
}
WindowEvent::Ime(ime) => {
if let Some(sel) = ui_state.focus {
let mut text = sel.edit(ui);
let mut text = sel.edit(rsc);
match ime {
Ime::Enabled | Ime::Disabled => (),
Ime::Preedit(content, _pos) => {
@@ -172,15 +296,59 @@ impl<State: DefaultAppState> AppState for State {
}
_ => (),
}
self.window_event(event);
(ui, ui_state) = self.ui_with_state();
if ui.needs_redraw() {
state.window_event(event, rsc, render);
let ui_state = self.state.default_state_mut();
if render.needs_redraw(&ui_state.root, rsc.widgets()) {
ui_state.renderer.window().request_redraw();
}
ui_state.input.end_frame();
}
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 std::sync::Arc;
use wgpu::{util::StagingBelt, *};
use wgpu::*;
use winit::{dpi::PhysicalSize, window::Window};
pub const CLEAR_COLOR: Color = Color::BLACK;
@@ -13,13 +13,12 @@ pub struct UiRenderer {
queue: Queue,
config: SurfaceConfiguration,
encoder: CommandEncoder,
staging_belt: StagingBelt,
pub ui: UiRenderNode,
}
impl UiRenderer {
pub fn update(&mut self, ui: &mut Ui) {
self.ui.update(&self.device, &self.queue, ui);
pub fn update(&mut self, ui: &mut UiData, render: &mut UiRenderState) {
self.ui.update(&self.device, &self.queue, ui, render);
}
pub fn draw(&mut self) {
@@ -46,9 +45,7 @@ impl UiRenderer {
}
self.queue.submit(std::iter::once(encoder.finish()));
self.staging_belt.finish();
output.present();
self.staging_belt.recall();
}
pub fn resize(&mut self, size: &PhysicalSize<u32>) {
@@ -126,10 +123,9 @@ impl UiRenderer {
surface.configure(&device, &config);
let staging_belt = StagingBelt::new(4096 * 4);
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 {
surface,
@@ -137,8 +133,7 @@ impl UiRenderer {
queue,
config,
encoder,
staging_belt,
ui: shape_pipeline,
ui,
window,
}
}

View File

@@ -28,12 +28,13 @@ pub struct CursorSenses(Vec<CursorSense>);
impl Event for CursorSenses {
type Data<'a> = CursorData<'a>;
type State = SensorState;
fn should_run(&self, data: &mut Self::Data<'_>) -> bool {
if let Some(sense) = should_run(self, data.cursor, data.hover) {
fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
if let Some(sense) = should_run(self, &data.cursor, data.hover) {
let mut data = data.clone();
data.sense = sense;
true
Some(data)
} else {
false
None
}
}
}
@@ -115,7 +116,7 @@ pub enum ActivationState {
/// or basically have some way to have custom senses
/// that depend on active widget positions
/// 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 f: Rc<dyn EventFn<Ctx, Data>>,
}
@@ -127,29 +128,46 @@ pub struct SensorState {
pub hover: ActivationState,
}
#[derive(Clone)]
pub struct CursorData<'a> {
/// where this widget was hit
pub pos: Vec2,
pub size: Vec2,
pub scroll_delta: Vec2,
pub hover: ActivationState,
pub cursor: &'a CursorState,
pub cursor: CursorState,
/// the first sense that triggered this
pub sense: CursorSense,
pub render: &'a UiRenderState,
}
pub trait SensorUi<State> {
fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2);
pub trait SensorUi {
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 {
fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2) {
let layers = std::mem::take(&mut self.ui_mut().layers);
let mut active = std::mem::take(&mut self.events_mut().get_type::<CursorSense>().active);
for layer in layers.indices().rev() {
impl SensorUi for UiRenderState {
fn run_sensors<Rsc: HasEvents>(
&self,
rsc: &mut Rsc,
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;
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 in_shape = cursor.exists && region.contains(cursor.pos);
sensor.hover.update(in_shape);
@@ -158,7 +176,9 @@ impl<State: RunEvents> SensorUi<State> for State {
}
sensed = true;
let mut data = CursorData {
let cursor = cursor.clone();
let data = CursorData {
pos: cursor.pos - region.top_left,
size: region.bot_right - region.top_left,
scroll_delta: cursor.scroll_delta,
@@ -167,15 +187,15 @@ impl<State: RunEvents> SensorUi<State> for State {
// this does not have any meaning;
// might wanna set up Event to have a prepare stage
sense: CursorSense::Hovering,
render: self,
};
self.run_event::<CursorSense>(*id, &mut data);
rsc.run_event::<CursorSense>(*id, data, state);
}
if sensed {
break;
}
}
self.events_mut().get_type::<CursorSense>().active = active;
self.ui_mut().layers = layers;
rsc.events_mut().get_type::<CursorSense>().active = active;
}
}

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 super::*;
widget_trait! {
pub trait Eventable<State: HasEvents + StateLike<State> + 'static>;
use crate::default::{TaskCtx, TaskUpdate, Tasks};
pub trait Eventable<Rsc: HasEvents, Tag>: WidgetLike<Rsc, Tag> {
fn on<E: EventLike>(
self,
event: E,
f: impl for<'a> WidgetEventFn<State::State, <E::Event as Event>::Data<'a>, WL::Widget>,
) -> impl WidgetIdFn<State, WL::Widget> {
move |state| {
let id = self.add(state);
state.register_event(id, event.into_event(), move |ctx| {
f(&mut EventIdCtx {
f: impl for<'a> WidgetEventFn<Rsc, <E::Event as Event>::Data<'a>, Self::Widget>,
) -> impl WidgetIdFn<Rsc, Self::Widget> {
move |rsc| {
let id = self.add(rsc);
rsc.register_event(id, event.into_event(), move |ctx, rsc| {
f(
EventIdCtx {
widget: id,
state: ctx.state,
data: ctx.data,
},
rsc,
);
});
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(unsize)]
#![feature(option_into_flat_iter)]
#![feature(async_fn_traits)]
pub mod default;
pub mod event;
pub mod typed;
pub mod widget;
pub use iris_core as core;
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 {
use super::*;
pub use default::*;
@@ -35,5 +22,5 @@ pub mod prelude {
pub use widget::*;
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");
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::*;
pub struct Masked {
pub inner: WidgetHandle,
pub inner: StrongWidget,
}
impl Widget for Masked {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ use std::marker::PhantomData;
use crate::prelude::*;
pub struct Stack {
pub children: Vec<WidgetHandle>,
pub children: Vec<StrongWidget>,
pub size: StackSize,
}
@@ -48,14 +48,15 @@ pub struct StackBuilder<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, T
_pd: PhantomData<(State, Tag)>,
}
impl<State: StateLike<State>, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag> FnOnce<(&mut State,)>
for StackBuilder<State, LEN, Wa, Tag>
impl<Rsc, const LEN: usize, Wa: WidgetArrLike<Rsc, LEN, Tag>, Tag> WidgetFnTrait<Rsc>
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 {
children: self.children.add(args.0).arr.into_iter().collect(),
children: self.children.add(rsc).arr.into_iter().collect(),
size: self.size,
}
}

View File

@@ -2,7 +2,7 @@ use crate::prelude::*;
use std::marker::{Sized, Unsize};
pub struct WidgetPtr {
pub inner: Option<WidgetHandle>,
pub inner: Option<StrongWidget>,
}
impl Widget for WidgetPtr {
@@ -38,14 +38,14 @@ impl WidgetPtr {
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)
}
pub fn replace<W: ?Sized + Unsize<dyn Widget>>(
&mut self,
to: WidgetHandle<W>,
) -> Option<WidgetHandle> {
to: StrongWidget<W>,
) -> Option<StrongWidget> {
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> {
pub fn hint<W: WidgetLike<State, Tag>, Tag>(
impl<Rsc: UiRsc, O> TextBuilder<Rsc, O> {
pub fn hint<W: WidgetLike<Rsc, Tag>, Tag>(
self,
hint: W,
) -> TextBuilder<State, O, impl WidgetOption<State>> {
) -> TextBuilder<Rsc, O, impl WidgetOption<Rsc>> {
TextBuilder {
content: self.content,
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,
state: PhantomData,
}
@@ -75,19 +75,19 @@ pub trait TextBuilderOutput<State>: Sized {
}
pub struct TextOutput;
impl<State: HasUi> TextBuilderOutput<State> for TextOutput {
impl<Rsc: UiRsc> TextBuilderOutput<Rsc> for TextOutput {
type Output = Text;
fn run<H: WidgetOption<State>>(
state: &mut State,
builder: TextBuilder<State, Self, H>,
fn run<H: WidgetOption<Rsc>>(
state: &mut Rsc,
builder: TextBuilder<Rsc, Self, H>,
) -> Self::Output {
let mut buf = TextBuffer::new_empty(Metrics::new(
builder.attrs.font_size,
builder.attrs.line_height,
));
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);
let mut text = Text {
content: builder.content.into(),
@@ -103,7 +103,7 @@ pub struct TextEditOutput {
mode: EditMode,
}
impl<State: HasUi> TextBuilderOutput<State> for TextEditOutput {
impl<State: UiRsc> TextBuilderOutput<State> for TextEditOutput {
type Output = TextEdit;
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)),
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
.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None);
builder.attrs.apply(font_system, &mut text.buf, None);

View File

@@ -617,11 +617,11 @@ impl DerefMut for TextEdit {
}
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 {
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();
TextEditCtx {
text: ui.widgets.get_mut(self).unwrap(),

View File

@@ -22,11 +22,11 @@ pub struct TextView {
// cache
tex: Option<RenderedText>,
width: Option<f32>,
pub hint: Option<WidgetHandle>,
pub hint: Option<StrongWidget>,
}
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 {
attrs: attrs.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
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 {
padding: padding.into(),
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 {
inner: self.add_strong(state),
align: align.into(),
}
}
fn center(self) -> impl WidgetFn<State, Aligned> {
fn center(self) -> impl WidgetFn<Rsc, Aligned> {
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| {
let id = self.add(state);
state.get_mut().set_label(id, label.into());
state.ui_mut().widgets.set_label(id, label.into());
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();
move |state| Sized {
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();
move |state| MaxSize {
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();
move |state| MaxSize {
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();
move |state| Sized {
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();
move |state| Sized {
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 {
inner: self.add_strong(state),
amt: amt.into(),
}
}
fn scrollable(self) -> impl WidgetIdFn<State, Scroll> where State: HasEvents {
use eventable::*;
fn scrollable(self) -> impl WidgetIdFn<Rsc, Scroll> where Rsc: HasEvents {
move |state| {
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;
ctx.widget().scroll(delta);
ctx.widget(rsc).scroll(delta);
})
.add(state)
}
}
fn masked(self) -> impl WidgetFn<State, Masked> {
fn masked(self) -> impl WidgetFn<Rsc, Masked> {
move |state| Masked {
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 {
children: vec![w.add_strong(state), self.add_strong(state)],
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 {
children: vec![self.add_strong(state), w.add_strong(state)],
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 {
inner: self.add_strong(state),
offset,
}
}
fn to_any(self) -> impl WidgetIdFn<State> {
fn to_any(self) -> impl WidgetIdFn<Rsc> {
|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);
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> {
fn span(self, dir: Dir) -> SpanBuilder<State, LEN, Wa, Tag>;
fn stack(self) -> StackBuilder<State, LEN, Wa, Tag>;
pub trait CoreWidgetArr<Rsc, const LEN: usize, Wa: WidgetArrLike<Rsc, LEN, Tag>, Tag> {
fn span(self, dir: Dir) -> SpanBuilder<Rsc, LEN, Wa, Tag>;
fn stack(self) -> StackBuilder<Rsc, LEN, Wa, Tag>;
}
impl<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag>