diff --git a/core/src/attr.rs b/core/src/attr.rs index 4090896..47ab390 100644 --- a/core/src/attr.rs +++ b/core/src/attr.rs @@ -1,8 +1,8 @@ -use crate::{Ui, WidgetIdFn, WidgetLike, WidgetView}; +use crate::{Ui, WidgetIdFn, WidgetLike, WidgetRef}; pub trait WidgetAttr { type Input; - fn run(ui: &mut Ui, id: WidgetView, input: Self::Input); + fn run(ui: &mut Ui, id: WidgetRef, input: Self::Input); } pub trait Attrable { diff --git a/core/src/event.rs b/core/src/event.rs index 5d9b3e8..2a4cd6c 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -1,5 +1,9 @@ -use crate::{Ui, UiModule, WidgetId, WidgetView, util::HashMap}; -use std::{hash::Hash, rc::Rc}; +use crate::{Ui, UiModule, Widget, WidgetId, WidgetRef, util::HashMap}; +use std::{ + hash::Hash, + ops::{Index, IndexMut}, + rc::Rc, +}; pub trait Event: Sized { type Module: EventModule; @@ -13,12 +17,32 @@ pub struct EventCtx<'a, Ctx, Data> { } pub struct EventIdCtx<'a, Ctx, Data, W: ?Sized> { - pub id: WidgetView, + pub widget: WidgetRef, pub ui: &'a mut Ui, pub state: &'a mut Ctx, pub data: Data, } +impl<'a, Ctx, Data, W2, W: Widget> Index> for EventIdCtx<'a, Ctx, Data, W2> { + type Output = W; + + fn index(&self, index: WidgetRef) -> &Self::Output { + &self.ui[index] + } +} + +impl<'a, Ctx, Data, W2, W: Widget> IndexMut> for EventIdCtx<'a, Ctx, Data, W2> { + fn index_mut(&mut self, index: WidgetRef) -> &mut Self::Output { + &mut self.ui[index] + } +} + +impl<'a, Ctx, Data, W: Widget> EventIdCtx<'a, Ctx, Data, W> { + pub fn widget(&mut self) -> &mut W { + &mut self.ui[self.widget] + } +} + pub trait EventFn: Fn(EventCtx) + 'static {} impl) + 'static, Ctx, Data> EventFn for F {} @@ -127,7 +151,7 @@ impl Ui { pub fn run_event( &mut self, ctx: &mut Ctx, - id: WidgetView, + id: WidgetRef, event: E, data: E::Data, ) { diff --git a/core/src/primitive/color.rs b/core/src/primitive/color.rs index 345ef0c..1681c2b 100644 --- a/core/src/primitive/color.rs +++ b/core/src/primitive/color.rs @@ -1,9 +1,9 @@ -#![allow(clippy::multiple_bound_locations)] +use std::marker::Destruct; /// stored in linear for sane manipulation #[repr(C)] #[derive(Clone, Copy, Hash, PartialEq, Eq, bytemuck::Zeroable, Debug)] -pub struct Color { +pub struct Color { pub r: T, pub g: T, pub b: T, @@ -56,23 +56,47 @@ pub trait ColorNum { const MAX: Self; } -impl Color { - pub fn mul_rgb(self, amt: impl F32Conversion) -> Self { +macro_rules! map_rgb { + ($x:ident,$self:ident, $e:tt) => { + #[allow(unused_braces)] + Self { + r: { + let $x = $self.r; + $e + }, + g: { + let $x = $self.g; + $e + }, + b: { + let $x = $self.b; + $e + }, + a: $self.a, + } + }; +} + +impl Color +where + Self: const Destruct, +{ + pub const fn mul_rgb(self, amt: impl const F32Conversion) -> Self { let amt = amt.to(); - self.map_rgb(|x| T::from(x.to() * amt)) + map_rgb!(x, self, { T::from(x.to() * amt) }) } - pub fn add_rgb(self, amt: impl F32Conversion) -> Self { + pub const fn add_rgb(self, amt: impl const F32Conversion) -> Self { let amt = amt.to(); - self.map_rgb(|x| T::from(x.to() + amt)) + map_rgb!(x, self, { T::from(x.to() + amt) }) } - pub fn darker(self, amt: f32) -> Self { + pub const fn darker(self, amt: f32) -> Self { self.mul_rgb(1.0 - amt) } - pub fn brighter(self, amt: f32) -> Self { - self.map_rgb(|x| { + pub const fn brighter(self, amt: f32) -> Self { + map_rgb!(x, self, { let x = x.to(); T::from(x + (1.0 - x) * amt) }) diff --git a/core/src/widget/handle.rs b/core/src/widget/handle.rs index 535d291..9f425ee 100644 --- a/core/src/widget/handle.rs +++ b/core/src/widget/handle.rs @@ -7,12 +7,9 @@ use crate::{ pub type WidgetId = SlotId; -/// An identifier for a widget that can index a UI to get the associated widget. -/// It should always remain valid; it keeps a ref count and removes the widget from the UI if all -/// references are dropped. -/// -/// W does not need to implement widget so that AnyWidget is valid; -/// Instead, add generic bounds on methods that take an ID if they need specific data. +/// An identifier for a widget that can index a UI or event ctx to get it. +/// This is a strong handle that does not impl Clone, and when it is dropped, +/// a signal is sent to the owning UI to clean up the resources. /// /// TODO: ergonomic clones when they get put in rust-analyzer & don't cause ICEs? pub struct WidgetHandle { @@ -22,13 +19,18 @@ pub struct WidgetHandle { ty: *const W, } -pub struct WidgetView { +/// A weak handle to a widget. +/// Will not keep it alive, but can still be used for indexing like WidgetHandle. +pub struct WidgetRef { pub(super) id: WidgetId, #[allow(unused)] ty: *const W, } -pub type WidgetHandles = (WidgetHandle, WidgetView); +pub struct WidgetHandles { + pub h: WidgetHandle, + pub r: WidgetRef, +} impl> WidgetHandle { pub fn any(self) -> WidgetHandle { @@ -59,18 +61,18 @@ impl WidgetHandle { self.counter.refs() } - pub fn weak(&self) -> WidgetView { + pub fn weak(&self) -> WidgetRef { let Self { ty, id, .. } = *self; - WidgetView { ty, id } + WidgetRef { ty, id } } pub fn handles(self) -> WidgetHandles { - let weak = self.weak(); - (self, weak) + let r = self.weak(); + WidgetHandles { h: self, r } } } -impl WidgetView { +impl WidgetRef { pub fn id(&self) -> WidgetId { self.id } @@ -99,7 +101,7 @@ impl IdLike for WidgetHandle { } } -impl IdLike for WidgetView { +impl IdLike for WidgetRef { type Widget = W; fn id(&self) -> WidgetId { self.id @@ -107,15 +109,15 @@ impl IdLike for WidgetView { } impl, U: ?Sized> CoerceUnsized> for WidgetHandle {} -impl, U: ?Sized> CoerceUnsized> for WidgetView {} +impl, U: ?Sized> CoerceUnsized> for WidgetRef {} -impl Clone for WidgetView { +impl Clone for WidgetRef { fn clone(&self) -> Self { *self } } -impl Copy for WidgetView {} -impl PartialEq for WidgetView { +impl Copy for WidgetRef {} +impl PartialEq for WidgetRef { fn eq(&self, other: &Self) -> bool { self.id == other.id } diff --git a/core/src/widget/widgets.rs b/core/src/widget/widgets.rs index 55dfb03..79366ea 100644 --- a/core/src/widget/widgets.rs +++ b/core/src/widget/widgets.rs @@ -32,7 +32,7 @@ impl Widgets { /// get_dyn but dynamic borrow checking of widgets /// lets you do recursive (tree) operations, like the painter does - pub fn get_dyn_dynamic(&self, id: WidgetId) -> WidgetWrapper<'_> { + pub(crate) fn get_dyn_dynamic(&self, id: WidgetId) -> WidgetWrapper<'_> { // SAFETY: must guarantee no other mutable references to this widget exist // done through the borrow variable #[allow(mutable_transmutes)] diff --git a/src/bin/test/main.rs b/src/bin/test/main.rs index ce72f30..5d22bc4 100644 --- a/src/bin/test/main.rs +++ b/src/bin/test/main.rs @@ -10,7 +10,7 @@ fn main() { } pub struct Client { - info: WidgetView, + info: WidgetRef, } event_ctx!(Client); @@ -56,24 +56,24 @@ impl DefaultAppState for Client { let add_button = rect(Color::LIME) .radius(30) - .on(CursorSense::click(), move |ctx| { + .on(CursorSense::click(), move |mut ctx| { let child = ctx .ui .add(image(include_bytes!("assets/sungals.png")).center()); - ctx.ui[span_add.1].children.push(child); + ctx[span_add.r].children.push(child); }) .sized((150, 150)) .align(Align::BOT_RIGHT); let del_button = rect(Color::RED) .radius(30) - .on(CursorSense::click(), move |ctx| { - ctx.ui[span_add.1].children.pop(); + .on(CursorSense::click(), move |mut ctx| { + ctx[span_add.r].children.pop(); }) .sized((150, 150)) .align(Align::BOT_LEFT); - let span_add_test = (span_add.0, add_button, del_button).stack().add(ui); + let span_add_test = (span_add.h, add_button, del_button).stack().add(ui); let btext = |content| wtext(content).size(30); @@ -99,14 +99,14 @@ impl DefaultAppState for Client { .add(ui); let texts = Span::empty(Dir::DOWN).gap(10).handles(ui); - let msg_area = texts.0.scroll().masked().background(rect(Color::SKY)); + let msg_area = texts.h.scroll().masked().background(rect(Color::SKY)); let add_text = wtext("add") .editable(false) .text_align(Align::LEFT) .size(30) .attr::(()) .on(Submit, move |ctx| { - let content = ctx.id.edit(ctx.ui).take(); + let content = ctx.widget.edit(ctx.ui).take(); let text = wtext(content) .editable(false) .size(30) @@ -114,7 +114,7 @@ impl DefaultAppState for Client { .wrap(true) .attr::(()); let msg_box = text.background(rect(Color::WHITE.darker(0.5))).add(ctx.ui); - ctx.ui[texts.1].children.push(msg_box); + ctx.ui[texts.r].children.push(msg_box); }) .handles(ui); let text_edit_scroll = ( @@ -122,10 +122,10 @@ impl DefaultAppState for Client { ( Rect::new(Color::WHITE.darker(0.9)), ( - add_text.0.width(rest(1)), + add_text.h.width(rest(1)), Rect::new(Color::GREEN) .on(CursorSense::click(), move |ctx| { - ctx.ui.run_event(ctx.state, add_text.1, Submit, ()); + ctx.ui.run_event(ctx.state, add_text.r, Submit, ()); }) .sized((40, 40)), ) @@ -148,34 +148,34 @@ impl DefaultAppState for Client { let i = vec.len(); if vec.is_empty() { vec.push(None); - ui[main.1].set(to); + ui[main.r].set(to); } else { vec.push(Some(to)); } let vals = vals.clone(); let rect = rect(color) - .on(CursorSense::click(), move |ctx| { + .on(CursorSense::click(), move |mut ctx| { let (prev, vec) = &mut *vals.borrow_mut(); if let Some(h) = vec[i].take() { - vec[*prev] = ctx.ui[main.1].replace(h); + vec[*prev] = ctx[main.r].replace(h); *prev = i; } - ctx.ui[ctx.id].color = color.darker(0.3); + ctx.widget().color = color.darker(0.3); }) .on( CursorSense::HoverStart | CursorSense::unclick(), - move |ctx| { - ctx.ui[ctx.id].color = color.brighter(0.2); + move |mut ctx| { + ctx.widget().color = color.brighter(0.2); }, ) - .on(CursorSense::HoverEnd, move |ctx| { - ctx.ui[ctx.id].color = color; + .on(CursorSense::HoverEnd, move |mut ctx| { + ctx.widget().color = color; }); (rect, wtext(label).size(30).text_align(Align::CENTER)).stack() }; let tabs = ( - switch_button(Color::RED, pad_test.0, "pad"), + switch_button(Color::RED, pad_test.h, "pad"), switch_button(Color::GREEN, span_test, "span"), switch_button(Color::BLUE, span_add_test, "image span"), switch_button(Color::MAGENTA, text_test, "text layout"), @@ -188,13 +188,13 @@ impl DefaultAppState for Client { .span(Dir::RIGHT); let info = wtext("").handles(ui); - let info_sect = info.0.pad(10).align(Align::RIGHT); + let info_sect = info.h.pad(10).align(Align::RIGHT); - ((tabs.height(40), main.0.pad(10)).span(Dir::DOWN), info_sect) + ((tabs.height(40), main.h.pad(10)).span(Dir::DOWN), info_sect) .stack() .set_root(ui); - Self { info: info.1 } + Self { info: info.r } } fn window_event(&mut self, _: WindowEvent, ui: &mut Ui, state: &UiState) { diff --git a/src/default/attr.rs b/src/default/attr.rs index acae2c7..d8f6e8b 100644 --- a/src/default/attr.rs +++ b/src/default/attr.rs @@ -5,9 +5,9 @@ use winit::dpi::{LogicalPosition, LogicalSize}; pub struct Selector; impl WidgetAttr for Selector { - type Input = WidgetView; + type Input = WidgetRef; - fn run(ui: &mut Ui, container: WidgetView, id: Self::Input) { + fn run(ui: &mut Ui, container: WidgetRef, id: Self::Input) { ui.register_event(&container, CursorSense::click_or_drag(), move |mut ctx| { let ui = ctx.ui; let region = ui.window_region(&id).unwrap(); @@ -25,14 +25,14 @@ pub struct Selectable; impl WidgetAttr for Selectable { type Input = (); - fn run(ui: &mut Ui, id: WidgetView, _: Self::Input) { + fn run(ui: &mut Ui, id: WidgetRef, _: Self::Input) { ui.register_event(&id, CursorSense::click_or_drag(), move |ctx| { select(ctx.ui, id, ctx.state, ctx.data); }); } } -fn select(ui: &mut Ui, id: WidgetView, state: &mut UiState, data: CursorData) { +fn select(ui: &mut Ui, id: WidgetRef, state: &mut UiState, data: CursorData) { let now = Instant::now(); let recent = (now - state.last_click) < Duration::from_millis(300); state.last_click = now; diff --git a/src/default/mod.rs b/src/default/mod.rs index 6ebce72..6dc1202 100644 --- a/src/default/mod.rs +++ b/src/default/mod.rs @@ -30,7 +30,7 @@ pub struct DefaultState { pub struct UiState { pub renderer: UiRenderer, pub input: Input, - pub focus: Option>, + pub focus: Option>, pub clipboard: Clipboard, pub window: Arc, pub ime: usize, diff --git a/src/event.rs b/src/event.rs index 9bb3847..e03a4d5 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,5 +1,31 @@ use crate::prelude::*; +pub mod eventable { + use super::*; + + widget_trait! { + pub trait Eventable; + fn on( + self, + event: E, + f: impl WidgetEventFn, + ) -> impl WidgetIdFn { + move |ui| { + let id = self.handles(ui); + ui.register_event(&id.r, event, move |ctx| { + f(EventIdCtx { + widget: id.r, + state: ctx.state, + data: ctx.data, + ui: ctx.ui, + }); + }); + id.h + } + } + } +} + // TODO: naming in here is a bit weird like eventable #[macro_export] macro_rules! event_ctx { @@ -31,29 +57,3 @@ macro_rules! event_ctx { }; } pub use event_ctx; - -pub mod eventable { - use super::*; - - widget_trait! { - pub trait Eventable; - fn on( - self, - event: E, - f: impl WidgetEventFn, - ) -> impl WidgetIdFn { - move |ui| { - let id = self.handles(ui); - ui.register_event(&id.1, event, move |ctx| { - f(EventIdCtx { - id: id.1, - state: ctx.state, - data: ctx.data, - ui: ctx.ui, - }); - }); - id.0 - } - } - } -} diff --git a/src/widget/sense.rs b/src/widget/sense.rs index f39f64f..4b54c32 100644 --- a/src/widget/sense.rs +++ b/src/widget/sense.rs @@ -6,7 +6,7 @@ use std::{ }; #[derive(Clone, Copy, PartialEq)] -pub enum Button { +pub enum CursorButton { Left, Right, Middle, @@ -14,9 +14,9 @@ pub enum Button { #[derive(Clone, Copy, PartialEq)] pub enum CursorSense { - PressStart(Button), - Pressing(Button), - PressEnd(Button), + PressStart(CursorButton), + Pressing(CursorButton), + PressEnd(CursorButton), HoverStart, Hovering, HoverEnd, @@ -27,16 +27,16 @@ pub struct CursorSenses(Vec); impl CursorSense { pub fn click() -> Self { - Self::PressStart(Button::Left) + Self::PressStart(CursorButton::Left) } pub fn click_or_drag() -> CursorSenses { - Self::click() | Self::Pressing(Button::Left) + Self::click() | Self::Pressing(CursorButton::Left) } pub fn unclick() -> Self { - Self::PressEnd(Button::Left) + Self::PressEnd(CursorButton::Left) } pub fn is_dragging(&self) -> bool { - matches!(self, CursorSense::Pressing(Button::Left)) + matches!(self, CursorSense::Pressing(CursorButton::Left)) } } @@ -56,11 +56,11 @@ pub struct CursorButtons { } impl CursorButtons { - pub fn select(&self, button: &Button) -> &ActivationState { + pub fn select(&self, button: &CursorButton) -> &ActivationState { match button { - Button::Left => &self.left, - Button::Right => &self.right, - Button::Middle => &self.middle, + CursorButton::Left => &self.left, + CursorButton::Right => &self.right, + CursorButton::Middle => &self.middle, } } diff --git a/src/widget/trait_fns.rs b/src/widget/trait_fns.rs index 32a4492..f70b26e 100644 --- a/src/widget/trait_fns.rs +++ b/src/widget/trait_fns.rs @@ -89,7 +89,7 @@ widget_trait! { move |ui| { Scroll::new(self.add(ui), Axis::Y) .on(CursorSense::Scroll, |ctx| { - let s = &mut ctx.ui[ctx.id]; + let s = &mut ctx.ui[ctx.widget]; s.scroll(ctx.data.scroll_delta.y * 50.0); }) .add(ui)