7 Commits

Author SHA1 Message Date
7b54aaf3c4 readme 2026-01-29 16:39:19 -05:00
17c436d944 stuff 2026-01-22 23:33:46 -05:00
a592318a6f impl idlike for widgetview 2026-01-20 18:11:21 -05:00
796bc41752 didn't actually remove state on widget remove (mem leak) 2026-01-19 21:39:13 -05:00
7bafb04a34 widget state 2026-01-19 20:39:58 -05:00
06dd015092 finished moving out render_state 2026-01-19 18:00:24 -05:00
79813db3ba work 2026-01-12 18:40:27 -05:00
45 changed files with 903 additions and 696 deletions

View File

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

View File

@@ -1,4 +1,4 @@
use crate::{HasEvents, HasUi, Widget, WidgetRef}; use crate::{HasEvents, WeakWidget, Widget};
pub struct EventCtx<'a, Rsc: HasEvents, Data> { pub struct EventCtx<'a, Rsc: HasEvents, Data> {
pub state: &'a mut Rsc::State, pub state: &'a mut Rsc::State,
@@ -6,13 +6,13 @@ pub struct EventCtx<'a, Rsc: HasEvents, Data> {
} }
pub struct EventIdCtx<'a, Rsc: HasEvents, Data, W: ?Sized> { pub struct EventIdCtx<'a, Rsc: HasEvents, Data, W: ?Sized> {
pub widget: WidgetRef<W>, pub widget: WeakWidget<W>,
pub state: &'a mut Rsc::State, pub state: &'a mut Rsc::State,
pub data: Data, pub data: Data,
} }
impl<Rsc: HasEvents + HasUi, Data, W: Widget> EventIdCtx<'_, Rsc, Data, W> { impl<Rsc: HasEvents, Data, W: Widget> EventIdCtx<'_, Rsc, Data, W> {
pub fn widget<'a>(&self, rsc: &'a mut Rsc) -> &'a mut W { pub fn widget<'a>(&self, rsc: &'a mut Rsc) -> &'a mut W {
&mut rsc.ui_mut()[self.widget] &mut rsc.ui_mut().widgets[self.widget]
} }
} }

View File

@@ -1,6 +1,6 @@
use crate::{ use crate::{
ActiveData, Event, EventCtx, EventFn, EventIdCtx, EventLike, HasEvents, IdLike, LayerId, ActiveData, Event, EventCtx, EventFn, EventIdCtx, EventLike, HasEvents, IdLike, LayerId,
Widget, WidgetEventFn, WidgetId, WidgetRef, WeakWidget, WidgetEventFn, WidgetId,
util::{HashMap, HashSet, TypeMap}, util::{HashMap, HashSet, TypeMap},
}; };
use std::{any::TypeId, rc::Rc}; use std::{any::TypeId, rc::Rc};
@@ -24,15 +24,16 @@ impl<Rsc: HasEvents + 'static> EventManager<Rsc> {
self.types.type_or_default() self.types.type_or_default()
} }
pub fn register<W: Widget + ?Sized, E: EventLike>( pub fn register<I: IdLike + 'static, E: EventLike>(
&mut self, &mut self,
id: WidgetRef<W>, id: I,
event: E, event: E,
f: impl WidgetEventFn<Rsc, <E::Event as Event>::Data, W>, f: impl for<'a> WidgetEventFn<Rsc, <E::Event as Event>::Data<'a>, I::Widget>,
) { ) {
let i = id.id();
self.get_type::<E>().register(id, event, f); self.get_type::<E>().register(id, event, f);
self.widget_to_types self.widget_to_types
.entry(id.id()) .entry(i)
.or_default() .or_default()
.insert(Self::type_key::<E>()); .insert(Self::type_key::<E>());
} }
@@ -74,7 +75,7 @@ pub trait EventManagerLike<State> {
fn undraw(&mut self, data: &ActiveData); fn undraw(&mut self, data: &ActiveData);
} }
type EventData<Rsc, E> = (E, Rc<dyn EventFn<Rsc, <E as Event>::Data>>); type EventData<Rsc, E> = (E, Rc<dyn for<'a> EventFn<Rsc, <E as Event>::Data<'a>>>);
pub struct TypeEventManager<Rsc: HasEvents, E: Event> { pub struct TypeEventManager<Rsc: HasEvents, E: Event> {
// TODO: reduce visiblity!! // TODO: reduce visiblity!!
pub active: HashMap<LayerId, HashMap<WidgetId, E::State>>, pub active: HashMap<LayerId, HashMap<WidgetId, E::State>>,
@@ -112,11 +113,11 @@ impl<Rsc: HasEvents, E: Event> Default for TypeEventManager<Rsc, E> {
} }
impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> { impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> {
fn register<W: Widget + ?Sized>( fn register<I: IdLike + 'static>(
&mut self, &mut self,
widget: WidgetRef<W>, widget: I,
event: impl EventLike<Event = E>, event: impl EventLike<Event = E>,
f: impl WidgetEventFn<Rsc, E::Data, W>, f: impl for<'a> WidgetEventFn<Rsc, E::Data<'a>, I::Widget>,
) { ) {
let event = event.into_event(); let event = event.into_event();
self.map.entry(widget.id()).or_default().push(( self.map.entry(widget.id()).or_default().push((
@@ -124,7 +125,7 @@ impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> {
Rc::new(move |ctx, rsc| { Rc::new(move |ctx, rsc| {
f( f(
EventIdCtx { EventIdCtx {
widget, widget: WeakWidget::new(widget.id()),
state: ctx.state, state: ctx.state,
data: ctx.data, data: ctx.data,
}, },
@@ -137,7 +138,7 @@ impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> {
pub fn run_fn<'a>( pub fn run_fn<'a>(
&mut self, &mut self,
id: impl IdLike, id: impl IdLike,
) -> impl FnOnce(EventCtx<'_, Rsc, E::Data>, &mut Rsc) + 'a { ) -> impl for<'b> FnOnce(EventCtx<'_, Rsc, E::Data<'b>>, &mut Rsc) + 'a {
let fs = self.map.get(&id.id()).cloned().unwrap_or_default(); let fs = self.map.get(&id.id()).cloned().unwrap_or_default();
move |ctx, rsc| { move |ctx, rsc| {
for (e, f) in fs { for (e, f) in fs {

View File

@@ -7,10 +7,10 @@ pub use manager::*;
pub use rsc::*; pub use rsc::*;
pub trait Event: Sized + 'static + Clone { pub trait Event: Sized + 'static + Clone {
type Data: Clone = (); type Data<'a>: Clone = ();
type State: Default = (); type State: Default = ();
#[allow(unused_variables)] #[allow(unused_variables)]
fn should_run(&self, data: &Self::Data) -> Option<Self::Data> { fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
Some(data.clone()) Some(data.clone())
} }
} }

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
use std::num::NonZero; use std::num::NonZero;
use crate::{ use crate::{
Ui, UiData, UiRenderState,
render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf}, render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf},
util::HashMap, util::HashMap,
}; };
@@ -59,12 +59,18 @@ impl UiRenderNode {
} }
} }
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &mut Ui) { pub fn update(
&mut self,
device: &Device,
queue: &Queue,
ui: &mut UiData,
ui_render: &mut UiRenderState,
) {
self.active.clear(); self.active.clear();
for (i, primitives) in ui.layers.iter_mut() { for (i, primitives) in ui_render.layers.iter_mut() {
self.active.push(i); self.active.push(i);
for change in primitives.apply_free() { for change in primitives.apply_free() {
if let Some(inst) = ui.active.get_mut(&change.id) { if let Some(inst) = ui_render.active.get_mut(&change.id) {
for h in &mut inst.primitives { for h in &mut inst.primitives {
if h.layer == i && h.inst_idx == change.old { if h.layer == i && h.inst_idx == change.old {
h.inst_idx = change.new; h.inst_idx = change.new;

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

@@ -61,27 +61,27 @@ pub trait WidgetFn<State, W: Widget + ?Sized>: FnOnce(&mut State) -> W {}
impl<State, W: Widget + ?Sized, F: FnOnce(&mut State) -> W> WidgetFn<State, W> for F {} impl<State, W: Widget + ?Sized, F: FnOnce(&mut State) -> W> WidgetFn<State, W> for F {}
pub struct WidgetArr<const LEN: usize> { pub struct WidgetArr<const LEN: usize> {
pub arr: [WidgetHandle; LEN], pub arr: [StrongWidget; LEN],
} }
impl<const LEN: usize> WidgetArr<LEN> { impl<const LEN: usize> WidgetArr<LEN> {
pub fn new(arr: [WidgetHandle; LEN]) -> Self { pub fn new(arr: [StrongWidget; LEN]) -> Self {
Self { arr } Self { arr }
} }
} }
pub trait WidgetOption<State> { pub trait WidgetOption<State> {
fn get(self, state: &mut State) -> Option<WidgetHandle>; fn get(self, state: &mut State) -> Option<StrongWidget>;
} }
impl<State> WidgetOption<State> for () { impl<State> WidgetOption<State> for () {
fn get(self, _: &mut State) -> Option<WidgetHandle> { fn get(self, _: &mut State) -> Option<StrongWidget> {
None None
} }
} }
impl<State, F: FnOnce(&mut State) -> Option<WidgetHandle>> WidgetOption<State> for F { impl<State, F: FnOnce(&mut State) -> Option<StrongWidget>> WidgetOption<State> for F {
fn get(self, state: &mut State) -> Option<WidgetHandle> { fn get(self, state: &mut State) -> Option<StrongWidget> {
self(state) self(state)
} }
} }

View File

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

View File

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

View File

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

View File

@@ -10,8 +10,12 @@ struct State {
} }
impl DefaultAppState for State { impl DefaultAppState for State {
fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, _: Proxy<Self::Event>) -> Self { fn new(
rect(Color::RED).set_root(rsc); mut ui_state: DefaultUiState,
rsc: &mut DefaultRsc<Self>,
_: Proxy<Self::Event>,
) -> Self {
rect(Color::RED).set_root(rsc, &mut ui_state);
Self { ui_state } Self { ui_state }
} }
} }

View File

@@ -12,11 +12,15 @@ fn main() {
#[derive(DefaultUiState)] #[derive(DefaultUiState)]
pub struct Client { pub struct Client {
ui_state: DefaultUiState, ui_state: DefaultUiState,
info: WidgetRef<Text>, info: WeakWidget<Text>,
} }
impl DefaultAppState for Client { impl DefaultAppState for Client {
fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, _: Proxy<Self::Event>) -> Self { fn new(
mut ui_state: DefaultUiState,
rsc: &mut DefaultRsc<Self>,
_: Proxy<Self::Event>,
) -> Self {
let rrect = rect(Color::WHITE).radius(20); let rrect = rect(Color::WHITE).radius(20);
let pad_test = ( let pad_test = (
rrect.color(Color::BLUE), rrect.color(Color::BLUE),
@@ -147,7 +151,7 @@ impl DefaultAppState for Client {
let main = WidgetPtr::new().add(rsc); let main = WidgetPtr::new().add(rsc);
let vals = Rc::new(RefCell::new((0, Vec::new()))); let vals = Rc::new(RefCell::new((0, Vec::new())));
let mut switch_button = |color, to: WidgetRef, label| { let mut switch_button = |color, to: WeakWidget, label| {
let to = to.upgrade(rsc); let to = to.upgrade(rsc);
let vec = &mut vals.borrow_mut().1; let vec = &mut vals.borrow_mut().1;
let i = vec.len(); let i = vec.len();
@@ -197,20 +201,25 @@ impl DefaultAppState for Client {
((tabs.height(40), main.pad(10)).span(Dir::DOWN), info_sect) ((tabs.height(40), main.pad(10)).span(Dir::DOWN), info_sect)
.stack() .stack()
.set_root(rsc); .set_root(rsc, &mut ui_state);
Self { ui_state, info } Self { ui_state, info }
} }
fn window_event(&mut self, _: WindowEvent, rsc: &mut DefaultRsc<Self>) { fn window_event(
&mut self,
_: WindowEvent,
rsc: &mut DefaultRsc<Self>,
render: &mut UiRenderState,
) {
let new = format!( let new = format!(
"widgets: {}\nactive: {}\nviews: {}", "widgets: {}\nactive: {}\nviews: {}",
rsc.ui.num_widgets(), rsc.widgets().len(),
rsc.ui.active_widgets(), render.active_widgets(),
self.ui_state.renderer.ui.view_count() self.ui_state.renderer.ui.view_count(),
); );
if new != *rsc.ui[self.info].content { if new != *rsc.widgets()[self.info].content {
*rsc.ui[self.info].content = new; *rsc.widgets_mut()[self.info].content = new;
} }
} }
} }

View File

@@ -11,11 +11,15 @@ struct State {
} }
impl DefaultAppState for State { impl DefaultAppState for State {
fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, _: Proxy<Self::Event>) -> Self { fn new(
mut ui_state: DefaultUiState,
rsc: &mut DefaultRsc<Self>,
_: Proxy<Self::Event>,
) -> Self {
let rect = rect(Color::RED).add(rsc); let rect = rect(Color::RED).add(rsc);
rect.task_on(CursorSense::click(), async move |mut ctx| { rect.task_on(CursorSense::click(), async move |mut ctx| {
tokio::time::sleep(Duration::from_secs(1)).await; tokio::time::sleep(Duration::from_secs(1)).await;
ctx.task.update(move |_, rsc| { ctx.update(move |_, rsc| {
let rect = rect(rsc); let rect = rect(rsc);
if rect.color == Color::RED { if rect.color == Color::RED {
rect.color = Color::BLUE; rect.color = Color::BLUE;
@@ -24,7 +28,7 @@ impl DefaultAppState for State {
} }
}); });
}) })
.set_root(rsc); .set_root(rsc, &mut ui_state);
Self { ui_state } Self { ui_state }
} }
} }

View File

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

View File

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

View File

@@ -8,16 +8,24 @@ impl<Rsc: HasEvents, W: Widget + 'static> WidgetAttr<Rsc, W> for Selector
where where
Rsc::State: HasDefaultUiState, Rsc::State: HasDefaultUiState,
{ {
type Input = WidgetRef<TextEdit>; type Input = WeakWidget<TextEdit>;
fn run(rsc: &mut Rsc, container: WidgetRef<W>, id: Self::Input) { fn run(rsc: &mut Rsc, container: WeakWidget<W>, id: Self::Input) {
rsc.register_event(container, CursorSense::click_or_drag(), move |ctx, rsc| { rsc.register_event(container, CursorSense::click_or_drag(), move |ctx, rsc| {
let region = rsc.ui().window_region(&id).unwrap(); let region = ctx.data.render.window_region(&id).unwrap();
let id_pos = region.top_left; let id_pos = region.top_left;
let container_pos = rsc.ui().window_region(&container).unwrap().top_left; let container_pos = ctx.data.render.window_region(&container).unwrap().top_left;
let pos = ctx.data.pos + container_pos - id_pos; let pos = ctx.data.pos + container_pos - id_pos;
let size = region.size(); let size = region.size();
select(rsc, ctx.state, id, pos, size, ctx.data.sense.is_dragging()); select(
rsc,
ctx.data.render,
ctx.state,
id,
pos,
size,
ctx.data.sense.is_dragging(),
);
}); });
} }
} }
@@ -30,10 +38,11 @@ where
{ {
type Input = (); type Input = ();
fn run(rsc: &mut Rsc, id: WidgetRef<TextEdit>, _: Self::Input) { fn run(rsc: &mut Rsc, id: WeakWidget<TextEdit>, _: Self::Input) {
rsc.register_event(id, CursorSense::click_or_drag(), move |ctx, rsc| { rsc.register_event(id, CursorSense::click_or_drag(), move |ctx, rsc| {
select( select(
rsc, rsc,
ctx.data.render,
ctx.state, ctx.state,
id, id,
ctx.data.pos, ctx.data.pos,
@@ -45,9 +54,10 @@ where
} }
fn select( fn select(
rsc: &mut impl HasUi, rsc: &mut impl UiRsc,
render: &UiRenderState,
state: &mut impl HasDefaultUiState, state: &mut impl HasDefaultUiState,
id: WidgetRef<TextEdit>, id: WeakWidget<TextEdit>,
pos: Vec2, pos: Vec2,
size: Vec2, size: Vec2,
dragging: bool, dragging: bool,
@@ -57,7 +67,7 @@ fn select(
let recent = (now - state.last_click) < Duration::from_millis(300); let recent = (now - state.last_click) < Duration::from_millis(300);
state.last_click = now; state.last_click = now;
id.edit(rsc).select(pos, size, dragging, recent); id.edit(rsc).select(pos, size, dragging, recent);
if let Some(region) = rsc.ui().window_region(&id) { if let Some(region) = render.window_region(&id) {
state.window.set_ime_allowed(true); state.window.set_ime_allowed(true);
state.window.set_ime_cursor_area( state.window.set_ime_cursor_area(
LogicalPosition::<f32>::from(region.top_left.tuple()), LogicalPosition::<f32>::from(region.top_left.tuple()),

View File

@@ -17,6 +17,7 @@ mod event;
mod input; mod input;
mod render; mod render;
mod sense; mod sense;
mod state;
mod task; mod task;
pub use app::*; pub use app::*;
@@ -25,24 +26,33 @@ pub use event::*;
pub use input::*; pub use input::*;
pub use render::*; pub use render::*;
pub use sense::*; pub use sense::*;
pub use state::*;
pub use task::*; pub use task::*;
pub type Proxy<Event> = EventLoopProxy<Event>; pub type Proxy<Event> = EventLoopProxy<Event>;
pub struct DefaultUiState { pub struct DefaultUiState {
pub root: Option<StrongWidget>,
pub renderer: UiRenderer, pub renderer: UiRenderer,
pub input: Input, pub input: Input,
pub focus: Option<WidgetRef<TextEdit>>, pub focus: Option<WeakWidget<TextEdit>>,
pub clipboard: Clipboard, pub clipboard: Clipboard,
pub window: Arc<Window>, pub window: Arc<Window>,
pub ime: usize, pub ime: usize,
pub last_click: Instant, pub last_click: Instant,
} }
impl HasRoot for DefaultUiState {
fn set_root(&mut self, root: StrongWidget) {
self.root = Some(root);
}
}
impl DefaultUiState { impl DefaultUiState {
pub fn new(window: impl Into<Arc<Window>>) -> Self { pub fn new(window: impl Into<Arc<Window>>) -> Self {
let window = window.into(); let window = window.into();
Self { Self {
root: None,
renderer: UiRenderer::new(window.clone()), renderer: UiRenderer::new(window.clone()),
window, window,
input: Input::default(), input: Input::default(),
@@ -64,20 +74,33 @@ pub trait DefaultAppState: HasDefaultUiState {
fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, proxy: Proxy<Self::Event>) fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, proxy: Proxy<Self::Event>)
-> Self; -> Self;
#[allow(unused_variables)] #[allow(unused_variables)]
fn event(&mut self, event: Self::Event, rsc: &mut DefaultRsc<Self>) {} fn event(
&mut self,
event: Self::Event,
rsc: &mut DefaultRsc<Self>,
render: &mut UiRenderState,
) {
}
#[allow(unused_variables)] #[allow(unused_variables)]
fn exit(&mut self, rsc: &mut DefaultRsc<Self>) {} fn exit(&mut self, rsc: &mut DefaultRsc<Self>, render: &mut UiRenderState) {}
#[allow(unused_variables)] #[allow(unused_variables)]
fn window_event(&mut self, event: WindowEvent, rsc: &mut DefaultRsc<Self>) {} fn window_event(
&mut self,
event: WindowEvent,
rsc: &mut DefaultRsc<Self>,
render: &mut UiRenderState,
) {
}
fn window_attributes() -> WindowAttributes { fn window_attributes() -> WindowAttributes {
Default::default() Default::default()
} }
} }
pub struct DefaultRsc<State: 'static> { pub struct DefaultRsc<State: 'static> {
pub ui: Ui, pub ui: UiData,
pub events: EventManager<Self>, pub events: EventManager<Self>,
pub tasks: Tasks<Self>, pub tasks: Tasks<Self>,
pub state: WidgetState,
_state: PhantomData<State>, _state: PhantomData<State>,
} }
@@ -89,21 +112,39 @@ impl<State> DefaultRsc<State> {
ui: Default::default(), ui: Default::default(),
events: Default::default(), events: Default::default(),
tasks, tasks,
state: Default::default(),
_state: Default::default(), _state: Default::default(),
}, },
recv, recv,
) )
} }
pub fn create_state<T: 'static>(&mut self, id: impl IdLike, data: T) -> WeakState<T> {
self.state.add(id.id(), data)
}
} }
impl<State> HasUi for DefaultRsc<State> { impl<State> UiRsc for DefaultRsc<State> {
fn ui(&self) -> &Ui { fn ui(&self) -> &UiData {
&self.ui &self.ui
} }
fn ui_mut(&mut self) -> &mut Ui { fn ui_mut(&mut self) -> &mut UiData {
&mut self.ui &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> { impl<State: 'static> HasState for DefaultRsc<State> {
@@ -126,8 +167,19 @@ impl<State: 'static> HasTasks for DefaultRsc<State> {
} }
} }
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> { pub struct DefaultApp<State: DefaultAppState> {
rsc: DefaultRsc<State>, rsc: DefaultRsc<State>,
render: UiRenderState,
state: State, state: State,
task_recv: TaskMsgReceiver<DefaultRsc<State>>, task_recv: TaskMsgReceiver<DefaultRsc<State>>,
} }
@@ -142,23 +194,32 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
let default_state = DefaultUiState::new(window); let default_state = DefaultUiState::new(window);
let (mut rsc, task_recv) = DefaultRsc::init(default_state.window.clone()); let (mut rsc, task_recv) = DefaultRsc::init(default_state.window.clone());
let state = State::new(default_state, &mut rsc, proxy); let state = State::new(default_state, &mut rsc, proxy);
let render = UiRenderState::new();
Self { Self {
rsc, rsc,
state, state,
render,
task_recv, task_recv,
} }
} }
fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) { fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) {
self.state.event(event, &mut self.rsc); self.state.event(event, &mut self.rsc, &mut self.render);
} }
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) { fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
for update in self.task_recv.try_iter() { let Self {
update(&mut self.state, &mut self.rsc); rsc,
render,
state,
task_recv,
} = self;
for update in task_recv.try_iter() {
update(state, rsc);
} }
let ui_state = self.state.default_state_mut(); let ui_state = state.default_state_mut();
let input_changed = ui_state.input.event(&event); let input_changed = ui_state.input.event(&event);
let cursor_state = ui_state.cursor_state().clone(); let cursor_state = ui_state.cursor_state().clone();
let old = ui_state.focus; let old = ui_state.focus;
@@ -167,45 +228,43 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
} }
if input_changed { if input_changed {
let window_size = ui_state.window_size(); let window_size = ui_state.window_size();
self.rsc render.run_sensors(rsc, state, cursor_state, window_size);
.run_sensors(&mut self.state, cursor_state, window_size);
} }
let ui = &mut self.rsc.ui; let ui_state = state.default_state_mut();
let ui_state = self.state.default_state_mut();
if old != ui_state.focus if old != ui_state.focus
&& let Some(old) = old && let Some(old) = old
{ {
old.edit(ui).deselect(); old.edit(rsc).deselect();
} }
match &event { match &event {
WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
ui.update(&mut self.rsc.events); render.update(&ui_state.root, rsc);
ui_state.renderer.update(ui); ui_state.renderer.update(&mut rsc.ui, render);
ui_state.renderer.draw(); ui_state.renderer.draw();
} }
WindowEvent::Resized(size) => { WindowEvent::Resized(size) => {
ui.resize((size.width, size.height)); render.resize((size.width, size.height));
ui_state.renderer.resize(size) ui_state.renderer.resize(size)
} }
WindowEvent::KeyboardInput { event, .. } => { WindowEvent::KeyboardInput { event, .. } => {
if let Some(sel) = ui_state.focus if let Some(sel) = ui_state.focus
&& event.state.is_pressed() && event.state.is_pressed()
{ {
let mut text = sel.edit(ui); let mut text = sel.edit(rsc);
match text.apply_event(event, &ui_state.input.modifiers) { match text.apply_event(event, &ui_state.input.modifiers) {
TextInputResult::Unfocus => { TextInputResult::Unfocus => {
ui_state.focus = None; ui_state.focus = None;
ui_state.window.set_ime_allowed(false); ui_state.window.set_ime_allowed(false);
} }
TextInputResult::Submit => { TextInputResult::Submit => {
self.rsc.run_event::<Submit>(sel, (), &mut self.state); rsc.run_event::<Submit>(sel, (), state);
} }
TextInputResult::Paste => { TextInputResult::Paste => {
if let Ok(t) = ui_state.clipboard.get_text() { if let Ok(t) = ui_state.clipboard.get_text() {
text.insert(&t); text.insert(&t);
} }
self.rsc.run_event::<Edited>(sel, (), &mut self.state); rsc.run_event::<Edited>(sel, (), state);
} }
TextInputResult::Copy(text) => { TextInputResult::Copy(text) => {
if let Err(err) = ui_state.clipboard.set_text(text) { if let Err(err) = ui_state.clipboard.set_text(text) {
@@ -213,7 +272,7 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
} }
} }
TextInputResult::Used => { TextInputResult::Used => {
self.rsc.run_event::<Edited>(sel, (), &mut self.state); rsc.run_event::<Edited>(sel, (), state);
} }
TextInputResult::Unused => {} TextInputResult::Unused => {}
} }
@@ -221,7 +280,7 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
} }
WindowEvent::Ime(ime) => { WindowEvent::Ime(ime) => {
if let Some(sel) = ui_state.focus { if let Some(sel) = ui_state.focus {
let mut text = sel.edit(ui); let mut text = sel.edit(rsc);
match ime { match ime {
Ime::Enabled | Ime::Disabled => (), Ime::Enabled | Ime::Disabled => (),
Ime::Preedit(content, _pos) => { Ime::Preedit(content, _pos) => {
@@ -237,15 +296,59 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
} }
_ => (), _ => (),
} }
self.state.window_event(event, &mut self.rsc); state.window_event(event, rsc, render);
let ui_state = self.state.default_state_mut(); let ui_state = self.state.default_state_mut();
if self.rsc.ui.needs_redraw() { if render.needs_redraw(&ui_state.root, rsc.widgets()) {
ui_state.renderer.window().request_redraw(); ui_state.renderer.window().request_redraw();
} }
ui_state.input.end_frame(); ui_state.input.end_frame();
} }
fn exit(&mut self) { fn exit(&mut self) {
self.state.exit(&mut self.rsc); self.state.exit(&mut self.rsc, &mut self.render);
}
}
pub trait RscIdx<Rsc> {
type Output;
fn get(self, rsc: &Rsc) -> &Self::Output;
fn get_mut(self, rsc: &mut Rsc) -> &mut Self::Output;
}
impl<State: 'static, I: RscIdx<DefaultRsc<State>>> std::ops::Index<I> for DefaultRsc<State> {
type Output = I::Output;
fn index(&self, index: I) -> &Self::Output {
index.get(self)
}
}
impl<State: 'static, I: RscIdx<DefaultRsc<State>>> std::ops::IndexMut<I> for DefaultRsc<State> {
fn index_mut(&mut self, index: I) -> &mut Self::Output {
index.get_mut(self)
}
}
impl<W: Widget, Rsc: UiRsc> RscIdx<Rsc> for WeakWidget<W> {
type Output = W;
fn get(self, rsc: &Rsc) -> &Self::Output {
&rsc.ui().widgets[self]
}
fn get_mut(self, rsc: &mut Rsc) -> &mut Self::Output {
&mut rsc.ui_mut().widgets[self]
}
}
impl<T: 'static, Rsc: HasWidgetState> RscIdx<Rsc> for WeakState<T> {
type Output = T;
fn get(self, rsc: &Rsc) -> &Self::Output {
rsc.widget_state().get(self)
}
fn get_mut(self, rsc: &mut Rsc) -> &mut Self::Output {
rsc.widget_state_mut().get_mut(self)
} }
} }

View File

@@ -1,4 +1,4 @@
use iris_core::{Ui, UiLimits, UiRenderNode}; use iris_core::{UiData, UiLimits, UiRenderNode, UiRenderState};
use pollster::FutureExt; use pollster::FutureExt;
use std::sync::Arc; use std::sync::Arc;
use wgpu::*; use wgpu::*;
@@ -17,8 +17,8 @@ pub struct UiRenderer {
} }
impl UiRenderer { impl UiRenderer {
pub fn update(&mut self, ui: &mut Ui) { pub fn update(&mut self, ui: &mut UiData, render: &mut UiRenderState) {
self.ui.update(&self.device, &self.queue, ui); self.ui.update(&self.device, &self.queue, ui, render);
} }
pub fn draw(&mut self) { pub fn draw(&mut self) {

View File

@@ -26,9 +26,9 @@ pub enum CursorSense {
pub struct CursorSenses(Vec<CursorSense>); pub struct CursorSenses(Vec<CursorSense>);
impl Event for CursorSenses { impl Event for CursorSenses {
type Data = CursorData; type Data<'a> = CursorData<'a>;
type State = SensorState; type State = SensorState;
fn should_run(&self, data: &Self::Data) -> Option<Self::Data> { fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
if let Some(sense) = should_run(self, &data.cursor, data.hover) { if let Some(sense) = should_run(self, &data.cursor, data.hover) {
let mut data = data.clone(); let mut data = data.clone();
data.sense = sense; data.sense = sense;
@@ -129,7 +129,7 @@ pub struct SensorState {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct CursorData { pub struct CursorData<'a> {
/// where this widget was hit /// where this widget was hit
pub pos: Vec2, pub pos: Vec2,
pub size: Vec2, pub size: Vec2,
@@ -138,20 +138,36 @@ pub struct CursorData {
pub cursor: CursorState, pub cursor: CursorState,
/// the first sense that triggered this /// the first sense that triggered this
pub sense: CursorSense, pub sense: CursorSense,
pub render: &'a UiRenderState,
} }
pub trait SensorUi<Rsc: HasEvents> { pub trait SensorUi {
fn run_sensors(&mut self, state: &mut Rsc::State, cursor: CursorState, window_size: Vec2); fn run_sensors<Rsc: HasEvents>(
&self,
rsc: &mut Rsc,
state: &mut Rsc::State,
cursor: CursorState,
window_size: Vec2,
);
} }
impl<Rsc: HasEvents> SensorUi<Rsc> for Rsc { impl SensorUi for UiRenderState {
fn run_sensors(&mut self, state: &mut Rsc::State, cursor: CursorState, window_size: Vec2) { fn run_sensors<Rsc: HasEvents>(
let layers = std::mem::take(&mut self.ui_mut().layers); &self,
let mut active = std::mem::take(&mut self.events_mut().get_type::<CursorSense>().active); rsc: &mut Rsc,
for layer in layers.indices().rev() { state: &mut Rsc::State,
cursor: CursorState,
window_size: Vec2,
) {
// in order to remove this take, need to store active list in UiRenderState somehow
// this would probably be done through a generic parameter that adds yet another rsc /
// state like thing, but local to render state, and is passed to UiRsc events so you can
// update it there?
let mut active = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().active);
for layer in self.layers.indices().rev() {
let mut sensed = false; let mut sensed = false;
for (id, sensor) in active.get_mut(&layer).into_flat_iter() { for (id, sensor) in active.get_mut(&layer).into_flat_iter() {
let shape = self.ui().active.get(id).unwrap().region; let shape = self.active.get(id).unwrap().region;
let region = shape.to_px(window_size); let region = shape.to_px(window_size);
let in_shape = cursor.exists && region.contains(cursor.pos); let in_shape = cursor.exists && region.contains(cursor.pos);
sensor.hover.update(in_shape); sensor.hover.update(in_shape);
@@ -171,15 +187,15 @@ impl<Rsc: HasEvents> SensorUi<Rsc> for Rsc {
// this does not have any meaning; // this does not have any meaning;
// might wanna set up Event to have a prepare stage // might wanna set up Event to have a prepare stage
sense: CursorSense::Hovering, sense: CursorSense::Hovering,
render: self,
}; };
self.run_event::<CursorSense>(*id, data, state); rsc.run_event::<CursorSense>(*id, data, state);
} }
if sensed { if sensed {
break; break;
} }
} }
self.events_mut().get_type::<CursorSense>().active = active; rsc.events_mut().get_type::<CursorSense>().active = active;
self.ui_mut().layers = layers;
} }
} }

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

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

View File

@@ -2,13 +2,13 @@ use iris_core::*;
use iris_macro::*; use iris_macro::*;
use std::sync::Arc; use std::sync::Arc;
use crate::default::{TaskCtx, Tasks}; use crate::default::{TaskCtx, TaskUpdate, Tasks};
pub trait Eventable<Rsc: HasEvents, Tag>: WidgetLike<Rsc, Tag> { pub trait Eventable<Rsc: HasEvents, Tag>: WidgetLike<Rsc, Tag> {
fn on<E: EventLike>( fn on<E: EventLike>(
self, self,
event: E, event: E,
f: impl WidgetEventFn<Rsc, <E::Event as Event>::Data, Self::Widget>, f: impl for<'a> WidgetEventFn<Rsc, <E::Event as Event>::Data<'a>, Self::Widget>,
) -> impl WidgetIdFn<Rsc, Self::Widget> { ) -> impl WidgetIdFn<Rsc, Self::Widget> {
move |rsc| { move |rsc| {
let id = self.add(rsc); let id = self.add(rsc);
@@ -30,24 +30,22 @@ impl<WL: WidgetLike<Rsc, Tag>, Rsc: HasEvents, Tag> Eventable<Rsc, Tag> for WL {
widget_trait! { widget_trait! {
pub trait TaskEventable<Rsc: HasEvents + HasTasks>; pub trait TaskEventable<Rsc: HasEvents + HasTasks>;
fn task_on<E: EventLike, F: AsyncWidgetEventFn<Rsc, <E::Event as Event>::Data, WL::Widget>>( fn task_on<'a, E: EventLike, F: AsyncWidgetEventFn<Rsc, WL::Widget>>(
self, self,
event: E, event: E,
f: F, f: F,
) -> impl WidgetIdFn<Rsc, WL::Widget> ) -> impl WidgetIdFn<Rsc, WL::Widget>
where <E::Event as Event>::Data: Send, where <E::Event as Event>::Data<'a>: Send,
for<'a> F::CallRefFuture<'a>: Send, for<'b> F::CallRefFuture<'b>: Send,
{ {
let f = Arc::new(f); let f = Arc::new(f);
move |rsc| { move |rsc| {
let id = self.add(rsc); let id = self.add(rsc);
rsc.register_event(id, event.into_event(), move |ctx, rsc| { rsc.register_event(id, event.into_event(), move |_, rsc| {
let data = ctx.data;
let f = f.clone(); let f = f.clone();
rsc.tasks_mut().spawn(async move |task| { rsc.tasks_mut().spawn(async move |task| {
f(AsyncEventIdCtx { f(AsyncEventIdCtx {
widget: id, widget: id,
data,
task, task,
}).await; }).await;
}); });
@@ -59,23 +57,31 @@ widget_trait! {
pub trait HasTasks: Sized + HasState + HasEvents { pub trait HasTasks: Sized + HasState + HasEvents {
fn tasks_mut(&mut self) -> &mut Tasks<Self>; 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, Data, W: ?Sized>: pub trait AsyncWidgetEventFn<Rsc: HasEvents, W: ?Sized>:
AsyncFn(AsyncEventIdCtx<Rsc, Data, W>) + Send + Sync + 'static AsyncFn(AsyncEventIdCtx<Rsc, W>) + Send + Sync + 'static
{ {
} }
impl< impl<Rsc: HasEvents, F: AsyncFn(AsyncEventIdCtx<Rsc, W>) + Send + Sync + 'static, W: ?Sized>
Rsc: HasEvents, AsyncWidgetEventFn<Rsc, W> for F
F: AsyncFn(AsyncEventIdCtx<Rsc, Data, W>) + Send + Sync + 'static,
Data,
W: ?Sized,
> AsyncWidgetEventFn<Rsc, Data, W> for F
{ {
} }
pub struct AsyncEventIdCtx<Rsc: HasEvents, Data, W: ?Sized> { pub struct AsyncEventIdCtx<Rsc: HasEvents, W: ?Sized> {
pub widget: WidgetRef<W>, pub widget: WeakWidget<W>,
pub data: Data, task: TaskCtx<Rsc>,
pub 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@ use crate::prelude::*;
use std::marker::PhantomData; use std::marker::PhantomData;
pub struct Span { pub struct Span {
pub children: Vec<WidgetHandle>, pub children: Vec<StrongWidget>,
pub dir: Dir, pub dir: Dir,
pub gap: f32, pub gap: f32,
} }
@@ -62,11 +62,11 @@ impl Span {
self self
} }
pub fn push(&mut self, w: WidgetHandle) { pub fn push(&mut self, w: StrongWidget) {
self.children.push(w); self.children.push(w);
} }
pub fn pop(&mut self) -> Option<WidgetHandle> { pub fn pop(&mut self) -> Option<StrongWidget> {
self.children.pop() self.children.pop()
} }
@@ -193,7 +193,7 @@ impl<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag>
} }
impl std::ops::Deref for Span { impl std::ops::Deref for Span {
type Target = Vec<WidgetHandle>; type Target = Vec<StrongWidget>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.children &self.children

View File

@@ -3,7 +3,7 @@ use std::marker::PhantomData;
use crate::prelude::*; use crate::prelude::*;
pub struct Stack { pub struct Stack {
pub children: Vec<WidgetHandle>, pub children: Vec<StrongWidget>,
pub size: StackSize, pub size: StackSize,
} }

View File

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

View File

@@ -51,7 +51,7 @@ impl<State, O, H: WidgetOption<State>> TextBuilder<State, O, H> {
} }
} }
impl<Rsc: HasUi, O> TextBuilder<Rsc, O> { impl<Rsc: UiRsc, O> TextBuilder<Rsc, O> {
pub fn hint<W: WidgetLike<Rsc, Tag>, Tag>( pub fn hint<W: WidgetLike<Rsc, Tag>, Tag>(
self, self,
hint: W, hint: W,
@@ -75,7 +75,7 @@ pub trait TextBuilderOutput<State>: Sized {
} }
pub struct TextOutput; pub struct TextOutput;
impl<Rsc: HasUi> TextBuilderOutput<Rsc> for TextOutput { impl<Rsc: UiRsc> TextBuilderOutput<Rsc> for TextOutput {
type Output = Text; type Output = Text;
fn run<H: WidgetOption<Rsc>>( fn run<H: WidgetOption<Rsc>>(
@@ -103,7 +103,7 @@ pub struct TextEditOutput {
mode: EditMode, mode: EditMode,
} }
impl<State: HasUi> TextBuilderOutput<State> for TextEditOutput { impl<State: UiRsc> TextBuilderOutput<State> for TextEditOutput {
type Output = TextEdit; type Output = TextEdit;
fn run<H: WidgetOption<State>>( fn run<H: WidgetOption<State>>(

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ use crate::prelude::*;
// these methods should "not require any context" (require unit) because they're in core // these methods should "not require any context" (require unit) because they're in core
widget_trait! { widget_trait! {
pub trait CoreWidget<Rsc: HasUi + 'static>; pub trait CoreWidget<Rsc: UiRsc + 'static>;
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Rsc, Pad> { fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Rsc, Pad> {
|state| Pad { |state| Pad {
@@ -26,7 +26,7 @@ widget_trait! {
fn label(self, label: impl Into<String>) -> impl WidgetIdFn<Rsc, WL::Widget> { fn label(self, label: impl Into<String>) -> impl WidgetIdFn<Rsc, WL::Widget> {
|state| { |state| {
let id = self.add(state); let id = self.add(state);
state.ui_mut().set_label(id, label.into()); state.ui_mut().widgets.set_label(id, label.into());
id id
} }
} }
@@ -125,9 +125,9 @@ widget_trait! {
|state| self.add(state) |state| self.add(state)
} }
fn set_ptr(self, ptr: WidgetRef<WidgetPtr>, state: &mut Rsc) { fn set_ptr(self, ptr: WeakWidget<WidgetPtr>, state: &mut Rsc) {
let id = self.add_strong(state); let id = self.add_strong(state);
state.ui_mut()[ptr].inner = Some(id); state.ui_mut().widgets[ptr].inner = Some(id);
} }
} }