fix reactivity 😭 + visual widget counter

This commit is contained in:
2025-08-24 22:02:50 -04:00
parent 6bb6db32a6
commit 74d01d14d4
9 changed files with 119 additions and 47 deletions

View File

@@ -1,8 +1,8 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Text { pub struct Text {
content: String, pub content: String,
attrs: TextAttrs, pub attrs: TextAttrs,
} }
impl Text { impl Text {

View File

@@ -1,10 +1,13 @@
use super::*; use super::*;
use crate::layout::{Dir, UiPos, UiRegion, Vec2, WidgetArrLike, WidgetFnRet, WidgetLike}; use crate::layout::{
Dir, UiPos, UiRegion, Vec2, WidgetArrLike, WidgetFnRet, WidgetIdFnRet, WidgetLike,
};
pub trait CoreWidget<W: 'static, Ctx: 'static, Tag> { pub trait CoreWidget<W, Ctx: 'static, Tag> {
fn pad(self, padding: impl Into<Padding>) -> WidgetFnRet!(Regioned, Ctx); fn pad(self, padding: impl Into<Padding>) -> WidgetFnRet!(Regioned, Ctx);
fn center(self, size: impl Into<Vec2>) -> WidgetFnRet!(Regioned, Ctx); fn center(self, size: impl Into<Vec2>) -> WidgetFnRet!(Regioned, Ctx);
fn region(self, region: UiRegion) -> WidgetFnRet!(Regioned, Ctx); fn region(self, region: UiRegion) -> WidgetFnRet!(Regioned, Ctx);
fn label(self, label: impl Into<String>) -> WidgetIdFnRet!(W, Ctx);
} }
impl<W: WidgetLike<Ctx, Tag>, Ctx: 'static, Tag> CoreWidget<W::Widget, Ctx, Tag> for W { impl<W: WidgetLike<Ctx, Tag>, Ctx: 'static, Tag> CoreWidget<W::Widget, Ctx, Tag> for W {
@@ -28,6 +31,14 @@ impl<W: WidgetLike<Ctx, Tag>, Ctx: 'static, Tag> CoreWidget<W::Widget, Ctx, Tag>
inner: self.add(ui).erase_type(), inner: self.add(ui).erase_type(),
} }
} }
fn label(self, label: impl Into<String>) -> WidgetIdFnRet!(W::Widget, Ctx) {
|ui| {
let id = self.add(ui);
ui.set_label(&id, label.into());
id
}
}
} }
pub trait CoreWidgetArr<const LEN: usize, Ctx: 'static, Tag> { pub trait CoreWidgetArr<const LEN: usize, Ctx: 'static, Tag> {

View File

@@ -79,6 +79,7 @@ impl<W> Drop for WidgetId<W> {
} }
pub struct IdTag; pub struct IdTag;
pub struct IdFnTag;
// pub trait WidgetIdFn<W, Ctx> = FnOnce(&mut Ui<Ctx>) -> WidgetId<W>; // pub trait WidgetIdFn<W, Ctx> = FnOnce(&mut Ui<Ctx>) -> WidgetId<W>;
macro_rules! WidgetIdFnRet { macro_rules! WidgetIdFnRet {
@@ -126,14 +127,14 @@ impl<F: FnOnce(&mut Ui<Ctx>) -> W, W: Widget<Ctx>, Ctx> Idable<Ctx, FnTag> for F
} }
} }
impl<W: 'static, Ctx> WidgetLike<Ctx, FnTag> for WidgetId<W> { impl<W: 'static, Ctx> WidgetLike<Ctx, IdTag> for WidgetId<W> {
type Widget = W; type Widget = W;
fn add(self, _: &mut Ui<Ctx>) -> WidgetId<W> { fn add(self, _: &mut Ui<Ctx>) -> WidgetId<W> {
self self
} }
} }
impl<W: 'static, F: FnOnce(&mut Ui<Ctx>) -> WidgetId<W>, Ctx> WidgetLike<Ctx, IdTag> for F { impl<W: 'static, F: FnOnce(&mut Ui<Ctx>) -> WidgetId<W>, Ctx> WidgetLike<Ctx, IdFnTag> for F {
type Widget = W; type Widget = W;
fn add(self, ui: &mut Ui<Ctx>) -> WidgetId<W> { fn add(self, ui: &mut Ui<Ctx>) -> WidgetId<W> {
self(ui) self(ui)

View File

@@ -6,7 +6,7 @@ use crate::{
WidgetInstance, Widgets, WidgetInstance, Widgets,
}, },
render::{Primitive, PrimitiveHandle, Primitives}, render::{Primitive, PrimitiveHandle, Primitives},
util::Id, util::{HashMap, Id},
}; };
struct State { struct State {
@@ -25,6 +25,7 @@ pub struct Painter<'a, Ctx: 'static> {
pub(super) primitives: &'a mut Primitives, pub(super) primitives: &'a mut Primitives,
textures: &'a mut Textures, textures: &'a mut Textures,
text: &'a mut TextData, text: &'a mut TextData,
labels: &'a HashMap<Id, String>,
screen_size: Vec2, screen_size: Vec2,
/// state of what's currently being drawn /// state of what's currently being drawn
state: State, state: State,
@@ -41,6 +42,7 @@ impl<'a, Ctx> Painter<'a, Ctx> {
text: &'a mut TextData, text: &'a mut TextData,
textures: &'a mut Textures, textures: &'a mut Textures,
screen_size: Vec2, screen_size: Vec2,
labels: &'a HashMap<Id, String>,
) -> Self { ) -> Self {
Self { Self {
widgets: nodes, widgets: nodes,
@@ -51,6 +53,7 @@ impl<'a, Ctx> Painter<'a, Ctx> {
text, text,
textures, textures,
screen_size, screen_size,
labels,
state: State { state: State {
region: UiRegion::full(), region: UiRegion::full(),
children: Vec::new(), children: Vec::new(),
@@ -123,7 +126,7 @@ impl<'a, Ctx> Painter<'a, Ctx> {
id, id,
WidgetInstance { WidgetInstance {
region, region,
primitives: (start_i..end_i).into(), span: start_i..end_i,
children: child_state.children, children: child_state.children,
parent: child_state.parent, parent: child_state.parent,
}, },
@@ -168,21 +171,51 @@ impl<'a, Ctx> Painter<'a, Ctx> {
} }
let instance = self.free(id); let instance = self.free(id);
self.state.id = instance.parent; self.state.id = instance.parent;
self.primitives.prepare(instance.primitives.into()); self.primitives.prepare(instance.span.clone());
self.draw_raw_at(id, instance.region); self.draw_raw_at(id, instance.region);
let delta = self.primitives.apply(instance.primitives.into()); let start = instance.span.start;
if let Some(parent) = self.state.id.take() { let delta = self.primitives.apply(instance.span);
self.shift_end(parent, delta); if delta != 0
&& let Some(parent) = self.state.id.take()
{
self.shift_parent(parent, start, delta);
} }
} }
fn shift_end(&mut self, id: Id, delta: isize) { fn shift_parent(&mut self, parent: Id, start: usize, delta: isize) {
let instance = self.active.widgets.get_mut(&id).unwrap(); let instance = self.active.widgets.get_mut(&parent).unwrap();
let end = &mut instance.primitives.end; let end = &mut instance.span.end;
*end = end.strict_add_signed(delta); *end = end.strict_add_signed(delta);
if let Some(parent) = &instance.parent { // ids are supposed to be unique so theoretically no cloning is needed
let parent = parent.duplicate(); // leaving for now for testing
self.shift_end(parent, delta); let parent = instance.parent.as_ref().map(|p| p.duplicate());
for child in instance
.children
.iter()
.map(|id| id.duplicate())
.collect::<Vec<_>>()
{
self.shift_child(&child, start, delta);
}
if let Some(parent) = parent {
self.shift_parent(parent, start, delta);
}
}
fn shift_child(&mut self, child: &Id, start: usize, delta: isize) {
let instance = self.active.widgets.get_mut(child).unwrap();
if instance.span.start <= start {
return;
}
instance.span.start = instance.span.start.strict_add_signed(delta);
instance.span.end = instance.span.end.strict_add_signed(delta);
for child in instance
.children
.iter()
.map(|id| id.duplicate())
.collect::<Vec<_>>()
{
self.shift_child(&child, start, delta);
} }
} }

View File

@@ -10,14 +10,14 @@ use crate::{
}; };
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
ops::{Index, IndexMut}, ops::{Index, IndexMut, Range},
range::Range,
sync::mpsc::{Receiver, Sender, channel}, sync::mpsc::{Receiver, Sender, channel},
}; };
pub struct Ui<Ctx> { pub struct Ui<Ctx> {
base: Option<WidgetId>, base: Option<WidgetId>,
widgets: Widgets<Ctx>, widgets: Widgets<Ctx>,
labels: HashMap<Id, String>,
updates: Vec<WidgetId>, updates: Vec<WidgetId>,
recv: Receiver<Id>, recv: Receiver<Id>,
send: Sender<Id>, send: Sender<Id>,
@@ -46,6 +46,11 @@ impl<Ctx> Ui<Ctx> {
w.add(self) w.add(self)
} }
/// useful for debugging
pub fn set_label<W>(&mut self, id: &WidgetId<W>, label: String) {
self.labels.insert(id.id.duplicate(), label);
}
pub fn add_widget<W: Widget<Ctx>>(&mut self, w: W) -> WidgetId<W> { pub fn add_widget<W: Widget<Ctx>>(&mut self, w: W) -> WidgetId<W> {
self.push(w) self.push(w)
} }
@@ -56,8 +61,8 @@ impl<Ctx> Ui<Ctx> {
id id
} }
pub fn set<W: Widget<Ctx>>(&mut self, i: &WidgetId<W>, w: W) { pub fn set<W: Widget<Ctx>>(&mut self, id: &WidgetId<W>, w: W) {
self.widgets.insert(i.id.duplicate(), w); self.widgets.insert(id.id.duplicate(), w);
} }
pub fn set_base<Tag>(&mut self, w: impl WidgetLike<Ctx, Tag>) { pub fn set_base<Tag>(&mut self, w: impl WidgetLike<Ctx, Tag>) {
@@ -108,6 +113,7 @@ impl<Ctx> Ui<Ctx> {
&mut self.text, &mut self.text,
&mut self.textures, &mut self.textures,
self.size, self.size,
&self.labels,
); );
if let Some(base) = &self.base { if let Some(base) = &self.base {
painter.draw(base); painter.draw(base);
@@ -131,6 +137,7 @@ impl<Ctx> Ui<Ctx> {
where where
Ctx: 'static, Ctx: 'static,
{ {
self.free();
let mut painter = Painter::new( let mut painter = Painter::new(
&self.widgets, &self.widgets,
&mut self.primitives, &mut self.primitives,
@@ -140,6 +147,7 @@ impl<Ctx> Ui<Ctx> {
&mut self.text, &mut self.text,
&mut self.textures, &mut self.textures,
self.size, self.size,
&self.labels,
); );
for id in self.updates.drain(..) { for id in self.updates.drain(..) {
painter.redraw(&id.id); painter.redraw(&id.id);
@@ -149,6 +157,7 @@ impl<Ctx> Ui<Ctx> {
/// free any resources that don't have references anymore /// free any resources that don't have references anymore
fn free(&mut self) { fn free(&mut self) {
for id in self.recv.try_iter() { for id in self.recv.try_iter() {
self.labels.remove(&id);
self.widgets.delete(id); self.widgets.delete(id);
} }
self.textures.free(); self.textures.free();
@@ -244,6 +253,7 @@ impl<Ctx: 'static> Default for Ui<Ctx> {
Self { Self {
base: Default::default(), base: Default::default(),
widgets: Widgets::new(), widgets: Widgets::new(),
labels: Default::default(),
updates: Default::default(), updates: Default::default(),
primitives: Default::default(), primitives: Default::default(),
textures: Textures::new(), textures: Textures::new(),
@@ -260,7 +270,7 @@ impl<Ctx: 'static> Default for Ui<Ctx> {
pub struct WidgetInstance { pub struct WidgetInstance {
pub region: UiRegion, pub region: UiRegion,
pub primitives: Range<usize>, pub span: Range<usize>,
pub children: Vec<Id>, pub children: Vec<Id>,
pub parent: Option<Id>, pub parent: Option<Id>,
} }

View File

@@ -3,7 +3,6 @@
#![feature(const_trait_impl)] #![feature(const_trait_impl)]
#![feature(const_from)] #![feature(const_from)]
#![feature(map_try_insert)] #![feature(map_try_insert)]
#![feature(new_range_api)]
pub mod core; pub mod core;
pub mod layout; pub mod layout;

View File

@@ -23,6 +23,9 @@ impl GpuTextures {
TextureUpdate::Free(i) => self.free(i), TextureUpdate::Free(i) => self.free(i),
} }
} }
// if changed {
// println!("{}", self.views.len());
// }
changed changed
} }
fn set(&mut self, i: u32, image: &DynamicImage) { fn set(&mut self, i: u32, image: &DynamicImage) {

View File

@@ -26,8 +26,8 @@ impl ApplicationHandler for App {
let window = event_loop let window = event_loop
.create_window(Window::default_attributes()) .create_window(Window::default_attributes())
.unwrap(); .unwrap();
let (ui, ids) = Client::create_ui(); let (ui, cui) = Client::create_ui();
let client = Client::new(window.into(), ids); let client = Client::new(window.into(), cui);
self.client = Some((client, ui)); self.client = Some((client, ui));
} }
} }

View File

@@ -18,15 +18,16 @@ pub fn main() {
pub struct Client { pub struct Client {
renderer: Renderer, renderer: Renderer,
input: Input, input: Input,
ui: UiIds, ui: ClientUi,
} }
pub struct UiIds { pub struct ClientUi {
span_add: WidgetId<Span>, info: WidgetId<Text>,
old_num: usize,
} }
impl Client { impl Client {
pub fn create_ui() -> (Ui<Self>, UiIds) { pub fn create_ui() -> (Ui<Self>, ClientUi) {
let mut ui = Ui::new(); let mut ui = Ui::new();
let span_add = ui.id(); let span_add = ui.id();
let rect = Rect { let rect = Rect {
@@ -109,10 +110,15 @@ impl Client {
) )
.span(Dir::RIGHT, [1, 1, 1]), .span(Dir::RIGHT, [1, 1, 1]),
); );
let test_button = Rect::new(Color::PURPLE) let s = span_add.clone();
let add_button = Rect::new(Color::LIME)
.radius(30) .radius(30)
.on(Sense::PressStart, move |ctx| { .on(Sense::PressStart, move |ctx| {
println!("{}", ctx.ui.num_widgets()); let child = ctx
.ui
.add(image(include_bytes!("assets/sungals.png")))
.erase_type();
ctx.ui[&s].children.push((child, ratio(1)));
}) })
.region( .region(
UiPos::corner(Corner::BotRight) UiPos::corner(Corner::BotRight)
@@ -131,22 +137,35 @@ impl Client {
.expand((150, 150)) .expand((150, 150))
.shifted((75, -75)), .shifted((75, -75)),
); );
let info = ui.add(text(""));
let info_sect = info.clone().region(
UiPos::corner(Corner::TopRight)
.expand((150, 150))
.shifted((-75, 0)),
);
ui.set_base( ui.set_base(
( (
tabs, tabs.label("tabs"),
(pad_test.pad(10).id(&main), test_button, del_button).stack(), (
pad_test.pad(10).id(&main),
add_button.label("add button"),
del_button.label("del button"),
info_sect.label("info sect"),
) )
.span(Dir::DOWN, [fixed(40), ratio(1)]), .stack().label("main stack"),
)
.span(Dir::DOWN, [fixed(40), ratio(1)]).label("root"),
); );
(ui, UiIds { span_add }) (ui, ClientUi { info, old_num: 0 })
} }
pub fn new(window: Arc<Window>, ui: UiIds) -> Self { pub fn new(window: Arc<Window>, ui: ClientUi) -> Self {
let renderer = Renderer::new(window); let renderer = Renderer::new(window);
Self { Self {
renderer, renderer,
ui,
input: Input::default(), input: Input::default(),
ui,
} }
} }
@@ -166,17 +185,13 @@ impl Client {
ui.resize((size.width, size.height)); ui.resize((size.width, size.height));
self.renderer.resize(&size) self.renderer.resize(&size)
} }
WindowEvent::KeyboardInput { event, .. } => {
if event.state.is_pressed() {
let child = ui
.add(image(include_bytes!("assets/sungals.png")))
.erase_type();
ui[&self.ui.span_add].children.push((child, ratio(1)));
self.renderer.window().request_redraw();
}
}
_ => (), _ => (),
} }
let num = ui.num_widgets();
if num != self.ui.old_num {
ui[&self.ui.info].content = format!("widgets: {}", num);
self.ui.old_num = num;
}
if ui.needs_redraw() { if ui.needs_redraw() {
self.renderer.window().request_redraw(); self.renderer.window().request_redraw();
} }