From e4171fdb28955a52d8e22c2d1e246c51fbef05ae Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Jan 2025 14:29:47 +0000 Subject: [PATCH 1/8] Move fn LayoutExt::id to WidgetCore; revise doc --- crates/kas-core/src/core/layout.rs | 36 ++++++++++++-------- crates/kas-core/src/core/widget.rs | 17 +++++---- crates/kas-core/src/core/widget_id.rs | 12 +++++-- crates/kas-core/src/event/cx/config.rs | 5 ++- crates/kas-core/src/popup.rs | 2 +- crates/kas-core/src/runner/window.rs | 3 +- crates/kas-macros/src/make_layout.rs | 4 +-- crates/kas-macros/src/widget.rs | 8 ++--- crates/kas-widgets/src/adapt/adapt_events.rs | 2 +- 9 files changed, 55 insertions(+), 34 deletions(-) diff --git a/crates/kas-core/src/core/layout.rs b/crates/kas-core/src/core/layout.rs index c56f731b9..9afb3331c 100644 --- a/crates/kas-core/src/core/layout.rs +++ b/crates/kas-core/src/core/layout.rs @@ -234,18 +234,34 @@ pub trait Tile: Layout { unimplemented!() // make rustdoc show that this is a provided method } - /// Get the widget's identifier + /// Get a reference to the widget's identifier /// - /// Note that the default-constructed [`Id`] is *invalid*: any - /// operations on this value will cause a panic. A valid identifier is - /// assigned when the widget is configured (immediately before calling - /// [`Events::configure`]). + /// The widget identifier is assigned when the widget is configured (see + /// [`Events::configure`] and [`Events::configure_recurse`]). In case the + /// [`Id`] is accessed before this, it will be [invalid](Id#invalid-state). + /// The identifier *may* change when widgets which are descendants of some + /// dynamic layout are reconfigured. /// /// This method is implemented by the `#[widget]` macro. fn id_ref(&self) -> &Id { unimplemented!() // make rustdoc show that this is a provided method } + /// Get the widget's identifier + /// + /// This method returns a [`Clone`] of [`Self::id_ref`]. Since cloning an + /// `Id` is [very cheap](Id#representation), this can mostly be ignored. + /// + /// The widget identifier is assigned when the widget is configured (see + /// [`Events::configure`] and [`Events::configure_recurse`]). In case the + /// [`Id`] is accessed before this, it will be [invalid](Id#invalid-state). + /// The identifier *may* change when widgets which are descendants of some + /// dynamic layout are reconfigured. + #[inline] + fn id(&self) -> Id { + self.id_ref().clone() + } + /// Get the widget's region, relative to its parent. /// /// This method is implemented by the `#[widget]` macro. @@ -351,16 +367,6 @@ impl HasId for &mut W { /// Extension trait over widgets pub trait TileExt: Tile { - /// Get the widget's identifier - /// - /// Note that the default-constructed [`Id`] is *invalid*: any - /// operations on this value will cause a panic. Valid identifiers are - /// assigned during configure. - #[inline] - fn id(&self) -> Id { - self.id_ref().clone() - } - /// Test widget identifier for equality /// /// This method may be used to test against `Id`, `Option` diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 5df8685eb..077e3e0ee 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -25,7 +25,8 @@ use kas_macros::autoimpl; /// /// # Widget lifecycle /// -/// 1. The widget is configured ([`Events::configure`]) and immediately updated +/// 1. The widget is configured ([`Events::configure`], +/// [`Events::configure_recurse`]) and immediately updated /// ([`Events::update`]). /// 2. The widget has its size-requirements checked by calling /// [`Layout::size_rules`] for each axis. @@ -80,12 +81,14 @@ pub trait Events: Widget + Sized { /// Configure children /// /// This method is called after [`Self::configure`]. - /// It usually configures all children. - /// - /// The default implementation suffices except where children should *not* - /// be configured (for example, to delay configuration of hidden children). - /// - /// Use [`Events::make_child_id`] and [`ConfigCx::configure`]. + /// The default implementation configures all children. + /// + /// An explicit implementation is required in cases where not all children + /// should be configured immediately (for example, a stack or paged list may + /// choose not to configure hidden children until just before they become + /// visible). To configure children explicitly, generate an [`Id`] by + /// calling [`Events::make_child_id`] on `self` then pass this `id` to + /// [`ConfigCx::configure`]. fn configure_recurse(&mut self, cx: &mut ConfigCx, data: &Self::Data) { for index in 0..self.num_children() { let id = self.make_child_id(index); diff --git a/crates/kas-core/src/core/widget_id.rs b/crates/kas-core/src/core/widget_id.rs index 8f0288e17..c995afaf4 100644 --- a/crates/kas-core/src/core/widget_id.rs +++ b/crates/kas-core/src/core/widget_id.rs @@ -218,6 +218,8 @@ impl<'a> Iterator for WidgetPathIter<'a> { /// `a4`. To interpret these values, first subtract 8 from each digit but the /// last digit, then read as base-8: `[1, 2, 8, 20]`. /// +/// # Representation +/// /// This type is small (64-bit) and non-zero: `Option` has the same /// size as `Id`. It is also very cheap to `Clone`: usually only one `if` /// check, and in the worst case a pointer dereference and ref-count increment. @@ -226,8 +228,13 @@ impl<'a> Iterator for WidgetPathIter<'a> { /// /// `Id` is neither `Send` nor `Sync`. /// -/// Identifiers are assigned when configured and when re-configured -/// (via [`Action::RECONFIGURE`] or [`ConfigCx::configure`]). +/// # Invalid state +/// +/// When `Id` is [`Default`] constructed, it uses a special **invalid** state. +/// Any attempt to use or compare such an invalid state will cause a panic. +/// +/// Widgets are initially constructed with an invalid `Id`, then assigned an +/// `Id` when configured (see [`Events::configure`]). /// In most cases values are persistent but this is not guaranteed (e.g. /// inserting or removing a child from a `List` widget will affect the /// identifiers of all following children). View-widgets assign path components @@ -236,6 +243,7 @@ impl<'a> Iterator for WidgetPathIter<'a> { /// [`Display`]: std::fmt::Display /// [`Action::RECONFIGURE`]: crate::Action::RECONFIGURE /// [`ConfigCx::configure`]: crate::event::ConfigCx::configure +/// [`Events::configure`]: crate::Events::configure #[allow(clippy::assigning_clones)] #[derive(Clone)] pub struct Id(IntOrPtr); diff --git a/crates/kas-core/src/event/cx/config.rs b/crates/kas-core/src/event/cx/config.rs index ee4024ba5..8a0c145dc 100644 --- a/crates/kas-core/src/event/cx/config.rs +++ b/crates/kas-core/src/event/cx/config.rs @@ -15,7 +15,7 @@ use crate::{Id, Node}; use std::ops::{Deref, DerefMut}; #[allow(unused)] use crate::event::{Event, EventCx}; -#[allow(unused)] use crate::{Events, Layout}; +#[allow(unused)] use crate::{Action, Events, Layout}; /// Widget configuration and update context /// @@ -72,6 +72,9 @@ impl<'a> ConfigCx<'a> { /// This method performs complete configuration of the widget by calling /// [`Events::configure`], [`Events::update`], [`Events::configure_recurse`]. /// + /// To trigger (re)-configuration of the entire widget tree, use + /// [`Action::RECONFIGURE`]. + /// /// Pass the `id` to assign to the widget. This is usually constructed with /// [`Events::make_child_id`]. #[inline] diff --git a/crates/kas-core/src/popup.rs b/crates/kas-core/src/popup.rs index f9e026db3..71c579eac 100644 --- a/crates/kas-core/src/popup.rs +++ b/crates/kas-core/src/popup.rs @@ -7,7 +7,7 @@ use crate::dir::Direction; use crate::event::{ConfigCx, Event, EventCx, IsUsed, Scroll, Unused, Used}; -use crate::{Events, Id, TileExt, Widget, WindowId}; +use crate::{Events, Id, Tile, TileExt, Widget, WindowId}; use kas_macros::{impl_scope, widget_index}; #[allow(unused)] use crate::event::EventState; diff --git a/crates/kas-core/src/runner/window.rs b/crates/kas-core/src/runner/window.rs index e640e6271..235b071d5 100644 --- a/crates/kas-core/src/runner/window.rs +++ b/crates/kas-core/src/runner/window.rs @@ -15,8 +15,9 @@ use crate::draw::{color::Rgba, AnimationState, DrawSharedImpl}; use crate::event::{ConfigCx, CursorIcon, EventState}; use crate::geom::{Coord, Rect, Size}; use crate::layout::SolveCache; +use crate::messages::MessageStack; use crate::theme::{DrawCx, SizeCx, Theme, ThemeSize, Window as _}; -use crate::{autoimpl, messages::MessageStack, Action, Id, Tile, TileExt, Widget, WindowId}; +use crate::{autoimpl, Action, Id, Tile, Widget, WindowId}; use std::mem::take; use std::sync::Arc; use std::time::{Duration, Instant}; diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index 865313424..a4e7b1a3f 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -187,7 +187,7 @@ impl Tree { #[cfg(debug_assertions)] #core_path.status.require_rect(&#core_path.id); - draw.set_id(::kas::TileExt::id(self)); + draw.set_id(::kas::Tile::id(self)); ::kas::layout::LayoutVisitor::layout_visitor(self).draw(draw); } @@ -217,7 +217,7 @@ impl Tree { let coord = coord + ::kas::Tile::translation(self); ::kas::layout::LayoutVisitor::layout_visitor(self) .try_probe(coord) - .unwrap_or_else(|| ::kas::TileExt::id(self)) + .unwrap_or_else(|| ::kas::Tile::id(self)) } fn handle_event( diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 1bb35ed18..c301c041c 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -377,7 +377,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul let mut fn_size_rules = None; let mut set_rect = quote! { self.#core.rect = rect; }; let mut probe = quote! { - ::kas::TileExt::id(self) + ::kas::Tile::id(self) }; let mut fn_draw = None; if let Some(Layout { tree, .. }) = args.layout.take() { @@ -418,14 +418,14 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul let coord = coord + ::kas::Tile::translation(self); ::kas::layout::LayoutVisitor::layout_visitor(self) .try_probe(coord) - .unwrap_or_else(|| ::kas::TileExt::id(self)) + .unwrap_or_else(|| ::kas::Tile::id(self)) }; fn_draw = Some(quote! { fn draw(&mut self, mut draw: ::kas::theme::DrawCx) { #[cfg(debug_assertions)] #core_path.status.require_rect(&#core_path.id); - draw.set_id(::kas::TileExt::id(self)); + draw.set_id(::kas::Tile::id(self)); ::kas::layout::LayoutVisitor::layout_visitor(self).draw(draw); } @@ -616,7 +616,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul if pat_ident.mutability.is_some() { let draw = &pat_ident.ident; f.block.stmts.insert(0, parse_quote! { - #draw.set_id(::kas::TileExt::id(self)); + #draw.set_id(::kas::Tile::id(self)); }); } } diff --git a/crates/kas-widgets/src/adapt/adapt_events.rs b/crates/kas-widgets/src/adapt/adapt_events.rs index 47cc1daca..44a9de719 100644 --- a/crates/kas-widgets/src/adapt/adapt_events.rs +++ b/crates/kas-widgets/src/adapt/adapt_events.rs @@ -9,7 +9,7 @@ use super::{AdaptConfigCx, AdaptEventCx}; use kas::autoimpl; use kas::event::{ConfigCx, Event, EventCx, IsUsed}; #[allow(unused)] use kas::Events; -use kas::{Id, Node, TileExt, Widget}; +use kas::{Id, Node, Widget}; use std::fmt::Debug; kas::impl_scope! { From 7ed0d3e366d0bf3a54bc5b767abf9681e1de1b51 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 16 Jan 2025 11:36:06 +0000 Subject: [PATCH 2/8] Window::try_probe: use non-mut fn iter() --- crates/kas-core/src/root.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/kas-core/src/root.rs b/crates/kas-core/src/root.rs index 0610c4d59..32f34ede0 100644 --- a/crates/kas-core/src/root.rs +++ b/crates/kas-core/src/root.rs @@ -156,7 +156,7 @@ impl_scope! { if !self.core.rect.contains(coord) { return None; } - for (_, popup, translation) in self.popups.iter_mut().rev() { + for (_, popup, translation) in self.popups.iter().rev() { if let Some(Some(id)) = self.inner.as_node(data).find_node(&popup.id, |mut node| node.try_probe(coord + *translation)) { return Some(id); } From 1ef5a6cffbbf21a2d59bab6bb8978763a36488dc Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 21 Jan 2025 14:17:56 +0000 Subject: [PATCH 3/8] Rename field CoreData::id -> _id This hints that it should not be used directly. --- crates/kas-core/src/core/data.rs | 5 +++-- crates/kas-macros/src/make_layout.rs | 14 +++++++------- crates/kas-macros/src/widget.rs | 28 ++++++++++++++-------------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/crates/kas-core/src/core/data.rs b/crates/kas-core/src/core/data.rs index f234e0f4c..b4abc0cbc 100644 --- a/crates/kas-core/src/core/data.rs +++ b/crates/kas-core/src/core/data.rs @@ -37,7 +37,7 @@ impl Icon { #[derive(Default, Debug)] pub struct CoreData { pub rect: Rect, - pub id: Id, + pub _id: Id, #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(docsrs, doc(cfg(internal_doc)))] #[cfg(debug_assertions)] @@ -50,7 +50,8 @@ impl Clone for CoreData { fn clone(&self) -> Self { CoreData { rect: self.rect, - ..CoreData::default() + _id: Default::default(), + status: self.status, } } } diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index a4e7b1a3f..ad3387492 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -142,7 +142,7 @@ impl Tree { let toks = quote! {{ struct #name #impl_generics { rect: ::kas::geom::Rect, - id: ::kas::Id, + _id: ::kas::Id, #[cfg(debug_assertions)] status: ::kas::WidgetStatus, #stor_ty @@ -162,7 +162,7 @@ impl Tree { axis: ::kas::layout::AxisInfo, ) -> ::kas::layout::SizeRules { #[cfg(debug_assertions)] - #core_path.status.size_rules(&#core_path.id, axis); + #core_path.status.size_rules(&#core_path._id, axis); ::kas::layout::LayoutVisitor::layout_visitor(self).size_rules(sizer, axis) } @@ -173,7 +173,7 @@ impl Tree { hints: ::kas::layout::AlignHints, ) { #[cfg(debug_assertions)] - #core_path.status.set_rect(&#core_path.id); + #core_path.status.set_rect(&#core_path._id); #core_path.rect = rect; ::kas::layout::LayoutVisitor::layout_visitor(self).set_rect(cx, rect, hints); @@ -185,7 +185,7 @@ impl Tree { fn draw(&mut self, mut draw: ::kas::theme::DrawCx) { #[cfg(debug_assertions)] - #core_path.status.require_rect(&#core_path.id); + #core_path.status.require_rect(&#core_path._id); draw.set_id(::kas::Tile::id(self)); @@ -212,7 +212,7 @@ impl Tree { #[inline] fn probe(&mut self, coord: ::kas::geom::Coord) -> ::kas::Id { #[cfg(debug_assertions)] - #core_path.status.require_rect(&#core_path.id); + #core_path.status.require_rect(&#core_path._id); let coord = coord + ::kas::Tile::translation(self); ::kas::layout::LayoutVisitor::layout_visitor(self) @@ -227,7 +227,7 @@ impl Tree { _: ::kas::event::Event, ) -> ::kas::event::IsUsed { #[cfg(debug_assertions)] - #core_path.status.require_rect(&#core_path.id); + #core_path.status.require_rect(&#core_path._id); ::kas::event::Unused } } @@ -236,7 +236,7 @@ impl Tree { #name { rect: Default::default(), - id: Default::default(), + _id: Default::default(), #[cfg(debug_assertions)] status: Default::default(), #stor_def diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index c301c041c..714fcee25 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -194,7 +194,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul scope.generated.push(quote! { struct #core_type { rect: ::kas::geom::Rect, - id: ::kas::Id, + _id: ::kas::Id, #[cfg(debug_assertions)] status: ::kas::WidgetStatus, #stor_ty @@ -204,7 +204,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul fn default() -> Self { #core_type { rect: Default::default(), - id: Default::default(), + _id: Default::default(), #[cfg(debug_assertions)] status: ::kas::WidgetStatus::New, #stor_def @@ -317,7 +317,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul let require_rect: syn::Stmt = parse_quote! { #[cfg(debug_assertions)] - #core_path.status.require_rect(&#core_path.id); + #core_path.status.require_rect(&#core_path._id); }; let mut required_tile_methods = impl_core_methods(&name.to_string(), &core_path); @@ -406,7 +406,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul axis: ::kas::layout::AxisInfo, ) -> ::kas::layout::SizeRules { #[cfg(debug_assertions)] - #core_path.status.size_rules(&#core_path.id, axis); + #core_path.status.size_rules(&#core_path._id, axis); ::kas::layout::LayoutVisitor::layout_visitor(self).size_rules(sizer, axis) } }); @@ -423,7 +423,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul fn_draw = Some(quote! { fn draw(&mut self, mut draw: ::kas::theme::DrawCx) { #[cfg(debug_assertions)] - #core_path.status.require_rect(&#core_path.id); + #core_path.status.require_rect(&#core_path._id); draw.set_id(::kas::Tile::id(self)); @@ -445,7 +445,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul hints: ::kas::layout::AlignHints, ) { #[cfg(debug_assertions)] - #core_path.status.set_rect(&#core_path.id); + #core_path.status.set_rect(&#core_path._id); #set_rect } }; @@ -546,7 +546,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul let fn_try_probe = quote! { fn try_probe(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { #[cfg(debug_assertions)] - self.#core.status.require_rect(&self.#core.id); + self.#core.status.require_rect(&self.#core._id); ::kas::Tile::rect(self).contains(coord).then(|| ::kas::Events::probe(self, coord)) } @@ -564,7 +564,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul let axis = &pat_ident.ident; f.block.stmts.insert(0, parse_quote! { #[cfg(debug_assertions)] - self.#core.status.size_rules(&self.#core.id, #axis); + self.#core.status.size_rules(&self.#core._id, #axis); }); } else { emit_error!(arg.pat, "hidden shenanigans require this parameter to have a name; suggestion: `_axis`"); @@ -581,7 +581,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul if let ImplItem::Fn(f) = &mut layout_impl.items[*index] { f.block.stmts.insert(0, parse_quote! { #[cfg(debug_assertions)] - self.#core.status.set_rect(&self.#core.id); + self.#core.status.set_rect(&self.#core._id); }); } } @@ -605,7 +605,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul if let ImplItem::Fn(f) = &mut layout_impl.items[*index] { f.block.stmts.insert(0, parse_quote! { #[cfg(debug_assertions)] - self.#core.status.require_rect(&self.#core.id); + self.#core.status.require_rect(&self.#core._id); }); if let Some(FnArg::Typed(arg)) = f.sig.inputs.iter().nth(1) { @@ -699,7 +699,7 @@ pub fn impl_core_methods(name: &str, core_path: &Toks) -> Toks { } #[inline] fn id_ref(&self) -> &::kas::Id { - &#core_path.id + &#core_path._id } #[inline] fn rect(&self) -> ::kas::geom::Rect { @@ -797,9 +797,9 @@ fn widget_recursive_methods(core_path: &Toks) -> Toks { ) { debug_assert!(id.is_valid(), "Widget::_configure called with invalid id!"); - #core_path.id = id; + #core_path._id = id; #[cfg(debug_assertions)] - #core_path.status.configure(&#core_path.id); + #core_path.status.configure(&#core_path._id); ::kas::Events::configure(self, cx); ::kas::Events::update(self, cx, data); @@ -812,7 +812,7 @@ fn widget_recursive_methods(core_path: &Toks) -> Toks { data: &Self::Data, ) { #[cfg(debug_assertions)] - #core_path.status.update(&#core_path.id); + #core_path.status.update(&#core_path._id); ::kas::Events::update(self, cx, data); ::kas::Events::update_recurse(self, cx, data); From 1a4f9f505c09262750dc2bebd20c87eb45f8e5c5 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 22 Jan 2025 08:48:41 +0000 Subject: [PATCH 4/8] Revise widget_index! macro code --- crates/kas-macros/src/lib.rs | 5 ++-- crates/kas-macros/src/widget_index.rs | 40 ++++++++++++++------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 2457764dc..d9a844df8 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -442,9 +442,8 @@ pub fn impl_anon(input: TokenStream) -> TokenStream { #[proc_macro_error] #[proc_macro] pub fn widget_index(input: TokenStream) -> TokenStream { - let input2 = input.clone(); - let _ = parse_macro_input!(input2 as widget_index::BaseInput); - input + let input = parse_macro_input!(input as widget_index::UnscopedInput); + input.into_token_stream().into() } trait ExpandLayout { diff --git a/crates/kas-macros/src/widget_index.rs b/crates/kas-macros/src/widget_index.rs index 7b76c8f54..13559bb7a 100644 --- a/crates/kas-macros/src/widget_index.rs +++ b/crates/kas-macros/src/widget_index.rs @@ -3,37 +3,39 @@ // You may obtain a copy of the License in the LICENSE-APACHE file or at: // https://www.apache.org/licenses/LICENSE-2.0 -use proc_macro2::Span; +use proc_macro2::{Span, TokenStream}; use proc_macro_error2::emit_error; use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; use syn::visit_mut::{self, VisitMut}; -use syn::{parse_quote, Error, Lit, Member, Result, Token}; +use syn::{parse_quote, Error, Member, Result, Token}; #[allow(non_camel_case_types)] mod kw { syn::custom_keyword!(error_emitted); + syn::custom_keyword!(expanded_result); } -pub struct BaseInput; -impl Parse for BaseInput { +pub struct UnscopedInput(TokenStream); +impl Parse for UnscopedInput { fn parse(input: ParseStream) -> Result { - if input.peek(Lit) { - // Okay: macro was expanded internally (or user wrote a number...) - let _ = input.parse::()?; - if input.is_empty() { - return Ok(Self); - } + if input.peek(kw::expanded_result) { + // Okay: macro was expanded internally + let _: kw::expanded_result = input.parse()?; + Ok(UnscopedInput(input.parse()?)) } else if input.peek(kw::error_emitted) { // An error was already emitted by the visitor let _ = input.parse::()?; - if input.is_empty() { - return Ok(Self); - } + Ok(UnscopedInput(input.parse()?)) + } else { + let msg = "usage invalid outside of `impl_scope!` macro with `#[widget]` attribute"; + Err(Error::new(Span::call_site(), msg)) } - - let msg = "usage of `widget_index!` invalid outside of `impl_scope!` macro with `#[widget]` attribute"; - Err(Error::new(Span::call_site(), msg)) + } +} +impl UnscopedInput { + pub fn into_token_stream(self) -> TokenStream { + self.0 } } @@ -66,20 +68,20 @@ impl<'a, I: Clone + Iterator> VisitMut for Visitor<' Ok(args) => args, Err(err) => { emit_error!(node.tokens.span(), "{}", err); - node.tokens = parse_quote! { error_emitted }; + node.tokens = parse_quote! { error_emitted 0 }; return; } }; for (i, child) in self.children.clone() { if args.ident == *child { - node.tokens = parse_quote! { #i }; + node.tokens = parse_quote! { expanded_result #i }; return; } } emit_error!(args.ident.span(), "does not match any child widget"); - node.tokens = parse_quote! { error_emitted }; + node.tokens = parse_quote! { error_emitted 0 }; return; } From e1b22e42e46ab99683b4a93710069707c47af9f9 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 22 Jan 2025 09:05:19 +0000 Subject: [PATCH 5/8] Add macro widget_set_rect! --- crates/kas-core/src/decorations.rs | 2 +- crates/kas-core/src/hidden.rs | 4 +-- crates/kas-core/src/layout/align.rs | 2 +- crates/kas-core/src/layout/visitor.rs | 2 +- crates/kas-core/src/lib.rs | 5 ++-- crates/kas-core/src/prelude.rs | 4 ++- crates/kas-core/src/root.rs | 4 +-- crates/kas-macros/src/lib.rs | 24 ++++++++++++++++ crates/kas-macros/src/widget.rs | 40 ++++++++++++++------------ crates/kas-macros/src/widget_index.rs | 20 ++++++++++++- crates/kas-view/src/list_view.rs | 2 +- crates/kas-view/src/matrix_view.rs | 2 +- crates/kas-widgets/src/check_box.rs | 4 +-- crates/kas-widgets/src/edit.rs | 2 +- crates/kas-widgets/src/grid.rs | 2 +- crates/kas-widgets/src/label.rs | 4 +-- crates/kas-widgets/src/list.rs | 2 +- crates/kas-widgets/src/menu/menubar.rs | 2 +- crates/kas-widgets/src/menu/submenu.rs | 2 +- crates/kas-widgets/src/progress.rs | 2 +- crates/kas-widgets/src/radio_box.rs | 4 +-- crates/kas-widgets/src/scroll.rs | 2 +- crates/kas-widgets/src/scroll_bar.rs | 4 +-- crates/kas-widgets/src/scroll_label.rs | 2 +- crates/kas-widgets/src/scroll_text.rs | 2 +- crates/kas-widgets/src/slider.rs | 2 +- crates/kas-widgets/src/spinner.rs | 2 +- crates/kas-widgets/src/splitter.rs | 2 +- crates/kas-widgets/src/stack.rs | 2 +- crates/kas-widgets/src/text.rs | 2 +- examples/mandlebrot/mandlebrot.rs | 2 +- examples/proxy.rs | 2 +- 32 files changed, 103 insertions(+), 56 deletions(-) diff --git a/crates/kas-core/src/decorations.rs b/crates/kas-core/src/decorations.rs index 5dff0ea79..347e133e0 100644 --- a/crates/kas-core/src/decorations.rs +++ b/crates/kas-core/src/decorations.rs @@ -123,7 +123,7 @@ impl_scope! { impl Layout for Self { fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); self.text.set_rect(cx, rect, hints.combine(AlignHints::CENTER)); } } diff --git a/crates/kas-core/src/hidden.rs b/crates/kas-core/src/hidden.rs index a4e7bdaa2..07fde8555 100644 --- a/crates/kas-core/src/hidden.rs +++ b/crates/kas-core/src/hidden.rs @@ -15,7 +15,7 @@ use crate::geom::Rect; use crate::layout::AlignHints; use crate::theme::{Text, TextClass}; use crate::{Events, Layout, Widget}; -use kas_macros::{autoimpl, impl_scope}; +use kas_macros::{autoimpl, impl_scope, widget_set_rect}; impl_scope! { /// A simple text label @@ -46,7 +46,7 @@ impl_scope! { impl Layout for Self { fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); self.text.set_rect(cx, rect, hints.combine(AlignHints::VERT_CENTER)); } } diff --git a/crates/kas-core/src/layout/align.rs b/crates/kas-core/src/layout/align.rs index 454f77770..de46dd6d0 100644 --- a/crates/kas-core/src/layout/align.rs +++ b/crates/kas-core/src/layout/align.rs @@ -27,7 +27,7 @@ pub use crate::text::Align; /// let rect = align /// .complete(Align::Stretch, Align::Center) /// .aligned_rect(pref_size, rect); -/// // self.core.rect = rect; +/// // widget_set_rect!(rect); /// ``` #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] pub struct AlignHints { diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index ea179f023..59c266000 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -176,7 +176,7 @@ impl Visitor { /// Apply a given `rect` to self /// - /// The caller is expected to set `self.core.rect = rect;`. + /// The caller is expected to call `widget_set_rect!(rect);`. /// In other respects, this functions identically to [`Layout::set_rect`]. #[inline] pub fn set_rect(mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { diff --git a/crates/kas-core/src/lib.rs b/crates/kas-core/src/lib.rs index b93b45700..9392c8b15 100644 --- a/crates/kas-core/src/lib.rs +++ b/crates/kas-core/src/lib.rs @@ -30,8 +30,9 @@ mod root; pub use crate::core::*; pub use action::Action; -pub use kas_macros::{autoimpl, extends, impl_default, widget}; -pub use kas_macros::{cell_collection, collection, impl_anon, impl_scope, widget_index}; +pub use kas_macros::{autoimpl, extends, impl_default}; +pub use kas_macros::{cell_collection, collection, impl_anon, impl_scope}; +pub use kas_macros::{widget, widget_index, widget_set_rect}; #[doc(inline)] pub use popup::Popup; #[doc(inline)] pub(crate) use popup::PopupDescriptor; #[doc(inline)] diff --git a/crates/kas-core/src/prelude.rs b/crates/kas-core/src/prelude.rs index cf8d138fa..528573e32 100644 --- a/crates/kas-core/src/prelude.rs +++ b/crates/kas-core/src/prelude.rs @@ -24,7 +24,9 @@ pub use crate::layout::{ #[doc(no_inline)] pub use crate::theme::{DrawCx, SizeCx}; #[doc(no_inline)] pub use crate::Action; #[doc(no_inline)] -pub use crate::{autoimpl, impl_anon, impl_default, impl_scope, widget, widget_index}; +pub use crate::{autoimpl, impl_anon, impl_default, impl_scope}; +#[doc(no_inline)] +pub use crate::{widget, widget_index, widget_set_rect}; #[doc(no_inline)] pub use crate::{Events, Layout, Tile, TileExt, Widget, Window, WindowCommand}; #[doc(no_inline)] pub use crate::{HasId, Id}; diff --git a/crates/kas-core/src/root.rs b/crates/kas-core/src/root.rs index 32f34ede0..2e8759709 100644 --- a/crates/kas-core/src/root.rs +++ b/crates/kas-core/src/root.rs @@ -13,7 +13,7 @@ use crate::geom::{Coord, Offset, Rect, Size}; use crate::layout::{self, AlignHints, AxisInfo, SizeRules}; use crate::theme::{DrawCx, FrameStyle, SizeCx}; use crate::{Action, Events, Icon, Id, Layout, Tile, TileExt, Widget}; -use kas_macros::impl_scope; +use kas_macros::{impl_scope, widget_set_rect}; use smallvec::SmallVec; use std::num::NonZeroU32; @@ -119,7 +119,7 @@ impl_scope! { } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); // Calculate position and size for nw, ne, and inner portions: let s_nw: Size = self.dec_offset.cast(); let s_se = self.dec_size - s_nw; diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index d9a844df8..c5e9c5994 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -446,6 +446,30 @@ pub fn widget_index(input: TokenStream) -> TokenStream { input.into_token_stream().into() } +/// Macro to set the `rect` stored in the widget core +/// +/// Widgets have a hidden field of type [`Rect`] in their `widget_core!()`, used +/// to implement method [`Tile::rect`]. This macro assigns to that field. +/// +/// This macro is usable only within an [`impl_scope!`] macro using the +/// [`widget`](macro@widget) attribute. +/// +/// Example usage: +/// ``` +/// fn set_rect(&mut self, _: &mut ConfigCx, rect: Rect, _: AlignHints) { +/// widget_set_rect!(rect); +/// } +/// ``` +/// +/// [`Rect`]: https://docs.rs/kas/latest/kas/geom/struct.Rect.html +/// [`Tile::rect`]: https://docs.rs/kas/latest/kas/trait.Tile.html#method.rect +#[proc_macro_error] +#[proc_macro] +pub fn widget_set_rect(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as widget_index::UnscopedInput); + input.into_token_stream().into() +} + trait ExpandLayout { fn expand_layout(self, name: &str) -> TokenStream; } diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 714fcee25..c8d33e1d1 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -261,6 +261,25 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul field.attrs = other_attrs; } + let Some(core) = core_data.clone() else { + let span = match scope.item { + ScopeItem::Struct { + fields: Fields::Named(ref fields), + .. + } => fields.brace_token.span, + ScopeItem::Struct { + fields: Fields::Unnamed(ref fields), + .. + } => fields.paren_token.span, + _ => unreachable!(), + }; + return Err(Error::new( + span.join(), + "expected: a field with type `widget_core!()`", + )); + }; + let core_path = quote! { self.#core }; + let named_child_iter = children .iter() .enumerate() @@ -268,7 +287,8 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul ChildIdent::Field(ref member) => Some((i, member)), ChildIdent::CoreField(_) => None, }); - crate::widget_index::visit_impls(named_child_iter, &mut scope.impls); + let path_rect = quote! { #core_path.rect }; + crate::widget_index::visit_impls(named_child_iter, path_rect, &mut scope.impls); if let Some(ref span) = num_children { if get_child.is_none() { @@ -296,24 +316,6 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul let (impl_generics, ty_generics, where_clause) = scope.generics.split_for_impl(); let impl_generics = impl_generics.to_token_stream(); let impl_target = quote! { #name #ty_generics #where_clause }; - let Some(core) = core_data.clone() else { - let span = match scope.item { - ScopeItem::Struct { - fields: Fields::Named(ref fields), - .. - } => fields.brace_token.span, - ScopeItem::Struct { - fields: Fields::Unnamed(ref fields), - .. - } => fields.paren_token.span, - _ => unreachable!(), - }; - return Err(Error::new( - span.join(), - "expected: a field with type `widget_core!()`", - )); - }; - let core_path = quote! { self.#core }; let require_rect: syn::Stmt = parse_quote! { #[cfg(debug_assertions)] diff --git a/crates/kas-macros/src/widget_index.rs b/crates/kas-macros/src/widget_index.rs index 13559bb7a..e80e6a21b 100644 --- a/crates/kas-macros/src/widget_index.rs +++ b/crates/kas-macros/src/widget_index.rs @@ -55,6 +55,7 @@ impl Parse for WidgetInput { struct Visitor<'a, I: Clone + Iterator> { children: I, + path_rect: TokenStream, } impl<'a, I: Clone + Iterator> VisitMut for Visitor<'a, I> { fn visit_macro_mut(&mut self, node: &mut syn::Macro) { @@ -83,6 +84,19 @@ impl<'a, I: Clone + Iterator> VisitMut for Visitor<' emit_error!(args.ident.span(), "does not match any child widget"); node.tokens = parse_quote! { error_emitted 0 }; return; + } else if node.path == parse_quote! { widget_set_rect } { + let expr = match syn::parse2::(node.tokens.clone()) { + Ok(expr) => expr, + Err(err) => { + emit_error!(node.tokens.span(), "{}", err); + node.tokens = parse_quote! { error_emitted }; + return; + } + }; + + let path_rect = &self.path_rect; + node.tokens = parse_quote! { expanded_result #path_rect = #expr }; + return; } visit_mut::visit_macro_mut(self, node); @@ -91,9 +105,13 @@ impl<'a, I: Clone + Iterator> VisitMut for Visitor<' pub fn visit_impls<'a, I: Clone + Iterator>( children: I, + path_rect: TokenStream, impls: &mut [syn::ItemImpl], ) { - let mut obj = Visitor { children }; + let mut obj = Visitor { + children, + path_rect, + }; for impl_ in impls { obj.visit_item_impl_mut(impl_); diff --git a/crates/kas-view/src/list_view.rs b/crates/kas-view/src/list_view.rs index 6c24d53ac..02720762b 100644 --- a/crates/kas-view/src/list_view.rs +++ b/crates/kas-view/src/list_view.rs @@ -501,7 +501,7 @@ impl_scope! { } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); self.align_hints = hints; // Widgets need configuring and updating: do so by updating self. diff --git a/crates/kas-view/src/matrix_view.rs b/crates/kas-view/src/matrix_view.rs index b1d79c272..a890daec9 100644 --- a/crates/kas-view/src/matrix_view.rs +++ b/crates/kas-view/src/matrix_view.rs @@ -442,7 +442,7 @@ impl_scope! { } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); self.align_hints = hints; // Widgets need configuring and updating: do so by updating self. diff --git a/crates/kas-widgets/src/check_box.rs b/crates/kas-widgets/src/check_box.rs index d58ad5f8f..706c95d87 100644 --- a/crates/kas-widgets/src/check_box.rs +++ b/crates/kas-widgets/src/check_box.rs @@ -56,7 +56,7 @@ impl_scope! { fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { let rect = cx.align_feature(Feature::CheckBox, rect, hints.complete_center()); - self.core.rect = rect; + widget_set_rect!(rect); } fn draw(&mut self, mut draw: DrawCx) { @@ -186,7 +186,7 @@ impl_scope! { impl Layout for Self { fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); self.layout_visitor().set_rect(cx, rect, hints); let dir = self.direction(); shrink_to_text(&mut self.core.rect, dir, &self.label); diff --git a/crates/kas-widgets/src/edit.rs b/crates/kas-widgets/src/edit.rs index 322547edf..2257ff57f 100644 --- a/crates/kas-widgets/src/edit.rs +++ b/crates/kas-widgets/src/edit.rs @@ -739,7 +739,7 @@ impl_scope! { } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, mut hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); self.outer_rect = rect; hints.vert = Some(if self.multi_line() { Align::Default } else { Align::Center }); self.text.set_rect(cx, rect, hints); diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index f48bd3033..bbebaf4eb 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -106,7 +106,7 @@ impl_scope! { } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); let mut setter = GridSetter::, Vec<_>, _>::new(rect, self.dim, &mut self.layout); for n in 0..self.widgets.len() { if let Some((info, child)) = self.widgets.cell_info(n).zip(self.widgets.get_mut_tile(n)) { diff --git a/crates/kas-widgets/src/label.rs b/crates/kas-widgets/src/label.rs index ed4ab6ba7..43386ea11 100644 --- a/crates/kas-widgets/src/label.rs +++ b/crates/kas-widgets/src/label.rs @@ -113,7 +113,7 @@ impl_scope! { impl Layout for Self { fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); self.text.set_rect(cx, rect, hints.combine(AlignHints::VERT_CENTER)); } } @@ -259,7 +259,7 @@ impl_scope! { impl Layout for Self { fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); self.text.set_rect(cx, rect, hints.combine(AlignHints::VERT_CENTER)); } } diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 461cb8224..85d101604 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -110,7 +110,7 @@ impl_scope! { } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); let dim = (self.direction, self.widgets.len()); let mut setter = RowSetter::, _>::new(rect, dim, &mut self.layout); diff --git a/crates/kas-widgets/src/menu/menubar.rs b/crates/kas-widgets/src/menu/menubar.rs index 53456cc53..455b47ece 100644 --- a/crates/kas-widgets/src/menu/menubar.rs +++ b/crates/kas-widgets/src/menu/menubar.rs @@ -92,7 +92,7 @@ impl_scope! { } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, _: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); let dim = (self.direction, self.widgets.len() + 1); let mut setter = RowSetter::, _>::new(rect, dim, &mut self.layout_store); let hints = AlignHints::CENTER; diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index 600eaaae0..3e47f59e8 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -315,7 +315,7 @@ impl_scope! { } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, _: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); let store = &mut self.store; let hints = AlignHints::NONE; let mut setter = layout::GridSetter::, Vec<_>, _>::new(rect, self.dim, store); diff --git a/crates/kas-widgets/src/progress.rs b/crates/kas-widgets/src/progress.rs index bf2b7942b..8eecbf8f3 100644 --- a/crates/kas-widgets/src/progress.rs +++ b/crates/kas-widgets/src/progress.rs @@ -78,7 +78,7 @@ impl_scope! { true => AlignPair::new(hints.horiz.unwrap_or(Align::Center), Align::Stretch), }; let rect = cx.align_feature(Feature::ProgressBar(self.direction()), rect, align); - self.core.rect = rect; + widget_set_rect!(rect); } fn draw(&mut self, mut draw: DrawCx) { diff --git a/crates/kas-widgets/src/radio_box.rs b/crates/kas-widgets/src/radio_box.rs index ce829d82d..493a6c61e 100644 --- a/crates/kas-widgets/src/radio_box.rs +++ b/crates/kas-widgets/src/radio_box.rs @@ -56,7 +56,7 @@ impl_scope! { fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { let align = hints.complete_center(); let rect = cx.align_feature(Feature::RadioBox, rect, align); - self.core.rect = rect; + widget_set_rect!(rect); } fn draw(&mut self, mut draw: DrawCx) { @@ -147,7 +147,7 @@ impl_scope! { impl Layout for Self { fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); self.layout_visitor().set_rect(cx, rect, hints); let dir = self.direction(); crate::check_box::shrink_to_text(&mut self.core.rect, dir, &self.label); diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index 6e1f13880..001dee636 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -108,7 +108,7 @@ impl_scope! { } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); let child_size = (rect.size - self.frame_size).max(self.min_child_size); let child_rect = Rect::new(rect.pos + self.offset, child_size); self.inner.set_rect(cx, child_rect, hints); diff --git a/crates/kas-widgets/src/scroll_bar.rs b/crates/kas-widgets/src/scroll_bar.rs index 1e0d71e46..9c193165d 100644 --- a/crates/kas-widgets/src/scroll_bar.rs +++ b/crates/kas-widgets/src/scroll_bar.rs @@ -281,7 +281,7 @@ impl_scope! { true => AlignPair::new(hints.horiz.unwrap_or(Align::Center), Align::Stretch), }; let rect = cx.align_feature(Feature::ScrollBar(self.direction()), rect, align); - self.core.rect = rect; + widget_set_rect!(rect); self.grip.set_track(rect); // We call grip.set_rect only for compliance with the widget model: @@ -459,7 +459,7 @@ impl_scope! { } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); let pos = rect.pos; let mut child_size = rect.size; diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index e408e4cd6..23013e298 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -46,7 +46,7 @@ impl_scope! { } fn set_rect(&mut self, cx: &mut ConfigCx, mut rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); self.text.set_rect(cx, rect, hints); self.text_size = Vec2::from(self.text.bounding_box().unwrap().1).cast_ceil(); diff --git a/crates/kas-widgets/src/scroll_text.rs b/crates/kas-widgets/src/scroll_text.rs index 365384da0..6efeb1ccf 100644 --- a/crates/kas-widgets/src/scroll_text.rs +++ b/crates/kas-widgets/src/scroll_text.rs @@ -46,7 +46,7 @@ impl_scope! { } fn set_rect(&mut self, cx: &mut ConfigCx, mut rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); self.text.set_rect(cx, rect, hints); self.text_size = Vec2::from(self.text.bounding_box().unwrap().1).cast_ceil(); diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index a6bcd5b3d..f3eb5cc0a 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -304,7 +304,7 @@ impl_scope! { true => AlignPair::new(hints.horiz.unwrap_or(Align::Center), Align::Stretch), }; let mut rect = cx.align_feature(Feature::Slider(self.direction()), rect, align); - self.core.rect = rect; + widget_set_rect!(rect); self.grip.set_track(rect); // Set the grip size (we could instead call set_size but the widget diff --git a/crates/kas-widgets/src/spinner.rs b/crates/kas-widgets/src/spinner.rs index 41d71b54d..167ac5f0c 100644 --- a/crates/kas-widgets/src/spinner.rs +++ b/crates/kas-widgets/src/spinner.rs @@ -281,7 +281,7 @@ impl_scope! { impl Layout for Self { fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); self.layout_visitor().set_rect(cx, rect, hints); self.edit.set_outer_rect(rect, FrameStyle::EditBox); } diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index 85d8a4c15..4c8e90436 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -182,7 +182,7 @@ impl_scope! { } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); self.align_hints = hints; self.size_solved = true; if self.widgets.is_empty() { diff --git a/crates/kas-widgets/src/stack.rs b/crates/kas-widgets/src/stack.rs index 99a3aa988..cf1783480 100644 --- a/crates/kas-widgets/src/stack.rs +++ b/crates/kas-widgets/src/stack.rs @@ -121,7 +121,7 @@ impl_scope! { } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); self.align_hints = hints; if let Some(entry) = self.widgets.get_mut(self.active) { debug_assert_eq!(entry.1, State::Sized); diff --git a/crates/kas-widgets/src/text.rs b/crates/kas-widgets/src/text.rs index c6192a153..7495f4cd3 100644 --- a/crates/kas-widgets/src/text.rs +++ b/crates/kas-widgets/src/text.rs @@ -110,7 +110,7 @@ impl_scope! { } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); self.text.set_rect(cx, rect, hints.combine(AlignHints::VERT_CENTER)); } diff --git a/examples/mandlebrot/mandlebrot.rs b/examples/mandlebrot/mandlebrot.rs index 892677f22..be0c391a5 100644 --- a/examples/mandlebrot/mandlebrot.rs +++ b/examples/mandlebrot/mandlebrot.rs @@ -342,7 +342,7 @@ impl_scope! { #[inline] fn set_rect(&mut self, _: &mut ConfigCx, rect: Rect, _: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); let size = DVec2::conv(rect.size); let rel_width = DVec2(size.0 / size.1, 1.0); self.view_alpha = 2.0 / size.1; diff --git a/examples/proxy.rs b/examples/proxy.rs index 4d18588ac..f49c1ba7b 100644 --- a/examples/proxy.rs +++ b/examples/proxy.rs @@ -70,7 +70,7 @@ impl_scope! { } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.core.rect = rect; + widget_set_rect!(rect); self.loading_text.set_rect(cx, rect, hints.combine(AlignHints::CENTER)); } From 6aec8718fbdd5b5591d0bb4e312441f018febf13 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 23 Jan 2025 07:51:17 +0000 Subject: [PATCH 6/8] Rename field CoreData::rect -> _rect --- crates/kas-core/src/core/data.rs | 4 +- crates/kas-core/src/decorations.rs | 2 +- crates/kas-core/src/root.rs | 6 +- crates/kas-macros/src/make_layout.rs | 6 +- crates/kas-macros/src/widget.rs | 16 ++- crates/kas-resvg/src/canvas.rs | 4 +- crates/kas-resvg/src/svg.rs | 6 +- crates/kas-view/src/list_view.rs | 10 +- crates/kas-view/src/matrix_view.rs | 10 +- crates/kas-widgets/src/check_box.rs | 2 +- crates/kas-widgets/src/edit.rs | 2 +- crates/kas-widgets/src/grip.rs | 186 ++++++++++++------------- crates/kas-widgets/src/image.rs | 2 +- crates/kas-widgets/src/mark.rs | 4 +- crates/kas-widgets/src/menu/menubar.rs | 3 +- crates/kas-widgets/src/radio_box.rs | 2 +- crates/kas-widgets/src/scroll.rs | 4 +- crates/kas-widgets/src/scroll_bar.rs | 8 +- crates/kas-widgets/src/splitter.rs | 6 +- crates/kas-widgets/src/stack.rs | 5 +- examples/clock.rs | 4 +- examples/mandlebrot/mandlebrot.rs | 2 +- examples/proxy.rs | 2 +- 23 files changed, 150 insertions(+), 146 deletions(-) diff --git a/crates/kas-core/src/core/data.rs b/crates/kas-core/src/core/data.rs index b4abc0cbc..20de06509 100644 --- a/crates/kas-core/src/core/data.rs +++ b/crates/kas-core/src/core/data.rs @@ -36,7 +36,7 @@ impl Icon { /// This type may be used for a [`Widget`]'s `core: widget_core!()` field. #[derive(Default, Debug)] pub struct CoreData { - pub rect: Rect, + pub _rect: Rect, pub _id: Id, #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(docsrs, doc(cfg(internal_doc)))] @@ -49,7 +49,7 @@ pub struct CoreData { impl Clone for CoreData { fn clone(&self) -> Self { CoreData { - rect: self.rect, + _rect: self._rect, _id: Default::default(), status: self.status, } diff --git a/crates/kas-core/src/decorations.rs b/crates/kas-core/src/decorations.rs index 347e133e0..d24bfabff 100644 --- a/crates/kas-core/src/decorations.rs +++ b/crates/kas-core/src/decorations.rs @@ -183,7 +183,7 @@ impl_scope! { } fn draw(&mut self, mut draw: DrawCx) { - draw.mark(self.core.rect, self.style); + draw.mark(self.rect(), self.style); } } diff --git a/crates/kas-core/src/root.rs b/crates/kas-core/src/root.rs index 2e8759709..64059664d 100644 --- a/crates/kas-core/src/root.rs +++ b/crates/kas-core/src/root.rs @@ -153,7 +153,7 @@ impl_scope! { impl Self { pub(crate) fn try_probe(&mut self, data: &Data, coord: Coord) -> Option { - if !self.core.rect.contains(coord) { + if !self.rect().contains(coord) { return None; } for (_, popup, translation) in self.popups.iter().rev() { @@ -181,7 +181,7 @@ impl_scope! { #[cfg(winit)] pub(crate) fn draw(&mut self, data: &Data, mut draw: DrawCx) { if self.dec_size != Size::ZERO { - draw.frame(self.core.rect, FrameStyle::Window, Default::default()); + draw.frame(self.rect(), FrameStyle::Window, Default::default()); if self.bar_h > 0 { self.title_bar.draw(draw.re()); } @@ -473,7 +473,7 @@ impl Window { fn resize_popup(&mut self, cx: &mut ConfigCx, data: &Data, index: usize) { // Notation: p=point/coord, s=size, m=margin // r=window/root rect, c=anchor rect - let r = self.core.rect; + let r = self.rect(); let (_, ref mut popup, ref mut translation) = self.popups[index]; let is_reversed = popup.direction.is_reversed(); diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index ad3387492..7cb2b8d86 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -141,7 +141,7 @@ impl Tree { let toks = quote! {{ struct #name #impl_generics { - rect: ::kas::geom::Rect, + _rect: ::kas::geom::Rect, _id: ::kas::Id, #[cfg(debug_assertions)] status: ::kas::WidgetStatus, @@ -175,7 +175,7 @@ impl Tree { #[cfg(debug_assertions)] #core_path.status.set_rect(&#core_path._id); - #core_path.rect = rect; + #core_path._rect = rect; ::kas::layout::LayoutVisitor::layout_visitor(self).set_rect(cx, rect, hints); } @@ -235,7 +235,7 @@ impl Tree { #widget_impl #name { - rect: Default::default(), + _rect: Default::default(), _id: Default::default(), #[cfg(debug_assertions)] status: Default::default(), diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index c8d33e1d1..4f24dcfd4 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -193,7 +193,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul let stor_def = &stor_defs.def_toks; scope.generated.push(quote! { struct #core_type { - rect: ::kas::geom::Rect, + _rect: ::kas::geom::Rect, _id: ::kas::Id, #[cfg(debug_assertions)] status: ::kas::WidgetStatus, @@ -203,7 +203,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul impl Default for #core_type { fn default() -> Self { #core_type { - rect: Default::default(), + _rect: Default::default(), _id: Default::default(), #[cfg(debug_assertions)] status: ::kas::WidgetStatus::New, @@ -215,7 +215,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul impl Clone for #core_type { fn clone(&self) -> Self { #core_type { - rect: self.rect, + _rect: self._rect, .. #core_type::default() } } @@ -287,7 +287,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul ChildIdent::Field(ref member) => Some((i, member)), ChildIdent::CoreField(_) => None, }); - let path_rect = quote! { #core_path.rect }; + let path_rect = quote! { #core_path._rect }; crate::widget_index::visit_impls(named_child_iter, path_rect, &mut scope.impls); if let Some(ref span) = num_children { @@ -377,7 +377,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul let fn_nav_next; let mut fn_size_rules = None; - let mut set_rect = quote! { self.#core.rect = rect; }; + let mut set_rect = quote! { self.#core._rect = rect; }; let mut probe = quote! { ::kas::Tile::id(self) }; @@ -413,7 +413,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul } }); set_rect = quote! { - #core_path.rect = rect; + #core_path._rect = rect; ::kas::layout::LayoutVisitor::layout_visitor(self).set_rect(cx, rect, hints); }; probe = quote! { @@ -705,7 +705,9 @@ pub fn impl_core_methods(name: &str, core_path: &Toks) -> Toks { } #[inline] fn rect(&self) -> ::kas::geom::Rect { - #core_path.rect + #[cfg(debug_assertions)] + #core_path.status.require_rect(&#core_path._id); + #core_path._rect } #[inline] diff --git a/crates/kas-resvg/src/canvas.rs b/crates/kas-resvg/src/canvas.rs index 1cad121a0..388783796 100644 --- a/crates/kas-resvg/src/canvas.rs +++ b/crates/kas-resvg/src/canvas.rs @@ -166,8 +166,8 @@ impl_scope! { fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { let align = hints.complete_default(); let scale_factor = cx.size_cx().scale_factor(); - self.core.rect = self.scaling.align_rect(rect, align, scale_factor); - let size = self.core.rect.size.cast(); + widget_set_rect!(self.scaling.align_rect(rect, align, scale_factor)); + let size = self.rect().size.cast(); if let Some(fut) = self.inner.resize(size) { cx.push_spawn(self.id(), fut); diff --git a/crates/kas-resvg/src/svg.rs b/crates/kas-resvg/src/svg.rs index 6acdfe135..b10d7e907 100644 --- a/crates/kas-resvg/src/svg.rs +++ b/crates/kas-resvg/src/svg.rs @@ -246,8 +246,8 @@ impl_scope! { fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { let align = hints.complete_default(); let scale_factor = cx.size_cx().scale_factor(); - self.core.rect = self.scaling.align_rect(rect, align, scale_factor); - let size: (u32, u32) = self.core.rect.size.cast(); + widget_set_rect!(self.scaling.align_rect(rect, align, scale_factor)); + let size: (u32, u32) = self.rect().size.cast(); if let Some(fut) = self.inner.resize(size) { cx.push_spawn(self.id(), fut); @@ -294,7 +294,7 @@ impl_scope! { State::Ready(source, _) => State::Ready(source, pixmap), }; - let own_size: (u32, u32) = self.core.rect.size.cast(); + let own_size: (u32, u32) = self.rect().size.cast(); if size != own_size { if let Some(fut) = self.inner.resize(size) { cx.push_spawn(self.id(), fut); diff --git a/crates/kas-view/src/list_view.rs b/crates/kas-view/src/list_view.rs index 02720762b..6235d920f 100644 --- a/crates/kas-view/src/list_view.rs +++ b/crates/kas-view/src/list_view.rs @@ -296,7 +296,7 @@ impl_scope! { let mut skip = Offset::ZERO; skip.set_component(self.direction, self.skip); - let mut pos_start = self.core.rect.pos + self.frame_offset; + let mut pos_start = self.rect().pos + self.frame_offset; if self.direction.is_reversed() { first_data = (data_len - first_data).saturating_sub(cur_len); pos_start += skip * i32::conv(data_len.saturating_sub(1)); @@ -557,7 +557,7 @@ impl_scope! { fn draw(&mut self, mut draw: DrawCx) { let offset = self.scroll_offset(); - draw.with_clip_region(self.core.rect, offset, |mut draw| { + draw.with_clip_region(self.rect(), offset, |mut draw| { for child in &mut self.widgets[..self.cur_len.cast()] { if let Some(ref key) = child.key { if self.selection.contains(key) { @@ -659,7 +659,7 @@ impl_scope! { }; return if let Some(i_data) = data_index { // Set nav focus to i_data and update scroll position - let act = self.scroll.focus_rect(cx, solver.rect(i_data), self.core.rect); + let act = self.scroll.focus_rect(cx, solver.rect(i_data), self.rect()); if !act.is_empty() { cx.action(&self, act); self.update_widgets(&mut cx.config_cx(), data); @@ -707,7 +707,7 @@ impl_scope! { let (moved, used_by_sber) = self .scroll - .scroll_by_event(cx, event, self.id(), self.core.rect); + .scroll_by_event(cx, event, self.id(), self.rect()); if moved { self.update_widgets(&mut cx.config_cx(), data); } @@ -827,7 +827,7 @@ impl_scope! { last_data }; - let act = self.scroll.self_focus_rect(solver.rect(data_index), self.core.rect); + let act = self.scroll.self_focus_rect(solver.rect(data_index), self.rect()); if !act.is_empty() { cx.action(&self, act); self.update_widgets(cx, data); diff --git a/crates/kas-view/src/matrix_view.rs b/crates/kas-view/src/matrix_view.rs index a890daec9..101acf596 100644 --- a/crates/kas-view/src/matrix_view.rs +++ b/crates/kas-view/src/matrix_view.rs @@ -239,7 +239,7 @@ impl_scope! { fn position_solver(&self) -> PositionSolver { PositionSolver { - pos_start: self.core.rect.pos + self.frame_offset, + pos_start: self.rect().pos + self.frame_offset, skip: self.child_size + self.child_inter_margin, size: self.child_size, first_data: self.first_data, @@ -493,7 +493,7 @@ impl_scope! { let offset = self.scroll_offset(); let rect = self.rect() + offset; let num = self.num_children(); - draw.with_clip_region(self.core.rect, offset, |mut draw| { + draw.with_clip_region(self.rect(), offset, |mut draw| { for child in &mut self.widgets[..num] { if let Some(ref key) = child.key { // Note: we don't know which widgets within 0..num are @@ -605,7 +605,7 @@ impl_scope! { }; return if let Some((ci, ri)) = data_index { // Set nav focus and update scroll position - let action = self.scroll.focus_rect(cx, solver.rect(ci, ri), self.core.rect); + let action = self.scroll.focus_rect(cx, solver.rect(ci, ri), self.rect()); if !action.is_empty() { cx.action(&self, action); solver = self.update_widgets(&mut cx.config_cx(), data); @@ -671,7 +671,7 @@ impl_scope! { let (moved, used_by_sber) = self .scroll - .scroll_by_event(cx, event, self.id(), self.core.rect); + .scroll_by_event(cx, event, self.id(), self.rect()); if moved { self.update_widgets(&mut cx.config_cx(), data); } @@ -801,7 +801,7 @@ impl_scope! { (d_cols - 1, d_rows - 1) }; - let action = self.scroll.self_focus_rect(solver.rect(ci, ri), self.core.rect); + let action = self.scroll.self_focus_rect(solver.rect(ci, ri), self.rect()); if !action.is_empty() { cx.action(&self, action); solver = self.update_widgets(cx, data); diff --git a/crates/kas-widgets/src/check_box.rs b/crates/kas-widgets/src/check_box.rs index 706c95d87..a5072310c 100644 --- a/crates/kas-widgets/src/check_box.rs +++ b/crates/kas-widgets/src/check_box.rs @@ -189,7 +189,7 @@ impl_scope! { widget_set_rect!(rect); self.layout_visitor().set_rect(cx, rect, hints); let dir = self.direction(); - shrink_to_text(&mut self.core.rect, dir, &self.label); + shrink_to_text(&mut self.rect(), dir, &self.label); } } diff --git a/crates/kas-widgets/src/edit.rs b/crates/kas-widgets/src/edit.rs index 2257ff57f..7ae54c53a 100644 --- a/crates/kas-widgets/src/edit.rs +++ b/crates/kas-widgets/src/edit.rs @@ -376,7 +376,7 @@ impl_scope! { } fn set_rect(&mut self, cx: &mut ConfigCx, outer_rect: Rect, hints: AlignHints) { - self.core.rect = outer_rect; + widget_set_rect!(outer_rect); let mut rect = outer_rect; rect.pos += self.frame_offset; rect.size -= self.frame_size; diff --git a/crates/kas-widgets/src/grip.rs b/crates/kas-widgets/src/grip.rs index dfd458c00..fb692af7f 100644 --- a/crates/kas-widgets/src/grip.rs +++ b/crates/kas-widgets/src/grip.rs @@ -114,108 +114,108 @@ impl_scope! { } } } -} -impl GripPart { - /// Construct - pub fn new() -> Self { - GripPart { - core: Default::default(), - track: Default::default(), - press_coord: Coord::ZERO, + impl GripPart { + /// Construct + pub fn new() -> Self { + GripPart { + core: Default::default(), + track: Default::default(), + press_coord: Coord::ZERO, + } } - } - /// Set the track - /// - /// The `track` is the region within which the grip may be moved. - /// - /// This method must be called to set the `track`, presumably from the - /// parent widget's [`Layout::set_rect`] method. - /// It is expected that [`GripPart::set_offset`] is called after this. - pub fn set_track(&mut self, track: Rect) { - self.track = track; - } + /// Set the track + /// + /// The `track` is the region within which the grip may be moved. + /// + /// This method must be called to set the `track`, presumably from the + /// parent widget's [`Layout::set_rect`] method. + /// It is expected that [`GripPart::set_offset`] is called after this. + pub fn set_track(&mut self, track: Rect) { + self.track = track; + } - /// Get the current track `Rect` - #[inline] - pub fn track(&self) -> Rect { - self.track - } + /// Get the current track `Rect` + #[inline] + pub fn track(&self) -> Rect { + self.track + } - /// Set the grip's size - /// - /// It is expected that for each axis the `size` is no larger than the size - /// of the `track` (see [`GripPart::set_track`]). If equal, then the grip - /// may not be moved on this axis. - /// - /// This method must be called at least once. - /// It is expected that [`GripPart::set_offset`] is called after this. - /// - /// This size may be read via `self.rect().size`. - pub fn set_size(&mut self, size: Size) { - self.core.rect.size = size; - } + /// Set the grip's size + /// + /// It is expected that for each axis the `size` is no larger than the size + /// of the `track` (see [`GripPart::set_track`]). If equal, then the grip + /// may not be moved on this axis. + /// + /// This method must be called at least once. + /// It is expected that [`GripPart::set_offset`] is called after this. + /// + /// This size may be read via `self.rect().size`. + pub fn set_size(&mut self, size: Size) { + widget_set_rect!(Rect { pos: self.rect().pos, size, }); + } - /// Get the current grip position - /// - /// The position returned is relative to `self.track().pos` and is always - /// between [`Offset::ZERO`] and [`Self::max_offset`]. - #[inline] - pub fn offset(&self) -> Offset { - self.core.rect.pos - self.track.pos - } + /// Get the current grip position + /// + /// The position returned is relative to `self.track().pos` and is always + /// between [`Offset::ZERO`] and [`Self::max_offset`]. + #[inline] + pub fn offset(&self) -> Offset { + self.rect().pos - self.track.pos + } - /// Get the maximum allowed offset - /// - /// This is the maximum allowed [`Self::offset`], equal to the size of the - /// track minus the size of the grip. - #[inline] - pub fn max_offset(&self) -> Offset { - Offset::conv(self.track.size) - Offset::conv(self.core.rect.size) - } + /// Get the maximum allowed offset + /// + /// This is the maximum allowed [`Self::offset`], equal to the size of the + /// track minus the size of the grip. + #[inline] + pub fn max_offset(&self) -> Offset { + Offset::conv(self.track.size) - Offset::conv(self.rect().size) + } - /// Set a new grip position - /// - /// The input `offset` is clamped between [`Offset::ZERO`] and - /// [`Self::max_offset`]. - /// - /// The return value is a tuple of the new offest and an action: - /// [`Action::REDRAW`] if the grip has moved, otherwise an empty action. - /// - /// It is expected that [`Self::set_track`] and [`Self::set_size`] are - /// called before this method. - pub fn set_offset(&mut self, offset: Offset) -> (Offset, Action) { - let offset = offset.min(self.max_offset()).max(Offset::ZERO); - let grip_pos = self.track.pos + offset; - if grip_pos != self.core.rect.pos { - self.core.rect.pos = grip_pos; - (offset, Action::REDRAW) - } else { - (offset, Action::empty()) + /// Set a new grip position + /// + /// The input `offset` is clamped between [`Offset::ZERO`] and + /// [`Self::max_offset`]. + /// + /// The return value is a tuple of the new offest and an action: + /// [`Action::REDRAW`] if the grip has moved, otherwise an empty action. + /// + /// It is expected that [`Self::set_track`] and [`Self::set_size`] are + /// called before this method. + pub fn set_offset(&mut self, offset: Offset) -> (Offset, Action) { + let offset = offset.min(self.max_offset()).max(Offset::ZERO); + let grip_pos = self.track.pos + offset; + if grip_pos != self.rect().pos { + widget_set_rect!(Rect { pos: grip_pos, size: self.rect().size }); + (offset, Action::REDRAW) + } else { + (offset, Action::empty()) + } } - } - /// Handle an event on the track itself - /// - /// If it is desired to make the grip move when the track area is clicked, - /// then the parent widget should call this method when receiving - /// [`Event::PressStart`]. - /// - /// Returns the new grip position relative to the track. - /// - /// The grip position is not adjusted; the caller should also call - /// [`Self::set_offset`] to do so. This is separate to allow adjustment of - /// the posision; e.g. `Slider` pins the position to the nearest detent. - pub fn handle_press_on_track(&mut self, cx: &mut EventCx, press: &Press) -> Offset { - press - .grab(self.id()) - .with_icon(CursorIcon::Grabbing) - .with_cx(cx); - - let offset = press.coord - self.track.pos - Offset::conv(self.core.rect.size / 2); - let offset = offset.clamp(Offset::ZERO, self.max_offset()); - self.press_coord = press.coord - offset; - offset + /// Handle an event on the track itself + /// + /// If it is desired to make the grip move when the track area is clicked, + /// then the parent widget should call this method when receiving + /// [`Event::PressStart`]. + /// + /// Returns the new grip position relative to the track. + /// + /// The grip position is not adjusted; the caller should also call + /// [`Self::set_offset`] to do so. This is separate to allow adjustment of + /// the posision; e.g. `Slider` pins the position to the nearest detent. + pub fn handle_press_on_track(&mut self, cx: &mut EventCx, press: &Press) -> Offset { + press + .grab(self.id()) + .with_icon(CursorIcon::Grabbing) + .with_cx(cx); + + let offset = press.coord - self.track.pos - Offset::conv(self.rect().size / 2); + let offset = offset.clamp(Offset::ZERO, self.max_offset()); + self.press_coord = press.coord - offset; + offset + } } } diff --git a/crates/kas-widgets/src/image.rs b/crates/kas-widgets/src/image.rs index 5f0e9200b..565d65864 100644 --- a/crates/kas-widgets/src/image.rs +++ b/crates/kas-widgets/src/image.rs @@ -154,7 +154,7 @@ impl_scope! { fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { let align = hints.complete_default(); let scale_factor = cx.size_cx().scale_factor(); - self.core.rect = self.scaling.align_rect(rect, align, scale_factor); + widget_set_rect!(self.scaling.align_rect(rect, align, scale_factor)); } fn draw(&mut self, mut draw: DrawCx) { diff --git a/crates/kas-widgets/src/mark.rs b/crates/kas-widgets/src/mark.rs index e93a7447d..51c75ca9c 100644 --- a/crates/kas-widgets/src/mark.rs +++ b/crates/kas-widgets/src/mark.rs @@ -51,7 +51,7 @@ impl_scope! { } fn draw(&mut self, mut draw: DrawCx) { - draw.mark(self.core.rect, self.style); + draw.mark(self.rect(), self.style); } } } @@ -92,7 +92,7 @@ impl_scope! { } fn draw(&mut self, mut draw: DrawCx) { - draw.mark(self.core.rect, self.style); + draw.mark(self.rect(), self.style); } } diff --git a/crates/kas-widgets/src/menu/menubar.rs b/crates/kas-widgets/src/menu/menubar.rs index 455b47ece..dab4a75ef 100644 --- a/crates/kas-widgets/src/menu/menubar.rs +++ b/crates/kas-widgets/src/menu/menubar.rs @@ -104,7 +104,8 @@ impl_scope! { fn draw(&mut self, mut draw: DrawCx) { let solver = RowPositionSolver::new(self.direction); - solver.for_children_mut(&mut self.widgets, self.core.rect, |w| w.draw(draw.re())); + let rect = self.rect(); + solver.for_children_mut(&mut self.widgets, rect, |w| w.draw(draw.re())); } } diff --git a/crates/kas-widgets/src/radio_box.rs b/crates/kas-widgets/src/radio_box.rs index 493a6c61e..ab4c2d5bd 100644 --- a/crates/kas-widgets/src/radio_box.rs +++ b/crates/kas-widgets/src/radio_box.rs @@ -150,7 +150,7 @@ impl_scope! { widget_set_rect!(rect); self.layout_visitor().set_rect(cx, rect, hints); let dir = self.direction(); - crate::check_box::shrink_to_text(&mut self.core.rect, dir, &self.label); + crate::check_box::shrink_to_text(&mut self.rect(), dir, &self.label); } } diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index 001dee636..ad1af5b05 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -118,7 +118,7 @@ impl_scope! { } fn draw(&mut self, mut draw: DrawCx) { - draw.with_clip_region(self.core.rect, self.scroll_offset(), |mut draw| { + draw.with_clip_region(self.rect(), self.scroll_offset(), |mut draw| { self.inner.draw(draw.re()); }); } @@ -143,7 +143,7 @@ impl_scope! { fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed { self.scroll - .scroll_by_event(cx, event, self.id(), self.core.rect) + .scroll_by_event(cx, event, self.id(), self.rect()) .1 } diff --git a/crates/kas-widgets/src/scroll_bar.rs b/crates/kas-widgets/src/scroll_bar.rs index 9c193165d..f0b5b7fd6 100644 --- a/crates/kas-widgets/src/scroll_bar.rs +++ b/crates/kas-widgets/src/scroll_bar.rs @@ -207,8 +207,8 @@ impl_scope! { #[inline] fn bar_len(&self) -> i32 { match self.direction.is_vertical() { - false => self.core.rect.size.0, - true => self.core.rect.size.1, + false => self.rect().size.0, + true => self.rect().size.1, } } @@ -217,7 +217,7 @@ impl_scope! { let total = 1i64.max(i64::from(self.max_value) + i64::from(self.grip_size)); let grip_len = i64::from(self.grip_size) * i64::conv(len) / total; self.grip_len = i32::conv(grip_len).max(self.min_grip_len).min(len); - let mut size = self.core.rect.size; + let mut size = self.rect().size; size.set_component(self.direction, self.grip_len); self.grip.set_size(size); self.grip.set_offset(self.offset()).1 @@ -489,7 +489,7 @@ impl_scope! { if self.show_bars.1 { let pos = Coord(rect.pos2().0 - bar_width, pos.1); - let size = Size::new(bar_width, self.core.rect.size.1); + let size = Size::new(bar_width, self.rect().size.1); self.vert_bar.set_rect(cx, Rect { pos, size }, AlignHints::NONE); let _ = self.vert_bar.set_limits(max_scroll_offset.1, rect.size.1); } else { diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index 4c8e90436..bda7d2201 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -316,12 +316,12 @@ impl Splitter { let index = 2 * n + 1; let hrect = self.grips[n].rect(); - let width1 = (hrect.pos - self.core.rect.pos).extract(self.direction); - let width2 = (self.core.rect.size - hrect.size).extract(self.direction) - width1; + let width1 = (hrect.pos - self.rect().pos).extract(self.direction); + let width2 = (self.rect().size - hrect.size).extract(self.direction) - width1; let dim = (self.direction, self.num_children()); let mut setter = - layout::RowSetter::, _>::new_unsolved(self.core.rect, dim, &mut self.data); + layout::RowSetter::, _>::new_unsolved(self.rect(), dim, &mut self.data); setter.solve_range(&mut self.data, 0..index, width1); setter.solve_range(&mut self.data, (index + 1)..dim.1, width2); setter.update_offsets(&mut self.data); diff --git a/crates/kas-widgets/src/stack.rs b/crates/kas-widgets/src/stack.rs index cf1783480..581928107 100644 --- a/crates/kas-widgets/src/stack.rs +++ b/crates/kas-widgets/src/stack.rs @@ -286,6 +286,7 @@ impl Stack { } self.active = index; + let rect = self.rect(); let id = self.make_child_id(index); if let Some(entry) = self.widgets.get_mut(index) { let node = entry.0.as_node(data); @@ -301,7 +302,7 @@ impl Stack { cx.resize(self); } else { debug_assert_eq!(entry.1, State::Sized); - entry.0.set_rect(cx, self.core.rect, self.align_hints); + entry.0.set_rect(cx, rect, self.align_hints); cx.region_moved(); } } else { @@ -349,10 +350,10 @@ impl Stack { /// Configure and size the page at index fn configure_and_size(&mut self, cx: &mut ConfigCx, data: &W::Data, index: usize) { + let Size(w, h) = self.rect().size; let id = self.make_child_id(index); if let Some(entry) = self.widgets.get_mut(index) { cx.configure(entry.0.as_node(data), id); - let Size(w, h) = self.core.rect.size; solve_size_rules(&mut entry.0, cx.size_cx(), Some(w), Some(h)); entry.1 = State::Sized; } diff --git a/examples/clock.rs b/examples/clock.rs index 523bb3f65..01cd88f91 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -52,7 +52,7 @@ impl_scope! { let size = Size::splat(size); let excess = rect.size - size; let pos = rect.pos + excess / 2; - self.core.rect = Rect { pos, size }; + widget_set_rect!(Rect { pos, size }); let text_size = Size(size.0, size.1 / 4); let text_height = text_size.1 as f32; @@ -81,7 +81,7 @@ impl_scope! { // not themeable, but gives us much more flexible draw routines. let mut draw = draw.draw_iface::<::DrawShared>().unwrap(); - let rect = self.core.rect; + let rect = self.rect(); let quad = Quad::conv(rect); draw.circle(quad, 0.0, col_back); draw.circle(quad, 0.98, col_face); diff --git a/examples/mandlebrot/mandlebrot.rs b/examples/mandlebrot/mandlebrot.rs index be0c391a5..a0a932e71 100644 --- a/examples/mandlebrot/mandlebrot.rs +++ b/examples/mandlebrot/mandlebrot.rs @@ -354,7 +354,7 @@ impl_scope! { let draw = draw.draw_device(); let draw = DrawIface::>::downcast_from(draw).unwrap(); let p = (self.alpha, self.delta, self.rel_width, self.iters); - draw.draw.custom(draw.get_pass(), self.core.rect, p); + draw.draw.custom(draw.get_pass(), self.rect(), p); } } diff --git a/examples/proxy.rs b/examples/proxy.rs index f49c1ba7b..9c037962a 100644 --- a/examples/proxy.rs +++ b/examples/proxy.rs @@ -79,7 +79,7 @@ impl_scope! { let draw = draw.draw_device(); draw.rect((self.rect()).cast(), color); } else { - draw.text(self.core.rect, &self.loading_text); + draw.text(self.rect(), &self.loading_text); } } } From 2ec2d2dce34c69214650545e8b86a6ad35b68f32 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 22 Jan 2025 09:20:43 +0000 Subject: [PATCH 7/8] Rename CoreData -> DefaultCoreType and hide --- crates/kas-core/src/core/data.rs | 12 +++++------- crates/kas-macros/src/lib.rs | 10 ++-------- crates/kas-macros/src/widget.rs | 2 +- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/crates/kas-core/src/core/data.rs b/crates/kas-core/src/core/data.rs index 20de06509..5227c75f5 100644 --- a/crates/kas-core/src/core/data.rs +++ b/crates/kas-core/src/core/data.rs @@ -34,21 +34,19 @@ impl Icon { /// Common widget data /// /// This type may be used for a [`Widget`]'s `core: widget_core!()` field. +#[cfg_attr(not(feature = "internal_doc"), doc(hidden))] +#[cfg_attr(docsrs, doc(cfg(internal_doc)))] #[derive(Default, Debug)] -pub struct CoreData { +pub struct DefaultCoreType { pub _rect: Rect, pub _id: Id, - #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] - #[cfg_attr(docsrs, doc(cfg(internal_doc)))] #[cfg(debug_assertions)] pub status: WidgetStatus, } -/// Note: the clone has default-initialised identifier. -/// Configuration and layout solving is required as for any other widget. -impl Clone for CoreData { +impl Clone for DefaultCoreType { fn clone(&self) -> Self { - CoreData { + DefaultCoreType { _rect: self._rect, _id: Default::default(), status: self.status, diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index c5e9c5994..4cf95296a 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -200,13 +200,8 @@ pub fn impl_scope(input: TokenStream) -> TokenStream { /// The struct must contain a field of type `widget_core!()` (usually named /// `core`). The macro `widget_core!()` is a placeholder, expanded by /// `#[widget]` and used to identify the field used (any name may be used). -/// This field *might* have type [`CoreData`] or might use a special generated -/// type; either way it has fields `id: Id` (assigned by during configure) -/// and `rect: Rect` (usually assigned by -/// `Layout::set_rect`). It may contain additional fields for layout data. The -/// type supports `Default` and `Clone` (although `Clone` actually -/// default-initializes all fields other than `rect` since clones of widgets -/// must themselves be configured). +/// This type implements [`Default`] and [`Clone`], though the clone is not an +/// exact clone (cloned widgets must still be configured). /// /// Assuming the deriving type is a `struct` or `tuple struct`, fields support /// the following attributes: @@ -359,7 +354,6 @@ pub fn impl_scope(input: TokenStream) -> TokenStream { /// [`Events`]: https://docs.rs/kas/latest/kas/trait.Events.html /// [`CursorIcon`]: https://docs.rs/kas/latest/kas/event/enum.CursorIcon.html /// [`IsUsed`]: https://docs.rs/kas/latest/kas/event/enum.IsUsed.html -/// [`CoreData`]: https://docs.rs/kas/latest/kas/struct.CoreData.html /// [`Deref`]: std::ops::Deref #[proc_macro_attribute] #[proc_macro_error] diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 4f24dcfd4..d59488739 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -226,7 +226,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul path: core_type.into(), }); } else { - field.ty = parse_quote! { ::kas::CoreData }; + field.ty = parse_quote! { ::kas::DefaultCoreType }; } continue; From a52f2ca5cf476a3eca47b24c60594b153dc911e3 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 23 Jan 2025 08:33:59 +0000 Subject: [PATCH 8/8] Ignore example code snippet --- crates/kas-macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 4cf95296a..5919cf4ec 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -449,7 +449,7 @@ pub fn widget_index(input: TokenStream) -> TokenStream { /// [`widget`](macro@widget) attribute. /// /// Example usage: -/// ``` +/// ```ignore /// fn set_rect(&mut self, _: &mut ConfigCx, rect: Rect, _: AlignHints) { /// widget_set_rect!(rect); /// }