2 Commits

Author SHA1 Message Date
1e4a7f8cf5 naw 2025-12-29 20:11:35 -05:00
93291badc1 (BROKEN) start on removing desired size 2025-12-23 23:48:35 -05:00
59 changed files with 1533 additions and 2058 deletions

709
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
[package]
name = "iris"
default-run = "test"
version.workspace = true
edition.workspace = true
@@ -15,10 +16,6 @@ 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"]
@@ -30,13 +27,12 @@ edition = "2024"
[workspace.dependencies]
pollster = "0.4.0"
winit = "0.30.12"
wgpu = "28.0.0"
wgpu = "27.0.1"
bytemuck = "1.23.1"
image = "0.25.6"
cosmic-text = "0.16.0"
cosmic-text = "0.15.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,22 +1,24 @@
use crate::{UiRsc, WidgetIdFn, WidgetLike, WeakWidget};
use crate::{HasUi, StateLike, WidgetIdFn, WidgetLike, WidgetRef};
pub trait WidgetAttr<Rsc, W: ?Sized> {
pub trait WidgetAttr<State, W: ?Sized> {
type Input;
fn run(rsc: &mut Rsc, id: WeakWidget<W>, input: Self::Input);
fn run(state: &mut State, id: WidgetRef<W>, input: Self::Input);
}
pub trait Attrable<Rsc, W: ?Sized, Tag> {
fn attr<A: WidgetAttr<Rsc, W>>(self, input: A::Input) -> impl WidgetIdFn<Rsc, W>;
pub trait Attrable<State, W: ?Sized, Tag> {
fn attr<A: WidgetAttr<State, W>>(self, input: A::Input) -> impl WidgetIdFn<State, W>;
}
impl<Rsc: UiRsc, WL: WidgetLike<Rsc, Tag>, Tag> Attrable<Rsc, WL::Widget, Tag> for WL {
fn attr<A: WidgetAttr<Rsc, WL::Widget>>(
impl<State: HasUi + StateLike<State>, WL: WidgetLike<State, Tag>, Tag>
Attrable<State, WL::Widget, Tag> for WL
{
fn attr<A: WidgetAttr<State, WL::Widget>>(
self,
input: A::Input,
) -> impl WidgetIdFn<Rsc, WL::Widget> {
|rsc| {
let id = self.add(rsc);
A::run(rsc, id, input);
) -> impl WidgetIdFn<State, WL::Widget> {
|state| {
let id = self.add(state);
A::run(state, id, input);
id
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@ use crate::vec2;
use super::*;
#[derive(Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Align {
pub x: Option<AxisAlign>,
pub y: Option<AxisAlign>,
@@ -24,13 +24,26 @@ impl Align {
pub const TOP: CardinalAlign = CardinalAlign::TOP;
pub const V_CENTER: CardinalAlign = CardinalAlign::V_CENTER;
pub const BOT: CardinalAlign = CardinalAlign::BOT;
pub const NONE: Align = Align { x: None, y: None };
pub fn tuple(&self) -> (Option<AxisAlign>, Option<AxisAlign>) {
(self.x, self.y)
}
/// naming is a bit inconsistent w option,
/// normally would return Self, but can't see
/// that being needed atm and would wanna do
/// a trait if so, so they can both be named .or
/// (because Self impls From<RegionAlign>)
pub fn or(&self, other: RegionAlign) -> RegionAlign {
RegionAlign {
x: self.x.unwrap_or(other.x),
y: self.y.unwrap_or(other.x),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AxisAlign {
Neg,
Center,
@@ -106,6 +119,7 @@ impl UiVec2 {
}
}
/// aligns this as a size within a region
pub fn align(&self, align: RegionAlign) -> UiRegion {
UiRegion {
x: self.x.align(align.x),

View File

@@ -140,12 +140,22 @@ where
}
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, Default, bytemuck::Zeroable)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, Default, bytemuck::Zeroable)]
pub struct UiScalar {
pub rel: f32,
pub abs: f32,
}
// TODO: unknown exactly what these should be
const REL_EPSILON: f32 = 0.00001;
const ABS_EPSILON: f32 = 0.1;
impl PartialEq for UiScalar {
fn eq(&self, other: &Self) -> bool {
(self.rel - other.rel).abs() < REL_EPSILON && (self.abs - other.abs).abs() < ABS_EPSILON
}
}
impl Eq for UiScalar {}
impl Hash for UiScalar {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {

View File

@@ -3,7 +3,7 @@ use cosmic_text::{
Attrs, AttrsList, Buffer, CacheKey, Color, Family, FontSystem, Metrics, Placement, SwashCache,
SwashContent,
};
use image::{DynamicImage, GenericImageView, RgbaImage};
use image::{GenericImageView, RgbaImage};
use std::simd::{Simd, num::SimdUint};
/// TODO: properly wrap this
@@ -185,7 +185,3 @@ 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::{
UiData, UiRenderState,
Ui,
render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf},
util::HashMap,
};
@@ -59,18 +59,12 @@ impl UiRenderNode {
}
}
pub fn update(
&mut self,
device: &Device,
queue: &Queue,
ui: &mut UiData,
ui_render: &mut UiRenderState,
) {
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &mut Ui) {
self.active.clear();
for (i, primitives) in ui_render.layers.iter_mut() {
for (i, primitives) in ui.layers.iter_mut() {
self.active.push(i);
for change in primitives.apply_free() {
if let Some(inst) = ui_render.active.get_mut(&change.id) {
if let Some(inst) = ui.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;
@@ -189,7 +183,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],
immediate_size: 0,
push_constant_ranges: &[],
});
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
label: Some("UI Shape Pipeline"),
@@ -225,7 +219,7 @@ impl UiRenderNode {
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview_mask: None,
multiview: None,
cache: None,
});

View File

@@ -1,14 +1,16 @@
use crate::{LayerId, MaskIdx, PrimitiveHandle, TextureHandle, UiRegion, WidgetId};
use crate::{Align, LayerId, MaskIdx, PrimitiveHandle, TextureHandle, UiRegion, WidgetId};
/// important non rendering data for retained drawing
#[derive(Debug)]
pub struct ActiveData {
pub id: WidgetId,
pub region: UiRegion,
pub region_used: UiRegion,
pub parent: Option<WidgetId>,
pub textures: Vec<TextureHandle>,
pub primitives: Vec<PrimitiveHandle>,
pub children: Vec<WidgetId>,
pub mask: MaskIdx,
pub layer: LayerId,
pub align: Align,
}

View File

@@ -1,18 +0,0 @@
use crate::{BothAxis, Len, UiVec2, WidgetId, util::HashMap};
#[derive(Default)]
pub struct Cache {
pub size: BothAxis<HashMap<WidgetId, (UiVec2, Len)>>,
}
impl Cache {
pub fn remove(&mut self, id: WidgetId) {
self.size.x.remove(&id);
self.size.y.remove(&id);
}
pub fn clear(&mut self) {
self.size.x.clear();
self.size.y.clear();
}
}

209
core/src/ui/draw_state.rs Normal file
View File

@@ -0,0 +1,209 @@
use crate::{
ActiveData, Align, Axis, EventsLike, Painter, Ui, UiRegion, 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);
let Some(active) = self.active.get(&id) else {
return;
};
let ActiveData {
id,
region,
parent,
mask,
layer,
align,
..
} = *active;
self.draw_inner(layer, id, region, parent, mask, align);
}
pub fn redraw_all(&mut self) {
// free all resources & cache
for (_, active) in self.ui.active.drain() {
self.events.undraw(&active);
}
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, Align::NONE);
}
}
pub(super) fn draw_inner(
&mut self,
layer: usize,
id: WidgetId,
region: UiRegion,
parent: Option<WidgetId>,
mask: MaskIdx,
align: Align,
) {
let mut old_children = Vec::new();
if let Some(active) = self.ui.active.get_mut(&id) {
// check to see if we can skip drawing first, and just need to move
if !self.ui.widgets.needs_redraw.contains(&id) {
if active.region == region {
return;
} else if active.region.size() == region.size() {
self.mov(id, active.region, region);
return;
}
}
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,
region_used: region,
mask,
layer,
id,
textures: Vec::new(),
primitives: Vec::new(),
children: Vec::new(),
};
let mut widget = painter.state.widgets.get_dyn(id);
widget.draw(&mut painter);
let walign = painter.state.widgets.data(id).unwrap().align;
let align = align.or(walign.into());
let Painter {
region_used,
state: _,
region,
mask,
textures,
primitives,
children,
layer,
id,
} = painter;
// remove old children that weren't kept
for c in &old_children {
if !children.contains(c) {
self.remove_rec(*c);
}
}
// add to active
self.active.insert(
id,
ActiveData {
id,
region,
parent,
textures,
primitives,
children,
region_used,
mask,
layer,
},
);
// TODO: unsure if there's a better way, currently using epsilon
// in PartialEq impl for UiScalar
let target = region_used.size().align(align);
if region_used != target {
self.mov(id, from, to);
}
self.events.draw(self.active.get(&id).unwrap());
}
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> {
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,47 +1,211 @@
use crate::{Mask, TextData, Textures, WeakWidget, WidgetId, Widgets, util::TrackedArena};
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},
};
mod active;
mod cache;
mod draw_state;
mod painter;
mod render_state;
mod size;
mod state;
pub use active::*;
pub use painter::Painter;
pub use render_state::*;
pub use size::*;
#[derive(Default)]
pub struct UiData {
pub struct Ui {
// TODO: edit visibilities
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 root: Option<WidgetHandle>,
old_root: Option<WidgetId>,
recv: Receiver<WidgetId>,
resized: bool,
}
pub trait UiRsc {
fn ui(&self) -> &UiData;
fn ui_mut(&mut self) -> &mut UiData;
#[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 widgets(&self) -> &Widgets {
&self.ui().widgets
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()
}
}
impl HasUi for Ui {
fn get(&self) -> &Ui {
self
}
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(),
output_size: Vec2::ZERO,
root: None,
old_root: None,
recv,
resized: false,
}
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,15 +1,15 @@
use crate::{
Axis, Len, RenderedText, Size, SizeCtx, StrongWidget, TextAttrs, TextBuffer, TextData,
TextureHandle, UiRegion, UiRenderState, UiRsc, Widget, WidgetId,
RenderedText, TextAttrs, TextBuffer, TextData, TextureHandle, UiRegion, WidgetHandle, WidgetId,
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
ui::draw_state::DrawState,
util::Vec2,
};
/// makes your surfaces look pretty
pub struct Painter<'a> {
pub(super) state: &'a mut UiRenderState,
pub(super) rsc: &'a mut dyn UiRsc,
pub struct Painter<'a, 'b> {
pub(super) state: &'a mut DrawState<'b>,
pub region_used: UiRegion,
pub(super) region: UiRegion,
pub(super) mask: MaskIdx,
pub(super) textures: Vec<TextureHandle>,
@@ -19,7 +19,7 @@ pub struct Painter<'a> {
pub(super) id: WidgetId,
}
impl<'a> Painter<'a> {
impl<'a, 'c> Painter<'a, 'c> {
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> Painter<'a> {
);
if self.mask != MaskIdx::NONE {
// TODO: I have no clue if this works at all :joy:
self.rsc.ui_mut().masks.push_ref(self.mask);
self.state.masks.push_ref(self.mask);
}
self.primitives.push(h);
}
@@ -48,31 +48,28 @@ impl<'a> Painter<'a> {
pub fn set_mask(&mut self, region: UiRegion) {
assert!(self.mask == MaskIdx::NONE);
self.mask = self.rsc.ui_mut().masks.push(Mask { region });
self.mask = self.state.masks.push(Mask { region });
}
/// Draws a widget within this widget's region.
pub fn widget<W: ?Sized>(&mut self, id: &StrongWidget<W>) {
pub fn widget<W: ?Sized>(&mut self, id: &WidgetHandle<W>) {
self.widget_at(id, self.region);
}
pub fn rest_hint(&self, id: &WidgetHandle) -> f32 {
self.state.widgets.data(id).unwrap().align
}
/// Draws a widget somewhere within this one.
/// Useful for drawing child widgets in select areas.
pub fn widget_within<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) {
pub fn widget_within<W: ?Sized>(&mut self, id: &WidgetHandle<W>, region: UiRegion) {
self.widget_at(id, region.within(&self.region));
}
fn widget_at<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) {
fn widget_at<W: ?Sized>(&mut self, id: &WidgetHandle<W>, region: UiRegion) {
self.children.push(id.id());
self.state.draw_inner(
self.layer,
id.id(),
region,
Some(self.id),
self.mask,
None,
self.rsc,
);
self.state
.draw_inner(self.layer, id.id(), region, Some(self.id), self.mask);
}
pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) {
@@ -85,32 +82,18 @@ impl<'a> Painter<'a> {
self.primitive(handle.primitive());
}
pub fn texture_at(&mut self, handle: &TextureHandle, region: UiRegion) {
self.textures.push(handle.clone());
self.primitive_at(handle.primitive(), region);
}
/// returns (handle, offset from top left)
pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText {
let ui = self.rsc.ui_mut();
ui.text.draw(buffer, attrs, &mut ui.textures)
self.state
.ui
.text
.draw(buffer, attrs, &mut self.state.ui.textures)
}
pub fn region(&self) -> UiRegion {
self.region
}
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: &StrongWidget<W>, axis: Axis) -> Len {
match axis {
Axis::X => self.size_ctx().width(id),
Axis::Y => self.size_ctx().height(id),
}
}
pub fn output_size(&self) -> Vec2 {
self.state.output_size
}
@@ -120,7 +103,7 @@ impl<'a> Painter<'a> {
}
pub fn text_data(&mut self) -> &mut TextData {
&mut self.rsc.ui_mut().text
&mut self.state.text
}
pub fn child_layer(&mut self) {
@@ -132,14 +115,10 @@ impl<'a> Painter<'a> {
}
pub fn label(&self) -> &str {
&self.rsc.widgets().data(self.id).unwrap().label
&self.state.widgets.data(self.id).unwrap().label
}
pub fn id(&self) -> &WidgetId {
&self.id
}
pub fn size_ctx(&mut self) -> SizeCtx<'_> {
self.state.size_ctx(self.id, self.region.size(), self.rsc)
}
}

View File

@@ -1,319 +0,0 @@
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,86 +0,0 @@
use crate::{
Axis, AxisT, IdLike, Len, RenderedText, Size, TextAttrs, TextBuffer, TextData, Textures,
UiVec2, WidgetAxisFns, WidgetId, Widgets, XAxis, YAxis, ui::cache::Cache, util::Vec2,
};
pub struct SizeCtx<'a> {
pub text: &'a mut TextData,
pub textures: &'a mut Textures,
pub(super) source: WidgetId,
pub(super) widgets: &'a Widgets,
pub(super) cache: &'a mut Cache,
/// TODO: should this be pub? rn used for sized
pub outer: UiVec2,
pub(super) output_size: Vec2,
pub(super) id: WidgetId,
}
impl SizeCtx<'_> {
pub fn id(&self) -> &WidgetId {
&self.id
}
pub fn source(&self) -> &WidgetId {
&self.source
}
pub(super) fn len_inner<A: const AxisT>(&mut self, id: WidgetId) -> Len {
if let Some((_, len)) = self.cache.size.axis::<A>().get(&id) {
return *len;
}
let len = self
.widgets
.get_dyn_dynamic(id)
.desired_len::<A>(&mut SizeCtx {
text: self.text,
textures: self.textures,
source: self.source,
widgets: self.widgets,
cache: self.cache,
outer: self.outer,
output_size: self.output_size,
id,
});
self.cache.size.axis::<A>().insert(id, (self.outer, len));
len
}
pub fn width(&mut self, id: impl IdLike) -> Len {
self.len_inner::<XAxis>(id.id())
}
pub fn height(&mut self, id: impl IdLike) -> Len {
self.len_inner::<YAxis>(id.id())
}
pub fn len_axis(&mut self, id: impl IdLike, axis: Axis) -> Len {
match axis {
Axis::X => self.width(id),
Axis::Y => self.height(id),
}
}
pub fn size(&mut self, id: impl IdLike) -> Size {
let id = id.id();
Size {
x: self.width(id),
y: self.height(id),
}
}
pub fn px_size(&mut self) -> Vec2 {
self.outer.to_abs(self.output_size)
}
pub fn output_size(&mut self) -> Vec2 {
self.output_size
}
pub fn draw_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText {
self.text.draw(buffer, attrs, self.textures)
}
pub fn label(&self, id: WidgetId) -> &String {
self.widgets.label(id)
}
}

3
core/src/ui/state.rs Normal file
View File

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

View File

@@ -1,8 +1,12 @@
use crate::Widget;
use crate::{RegionAlign, Widget};
pub struct WidgetData {
pub widget: Box<dyn Widget>,
pub label: String,
/// alignment used if this does not fill up container
/// and there is no enforced alignment from parent
pub align: RegionAlign,
pub rest_len: Option<f32>,
/// dynamic borrow checking
pub borrowed: bool,
}
@@ -15,7 +19,9 @@ impl WidgetData {
}
Self {
widget: Box::new(widget),
align: RegionAlign::CENTER,
label,
rest_len: None,
borrowed: false,
}
}

View File

@@ -1,7 +1,7 @@
use std::{marker::Unsize, ops::CoerceUnsized, sync::mpsc::Sender};
use crate::{
UiRsc, Widget,
HasUi, 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 StrongWidget<W: ?Sized = dyn Widget> {
pub struct WidgetHandle<W: ?Sized = dyn Widget> {
pub(super) id: WidgetId,
counter: RefCounter,
send: Sender<WidgetId>,
@@ -21,19 +21,24 @@ pub struct StrongWidget<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 WeakWidget<W: ?Sized = dyn Widget> {
pub struct WidgetRef<W: ?Sized = dyn Widget> {
pub(super) id: WidgetId,
#[allow(unused)]
ty: *const W,
}
impl<W: Widget + ?Sized + Unsize<dyn Widget>> StrongWidget<W> {
pub fn any(self) -> StrongWidget<dyn Widget> {
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> {
self
}
}
impl<W: ?Sized> StrongWidget<W> {
impl<W: ?Sized> WidgetHandle<W> {
pub(crate) fn new(id: WidgetId, send: Sender<WidgetId>) -> Self {
Self {
id,
@@ -51,13 +56,18 @@ impl<W: ?Sized> StrongWidget<W> {
self.counter.refs()
}
pub fn weak(&self) -> WeakWidget<W> {
pub fn weak(&self) -> WidgetRef<W> {
let Self { ty, id, .. } = *self;
WeakWidget { ty, id }
WidgetRef { ty, id }
}
pub fn handles(self) -> WidgetHandles<W> {
let r = self.weak();
WidgetHandles { h: self, r }
}
}
impl<W: ?Sized> WeakWidget<W> {
impl<W: ?Sized> WidgetRef<W> {
pub(crate) fn new(id: WidgetId) -> Self {
Self { id, ty: null_ptr() }
}
@@ -67,12 +77,12 @@ impl<W: ?Sized> WeakWidget<W> {
}
#[track_caller]
pub fn upgrade(self, ui: &mut impl UiRsc) -> StrongWidget<W> {
ui.widgets_mut().upgrade(self)
pub fn upgrade(self, ui: &mut impl HasUi) -> WidgetHandle<W> {
ui.ui_mut().widgets.upgrade(self)
}
}
impl<W: ?Sized> Drop for StrongWidget<W> {
impl<W: ?Sized> Drop for WidgetHandle<W> {
fn drop(&mut self) {
if self.counter.drop() {
let _ = self.send.send(self.id);
@@ -80,29 +90,29 @@ impl<W: ?Sized> Drop for StrongWidget<W> {
}
}
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 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 IdLike {
type Widget: ?Sized;
fn id(&self) -> WidgetId;
}
impl<W: ?Sized> IdLike for &StrongWidget<W> {
impl<W: ?Sized> IdLike for &WidgetHandle<W> {
type Widget = W;
fn id(&self) -> WidgetId {
self.id
}
}
impl<W: ?Sized> IdLike for StrongWidget<W> {
impl<W: ?Sized> IdLike for WidgetHandle<W> {
type Widget = W;
fn id(&self) -> WidgetId {
self.id
}
}
impl<W: ?Sized> IdLike for WeakWidget<W> {
impl<W: ?Sized> IdLike for WidgetRef<W> {
type Widget = W;
fn id(&self) -> WidgetId {
self.id
@@ -116,38 +126,38 @@ impl IdLike for WidgetId {
}
}
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<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<W: ?Sized> Clone for WeakWidget<W> {
impl<W: ?Sized> Clone for WidgetRef<W> {
fn clone(&self) -> Self {
*self
}
}
impl<W: ?Sized> Copy for WeakWidget<W> {}
impl<W: ?Sized> PartialEq for WeakWidget<W> {
impl<W: ?Sized> Copy for WidgetRef<W> {}
impl<W: ?Sized> PartialEq for WidgetRef<W> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<W> PartialEq for StrongWidget<W> {
impl<W> PartialEq for WidgetHandle<W> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<W> std::fmt::Debug for StrongWidget<W> {
impl<W> std::fmt::Debug for WidgetHandle<W> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.id.fmt(f)
}
}
impl<'a, W: Widget + 'a, State: UiRsc> FnOnce<(&'a mut State,)> for WeakWidget<W> {
impl<'a, W: Widget + 'a, State: HasUi> FnOnce<(&'a mut State,)> for WidgetRef<W> {
type Output = &'a mut W;
extern "rust-call" fn call_once(self, args: (&'a mut State,)) -> Self::Output {
&mut args.0.widgets_mut()[self]
&mut args.0.ui_mut()[self]
}
}
@@ -159,6 +169,3 @@ 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,44 +1,55 @@
use crate::UiRsc;
use crate::{HasUi, Ui};
use super::*;
use std::marker::Unsize;
pub trait WidgetLike<Rsc: UiRsc, Tag>: Sized {
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 {
type Widget: Widget + ?Sized + Unsize<dyn Widget>;
fn add(self, rsc: &mut Rsc) -> WeakWidget<Self::Widget>;
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<Self::Widget>;
fn add_strong(self, rsc: &mut Rsc) -> StrongWidget<Self::Widget> {
self.add(rsc).upgrade(rsc)
fn add_strong(self, state: &mut impl StateLike<State>) -> WidgetHandle<Self::Widget> {
self.add(state).upgrade(state.as_state().ui_mut())
}
fn with_id<W2>(
self,
f: impl FnOnce(&mut Rsc, WeakWidget<Self::Widget>) -> WeakWidget<W2>,
) -> impl WidgetIdFn<Rsc, W2> {
f: impl FnOnce(&mut State, WidgetRef<Self::Widget>) -> WidgetRef<W2>,
) -> impl WidgetIdFn<State, W2> {
move |state| {
let id = self.add(state);
f(state, id)
}
}
fn set_root(self, rsc: &mut Rsc, root: &mut impl HasRoot) {
let id = self.add_strong(rsc);
root.set_root(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()
}
}
pub trait HasRoot {
fn set_root(&mut self, root: StrongWidget);
}
pub trait WidgetArrLike<Rsc, const LEN: usize, Tag> {
pub trait WidgetArrLike<State, const LEN: usize, Tag> {
#[track_caller]
fn add(self, state: &mut Rsc) -> WidgetArr<LEN>;
fn add(self, state: &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> {
impl<State, const LEN: usize> WidgetArrLike<State, LEN, ArrTag> for WidgetArr<LEN> {
fn add(self, _: &mut impl StateLike<State>) -> WidgetArr<LEN> {
self
}
}
@@ -49,12 +60,12 @@ macro_rules! impl_widget_arr {
impl_widget_arr!($n;$($W)*;$(${concat($W,Tag)})*);
};
($n:expr;$($W:ident)*;$($Tag:ident)*) => {
impl<Rsc: UiRsc, $($W: WidgetLike<Rsc, $Tag>,$Tag,)*> WidgetArrLike<Rsc, $n, ($($Tag,)*)> for ($($W,)*) {
fn add(self, rsc: &mut Rsc) -> WidgetArr<$n> {
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> {
#[allow(non_snake_case)]
let ($($W,)*) = self;
WidgetArr::new(
[$($W.add(rsc).upgrade(rsc),)*],
[$($W.add(state).upgrade(state.as_state().ui_mut()),)*],
)
}
}

View File

@@ -1,47 +1,24 @@
use crate::{Axis, AxisT, Len, Painter, SizeCtx};
use crate::Painter;
use std::any::Any;
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 {
fn draw(&mut self, painter: &mut Painter);
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len;
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len;
}
pub trait WidgetAxisFns {
fn desired_len<A: AxisT>(&mut self, ctx: &mut SizeCtx) -> Len;
}
impl<W: Widget + ?Sized> WidgetAxisFns for W {
fn desired_len<A: AxisT>(&mut self, ctx: &mut SizeCtx) -> Len {
match A::get() {
Axis::X => self.desired_width(ctx),
Axis::Y => self.desired_height(ctx),
}
}
}
impl Widget for () {
fn draw(&mut self, _: &mut Painter) {}
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
Len::ZERO
}
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
Len::ZERO
}
}
impl dyn Widget {
@@ -61,27 +38,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: [StrongWidget; LEN],
pub arr: [WidgetHandle; LEN],
}
impl<const LEN: usize> WidgetArr<LEN> {
pub fn new(arr: [StrongWidget; LEN]) -> Self {
pub fn new(arr: [WidgetHandle; LEN]) -> Self {
Self { arr }
}
}
pub trait WidgetOption<State> {
fn get(self, state: &mut State) -> Option<StrongWidget>;
fn get(self, state: &mut State) -> Option<WidgetHandle>;
}
impl<State> WidgetOption<State> for () {
fn get(self, _: &mut State) -> Option<StrongWidget> {
fn get(self, _: &mut State) -> Option<WidgetHandle> {
None
}
}
impl<State, F: FnOnce(&mut State) -> Option<StrongWidget>> WidgetOption<State> for F {
fn get(self, state: &mut State) -> Option<StrongWidget> {
impl<State, F: FnOnce(&mut State) -> Option<WidgetHandle>> WidgetOption<State> for F {
fn get(self, state: &mut State) -> Option<WidgetHandle> {
self(state)
}
}

View File

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

View File

@@ -1,24 +0,0 @@
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::{Receiver, Sender, channel};
use std::sync::mpsc::Sender;
use crate::{
IdLike, StrongWidget, WeakWidget, Widget, WidgetData, WidgetId,
IdLike, Widget, WidgetData, WidgetHandle, WidgetId, WidgetRef,
util::{DynBorrower, HashSet, SlotVec, forget_mut, to_mut},
};
@@ -9,19 +9,16 @@ 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() -> Self {
let (send, recv) = channel();
pub fn new(send: Sender<WidgetId>) -> Self {
Self {
needs_redraw: Default::default(),
vec: Default::default(),
waiting: Default::default(),
send,
recv,
}
}
@@ -29,18 +26,9 @@ impl Widgets {
!self.needs_redraw.is_empty()
}
pub fn get_dyn(&self, id: WidgetId) -> Option<&dyn Widget> {
Some(self.vec.get(id)?.widget.as_ref())
}
pub fn get_dyn_mut(&mut self, id: WidgetId) -> Option<&mut dyn Widget> {
self.needs_redraw.insert(id);
Some(self.vec.get_mut(id)?.widget.as_mut())
}
/// get_dyn but dynamic borrow checking of widgets
/// lets you do recursive (tree) operations, like the painter does
pub(crate) fn get_dyn_dynamic<'a>(&self, id: WidgetId) -> WidgetWrapper<'a> {
pub(crate) fn get_dyn<'a>(&self, id: WidgetId) -> WidgetWrapper<'a> {
// SAFETY: must guarantee no other mutable references to this widget exist
// done through the borrow variable
let data = unsafe { forget_mut(to_mut(self.vec.get(id).unwrap())) };
@@ -54,29 +42,41 @@ impl Widgets {
where
I::Widget: Sized + Widget,
{
self.get_dyn(id.id())?.as_any().downcast_ref()
self.vec
.get(id.id())?
.widget
.as_ref()
.as_any()
.downcast_ref()
}
pub fn get_mut<I: IdLike>(&mut self, id: &I) -> Option<&mut I::Widget>
where
I::Widget: Sized + Widget,
{
self.get_dyn_mut(id.id())?.as_any_mut().downcast_mut()
let id = id.id();
self.needs_redraw.insert(id);
self.vec
.get_mut(id)?
.widget
.as_mut()
.as_any_mut()
.downcast_mut()
}
pub fn add_strong<W: Widget>(&mut self, widget: W) -> StrongWidget<W> {
pub fn add_strong<W: Widget>(&mut self, widget: W) -> WidgetHandle<W> {
let id = self.vec.add(WidgetData::new(widget));
StrongWidget::new(id, self.send.clone())
WidgetHandle::new(id, self.send.clone())
}
pub fn add_weak<W: Widget>(&mut self, widget: W) -> WeakWidget<W> {
pub fn add_weak<W: Widget>(&mut self, widget: W) -> WidgetRef<W> {
let id = self.vec.add(WidgetData::new(widget));
self.waiting.insert(id);
WeakWidget::new(id)
WidgetRef::new(id)
}
#[track_caller]
pub fn upgrade<W: ?Sized>(&mut self, rf: WeakWidget<W>) -> StrongWidget<W> {
pub fn upgrade<W: ?Sized>(&mut self, rf: WidgetRef<W>) -> WidgetHandle<W> {
if !self.waiting.remove(&rf.id()) {
let label = self.label(rf);
let id = rf.id();
@@ -84,7 +84,7 @@ impl Widgets {
"widget '{label}' ({id:?}) was already added\ncannot add a widget twice; consider creating two"
)
}
StrongWidget::new(rf.id(), self.send.clone())
WidgetHandle::new(rf.id(), self.send.clone())
}
pub fn data(&self, id: impl IdLike) -> Option<&WidgetData> {
@@ -95,19 +95,14 @@ 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 free_next(&mut self) -> Option<WidgetId> {
let next = self.recv.try_recv().ok()?;
self.vec.free(next);
Some(next)
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);
}
#[allow(clippy::len_without_is_empty)]
@@ -116,30 +111,4 @@ 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,21 +1,20 @@
use iris::prelude::*;
fn main() {
DefaultApp::<State>::run();
App::<State>::run();
}
#[derive(DefaultUiState)]
struct State {
ui_state: DefaultUiState,
}
#[default_ui_state]
struct State {}
impl DefaultAppState for State {
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 }
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(),
}
}
}

View File

@@ -1,34 +0,0 @@
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 }
}
}

View File

@@ -1,53 +0,0 @@
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, GenericParam, Generics, Ident, ItemStruct, ItemTrait, Signature,
Token, Type, Visibility,
Attribute, Block, Error, Fields, FieldsNamed, GenericParam, Generics, Ident, ItemStruct,
ItemTrait, Meta, Signature, Token, Visibility,
parse::{Parse, ParseStream, Result},
parse_macro_input, parse_quote,
spanned::Spanned,
@@ -89,108 +89,104 @@ pub fn widget_trait(input: TokenStream) -> TokenStream {
quote! {
#trai
impl #generics #name<Rsc, WL, Tag> for WL {
impl #generics #name<State, WL, Tag> for WL {
#(#impls)*
}
}
.into()
}
#[proc_macro_derive(DefaultUiState, attributes(default_ui_state))]
pub fn derive_default_ui_state(input: TokenStream) -> TokenStream {
#[proc_macro_derive(UiState, attributes(rsc))]
pub fn derive_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();
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 {
for field in state.fields {
let Some(attr) = field.attrs.iter().find(|a| a.path().is_ident("rsc")) else {
continue;
};
if found_attr {
output.extend(
Error::new(
attr.span(),
"cannot have more than one default_ui_state attribute",
)
.into_compile_error(),
);
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());
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 sname = &state.ident;
let fname = field.ident.as_ref().unwrap();
let fty = &field.ty;
let fname = &field.ident.unwrap();
rsc_fields.extend(quote! {#fname: #fty,});
output.extend(quote! {
impl iris::default::HasDefaultUiState for #sname {
fn default_state(&self) -> &iris::default::DefaultUiState {
impl #tname for #sname {
fn get(&self) -> &#fty {
&self.#fname
}
fn default_state_mut(&mut self) -> &mut iris::default::DefaultUiState {
fn get_mut(&mut self) -> &mut #fty {
&mut self.#fname
}
}
impl #tname for #rscname {
fn get(&self) -> &#fty {
&self.#fname
}
fn get_mut(&mut self) -> &mut #fty {
&mut self.#fname
}
}
});
output.into()
}
#[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;
};
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;
let vis = state.vis;
output.extend(quote! {
impl iris::core::WidgetView for #sname {
type Widget = <#fty as iris::core::HasWidget>::Widget;
fn root(&self) -> #fty {
self.#fname
#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");
};
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()
}

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.
Examples are in `examples`, eg. `cargo run --example tabs`.
There's a `main.rs` that runs a testing window, so you can just `cargo run` to see it working.
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, try to beat iced and xilem)
3. reasonably fast / efficient (a lot faster than electron, save battery life)
## dev details

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

View File

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

View File

@@ -1,10 +1,7 @@
use crate::prelude::*;
use arboard::Clipboard;
use std::{
marker::{PhantomData, Sized},
sync::Arc,
time::Instant,
};
use iris_core::util::forget_mut;
use std::{marker::Sized, sync::Arc, time::Instant};
use winit::{
event::{Ime, WindowEvent},
event_loop::{ActiveEventLoop, EventLoopProxy},
@@ -17,8 +14,6 @@ mod event;
mod input;
mod render;
mod sense;
mod state;
mod task;
pub use app::*;
pub use attr::*;
@@ -26,33 +21,23 @@ 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<WeakWidget<TextEdit>>,
pub focus: Option<WidgetRef<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(),
@@ -64,162 +49,53 @@ impl DefaultUiState {
}
}
pub trait HasDefaultUiState: Sized + 'static {
fn default_state(&self) -> &DefaultUiState;
fn default_state_mut(&mut self) -> &mut 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 DefaultAppState: HasDefaultUiState {
type Event = ();
fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, proxy: Proxy<Self::Event>)
-> Self;
pub trait DefaultAppState: RunEvents + HasDefaultUiState {
type Event: 'static = ();
fn new(ui_state: DefaultUiState, proxy: Proxy<Self::Event>) -> Self;
#[allow(unused_variables)]
fn event(
&mut self,
event: Self::Event,
rsc: &mut DefaultRsc<Self>,
render: &mut UiRenderState,
) {
}
fn event(&mut self, event: Self::Event) {}
#[allow(unused_variables)]
fn exit(&mut self, rsc: &mut DefaultRsc<Self>, render: &mut UiRenderState) {}
fn exit(&mut self) {}
#[allow(unused_variables)]
fn window_event(
&mut self,
event: WindowEvent,
rsc: &mut DefaultRsc<Self>,
render: &mut UiRenderState,
) {
}
fn window_event(&mut self, event: WindowEvent) {}
fn window_attributes() -> WindowAttributes {
Default::default()
}
}
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> {
impl<State: DefaultAppState> AppState for State {
type Event = State::Event;
fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> Self {
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,
}
let window = event_loop.create_window(Self::window_attributes()).unwrap();
Self::new(DefaultUiState::new(window), proxy)
}
fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) {
self.state.event(event, &mut self.rsc, &mut self.render);
self.event(event);
}
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
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 events = unsafe { forget_mut(self.events_mut()) };
let ui_state = HasDefaultUiState::get_mut(self);
let input_changed = ui_state.input.event(&event);
let cursor_state = ui_state.cursor_state().clone();
let old = ui_state.focus;
@@ -228,43 +104,43 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
}
if input_changed {
let window_size = ui_state.window_size();
render.run_sensors(rsc, state, cursor_state, window_size);
self.run_sensors(&cursor_state, window_size);
}
let ui_state = state.default_state_mut();
let (mut ui, mut ui_state) = self.ui_with_state();
if old != ui_state.focus
&& let Some(old) = old
{
old.edit(rsc).deselect();
old.edit(ui).deselect();
}
match &event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => {
render.update(&ui_state.root, rsc);
ui_state.renderer.update(&mut rsc.ui, render);
ui.update(events);
ui_state.renderer.update(ui);
ui_state.renderer.draw();
}
WindowEvent::Resized(size) => {
render.resize((size.width, size.height));
ui.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(rsc);
let mut text = sel.edit(ui);
match text.apply_event(event, &ui_state.input.modifiers) {
TextInputResult::Unfocus => {
ui_state.focus = None;
ui_state.window.set_ime_allowed(false);
}
TextInputResult::Submit => {
rsc.run_event::<Submit>(sel, (), state);
self.run_event::<Submit>(sel, &mut ());
}
TextInputResult::Paste => {
if let Ok(t) = ui_state.clipboard.get_text() {
text.insert(&t);
}
rsc.run_event::<Edited>(sel, (), state);
self.run_event::<Edited>(sel, &mut ());
}
TextInputResult::Copy(text) => {
if let Err(err) = ui_state.clipboard.set_text(text) {
@@ -272,7 +148,7 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
}
}
TextInputResult::Used => {
rsc.run_event::<Edited>(sel, (), state);
self.run_event::<Edited>(sel, &mut ());
}
TextInputResult::Unused => {}
}
@@ -280,7 +156,7 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
}
WindowEvent::Ime(ime) => {
if let Some(sel) = ui_state.focus {
let mut text = sel.edit(rsc);
let mut text = sel.edit(ui);
match ime {
Ime::Enabled | Ime::Disabled => (),
Ime::Preedit(content, _pos) => {
@@ -296,59 +172,15 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
}
_ => (),
}
state.window_event(event, rsc, render);
let ui_state = self.state.default_state_mut();
if render.needs_redraw(&ui_state.root, rsc.widgets()) {
self.window_event(event);
(ui, ui_state) = self.ui_with_state();
if ui.needs_redraw() {
ui_state.renderer.window().request_redraw();
}
ui_state.input.end_frame();
}
fn exit(&mut self) {
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)
self.exit();
}
}

View File

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

View File

@@ -28,13 +28,12 @@ pub struct CursorSenses(Vec<CursorSense>);
impl Event for CursorSenses {
type Data<'a> = CursorData<'a>;
type State = SensorState;
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();
fn should_run(&self, data: &mut Self::Data<'_>) -> bool {
if let Some(sense) = should_run(self, data.cursor, data.hover) {
data.sense = sense;
Some(data)
true
} else {
None
false
}
}
}
@@ -116,7 +115,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: HasEvents, Data> {
pub struct Sensor<Ctx, Data> {
pub senses: CursorSenses,
pub f: Rc<dyn EventFn<Ctx, Data>>,
}
@@ -128,46 +127,29 @@ 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: CursorState,
pub cursor: &'a CursorState,
/// the first sense that triggered this
pub sense: CursorSense,
pub render: &'a UiRenderState,
}
pub trait SensorUi {
fn run_sensors<Rsc: HasEvents>(
&self,
rsc: &mut Rsc,
state: &mut Rsc::State,
cursor: CursorState,
window_size: Vec2,
);
pub trait SensorUi<State> {
fn run_sensors(&mut self, cursor: &CursorState, window_size: Vec2);
}
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() {
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() {
let mut sensed = false;
for (id, sensor) in active.get_mut(&layer).into_flat_iter() {
let shape = self.active.get(id).unwrap().region;
let shape = self.ui().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);
@@ -176,9 +158,7 @@ impl SensorUi for UiRenderState {
}
sensed = true;
let cursor = cursor.clone();
let data = CursorData {
let mut data = CursorData {
pos: cursor.pos - region.top_left,
size: region.bot_right - region.top_left,
scroll_delta: cursor.scroll_delta,
@@ -187,15 +167,15 @@ impl SensorUi for UiRenderState {
// this does not have any meaning;
// might wanna set up Event to have a prepare stage
sense: CursorSense::Hovering,
render: self,
};
rsc.run_event::<CursorSense>(*id, data, state);
self.run_event::<CursorSense>(*id, &mut data);
}
if sensed {
break;
}
}
rsc.events_mut().get_type::<CursorSense>().active = active;
self.events_mut().get_type::<CursorSense>().active = active;
self.ui_mut().layers = layers;
}
}

View File

@@ -1,75 +0,0 @@
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)
}
}

View File

@@ -1,82 +0,0 @@
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,87 +1,25 @@
use iris_core::*;
use iris_macro::*;
use std::sync::Arc;
use crate::prelude::*;
use crate::default::{TaskCtx, TaskUpdate, Tasks};
pub trait Eventable<Rsc: HasEvents, Tag>: WidgetLike<Rsc, Tag> {
pub mod eventable {
use super::*;
widget_trait! {
pub trait Eventable<State: HasEvents + StateLike<State> + 'static>;
fn on<E: EventLike>(
self,
event: E,
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 {
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 {
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,15 +4,28 @@
#![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::*;
@@ -22,5 +35,5 @@ pub mod prelude {
pub use widget::*;
pub use iris_core::util::Vec2;
pub use len_fns::*;
pub use typed::*;
}

37
src/typed.rs Normal file
View File

@@ -0,0 +1,37 @@
#[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: UiRsc>(image: impl LoadableImage) -> impl WidgetFn<State, Image> {
pub fn image<State: HasUi>(image: impl LoadableImage) -> impl WidgetFn<State, Image> {
let image = image.get_image().expect("Failed to load image");
move |state| Image {
handle: state.ui_mut().textures.add(image),
handle: state.get_mut().add_texture(image),
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,18 +2,19 @@ use crate::prelude::*;
use std::marker::PhantomData;
pub struct Span {
pub children: Vec<StrongWidget>,
pub children: Vec<WidgetHandle>,
pub dir: Dir,
pub gap: f32,
}
impl Widget for Span {
fn draw(&mut self, painter: &mut Painter) {
let total = self.len_sum(&mut painter.size_ctx());
let total = self.len_sum();
let mut start = UiScalar::rel_min();
for child in &self.children {
let mut span = UiSpan::FULL;
span.start = start;
painter.widget(id);
let len = painter.len_axis(child, self.dir.axis);
if len.rest > 0.0 {
let offset = UiScalar::new(total.rel, total.abs);
@@ -32,20 +33,6 @@ impl Widget for Span {
start.abs += self.gap;
}
}
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
match self.dir.axis {
Axis::X => self.desired_len(ctx),
Axis::Y => self.desired_ortho(ctx),
}
}
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
match self.dir.axis {
Axis::X => self.desired_ortho(ctx),
Axis::Y => self.desired_len(ctx),
}
}
}
impl Span {
@@ -62,30 +49,14 @@ impl Span {
self
}
pub fn push(&mut self, w: StrongWidget) {
pub fn push(&mut self, w: WidgetHandle) {
self.children.push(w);
}
pub fn pop(&mut self) -> Option<StrongWidget> {
pub fn pop(&mut self) -> Option<WidgetHandle> {
self.children.pop()
}
fn len_sum(&mut self, ctx: &mut SizeCtx) -> Len {
let gap = self.gap * self.children.len().saturating_sub(1) as f32;
self.children.iter().fold(Len::abs(gap), |mut s, id| {
// it's tempting to subtract the abs & rel from the ctx outer,
// but that would create inconsistent sizing if you put
// a rest first vs last & only speed up in one direction.
// I think this is only solvable by restricting how you can
// compute size, bc currently you need child to define parent's
// sectioning and you need parent's sectioning to define child.
// Fortunately, that doesn't matter in most cases
let len = ctx.len_axis(id, self.dir.axis);
s += len;
s
})
}
fn desired_len(&mut self, ctx: &mut SizeCtx) -> Len {
let len = self.len_sum(ctx);
if len.rest == 0.0 && len.rel == 0.0 {
@@ -159,15 +130,15 @@ pub struct SpanBuilder<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Ta
_pd: PhantomData<(State, Tag)>,
}
impl<Rsc, const LEN: usize, Wa: WidgetArrLike<Rsc, LEN, Tag>, Tag> WidgetFnTrait<Rsc>
for SpanBuilder<Rsc, LEN, Wa, Tag>
impl<State: StateLike<State>, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag>
WidgetFnTrait<State> for SpanBuilder<State, LEN, Wa, Tag>
{
type Widget = Span;
#[track_caller]
fn run(self, rsc: &mut Rsc) -> Self::Widget {
fn run(self, state: &mut State) -> Self::Widget {
Span {
children: self.children.add(rsc).arr.into_iter().collect(),
children: self.children.add(state).arr.into_iter().collect(),
dir: self.dir,
gap: self.gap,
}
@@ -193,7 +164,7 @@ impl<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag>
}
impl std::ops::Deref for Span {
type Target = Vec<StrongWidget>;
type Target = Vec<WidgetHandle>;
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<StrongWidget>,
pub children: Vec<WidgetHandle>,
pub size: StackSize,
}
@@ -48,15 +48,14 @@ pub struct StackBuilder<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, T
_pd: PhantomData<(State, Tag)>,
}
impl<Rsc, const LEN: usize, Wa: WidgetArrLike<Rsc, LEN, Tag>, Tag> WidgetFnTrait<Rsc>
for StackBuilder<Rsc, LEN, Wa, Tag>
impl<State: StateLike<State>, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag> FnOnce<(&mut State,)>
for StackBuilder<State, LEN, Wa, Tag>
{
type Widget = Stack;
type Output = Stack;
#[track_caller]
fn run(self, rsc: &mut Rsc) -> Self::Widget {
extern "rust-call" fn call_once(self, args: (&mut State,)) -> Self::Output {
Stack {
children: self.children.add(rsc).arr.into_iter().collect(),
children: self.children.add(args.0).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<StrongWidget>,
pub inner: Option<WidgetHandle>,
}
impl Widget for WidgetPtr {
@@ -38,14 +38,14 @@ impl WidgetPtr {
inner: Default::default(),
}
}
pub fn set<W: ?Sized + Unsize<dyn Widget>>(&mut self, to: StrongWidget<W>) {
pub fn set<W: ?Sized + Unsize<dyn Widget>>(&mut self, to: WidgetHandle<W>) {
self.inner = Some(to)
}
pub fn replace<W: ?Sized + Unsize<dyn Widget>>(
&mut self,
to: StrongWidget<W>,
) -> Option<StrongWidget> {
to: WidgetHandle<W>,
) -> Option<WidgetHandle> {
self.inner.replace(to)
}
}

View File

@@ -51,15 +51,15 @@ impl<State, O, H: WidgetOption<State>> TextBuilder<State, O, H> {
}
}
impl<Rsc: UiRsc, O> TextBuilder<Rsc, O> {
pub fn hint<W: WidgetLike<Rsc, Tag>, Tag>(
impl<State: HasUi + StateLike<State>, O> TextBuilder<State, O> {
pub fn hint<W: WidgetLike<State, Tag>, Tag>(
self,
hint: W,
) -> TextBuilder<Rsc, O, impl WidgetOption<Rsc>> {
) -> TextBuilder<State, O, impl WidgetOption<State>> {
TextBuilder {
content: self.content,
attrs: self.attrs,
hint: move |rsc: &mut Rsc| Some(hint.add_strong(rsc).any()),
hint: move |ui: &mut State| Some(hint.add_strong(ui).any()),
output: self.output,
state: PhantomData,
}
@@ -75,19 +75,19 @@ pub trait TextBuilderOutput<State>: Sized {
}
pub struct TextOutput;
impl<Rsc: UiRsc> TextBuilderOutput<Rsc> for TextOutput {
impl<State: HasUi> TextBuilderOutput<State> for TextOutput {
type Output = Text;
fn run<H: WidgetOption<Rsc>>(
state: &mut Rsc,
builder: TextBuilder<Rsc, Self, H>,
fn run<H: WidgetOption<State>>(
state: &mut State,
builder: TextBuilder<State, 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.ui_mut().text.font_system;
let font_system = &mut state.get_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: UiRsc> TextBuilderOutput<State> for TextEditOutput {
impl<State: HasUi> TextBuilderOutput<State> for TextEditOutput {
type Output = TextEdit;
fn run<H: WidgetOption<State>>(
@@ -118,7 +118,7 @@ impl<State: UiRsc> TextBuilderOutput<State> for TextEditOutput {
TextView::new(buf, builder.attrs, builder.hint.get(state)),
builder.output.mode,
);
let font_system = &mut state.ui_mut().text.font_system;
let font_system = &mut state.get_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 UiRsc) -> TextEditCtx<'a>;
fn edit<'a>(&self, ui: &'a mut impl HasUi) -> TextEditCtx<'a>;
}
impl<I: IdLike<Widget = TextEdit>> TextEditable for I {
fn edit<'a>(&self, ui: &'a mut impl UiRsc) -> TextEditCtx<'a> {
fn edit<'a>(&self, ui: &'a mut impl HasUi) -> 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<StrongWidget>,
pub hint: Option<WidgetHandle>,
}
impl TextView {
pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option<StrongWidget>) -> Self {
pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option<WidgetHandle>) -> 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<Rsc: UiRsc + 'static>;
pub trait CoreWidget<State: HasUi + StateLike<State> + 'static>;
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Rsc, Pad> {
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<State, Pad> {
|state| Pad {
padding: padding.into(),
inner: self.add_strong(state),
}
}
fn align(self, align: impl Into<Align>) -> impl WidgetFn<Rsc, Aligned> {
fn align(self, align: impl Into<Align>) -> impl WidgetFn<State, Aligned> {
move |state| Aligned {
inner: self.add_strong(state),
align: align.into(),
}
}
fn center(self) -> impl WidgetFn<Rsc, Aligned> {
fn center(self) -> impl WidgetFn<State, Aligned> {
self.align(Align::CENTER)
}
fn label(self, label: impl Into<String>) -> impl WidgetIdFn<Rsc, WL::Widget> {
fn label(self, label: impl Into<String>) -> impl WidgetIdFn<State, WL::Widget> {
|state| {
let id = self.add(state);
state.ui_mut().widgets.set_label(id, label.into());
state.get_mut().set_label(id, label.into());
id
}
}
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Rsc, Sized> {
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<State, 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<Rsc, MaxSize> {
fn max_width(self, len: impl Into<Len>) -> impl WidgetFn<State, 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<Rsc, MaxSize> {
fn max_height(self, len: impl Into<Len>) -> impl WidgetFn<State, 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<Rsc, Sized> {
fn width(self, len: impl Into<Len>) -> impl WidgetFn<State, 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<Rsc, Sized> {
fn height(self, len: impl Into<Len>) -> impl WidgetFn<State, Sized> {
let len = len.into();
move |state| Sized {
inner: self.add_strong(state),
@@ -76,64 +76,65 @@ widget_trait! {
}
}
fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<Rsc, Offset> {
fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<State, Offset> {
move |state| Offset {
inner: self.add_strong(state),
amt: amt.into(),
}
}
fn scrollable(self) -> impl WidgetIdFn<Rsc, Scroll> where Rsc: HasEvents {
fn scrollable(self) -> impl WidgetIdFn<State, Scroll> where State: HasEvents {
use eventable::*;
move |state| {
Scroll::new(self.add_strong(state), Axis::Y)
.on(CursorSense::Scroll, |ctx, rsc| {
.on(CursorSense::Scroll, |ctx: &mut EventIdCtx<'_, State::State, CursorData<'_>, Scroll>| {
let delta = ctx.data.scroll_delta.y * 50.0;
ctx.widget(rsc).scroll(delta);
ctx.widget().scroll(delta);
})
.add(state)
}
}
fn masked(self) -> impl WidgetFn<Rsc, Masked> {
fn masked(self) -> impl WidgetFn<State, Masked> {
move |state| Masked {
inner: self.add_strong(state),
}
}
fn background<T>(self, w: impl WidgetLike<Rsc, T>) -> impl WidgetFn<Rsc, Stack> {
fn background<T>(self, w: impl WidgetLike<State, T>) -> impl WidgetFn<State, 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<Rsc, T>) -> impl WidgetFn<Rsc, Stack> {
fn foreground<T>(self, w: impl WidgetLike<State, T>) -> impl WidgetFn<State, 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<Rsc, LayerOffset> {
fn layer_offset(self, offset: usize) -> impl WidgetFn<State, LayerOffset> {
move |state| LayerOffset {
inner: self.add_strong(state),
offset,
}
}
fn to_any(self) -> impl WidgetIdFn<Rsc> {
fn to_any(self) -> impl WidgetIdFn<State> {
|state| self.add(state)
}
fn set_ptr(self, ptr: WeakWidget<WidgetPtr>, state: &mut Rsc) {
fn set_ptr(self, ptr: WidgetRef<WidgetPtr>, state: &mut State) {
let id = self.add_strong(state);
state.ui_mut().widgets[ptr].inner = Some(id);
state.get_mut()[ptr].inner = Some(id);
}
}
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>;
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>;
}
impl<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag>