Add arbitrary properties to widgets (#873)

Add `Properties[Mut]` argument to widget methods.
Add a third TreeArena to sort per-widget arbitrary property values.

For the type-to-value map, I considered the following crates:
- https://docs.rs/typemap/latest/typemap/
- https://crates.io/crates/typemap_rev
- https://crates.io/crates/typemap-ors
- https://github.com/chris-morgan/anymap
- https://github.com/reivilibre/anymap3

Of these, anymap3 is the only one actively maintained (last commit less
than 12 months ago). The code source itself is extremely light and
simple; we may or may not decide to roll out our own implementation down
the line.

Add a BackgroundBrush property used by SizedBox as a proof of concept.

Note that SizedBox still has its `background` field, but we should
expect future widgets to use almost *exclusively* properties; properties
usually shouldn't be redundant with local fields.

To get there, we'll first need to better integrate properties in Xilem.
This commit is contained in:
Olivier FAURE 2025-03-06 11:32:06 +00:00 committed by GitHub
parent 4e23dc5425
commit 60c037dc1f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 1919 additions and 418 deletions

7
Cargo.lock generated
View File

@ -204,6 +204,12 @@ version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "anymap3"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "170433209e817da6aae2c51aa0dd443009a613425dd041ebfb2492d1c4c11a25"
[[package]]
name = "arrayref"
version = "0.3.9"
@ -1879,6 +1885,7 @@ version = "0.2.0"
dependencies = [
"accesskit",
"accesskit_winit",
"anymap3",
"assert_matches",
"cursor-icon",
"dpi",

View File

@ -57,6 +57,7 @@ dpi.workspace = true
nv-flip.workspace = true
tracing-tracy = { version = "0.11.3", optional = true }
wgpu-profiler = { optional = true, version = "0.19.0", default-features = false }
anymap3 = "1.0.1"
[target.'cfg(target_arch = "wasm32")'.dependencies]
web-time.workspace = true

View File

@ -16,8 +16,8 @@ use accesskit::{Node, Role};
use masonry::app::{AppDriver, DriverCtx};
use masonry::core::{
AccessCtx, AccessEvent, Action, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent,
QueryCtx, RegisterCtx, StyleProperty, TextEvent, Update, UpdateCtx, Widget, WidgetId,
WidgetPod,
PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, StyleProperty, TextEvent, Update,
UpdateCtx, Widget, WidgetId, WidgetPod,
};
use masonry::dpi::LogicalSize;
use masonry::kurbo::{Point, Size};
@ -148,7 +148,12 @@ impl CalcButton {
}
impl Widget for CalcButton {
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) {
fn on_pointer_event(
&mut self,
ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
event: &PointerEvent,
) {
match event {
PointerEvent::PointerDown(_, _) => {
if !ctx.is_disabled() {
@ -176,9 +181,20 @@ impl Widget for CalcButton {
}
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) {
fn on_access_event(
&mut self,
ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
event: &AccessEvent,
) {
if ctx.target() == ctx.widget_id() {
match event.action {
accesskit::Action::Click => {
@ -189,7 +205,7 @@ impl Widget for CalcButton {
}
}
fn update(&mut self, ctx: &mut UpdateCtx, event: &Update) {
fn update(&mut self, ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, event: &Update) {
// Masonry doesn't let us change a widget's attributes directly.
// We use `mutate_later` to get a mutable reference to the inner widget
// and change its border color. This is a simple way to implement a
@ -214,20 +230,25 @@ impl Widget for CalcButton {
ctx.register_child(&mut self.inner);
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
let size = ctx.run_layout(&mut self.inner, bc);
ctx.place_child(&mut self.inner, Point::ORIGIN);
size
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut Scene) {}
fn paint(&mut self, _ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, _scene: &mut Scene) {}
fn accessibility_role(&self) -> Role {
Role::Button
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, node: &mut Node) {
fn accessibility(&mut self, _ctx: &mut AccessCtx, _props: &PropertiesRef<'_>, node: &mut Node) {
let _name = match self.action {
CalcAction::Digit(digit) => digit.to_string(),
CalcAction::Op(op) => op.to_string(),

View File

@ -14,7 +14,7 @@ use accesskit::{Node, Role};
use masonry::app::{AppDriver, DriverCtx};
use masonry::core::{
AccessCtx, AccessEvent, Action, BoxConstraints, EventCtx, LayoutCtx, ObjectFit, PaintCtx,
PointerEvent, QueryCtx, RegisterCtx, TextEvent, Widget, WidgetId,
PointerEvent, PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, TextEvent, Widget, WidgetId,
};
use masonry::kurbo::{Affine, BezPath, Point, Rect, Size, Stroke};
use masonry::palette;
@ -37,15 +37,38 @@ impl AppDriver for Driver {
struct CustomWidget(String);
impl Widget for CustomWidget {
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {}
fn on_pointer_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &PointerEvent,
) {
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn register_children(&mut self, _ctx: &mut RegisterCtx) {}
fn layout(&mut self, _layout_ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
_layout_ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
// BoxConstraints are passed by the parent widget.
// This method can return any Size within those constraints:
// bc.constrain(my_size)
@ -68,7 +91,7 @@ impl Widget for CustomWidget {
// The paint method gets called last, after an event flow.
// It goes event -> update -> layout -> paint, and each method can influence the next.
// Basically, anything that changes the appearance of a widget causes a paint.
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, scene: &mut Scene) {
// Clear the whole widget with the color of your choice
// (ctx.size() returns the size of the layout rect we're painting in)
// Note: ctx also has a `clear` method, but that clears the whole context,
@ -137,7 +160,7 @@ impl Widget for CustomWidget {
Role::Window
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, node: &mut Node) {
fn accessibility(&mut self, _ctx: &mut AccessCtx, _props: &PropertiesRef<'_>, node: &mut Node) {
let text = &self.0;
node.set_label(
format!("This is a demo of the Masonry Widget trait. Masonry has accessibility tree support. The demo shows colored shapes with the text '{text}'."),

View File

@ -741,7 +741,8 @@ impl MasonryState<'_> {
window.show_window_menu(position);
}
RenderRootSignal::WidgetSelectedInInspector(widget_id) => {
let (widget, state) = self.render_root.widget_arena.get_pair(widget_id);
let (widget, state, _properties) =
self.render_root.widget_arena.get_all(widget_id);
let widget_name = widget.item.short_type_name();
let display_name = if let Some(debug_text) = widget.item.get_debug_text() {
format!("{widget_name}<{debug_text}>")

View File

@ -4,6 +4,7 @@
use std::collections::{HashMap, VecDeque};
use accesskit::{ActionRequest, TreeUpdate};
use anymap3::AnyMap;
use parley::fontique::{self, Collection, CollectionOptions, SourceCache};
use parley::{FontContext, LayoutContext};
use tracing::{info_span, warn};
@ -21,8 +22,8 @@ use web_time::Instant;
use crate::Handled;
use crate::core::{
AccessEvent, Action, BrushIndex, PointerEvent, QueryCtx, TextEvent, Widget, WidgetArena,
WidgetId, WidgetMut, WidgetPod, WidgetRef, WidgetState, WindowEvent,
AccessEvent, Action, BrushIndex, PointerEvent, PropertiesRef, QueryCtx, TextEvent, Widget,
WidgetArena, WidgetId, WidgetMut, WidgetPod, WidgetRef, WidgetState, WindowEvent,
};
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize};
use crate::passes::accessibility::run_accessibility_pass;
@ -294,6 +295,7 @@ impl RenderRoot {
widget_arena: WidgetArena {
widgets: TreeArena::new(),
states: TreeArena::new(),
properties: TreeArena::new(),
},
rebuild_access_tree: true,
debug_paint,
@ -476,21 +478,32 @@ impl RenderRoot {
pub fn get_root_widget(&self) -> WidgetRef<dyn Widget> {
let root_state_token = self.widget_arena.states.roots();
let root_widget_token = self.widget_arena.widgets.roots();
let root_properties_token = self.widget_arena.properties.roots();
let state_ref = root_state_token
.into_item(self.root.id())
.expect("root widget not in widget tree");
let widget_ref = root_widget_token
.into_item(self.root.id())
.expect("root widget not in widget tree");
let properties_ref = root_properties_token
.into_item(self.root.id())
.expect("root widget not in widget tree");
let widget = &**widget_ref.item;
let ctx = QueryCtx {
global_state: &self.global_state,
widget_state_children: state_ref.children,
widget_children: widget_ref.children,
widget_state: state_ref.item,
properties_children: properties_ref.children,
};
WidgetRef { ctx, widget }
let properties = PropertiesRef {
map: properties_ref.item,
};
WidgetRef {
ctx,
properties,
widget,
}
}
/// Get a [`WidgetRef`] to a specific widget.
@ -501,6 +514,11 @@ impl RenderRoot {
.widgets
.find(id)
.expect("found state but not widget");
let properties_ref = self
.widget_arena
.properties
.find(id)
.expect("found state but not properties");
let widget = &**widget_ref.item;
let ctx = QueryCtx {
@ -508,8 +526,16 @@ impl RenderRoot {
widget_state_children: state_ref.children,
widget_children: widget_ref.children,
widget_state: state_ref.item,
properties_children: properties_ref.children,
};
Some(WidgetRef { ctx, widget })
let properties = PropertiesRef {
map: properties_ref.item,
};
Some(WidgetRef {
ctx,
properties,
widget,
})
}
/// Get a [`WidgetMut`] to the root widget.
@ -622,6 +648,7 @@ impl RenderRoot {
fn request_render_all_in(
mut widget: ArenaMut<'_, Box<dyn Widget>>,
state: ArenaMut<'_, WidgetState>,
properties: ArenaMut<'_, AnyMap>,
) {
state.item.needs_paint = true;
state.item.needs_accessibility = true;
@ -633,14 +660,16 @@ impl RenderRoot {
id,
widget.reborrow_mut(),
state.children,
|widget, mut state| {
request_render_all_in(widget, state.reborrow_mut());
properties.children,
|widget, state, properties| {
request_render_all_in(widget, state, properties);
},
);
}
let (root_widget, mut root_state) = self.widget_arena.get_pair_mut(self.root.id());
request_render_all_in(root_widget, root_state.reborrow_mut());
let (root_widget, root_state, root_properties) =
self.widget_arena.get_all_mut(self.root.id());
request_render_all_in(root_widget, root_state, root_properties);
self.global_state
.emit_signal(RenderRootSignal::RequestRedraw);
}

View File

@ -4,6 +4,7 @@
//! The context types that are passed into various widget methods.
use accesskit::TreeUpdate;
use anymap3::AnyMap;
use dpi::LogicalPosition;
use parley::{FontContext, LayoutContext};
use tracing::{trace, warn};
@ -12,8 +13,8 @@ use winit::window::ResizeDirection;
use crate::app::{MutateCallback, RenderRootSignal, RenderRootState};
use crate::core::{
Action, AllowRawMut, BoxConstraints, BrushIndex, CreateWidget, FromDynWidget, Widget, WidgetId,
WidgetMut, WidgetPod, WidgetRef, WidgetState,
Action, AllowRawMut, BoxConstraints, BrushIndex, CreateWidget, FromDynWidget, PropertiesMut,
PropertiesRef, Widget, WidgetId, WidgetMut, WidgetPod, WidgetRef, WidgetState,
};
use crate::kurbo::{Affine, Insets, Point, Rect, Size, Vec2};
use crate::passes::layout::run_layout_on;
@ -50,6 +51,8 @@ pub struct MutateCtx<'a> {
pub(crate) widget_state: &'a mut WidgetState,
pub(crate) widget_state_children: ArenaMutList<'a, WidgetState>,
pub(crate) widget_children: ArenaMutList<'a, Box<dyn Widget>>,
pub(crate) properties: PropertiesMut<'a>,
pub(crate) properties_children: ArenaMutList<'a, AnyMap>,
}
/// A context provided inside of [`WidgetRef`].
@ -61,6 +64,7 @@ pub struct QueryCtx<'a> {
pub(crate) widget_state: &'a WidgetState,
pub(crate) widget_state_children: ArenaRefList<'a, WidgetState>,
pub(crate) widget_children: ArenaRefList<'a, Box<dyn Widget>>,
pub(crate) properties_children: ArenaRefList<'a, AnyMap>,
}
/// A context provided to Widget event-handling methods.
@ -69,6 +73,7 @@ pub struct EventCtx<'a> {
pub(crate) widget_state: &'a mut WidgetState,
pub(crate) widget_state_children: ArenaMutList<'a, WidgetState>,
pub(crate) widget_children: ArenaMutList<'a, Box<dyn Widget>>,
pub(crate) properties_children: ArenaMutList<'a, AnyMap>,
pub(crate) target: WidgetId,
pub(crate) allow_pointer_capture: bool,
pub(crate) is_handled: bool,
@ -78,6 +83,7 @@ pub struct EventCtx<'a> {
pub struct RegisterCtx<'a> {
pub(crate) widget_state_children: ArenaMutList<'a, WidgetState>,
pub(crate) widget_children: ArenaMutList<'a, Box<dyn Widget>>,
pub(crate) properties_children: ArenaMutList<'a, AnyMap>,
#[cfg(debug_assertions)]
pub(crate) registered_ids: Vec<WidgetId>,
}
@ -88,6 +94,7 @@ pub struct UpdateCtx<'a> {
pub(crate) widget_state: &'a mut WidgetState,
pub(crate) widget_state_children: ArenaMutList<'a, WidgetState>,
pub(crate) widget_children: ArenaMutList<'a, Box<dyn Widget>>,
pub(crate) properties_children: ArenaMutList<'a, AnyMap>,
}
// TODO - Change this once other layout methods are added.
@ -97,6 +104,7 @@ pub struct LayoutCtx<'a> {
pub(crate) widget_state: &'a mut WidgetState,
pub(crate) widget_state_children: ArenaMutList<'a, WidgetState>,
pub(crate) widget_children: ArenaMutList<'a, Box<dyn Widget>>,
pub(crate) properties_children: ArenaMutList<'a, AnyMap>,
}
/// A context provided to the [`Widget::compose`] method.
@ -122,6 +130,7 @@ pub struct AccessCtx<'a> {
pub(crate) widget_state: &'a WidgetState,
pub(crate) widget_state_children: ArenaMutList<'a, WidgetState>,
pub(crate) widget_children: ArenaMutList<'a, Box<dyn Widget>>,
pub(crate) properties_children: ArenaMutList<'a, AnyMap>,
pub(crate) tree_update: &'a mut TreeUpdate,
pub(crate) rebuild_all: bool,
}
@ -220,12 +229,20 @@ impl MutateCtx<'_> {
.widget_children
.item_mut(child.id())
.expect("get_mut: child not found");
let child_properties = self
.properties_children
.item_mut(child.id())
.expect("get_mut: child not found");
let child_ctx = MutateCtx {
global_state: self.global_state,
parent_widget_state: Some(&mut self.widget_state),
widget_state: child_state_mut.item,
widget_state_children: child_state_mut.children,
widget_children: child_mut.children,
properties: PropertiesMut {
map: child_properties.item,
},
properties_children: child_properties.children,
};
WidgetMut {
ctx: child_ctx,
@ -243,6 +260,18 @@ impl MutateCtx<'_> {
widget_state: self.widget_state,
widget_state_children: self.widget_state_children.reborrow_mut(),
widget_children: self.widget_children.reborrow_mut(),
properties: self.properties.reborrow_mut(),
properties_children: self.properties_children.reborrow_mut(),
}
}
pub(crate) fn update_mut(&mut self) -> UpdateCtx<'_> {
UpdateCtx {
global_state: self.global_state,
widget_state: self.widget_state,
widget_state_children: self.widget_state_children.reborrow_mut(),
widget_children: self.widget_children.reborrow_mut(),
properties_children: self.properties_children.reborrow_mut(),
}
}
@ -265,21 +294,30 @@ impl<'w> QueryCtx<'w> {
.widget_state_children
.into_item(child)
.expect("get: child not found");
let child = self
let child_widget = self
.widget_children
.into_item(child)
.expect("get: child not found");
let child_properties = self
.properties_children
.into_item(child)
.expect("get: child not found");
let ctx = QueryCtx {
global_state: self.global_state,
widget_state_children: child_state.children,
widget_children: child.children,
widget_children: child_widget.children,
widget_state: child_state.item,
properties_children: child_properties.children,
};
let properties = PropertiesRef {
map: child_properties.item,
};
WidgetRef {
ctx,
widget: &**child.item,
properties,
widget: &**child_widget.item,
}
}
}
@ -1185,7 +1223,12 @@ impl RegisterCtx<'_> {
/// Container widgets should call this on all their children in
/// their implementation of [`Widget::register_children`].
pub fn register_child(&mut self, child: &mut WidgetPod<impl Widget + ?Sized>) {
let Some(CreateWidget { widget, transform }) = child.take_inner() else {
let Some(CreateWidget {
widget,
transform,
properties,
}) = child.take_inner()
else {
return;
};
@ -1199,6 +1242,7 @@ impl RegisterCtx<'_> {
self.widget_children.insert(id, widget.as_box_dyn());
self.widget_state_children.insert(id, state);
self.properties_children.insert(id, properties.map);
}
}
@ -1250,11 +1294,16 @@ macro_rules! impl_get_raw {
.widget_children
.item_mut(child.id())
.expect("get_raw_ref: child not found");
let child_properties = self
.properties_children
.item_mut(child.id())
.expect("get_raw_ref: child not found");
#[allow(clippy::needless_update)]
let child_ctx = $SomeCtx {
widget_state: child_state_mut.item,
widget_state_children: child_state_mut.children,
widget_children: child_mut.children,
properties_children: child_properties.children,
global_state: self.global_state,
..*self
};
@ -1283,11 +1332,16 @@ macro_rules! impl_get_raw {
.widget_children
.item_mut(child.id())
.expect("get_raw_mut: child not found");
let child_properties = self
.properties_children
.item_mut(child.id())
.expect("get_raw_mut: child not found");
#[allow(clippy::needless_update)]
let child_ctx = $SomeCtx {
widget_state: child_state_mut.item,
widget_state_children: child_state_mut.children,
widget_children: child_mut.children,
properties_children: child_properties.children,
global_state: self.global_state,
..*self
};
@ -1323,10 +1377,15 @@ impl<'s> AccessCtx<'s> {
.widget_children
.item_mut(child.id())
.expect("get_raw_ref: child not found");
let child_properties = self
.properties_children
.item_mut(child.id())
.expect("get_raw_ref: child not found");
let child_ctx = AccessCtx {
widget_state: child_state_mut.item,
widget_state_children: child_state_mut.children,
widget_children: child_mut.children,
properties_children: child_properties.children,
global_state: self.global_state,
tree_update: self.tree_update,
rebuild_all: self.rebuild_all,

View File

@ -8,6 +8,7 @@ mod box_constraints;
mod contexts;
mod event;
mod object_fit;
mod properties;
mod text;
#[allow(missing_docs, reason = "TODO")]
mod widget;
@ -30,6 +31,7 @@ pub use event::{
WindowTheme,
};
pub use object_fit::ObjectFit;
pub use properties::{Properties, PropertiesMut, PropertiesRef};
pub use text::{ArcStr, BrushIndex, StyleProperty, StyleSet, render_text};
pub use widget::find_widget_at_pos;
pub use widget::{AllowRawMut, FromDynWidget, Widget, WidgetId};

View File

@ -0,0 +1,124 @@
// Copyright 2025 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0
use anymap3::{AnyMap, Entry};
// TODO - Add PropertyValue wrapper struct that implements receiver trait.
// Return PropertyValue<T> instead of Option<T> from methods.
/// A collection of properties that a widget can be created with.
///
/// See [properties documentation](crate::doc::doc_04b_widget_properties) for details.
#[derive(Default)]
pub struct Properties {
pub(crate) map: AnyMap,
}
/// Reference to a collection of properties that a widget has access to.
///
/// Used by the [`Widget`] trait during rendering passes and in some search methods.
///
/// See [properties documentation](crate::doc::doc_04b_widget_properties) for
/// details.
///
/// [`Widget`]: crate::core::Widget
#[derive(Clone, Copy)]
pub struct PropertiesRef<'a> {
pub(crate) map: &'a AnyMap,
}
/// Mutable reference to a collection of properties that a widget has access to.
///
/// Used by the [`Widget`] trait during most passes.
///
/// See [properties documentation](crate::doc::doc_04b_widget_properties) for
/// details.
///
/// [`Widget`]: crate::core::Widget
pub struct PropertiesMut<'a> {
pub(crate) map: &'a mut AnyMap,
}
impl Properties {
/// Create an empty collection of properties.
pub fn new() -> Self {
Self { map: AnyMap::new() }
}
/// Get a reference to the properties.
pub fn ref_(&self) -> PropertiesRef<'_> {
PropertiesRef { map: &self.map }
}
/// Get a mutable reference to the properties.
pub fn mut_(&mut self) -> PropertiesMut<'_> {
PropertiesMut { map: &mut self.map }
}
}
impl PropertiesRef<'_> {
/// Returns true if the widget has a property of type `T`.
pub fn contains<T: 'static>(&self) -> bool {
self.map.contains::<T>()
}
/// Get value of property `T`, or None if the widget has no `T` property.
pub fn get<T: 'static>(&self) -> Option<&T> {
self.map.get::<T>()
}
}
impl PropertiesMut<'_> {
/// Returns true if the widget has a property of type `T`.
pub fn contains<T: 'static>(&self) -> bool {
self.map.contains::<T>()
}
/// Get value of property `T`, or None if the widget has no `T` property.
pub fn get<T: 'static>(&self) -> Option<&T> {
self.map.get::<T>()
}
/// Get value of property `T`, or None if the widget has no `T` property.
///
/// If you're using a `WidgetMut`, call [`WidgetMut::get_prop_mut`] instead.
///
/// [`WidgetMut::get_prop_mut`]: crate::core::WidgetMut::get_prop_mut
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.map.get_mut::<T>()
}
/// Set property `T` to given value. Returns the previous value if `T` was already set.
///
/// If you're using a `WidgetMut`, call [`WidgetMut::insert_prop`] instead.
///
/// [`WidgetMut::insert_prop`]: crate::core::WidgetMut::insert_prop
pub fn insert<T: 'static>(&mut self, value: T) -> Option<T> {
self.map.insert(value)
}
/// Remove property `T`. Returns the previous value if `T` was set.
///
/// If you're using a `WidgetMut`, call [`WidgetMut::remove_prop`] instead.
///
/// [`WidgetMut::remove_prop`]: crate::core::WidgetMut::remove_prop
pub fn remove<T: 'static>(&mut self) -> Option<T> {
self.map.remove::<T>()
}
/// Returns an entry that can be used to add, update, or remove a property.
///
/// If you're using a `WidgetMut`, call [`WidgetMut::prop_entry`] instead.
///
/// [`WidgetMut::prop_entry`]: crate::core::WidgetMut::prop_entry
pub fn entry<T: 'static>(&mut self) -> Entry<'_, dyn std::any::Any, T> {
self.map.entry::<T>()
}
/// Get a `PropertiesMut` for the same underlying properties with a shorter lifetime.
pub fn reborrow_mut(&mut self) -> PropertiesMut<'_> {
PropertiesMut {
map: &mut *self.map,
}
}
}

View File

@ -1,7 +1,7 @@
// Copyright 2018 the Xilem Authors and the Druid Authors
// SPDX-License-Identifier: Apache-2.0
use std::any::Any;
use std::any::{Any, TypeId};
use std::fmt::Display;
use std::num::NonZeroU64;
use std::sync::atomic::{AtomicU64, Ordering};
@ -16,7 +16,8 @@ use vello::Scene;
use crate::AsAny;
use crate::core::{
AccessCtx, AccessEvent, BoxConstraints, ComposeCtx, EventCtx, LayoutCtx, PaintCtx,
PointerEvent, QueryCtx, RegisterCtx, TextEvent, Update, UpdateCtx, WidgetRef,
PointerEvent, PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, TextEvent, Update,
UpdateCtx, WidgetRef,
};
use crate::kurbo::{Point, Size};
@ -128,19 +129,37 @@ pub trait Widget: AsAny + AsDynWidget {
///
/// Pointer events will target the widget under the pointer, and then the
/// event will bubble to each of its parents.
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) {}
fn on_pointer_event(
&mut self,
ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
event: &PointerEvent,
) {
}
/// Handle a text event.
///
/// Text events will target the [focused widget], then bubble to each parent.
///
/// [focused widget]: crate::doc::doc_06_masonry_concepts#text-focus
fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent) {}
fn on_text_event(
&mut self,
ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
event: &TextEvent,
) {
}
/// Handle an event from the platform's accessibility API.
///
/// Accessibility events target a specific widget id, then bubble to each parent.
fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) {}
fn on_access_event(
&mut self,
ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
event: &AccessEvent,
) {
}
/// Called at the beginning of a new animation frame.
///
@ -161,7 +180,13 @@ pub trait Widget: AsAny + AsDynWidget {
/// For that reason, you should try to avoid doing anything computationally
/// intensive in response to an `AnimFrame` event: it might make the app miss
/// the monitor's refresh, causing lag or jerky animations.
fn on_anim_frame(&mut self, ctx: &mut UpdateCtx, interval: u64) {}
fn on_anim_frame(
&mut self,
ctx: &mut UpdateCtx,
_props: &mut PropertiesMut<'_>,
interval: u64,
) {
}
// TODO - Reorder methods to match 02_implementing_widget.md
@ -179,7 +204,11 @@ pub trait Widget: AsAny + AsDynWidget {
/// This method is called to notify your widget of certain special events,
/// (available in the [`Update`] enum) that are generally related to
/// changes in the widget graph or in the state of your specific widget.
fn update(&mut self, ctx: &mut UpdateCtx, event: &Update) {}
fn update(&mut self, ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, event: &Update) {}
// TODO - Remove default implementation
/// Handle a property being added, changed, or removed.
fn property_changed(&mut self, ctx: &mut UpdateCtx, property_type: TypeId) {}
/// Compute layout and return the widget's size.
///
@ -206,7 +235,12 @@ pub trait Widget: AsAny + AsDynWidget {
/// While each widget should try to return a size that fits the input constraints,
/// **any widget may return a size that doesn't fit its constraints**, and container
/// widgets should handle those cases gracefully.
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size;
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size;
fn compose(&mut self, ctx: &mut ComposeCtx) {}
@ -216,11 +250,11 @@ pub trait Widget: AsAny + AsDynWidget {
/// children, or annotations (for example, scrollbars) by painting
/// afterwards. In addition, they can apply masks and transforms on
/// the render context, which is especially useful for scrolling.
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene);
fn paint(&mut self, ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, scene: &mut Scene);
fn accessibility_role(&self) -> Role;
fn accessibility(&mut self, ctx: &mut AccessCtx, node: &mut Node);
fn accessibility(&mut self, ctx: &mut AccessCtx, _props: &PropertiesRef<'_>, node: &mut Node);
/// Return ids of this widget's children.
///
@ -315,11 +349,13 @@ pub trait Widget: AsAny + AsDynWidget {
fn find_widget_at_pos<'c>(
&'c self,
ctx: QueryCtx<'c>,
props: PropertiesRef<'c>,
pos: Point,
) -> Option<WidgetRef<'c, dyn Widget>> {
find_widget_at_pos(
&WidgetRef {
widget: self.as_dyn(),
properties: props,
ctx,
},
pos,
@ -383,7 +419,11 @@ pub fn find_widget_at_pos<'c>(
// of overlapping children.
for child_id in widget.children_ids().iter().rev() {
let child_ref = widget.ctx.get(*child_id);
if let Some(child) = child_ref.widget.find_widget_at_pos(child_ref.ctx, pos) {
if let Some(child) =
child_ref
.widget
.find_widget_at_pos(child_ref.ctx, child_ref.properties, pos)
{
return Some(child);
}
}

View File

@ -1,6 +1,7 @@
// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0
use anymap3::AnyMap;
use tree_arena::{ArenaMut, ArenaRef, TreeArena};
use crate::core::{Widget, WidgetId, WidgetState};
@ -8,6 +9,7 @@ use crate::core::{Widget, WidgetId, WidgetState};
pub(crate) struct WidgetArena {
pub(crate) widgets: TreeArena<Box<dyn Widget>>,
pub(crate) states: TreeArena<WidgetState>,
pub(crate) properties: TreeArena<AnyMap>,
}
impl WidgetArena {
@ -27,10 +29,14 @@ impl WidgetArena {
}
#[track_caller]
pub(crate) fn get_pair(
pub(crate) fn get_all(
&self,
widget_id: WidgetId,
) -> (ArenaRef<Box<dyn Widget>>, ArenaRef<WidgetState>) {
) -> (
ArenaRef<Box<dyn Widget>>,
ArenaRef<WidgetState>,
ArenaRef<AnyMap>,
) {
let widget = self
.widgets
.find(widget_id)
@ -39,14 +45,22 @@ impl WidgetArena {
.states
.find(widget_id)
.expect("get_pair: widget state not in widget tree");
(widget, state)
let properties = self
.properties
.find(widget_id)
.expect("get_pair: widget properties not in widget tree");
(widget, state, properties)
}
#[track_caller]
pub(crate) fn get_pair_mut(
pub(crate) fn get_all_mut(
&mut self,
widget_id: WidgetId,
) -> (ArenaMut<Box<dyn Widget>>, ArenaMut<WidgetState>) {
) -> (
ArenaMut<Box<dyn Widget>>,
ArenaMut<WidgetState>,
ArenaMut<AnyMap>,
) {
let widget = self
.widgets
.find_mut(widget_id)
@ -55,7 +69,11 @@ impl WidgetArena {
.states
.find_mut(widget_id)
.expect("get_pair_mut: widget state not in widget tree");
(widget, state)
let properties = self
.properties
.find_mut(widget_id)
.expect("get_pair_mut: widget properties not in widget tree");
(widget, state, properties)
}
#[allow(dead_code)]

View File

@ -1,6 +1,10 @@
// Copyright 2018 the Xilem Authors and the Druid Authors
// SPDX-License-Identifier: Apache-2.0
use std::any::TypeId;
use anymap3::Entry;
use crate::core::{FromDynWidget, MutateCtx, Widget};
use crate::kurbo::Affine;
@ -23,6 +27,7 @@ use crate::kurbo::Affine;
///
/// Once the Receiver trait is stabilized, `WidgetMut` will implement it so that custom
/// widgets in downstream crates can use `WidgetMut` as the receiver for inherent methods.
#[non_exhaustive]
pub struct WidgetMut<'a, W: Widget + ?Sized> {
pub ctx: MutateCtx<'a>,
pub widget: &'a mut W,
@ -48,6 +53,44 @@ impl<W: Widget + ?Sized> WidgetMut<'_, W> {
}
}
/// Returns true if the widget has a property of type `T`.
pub fn get_prop<T: 'static>(&self) -> Option<&T> {
self.ctx.properties.get::<T>()
}
/// Get value of property `T`, or None if the widget has no `T` property.
pub fn contains_prop<T: 'static>(&self) -> bool {
self.ctx.properties.contains::<T>()
}
/// Get value of property `T`, or None if the widget has no `T` property.
pub fn get_prop_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.widget
.property_changed(&mut self.ctx.update_mut(), TypeId::of::<T>());
self.ctx.properties.get_mut::<T>()
}
/// Set property `T` to given value. Returns the previous value if `T` was already set.
pub fn insert_prop<T: 'static>(&mut self, value: T) -> Option<T> {
self.widget
.property_changed(&mut self.ctx.update_mut(), TypeId::of::<T>());
self.ctx.properties.insert(value)
}
/// Remove property `T`. Returns the previous value if `T` was set.
pub fn remove_prop<T: 'static>(&mut self) -> Option<T> {
self.widget
.property_changed(&mut self.ctx.update_mut(), TypeId::of::<T>());
self.ctx.properties.remove::<T>()
}
/// Returns an entry that can be used to add, update, or remove a property.
pub fn prop_entry<T: 'static>(&mut self) -> Entry<'_, dyn std::any::Any, T> {
self.widget
.property_changed(&mut self.ctx.update_mut(), TypeId::of::<T>());
self.ctx.properties.entry::<T>()
}
/// Set the local transform of this widget.
///
/// It behaves similarly as CSS transforms.

View File

@ -1,7 +1,7 @@
// Copyright 2018 the Xilem Authors and the Druid Authors
// SPDX-License-Identifier: Apache-2.0
use crate::core::{Widget, WidgetId};
use crate::core::{Properties, Widget, WidgetId};
use crate::kurbo::Affine;
/// A container for one widget in the hierarchy.
@ -25,6 +25,7 @@ pub struct WidgetPod<W: ?Sized> {
pub(crate) struct CreateWidget<W: ?Sized> {
pub(crate) widget: Box<W>,
pub(crate) transform: Affine,
pub(crate) properties: Properties,
}
enum WidgetPodInner<W: ?Sized> {
@ -60,9 +61,23 @@ impl<W: Widget + ?Sized> WidgetPod<W> {
inner: WidgetPodInner::Create(CreateWidget {
widget: inner,
transform,
properties: Properties::new(),
}),
}
}
// TODO - Remove transform, have it as a special-case property instead.
pub fn new_with(inner: Box<W>, id: WidgetId, transform: Affine, props: Properties) -> Self {
Self {
id,
inner: WidgetPodInner::Create(CreateWidget {
widget: inner,
transform,
properties: props,
}),
}
}
pub(crate) fn incomplete(&self) -> bool {
matches!(self.inner, WidgetPodInner::Create(_))
}
@ -94,6 +109,7 @@ impl<W: Widget + ?Sized> WidgetPod<W> {
inner: WidgetPodInner::Create(CreateWidget {
widget: inner.widget.as_box_dyn(),
transform: inner.transform,
properties: inner.properties,
}),
}
}

View File

@ -6,7 +6,7 @@ use std::ops::Deref;
use smallvec::SmallVec;
use vello::kurbo::Point;
use crate::core::{QueryCtx, Widget, WidgetId};
use crate::core::{PropertiesRef, QueryCtx, Widget, WidgetId};
/// A rich reference to a [`Widget`].
///
@ -22,18 +22,16 @@ use crate::core::{QueryCtx, Widget, WidgetId};
/// This is only for shared access to widgets. For widget mutation, see [`WidgetMut`](crate::core::WidgetMut).
pub struct WidgetRef<'w, W: Widget + ?Sized> {
pub(crate) ctx: QueryCtx<'w>,
pub(crate) properties: PropertiesRef<'w>,
pub(crate) widget: &'w W,
}
// --- TRAIT IMPLS ---
// --- MARK: TRAIT IMPLS ---
#[allow(clippy::non_canonical_clone_impl)]
impl<W: Widget + ?Sized> Clone for WidgetRef<'_, W> {
fn clone(&self) -> Self {
Self {
ctx: self.ctx,
widget: self.widget,
}
Self { ..*self }
}
}
@ -70,7 +68,7 @@ impl<W: Widget + ?Sized> Deref for WidgetRef<'_, W> {
}
}
// --- IMPLS ---
// --- MARK: IMPLS ---
impl<'w, W: Widget + ?Sized> WidgetRef<'w, W> {
/// Get a [`QueryCtx`] with information about the current widget.
@ -88,10 +86,21 @@ impl<'w, W: Widget + ?Sized> WidgetRef<'w, W> {
self.ctx.widget_state.id
}
/// Returns true if the widget has a property of type `T`.
pub fn get_prop<T: 'static>(&self) -> Option<&T> {
self.properties.get::<T>()
}
/// Get value of property `T`, or None if the widget has no `T` property.
pub fn contains_prop<T: 'static>(&self) -> bool {
self.properties.contains::<T>()
}
/// Attempt to downcast to `WidgetRef` of concrete Widget type.
pub fn downcast<W2: Widget>(&self) -> Option<WidgetRef<'w, W2>> {
Some(WidgetRef {
ctx: self.ctx,
properties: self.properties,
widget: self.widget.as_any().downcast_ref()?,
})
}
@ -115,6 +124,12 @@ impl<'w, W: Widget + ?Sized> WidgetRef<'w, W> {
self.widget.short_type_name()
);
};
let Some(properties_ref) = self.ctx.properties_children.into_item(id) else {
panic!(
"Error in '{}' #{parent_id}: child #{id} has not been added to tree",
self.widget.short_type_name()
);
};
// Box<dyn Widget> -> &dyn Widget
// Without this step, the type of `WidgetRef::widget` would be
@ -128,9 +143,17 @@ impl<'w, W: Widget + ?Sized> WidgetRef<'w, W> {
widget_state_children: state_ref.children,
widget_children: widget_ref.children,
widget_state: state_ref.item,
properties_children: properties_ref.children,
};
let properties = PropertiesRef {
map: properties_ref.item,
};
WidgetRef { ctx, widget }
WidgetRef {
ctx,
properties,
widget,
}
})
.collect()
}
@ -141,6 +164,7 @@ impl<'w, W: Widget> WidgetRef<'w, W> {
pub fn as_dyn(&self) -> WidgetRef<'w, dyn Widget> {
WidgetRef {
ctx: self.ctx,
properties: self.properties,
widget: self.widget,
}
}
@ -165,7 +189,8 @@ impl WidgetRef<'_, dyn Widget> {
/// **pos** - the position in global coordinates (e.g. `(0,0)` is the top-left corner of the
/// window).
pub fn find_widget_at_pos(&self, pos: Point) -> Option<Self> {
self.widget.find_widget_at_pos(self.ctx, pos)
self.widget
.find_widget_at_pos(self.ctx, self.properties, pos)
}
}

View File

@ -25,18 +25,18 @@ Widgets are types which implement the [`Widget`] trait:
```rust,ignore
trait Widget {
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent);
fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent);
fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent);
fn on_pointer_event(&mut self, ctx: &mut EventCtx, _props: &mut PropertiesMut<'_>, event: &PointerEvent);
fn on_text_event(&mut self, ctx: &mut EventCtx, _props: &mut PropertiesMut<'_>, event: &TextEvent);
fn on_access_event(&mut self, ctx: &mut EventCtx, _props: &mut PropertiesMut<'_>, event: &AccessEvent);
fn on_anim_frame(&mut self, ctx: &mut UpdateCtx, interval: u64);
fn update(&mut self, ctx: &mut UpdateCtx, event: &Update);
fn on_anim_frame(&mut self, ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, interval: u64);
fn update(&mut self, ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, event: &Update);
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size;
fn layout(&mut self, ctx: &mut LayoutCtx, _props: &mut PropertiesMut<'_>, bc: &BoxConstraints) -> Size;
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene);
fn paint(&mut self, ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, scene: &mut Scene);
fn accessibility_role(&self) -> Role;
fn accessibility(&mut self, ctx: &mut AccessCtx, node: &mut Node);
fn accessibility(&mut self, ctx: &mut AccessCtx, _props: &PropertiesRef<'_>, node: &mut Node);
// ...
}
@ -90,7 +90,7 @@ use masonry::core::{
};
impl Widget for ColorRectangle {
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) {
fn on_pointer_event(&mut self, ctx: &mut EventCtx, _props: &mut PropertiesMut<'_>, event: &PointerEvent) {
match event {
PointerEvent::PointerDown(PointerButton::Primary, _) => {
ctx.submit_action(Action::ButtonPressed(PointerButton::Primary));
@ -99,9 +99,9 @@ impl Widget for ColorRectangle {
}
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _props: &mut PropertiesMut<'_>, _event: &TextEvent) {}
fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) {
fn on_access_event(&mut self, ctx: &mut EventCtx, _props: &mut PropertiesMut<'_>, event: &AccessEvent) {
match event.action {
accesskit::Action::Default => {
ctx.submit_action(Action::ButtonPressed(PointerButton::Primary));
@ -116,7 +116,7 @@ impl Widget for ColorRectangle {
We handle pointer events and accessibility events the same way: we check the event type, and if it's a left-click, we submit an action.
Submitting an action lets Masonry that a semantically meaningful event has occurred; Masonry will call `AppDriver::on_action()` with the action before the end of the frame.
Submitting an action lets Masonry know that a semantically meaningful event has occurred; Masonry will call `AppDriver::on_action()` with the action before the end of the frame.
This lets higher-level frameworks like Xilem react to UI events, like a button being pressed.
Implementing `on_access_event` lets us emulate click behaviors for people using assistive technologies.
@ -136,8 +136,8 @@ use masonry::core::{
impl Widget for ColorRectangle {
// ...
fn on_anim_frame(&mut self, _ctx: &mut UpdateCtx, _interval: u64) {}
fn update(&mut self, _ctx: &mut UpdateCtx, _event: &Update) {}
fn on_anim_frame(&mut self, _ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, _interval: u64) {}
fn update(&mut self, _ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, _event: &Update) {}
// ...
}
@ -155,7 +155,7 @@ use masonry::core::{
impl Widget for ColorRectangle {
// ...
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(&mut self, _ctx: &mut LayoutCtx, _props: &mut PropertiesMut<'_>, bc: &BoxConstraints) -> Size {
bc.constrain(self.size)
}
@ -182,7 +182,7 @@ use accesskit::{Node, Role};
impl Widget for ColorRectangle {
// ...
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, scene: &mut Scene) {
let rect = ctx.size().to_rect();
scene.fill(
Fill::NonZero,
@ -197,7 +197,7 @@ impl Widget for ColorRectangle {
Role::Button
}
fn accessibility(&mut self, ctx: &mut AccessCtx, node: &mut Node) {
fn accessibility(&mut self, ctx: &mut AccessCtx, _props: &PropertiesRef<'_>, node: &mut Node) {
node.set_default_action_verb(DefaultActionVerb::Click);
}
@ -302,7 +302,7 @@ First, we update our paint method:
impl Widget for ColorRectangle {
// ...
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, scene: &mut Scene) {
let rect = ctx.size().to_rect();
let color = if ctx.is_hovered() {
Color::WHITE
@ -330,7 +330,7 @@ Let's re-implement the `update` method:
impl Widget for ColorRectangle {
// ...
fn update(&mut self, ctx: &mut UpdateCtx, event: &Update) {
fn update(&mut self, ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, event: &Update) {
match event {
Update::HoveredChanged(_) => {
ctx.request_render();
@ -343,6 +343,14 @@ impl Widget for ColorRectangle {
}
```
## Properties
Most of the methods we've listed take a `props: &mut PropertiesMut<'_>` argument.
We won't cover properties in this chapter.
See [Reading Widget Properties](crate::doc::doc_04b_widget_properties) for more info.
## Widget mutation
In Masonry, widgets generally can't be mutated directly.

View File

@ -85,7 +85,7 @@ use masonry::core::{
impl Widget for VerticalStack {
// ...
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(&mut self, ctx: &mut LayoutCtx, _props: &mut PropertiesMut<'_>, bc: &BoxConstraints) -> Size {
let total_width = bc.max().width;
let total_height = bc.max().height;
let total_child_height = total_height - self.gap * (self.children.len() - 1) as f64;
@ -228,22 +228,22 @@ In the case of our `VerticalStack`, all of them can be left empty:
```rust,ignore
impl Widget for VerticalStack {
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _props: &mut PropertiesMut<'_>, _event: &PointerEvent) {}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _props: &mut PropertiesMut<'_>, _event: &TextEvent) {}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _props: &mut PropertiesMut<'_>, _event: &AccessEvent) {}
fn on_anim_frame(&mut self, _ctx: &mut UpdateCtx, _interval: u64) {}
fn update(&mut self, _ctx: &mut UpdateCtx, _event: &Update) {}
fn on_anim_frame(&mut self, _ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, _interval: u64) {}
fn update(&mut self, _ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, _event: &Update) {}
// ...
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut Scene) {}
fn paint(&mut self, _ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, _scene: &mut Scene) {}
fn accessibility_role(&self) -> Role {
Role::GenericContainer
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) {}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _props: &PropertiesRef<'_>, _node: &mut Node) {}
// ...
}

View File

@ -0,0 +1,112 @@
# Reading Widget Properties
<!-- Copyright 2024 the Xilem Authors -->
<!-- SPDX-License-Identifier: Apache-2.0 -->
<div class="rustdoc-hidden">
> 💡 Tip
>
> This file is intended to be read in rustdoc.
> Use `cargo doc --open --package masonry --no-deps`.
</div>
<!-- TODO - Rewrite this chapter -->
**TODO - Add screenshots - see [#501](https://github.com/linebender/xilem/issues/501)**
Throughout the previous chapters, you may have noticed that most Widget methods take a `props: &PropertiesRef<'_>` or `props: &mut PropertiesMut<'_>` argument.
We haven't used these arguments so far, and you can build a robust widget set without them, but they're helpful for making your widgets more customizable and modular.
## What are Properties?
In Masonry, **Properties** (often abbreviated to **props**) are values of arbitrary static types stored alongside each widget.
In simpler terms, that means you can take any non-ref type (e.g. `struct RubberDuck(Color, String, Buoyancy);`) and associate a value of that type to any widget, including widgets of existing types (`Button`, `Checkbox`, `Textbox`, etc) or your own custom widget (`ColorRectangle`).
Code accessing the property will look like:
```rust,ignore
if let Some(ducky) = props.get::<RubberDuck>() {
let (color, name, buoyancy) = ducky;
// ...
}
```
### When to use Properties?
<!-- TODO - Mention event handling -->
<!-- I expect that properties will be used to share the same pointer event handling code between Button, SizedBox, Textbox, etc... -->
In practice, properties should mostly be used for styling.
Properties should be defined to represent self-contained values that a widget can have, that are expected to make sense for multiple types of widgets, and where code handling those values should be shared between widgets.
Some examples:
- `BackgroundColor`
- BorderColor
- Padding
- TextFont
- TextSize
- TextWeight
**TODO: Most of the properties cited above do *not* exist in Masonry's codebase. They should hopefully be added quickly.**
Properties should *not* be used to represent an individual widget's state. The following should *not* be properties:
- Text contents.
- Cached values.
- A checkbox's status.
<!-- TODO - Mention properties as a unit of code sharing, once we have concrete examples of that. -->
## Using properties in `ColorRectangle`
With that in mind, let's rewrite our `ColorRectangle` widget to use properties:
```rust,ignore
use masonry::properties::BackgroundColor;
impl Widget for ColorRectangle {
// ...
fn paint(&mut self, ctx: &mut PaintCtx, props: &PropertiesRef<'_>, scene: &mut Scene) {
let color = props.get::<BackgroundColor>().unwrap_or(masonry::palette::css::WHITE);
let rect = ctx.size().to_rect();
scene.fill(
Fill::NonZero,
Affine::IDENTITY,
color,
Some(Affine::IDENTITY),
&rect,
);
}
// ...
}
```
## Setting properties in `WidgetMut`
The most idiomatic way to set properties is through `WidgetMut`:
```rust,ignore
let color_rectangle_mut: WidgetMut<ColorRectangle> = ...;
let bg = BackgroundColor { color: masonry::palette::css::BLUE };
color_rectangle_mut.insert_prop(bg);
```
This code will set the given rectangle's `BackgroundColor` (replacing the old one if there was one) to blue.
You can set as many properties as you want.
Properties are an associative map, where types are the keys.
But setting a property to a given value doesn't change anything by default, unless your widget code specifically reads that value and does something with it.
<!-- TODO - Mention "transform" property. -->

View File

@ -100,6 +100,13 @@ A widget is considered "interactive" if it can still get text and/or pointer eve
Stashed and disabled widget are non-interactive.
## Properties / Props
All widgets have associated data of arbitrary types called "properties".
These properties are mostly used for styling and event handling.
See [Reading Widget Properties](crate::doc::doc_04b_widget_properties) for more info.
## Safety rails
When debug assertions are on, Masonry runs a bunch of checks every frame to make sure widget code doesn't have logical errors.

View File

@ -31,6 +31,10 @@ pub mod doc_03_implementing_container_widget {}
/// <style> .rustdoc-hidden { display: none; } </style>
pub mod doc_04_testing_widget {}
#[doc = include_str!("./04b_widget_properties.md")]
/// <style> .rustdoc-hidden { display: none; } </style>
pub mod doc_04b_widget_properties {}
#[doc = include_str!("./05_pass_system.md")]
/// <style> .rustdoc-hidden { display: none; } </style>
pub mod doc_05_pass_system {}

View File

@ -150,6 +150,7 @@ mod passes;
pub mod app;
pub mod core;
pub mod properties;
pub mod testing;
pub mod theme;
pub mod widgets;

View File

@ -2,12 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
use accesskit::{Node, NodeId, Tree, TreeUpdate};
use anymap3::AnyMap;
use tracing::{debug, info_span, trace};
use tree_arena::ArenaMut;
use vello::kurbo::Rect;
use crate::app::{RenderRoot, RenderRootState};
use crate::core::{AccessCtx, Widget, WidgetState};
use crate::core::{AccessCtx, PropertiesRef, Widget, WidgetState};
use crate::passes::{enter_span_if, recurse_on_children};
// --- MARK: BUILD TREE ---
@ -16,6 +17,7 @@ fn build_accessibility_tree(
tree_update: &mut TreeUpdate,
mut widget: ArenaMut<'_, Box<dyn Widget>>,
mut state: ArenaMut<'_, WidgetState>,
mut properties: ArenaMut<'_, AnyMap>,
rebuild_all: bool,
scale_factor: Option<f64>,
) {
@ -24,6 +26,7 @@ fn build_accessibility_tree(
global_state,
widget.reborrow(),
state.reborrow(),
properties.reborrow(),
);
let id = state.item.id;
@ -45,11 +48,15 @@ fn build_accessibility_tree(
widget_state: state.item,
widget_state_children: state.children.reborrow_mut(),
widget_children: widget.children.reborrow_mut(),
properties_children: properties.children.reborrow_mut(),
tree_update,
rebuild_all,
};
let mut node = build_access_node(&mut **widget.item, &mut ctx, scale_factor);
widget.item.accessibility(&mut ctx, &mut node);
let props = PropertiesRef {
map: properties.item,
};
widget.item.accessibility(&mut ctx, &props, &mut node);
let id: NodeId = ctx.widget_state.id.into();
if ctx.global_state.trace.access {
@ -67,7 +74,8 @@ fn build_accessibility_tree(
id,
widget.reborrow_mut(),
state.children,
|widget, mut state| {
properties.children,
|widget, mut state, properties| {
// TODO - We don't skip updating stashed items because doing so
// is error-prone. We may want to revisit that decision.
build_accessibility_tree(
@ -75,6 +83,7 @@ fn build_accessibility_tree(
tree_update,
widget,
state.reborrow_mut(),
properties,
rebuild_all,
None,
);
@ -154,7 +163,7 @@ pub(crate) fn run_accessibility_pass(root: &mut RenderRoot, scale_factor: f64) -
.into(),
};
let (root_widget, root_state) = {
let (root_widget, root_state, root_properties) = {
let widget_id = root.root.id();
let widget = root
.widget_arena
@ -166,7 +175,12 @@ pub(crate) fn run_accessibility_pass(root: &mut RenderRoot, scale_factor: f64) -
.states
.find_mut(widget_id)
.expect("root_accessibility: root state not in widget tree");
(widget, state)
let properties = root
.widget_arena
.properties
.find_mut(widget_id)
.expect("root_accessibility: root properties not in widget tree");
(widget, state, properties)
};
if root.rebuild_access_tree {
@ -177,6 +191,7 @@ pub(crate) fn run_accessibility_pass(root: &mut RenderRoot, scale_factor: f64) -
&mut tree_update,
root_widget,
root_state,
root_properties,
root.rebuild_access_tree,
Some(scale_factor),
);

View File

@ -1,11 +1,12 @@
// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0
use anymap3::AnyMap;
use tracing::info_span;
use tree_arena::ArenaMut;
use crate::app::{RenderRoot, RenderRootState};
use crate::core::{UpdateCtx, Widget, WidgetState};
use crate::core::{PropertiesMut, UpdateCtx, Widget, WidgetState};
use crate::passes::{enter_span_if, recurse_on_children};
// --- MARK: UPDATE ANIM ---
@ -13,6 +14,7 @@ fn update_anim_for_widget(
global_state: &mut RenderRootState,
mut widget: ArenaMut<'_, Box<dyn Widget>>,
mut state: ArenaMut<'_, WidgetState>,
mut properties: ArenaMut<'_, AnyMap>,
elapsed_ns: u64,
) {
let _span = enter_span_if(
@ -20,6 +22,7 @@ fn update_anim_for_widget(
global_state,
widget.reborrow(),
state.reborrow(),
properties.reborrow(),
);
if !state.item.needs_anim {
return;
@ -36,8 +39,12 @@ fn update_anim_for_widget(
widget_state: state.item,
widget_state_children: state.children.reborrow_mut(),
widget_children: widget.children.reborrow_mut(),
properties_children: properties.children.reborrow_mut(),
};
widget.item.on_anim_frame(&mut ctx, elapsed_ns);
let mut props = PropertiesMut {
map: properties.item,
};
widget.item.on_anim_frame(&mut ctx, &mut props, elapsed_ns);
}
let id = state.item.id;
@ -46,8 +53,15 @@ fn update_anim_for_widget(
id,
widget.reborrow_mut(),
state.children,
|widget, mut state| {
update_anim_for_widget(global_state, widget, state.reborrow_mut(), elapsed_ns);
properties.children,
|widget, mut state, properties| {
update_anim_for_widget(
global_state,
widget,
state.reborrow_mut(),
properties,
elapsed_ns,
);
parent_state.merge_up(state.item);
},
);
@ -63,11 +77,13 @@ fn update_anim_for_widget(
pub(crate) fn run_update_anim_pass(root: &mut RenderRoot, elapsed_ns: u64) {
let _span = info_span!("update_anim").entered();
let (root_widget, mut root_state) = root.widget_arena.get_pair_mut(root.root.id());
let (root_widget, mut root_state, root_properties) =
root.widget_arena.get_all_mut(root.root.id());
update_anim_for_widget(
&mut root.global_state,
root_widget,
root_state.reborrow_mut(),
root_properties,
elapsed_ns,
);
}

View File

@ -1,6 +1,7 @@
// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0
use anymap3::AnyMap;
use tracing::info_span;
use tree_arena::ArenaMut;
use vello::kurbo::Affine;
@ -14,6 +15,7 @@ fn compose_widget(
global_state: &mut RenderRootState,
mut widget: ArenaMut<'_, Box<dyn Widget>>,
mut state: ArenaMut<'_, WidgetState>,
mut properties: ArenaMut<'_, AnyMap>,
parent_transformed: bool,
parent_window_transform: Affine,
) {
@ -22,6 +24,7 @@ fn compose_widget(
global_state,
widget.reborrow(),
state.reborrow(),
properties.reborrow(),
);
let transformed = parent_transformed || state.item.transform_changed;
@ -65,11 +68,13 @@ fn compose_widget(
id,
widget.reborrow_mut(),
state.children,
|widget, mut state| {
properties.children,
|widget, mut state, properties| {
compose_widget(
global_state,
widget,
state.reborrow_mut(),
properties,
transformed,
parent_transform,
);
@ -103,11 +108,12 @@ pub(crate) fn run_compose_pass(root: &mut RenderRoot) {
root.global_state.needs_pointer_pass = true;
}
let (root_widget, root_state) = root.widget_arena.get_pair_mut(root.root.id());
let (root_widget, root_state, root_properties) = root.widget_arena.get_all_mut(root.root.id());
compose_widget(
&mut root.global_state,
root_widget,
root_state,
root_properties,
false,
Affine::IDENTITY,
);

View File

@ -8,7 +8,9 @@ use winit::keyboard::{KeyCode, PhysicalKey};
use crate::Handled;
use crate::app::{RenderRoot, RenderRootSignal};
use crate::core::{AccessEvent, EventCtx, PointerEvent, TextEvent, Widget, WidgetId};
use crate::core::{
AccessEvent, EventCtx, PointerEvent, PropertiesMut, TextEvent, Widget, WidgetId,
};
use crate::passes::{enter_span, merge_state_up};
// --- MARK: HELPERS ---
@ -38,7 +40,7 @@ fn run_event_pass<E>(
target: Option<WidgetId>,
event: &E,
allow_pointer_capture: bool,
pass_fn: impl FnMut(&mut dyn Widget, &mut EventCtx, &E),
pass_fn: impl FnMut(&mut dyn Widget, &mut EventCtx, &mut PropertiesMut<'_>, &E),
trace: bool,
) -> Handled {
let mut pass_fn = pass_fn;
@ -48,19 +50,22 @@ fn run_event_pass<E>(
let mut is_handled = false;
while let Some(widget_id) = target_widget_id {
let parent_id = root.widget_arena.parent_of(widget_id);
let (mut widget_mut, mut state_mut) = root.widget_arena.get_pair_mut(widget_id);
let (mut widget_mut, mut state_mut, mut properties_mut) =
root.widget_arena.get_all_mut(widget_id);
if !is_handled {
let _span = enter_span(
&root.global_state,
widget_mut.reborrow(),
state_mut.reborrow(),
properties_mut.reborrow(),
);
let mut ctx = EventCtx {
global_state: &mut root.global_state,
widget_state: state_mut.item,
widget_state_children: state_mut.children,
widget_children: widget_mut.children,
properties_children: properties_mut.children.reborrow_mut(),
target: original_target.unwrap(),
allow_pointer_capture,
is_handled: false,
@ -74,7 +79,10 @@ fn run_event_pass<E>(
);
}
pass_fn(&mut **widget, &mut ctx, event);
let mut props = PropertiesMut {
map: properties_mut.item,
};
pass_fn(&mut **widget, &mut ctx, &mut props, event);
is_handled = ctx.is_handled;
}
@ -157,8 +165,8 @@ pub(crate) fn run_on_pointer_event_pass(root: &mut RenderRoot, event: &PointerEv
target_widget_id,
event,
matches!(event, PointerEvent::PointerDown(..)),
|widget, ctx, event| {
widget.on_pointer_event(ctx, event);
|widget, ctx, props, event| {
widget.on_pointer_event(ctx, props, event);
},
!event.is_high_density(),
);
@ -213,8 +221,8 @@ pub(crate) fn run_on_text_event_pass(root: &mut RenderRoot, event: &TextEvent) -
target,
event,
false,
|widget, ctx, event| {
widget.on_text_event(ctx, event);
|widget, ctx, props, event| {
widget.on_text_event(ctx, props, event);
},
!event.is_high_density(),
);
@ -279,8 +287,8 @@ pub(crate) fn run_on_access_event_pass(
Some(target),
event,
false,
|widget, ctx, event| {
widget.on_access_event(ctx, event);
|widget, ctx, props, event| {
widget.on_access_event(ctx, props, event);
},
true,
);

View File

@ -11,7 +11,7 @@ use tracing::{info_span, trace};
use vello::kurbo::{Point, Rect, Size};
use crate::app::{RenderRoot, RenderRootSignal, WindowSizePolicy};
use crate::core::{BoxConstraints, LayoutCtx, Widget, WidgetPod, WidgetState};
use crate::core::{BoxConstraints, LayoutCtx, PropertiesMut, Widget, WidgetPod, WidgetState};
use crate::passes::{enter_span_if, recurse_on_children};
// --- MARK: RUN LAYOUT ---
@ -25,6 +25,7 @@ pub(crate) fn run_layout_on<W: Widget + ?Sized>(
let id = pod.id();
let mut widget = parent_ctx.widget_children.item_mut(id).unwrap();
let mut state = parent_ctx.widget_state_children.item_mut(id).unwrap();
let mut properties = parent_ctx.properties_children.item_mut(id).unwrap();
let trace = parent_ctx.global_state.trace.layout;
let _span = enter_span_if(
@ -32,6 +33,7 @@ pub(crate) fn run_layout_on<W: Widget + ?Sized>(
parent_ctx.global_state,
widget.reborrow(),
state.reborrow(),
properties.reborrow(),
);
let mut children_ids = SmallVec::new();
@ -86,7 +88,8 @@ pub(crate) fn run_layout_on<W: Widget + ?Sized>(
pod.id(),
widget.reborrow_mut(),
state.children.reborrow_mut(),
|_, state| {
properties.children.reborrow_mut(),
|_, state, _| {
if state.item.is_stashed {
state.item.needs_layout = false;
state.item.request_layout = false;
@ -99,13 +102,17 @@ pub(crate) fn run_layout_on<W: Widget + ?Sized>(
widget_state: state.item,
widget_state_children: state.children.reborrow_mut(),
widget_children: widget.children,
properties_children: properties.children.reborrow_mut(),
global_state: parent_ctx.global_state,
};
// TODO - If constraints are the same and request_layout isn't set,
// skip calling layout
inner_ctx.widget_state.request_layout = false;
widget.item.layout(&mut inner_ctx, bc)
let mut props = PropertiesMut {
map: properties.item,
};
widget.item.layout(&mut inner_ctx, &mut props, bc)
};
if state.item.request_layout {
debug_panic!(
@ -204,11 +211,14 @@ pub(crate) fn run_layout_pass(root: &mut RenderRoot) {
let mut dummy_state = WidgetState::synthetic(root.root.id(), root.get_kurbo_size());
let root_state_token = root.widget_arena.states.roots_mut();
let root_widget_token = root.widget_arena.widgets.roots_mut();
let root_properties_token = root.widget_arena.properties.roots_mut();
let mut ctx = LayoutCtx {
global_state: &mut root.global_state,
widget_state: &mut dummy_state,
widget_state_children: root_state_token,
widget_children: root_widget_token,
properties_children: root_properties_token,
};
let size = run_layout_on(&mut ctx, &mut root.root, &bc);

View File

@ -7,6 +7,7 @@
//!
//! This file includes utility functions used by multiple passes.
use anymap3::AnyMap;
use tracing::span::EnteredSpan;
use tree_arena::{ArenaMut, ArenaMutList, ArenaRef};
@ -28,9 +29,10 @@ pub(crate) fn enter_span_if(
global_state: &RenderRootState,
widget: ArenaRef<'_, Box<dyn Widget>>,
state: ArenaRef<'_, WidgetState>,
properties: ArenaRef<'_, AnyMap>,
) -> Option<EnteredSpan> {
if enabled {
Some(enter_span(global_state, widget, state))
Some(enter_span(global_state, widget, state, properties))
} else {
None
}
@ -41,12 +43,14 @@ pub(crate) fn enter_span(
global_state: &RenderRootState,
widget: ArenaRef<'_, Box<dyn Widget>>,
state: ArenaRef<'_, WidgetState>,
properties: ArenaRef<'_, AnyMap>,
) -> EnteredSpan {
let ctx = QueryCtx {
global_state,
widget_state: state.item,
widget_state_children: state.children,
widget_children: widget.children,
properties_children: properties.children,
};
widget.item.make_trace_span(&ctx).entered()
}
@ -55,7 +59,12 @@ pub(crate) fn recurse_on_children(
id: WidgetId,
mut widget: ArenaMut<'_, Box<dyn Widget>>,
mut state: ArenaMutList<'_, WidgetState>,
mut callback: impl FnMut(ArenaMut<'_, Box<dyn Widget>>, ArenaMut<'_, WidgetState>),
mut properties: ArenaMutList<'_, AnyMap>,
mut callback: impl FnMut(
ArenaMut<'_, Box<dyn Widget>>,
ArenaMut<'_, WidgetState>,
ArenaMut<'_, AnyMap>,
),
) {
let parent_name = widget.item.short_type_name();
let parent_id = id;
@ -73,8 +82,14 @@ pub(crate) fn recurse_on_children(
parent_name, parent_id, child_id
)
});
let properties = properties.item_mut(child_id).unwrap_or_else(|| {
panic!(
"Error in '{}' #{}: cannot find child #{} returned by children_ids()",
parent_name, parent_id, child_id
)
});
callback(widget, state);
callback(widget, state, properties);
}
}

View File

@ -4,7 +4,7 @@
use tracing::info_span;
use crate::app::RenderRoot;
use crate::core::{MutateCtx, Widget, WidgetId, WidgetMut};
use crate::core::{MutateCtx, PropertiesMut, Widget, WidgetId, WidgetMut};
use crate::passes::merge_state_up;
pub(crate) fn mutate_widget<R>(
@ -12,7 +12,7 @@ pub(crate) fn mutate_widget<R>(
id: WidgetId,
mutate_fn: impl FnOnce(WidgetMut<'_, dyn Widget>) -> R,
) -> R {
let (widget_mut, state_mut) = root.widget_arena.get_pair_mut(id);
let (widget_mut, state_mut, properties_mut) = root.widget_arena.get_all_mut(id);
let _span = info_span!("mutate_widget", name = widget_mut.item.short_type_name()).entered();
// NOTE - we can set parent_widget_state to None here, because the loop below will merge the
@ -24,6 +24,10 @@ pub(crate) fn mutate_widget<R>(
widget_state: state_mut.item,
widget_state_children: state_mut.children,
widget_children: widget_mut.children,
properties: PropertiesMut {
map: properties_mut.item,
},
properties_children: properties_mut.children,
},
widget: &mut **widget_mut.item,
};

View File

@ -3,6 +3,7 @@
use std::collections::HashMap;
use anymap3::AnyMap;
use tracing::{info_span, trace};
use tree_arena::ArenaMut;
use vello::Scene;
@ -10,7 +11,7 @@ use vello::kurbo::Affine;
use vello::peniko::{Color, Fill, Mix};
use crate::app::{RenderRoot, RenderRootState};
use crate::core::{PaintCtx, Widget, WidgetId, WidgetState};
use crate::core::{PaintCtx, PropertiesRef, Widget, WidgetId, WidgetState};
use crate::kurbo::Rect;
use crate::passes::{enter_span_if, recurse_on_children};
use crate::theme::get_debug_color;
@ -23,10 +24,17 @@ fn paint_widget(
scenes: &mut HashMap<WidgetId, Scene>,
mut widget: ArenaMut<'_, Box<dyn Widget>>,
mut state: ArenaMut<'_, WidgetState>,
mut properties: ArenaMut<'_, AnyMap>,
debug_paint: bool,
) {
let trace = global_state.trace.paint;
let _span = enter_span_if(trace, global_state, widget.reborrow(), state.reborrow());
let _span = enter_span_if(
trace,
global_state,
widget.reborrow(),
state.reborrow(),
properties.reborrow(),
);
let id = state.item.id;
@ -48,7 +56,10 @@ fn paint_widget(
// https://github.com/linebender/xilem/issues/524
let scene = scenes.entry(id).or_default();
scene.reset();
widget.item.paint(&mut ctx, scene);
let props = PropertiesRef {
map: properties.item,
};
widget.item.paint(&mut ctx, &props, scene);
}
state.item.request_paint = false;
@ -72,7 +83,8 @@ fn paint_widget(
id,
widget.reborrow_mut(),
state.children,
|widget, mut state| {
properties.children,
|widget, mut state, properties| {
// TODO - We skip painting stashed items.
// This may lead to zombie flags in rare cases, we need to fix this.
if state.item.is_stashed {
@ -88,6 +100,7 @@ fn paint_widget(
scenes,
widget,
state.reborrow_mut(),
properties,
debug_paint,
);
parent_state.merge_up(state.item);
@ -116,7 +129,7 @@ pub(crate) fn run_paint_pass(root: &mut RenderRoot) -> Scene {
// https://github.com/linebender/xilem/issues/524
let mut complete_scene = Scene::new();
let (root_widget, root_state) = {
let (root_widget, root_state, root_properties) = {
let widget_id = root.root.id();
let widget = root
.widget_arena
@ -128,7 +141,12 @@ pub(crate) fn run_paint_pass(root: &mut RenderRoot) -> Scene {
.states
.find_mut(widget_id)
.expect("root_paint: root state not in widget tree");
(widget, state)
let properties = root
.widget_arena
.properties
.find_mut(widget_id)
.expect("root_paint: root properties not in widget tree");
(widget, state, properties)
};
// TODO - This is a bit of a hack until we refactor widget tree mutation.
@ -141,6 +159,7 @@ pub(crate) fn run_paint_pass(root: &mut RenderRoot) -> Scene {
&mut scenes,
root_widget,
root_state,
root_properties,
root.debug_paint,
);
root.global_state.scenes = scenes;

View File

@ -3,14 +3,15 @@
use std::collections::HashSet;
use anymap3::AnyMap;
use cursor_icon::CursorIcon;
use tracing::{info_span, trace};
use tree_arena::ArenaMut;
use crate::app::{RenderRoot, RenderRootSignal, RenderRootState};
use crate::core::{
PointerEvent, QueryCtx, RegisterCtx, TextEvent, Update, UpdateCtx, Widget, WidgetId,
WidgetState,
PointerEvent, PropertiesMut, QueryCtx, RegisterCtx, TextEvent, Update, UpdateCtx, Widget,
WidgetId, WidgetState,
};
use crate::passes::event::{run_on_pointer_event_pass, run_on_text_event_pass};
use crate::passes::{enter_span, enter_span_if, merge_state_up, recurse_on_children};
@ -35,20 +36,24 @@ fn get_id_path(root: &RenderRoot, widget_id: Option<WidgetId>) -> Vec<WidgetId>
fn run_targeted_update_pass(
root: &mut RenderRoot,
target: Option<WidgetId>,
mut pass_fn: impl FnMut(&mut dyn Widget, &mut UpdateCtx),
mut pass_fn: impl FnMut(&mut dyn Widget, &mut UpdateCtx, &mut PropertiesMut<'_>),
) {
let mut current_id = target;
while let Some(widget_id) = current_id {
let parent_id = root.widget_arena.parent_of(widget_id);
let (widget_mut, state_mut) = root.widget_arena.get_pair_mut(widget_id);
let (widget_mut, state_mut, properties_mut) = root.widget_arena.get_all_mut(widget_id);
let mut ctx = UpdateCtx {
global_state: &mut root.global_state,
widget_state: state_mut.item,
widget_state_children: state_mut.children,
widget_children: widget_mut.children,
properties_children: properties_mut.children,
};
pass_fn(&mut **widget_mut.item, &mut ctx);
let mut props = PropertiesMut {
map: properties_mut.item,
};
pass_fn(&mut **widget_mut.item, &mut ctx, &mut props);
merge_state_up(&mut root.widget_arena, widget_id);
current_id = parent_id;
@ -58,7 +63,7 @@ fn run_targeted_update_pass(
fn run_single_update_pass(
root: &mut RenderRoot,
target: Option<WidgetId>,
mut pass_fn: impl FnMut(&mut dyn Widget, &mut UpdateCtx),
mut pass_fn: impl FnMut(&mut dyn Widget, &mut UpdateCtx, &mut PropertiesMut<'_>),
) {
let Some(target) = target else {
return;
@ -67,15 +72,19 @@ fn run_single_update_pass(
return;
}
let (widget_mut, state_mut) = root.widget_arena.get_pair_mut(target);
let (widget_mut, state_mut, properties_mut) = root.widget_arena.get_all_mut(target);
let mut ctx = UpdateCtx {
global_state: &mut root.global_state,
widget_state: state_mut.item,
widget_state_children: state_mut.children,
widget_children: widget_mut.children,
properties_children: properties_mut.children,
};
pass_fn(&mut **widget_mut.item, &mut ctx);
let mut props = PropertiesMut {
map: properties_mut.item,
};
pass_fn(&mut **widget_mut.item, &mut ctx, &mut props);
let mut current_id = Some(target);
while let Some(widget_id) = current_id {
@ -89,9 +98,16 @@ fn update_widget_tree(
global_state: &mut RenderRootState,
mut widget: ArenaMut<'_, Box<dyn Widget>>,
mut state: ArenaMut<'_, WidgetState>,
mut properties: ArenaMut<'_, AnyMap>,
) {
let trace = global_state.trace.update_tree;
let _span = enter_span_if(trace, global_state, widget.reborrow(), state.reborrow());
let _span = enter_span_if(
trace,
global_state,
widget.reborrow(),
state.reborrow(),
properties.reborrow(),
);
let id = state.item.id;
if !state.item.children_changed {
@ -103,6 +119,7 @@ fn update_widget_tree(
let mut ctx = RegisterCtx {
widget_state_children: state.children.reborrow_mut(),
widget_children: widget.children.reborrow_mut(),
properties_children: properties.children.reborrow_mut(),
#[cfg(debug_assertions)]
registered_ids: Vec::new(),
};
@ -147,8 +164,14 @@ fn update_widget_tree(
widget_state: state.item,
widget_state_children: state.children.reborrow_mut(),
widget_children: widget.children.reborrow_mut(),
properties_children: properties.children.reborrow_mut(),
};
widget.item.update(&mut ctx, &Update::WidgetAdded);
let mut props = PropertiesMut {
map: properties.item,
};
widget
.item
.update(&mut ctx, &mut props, &Update::WidgetAdded);
if trace {
trace!(
"{} received Update::WidgetAdded",
@ -168,8 +191,9 @@ fn update_widget_tree(
id,
widget.reborrow_mut(),
state.children,
|widget, mut state| {
update_widget_tree(global_state, widget, state.reborrow_mut());
properties.children,
|widget, mut state, properties| {
update_widget_tree(global_state, widget, state.reborrow_mut(), properties);
parent_state.merge_up(state.item);
},
);
@ -183,17 +207,20 @@ pub(crate) fn run_update_widget_tree_pass(root: &mut RenderRoot) {
let mut ctx = RegisterCtx {
widget_state_children: root.widget_arena.states.roots_mut(),
widget_children: root.widget_arena.widgets.roots_mut(),
properties_children: root.widget_arena.properties.roots_mut(),
#[cfg(debug_assertions)]
registered_ids: Vec::new(),
};
ctx.register_child(&mut root.root);
}
let (root_widget, mut root_state) = root.widget_arena.get_pair_mut(root.root.id());
let (root_widget, mut root_state, root_properties) =
root.widget_arena.get_all_mut(root.root.id());
update_widget_tree(
&mut root.global_state,
root_widget,
root_state.reborrow_mut(),
root_properties,
);
}
@ -206,9 +233,15 @@ fn update_disabled_for_widget(
global_state: &mut RenderRootState,
mut widget: ArenaMut<'_, Box<dyn Widget>>,
mut state: ArenaMut<'_, WidgetState>,
mut properties: ArenaMut<'_, AnyMap>,
parent_disabled: bool,
) {
let _span = enter_span(global_state, widget.reborrow(), state.reborrow());
let _span = enter_span(
global_state,
widget.reborrow(),
state.reborrow(),
properties.reborrow(),
);
let id = state.item.id;
let disabled = state.item.is_explicitly_disabled || parent_disabled;
@ -222,10 +255,14 @@ fn update_disabled_for_widget(
widget_state: state.item,
widget_state_children: state.children.reborrow_mut(),
widget_children: widget.children.reborrow_mut(),
properties_children: properties.children.reborrow_mut(),
};
let mut props = PropertiesMut {
map: properties.item,
};
widget
.item
.update(&mut ctx, &Update::DisabledChanged(disabled));
.update(&mut ctx, &mut props, &Update::DisabledChanged(disabled));
state.item.is_disabled = disabled;
state.item.needs_update_focus_chain = true;
state.item.request_accessibility = true;
@ -239,8 +276,15 @@ fn update_disabled_for_widget(
id,
widget.reborrow_mut(),
state.children,
|widget, mut state| {
update_disabled_for_widget(global_state, widget, state.reborrow_mut(), disabled);
properties.children,
|widget, mut state, properties| {
update_disabled_for_widget(
global_state,
widget,
state.reborrow_mut(),
properties,
disabled,
);
parent_state.merge_up(state.item);
},
);
@ -254,8 +298,14 @@ pub(crate) fn run_update_disabled_pass(root: &mut RenderRoot) {
root.global_state.needs_pointer_pass = true;
}
let (root_widget, root_state) = root.widget_arena.get_pair_mut(root.root.id());
update_disabled_for_widget(&mut root.global_state, root_widget, root_state, false);
let (root_widget, root_state, root_properties) = root.widget_arena.get_all_mut(root.root.id());
update_disabled_for_widget(
&mut root.global_state,
root_widget,
root_state,
root_properties,
false,
);
}
// ----------------
@ -270,9 +320,15 @@ fn update_stashed_for_widget(
global_state: &mut RenderRootState,
mut widget: ArenaMut<'_, Box<dyn Widget>>,
mut state: ArenaMut<'_, WidgetState>,
mut properties: ArenaMut<'_, AnyMap>,
parent_stashed: bool,
) {
let _span = enter_span(global_state, widget.reborrow(), state.reborrow());
let _span = enter_span(
global_state,
widget.reborrow(),
state.reborrow(),
properties.reborrow(),
);
let id = state.item.id;
let stashed = state.item.is_explicitly_stashed || parent_stashed;
@ -286,10 +342,14 @@ fn update_stashed_for_widget(
widget_state: state.item,
widget_state_children: state.children.reborrow_mut(),
widget_children: widget.children.reborrow_mut(),
properties_children: properties.children.reborrow_mut(),
};
let mut props = PropertiesMut {
map: properties.item,
};
widget
.item
.update(&mut ctx, &Update::StashedChanged(stashed));
.update(&mut ctx, &mut props, &Update::StashedChanged(stashed));
state.item.is_stashed = stashed;
state.item.needs_update_focus_chain = true;
// Note: We don't need request_repaint because stashing doesn't actually change
@ -312,8 +372,15 @@ fn update_stashed_for_widget(
id,
widget.reborrow_mut(),
state.children,
|widget, mut state| {
update_stashed_for_widget(global_state, widget, state.reborrow_mut(), stashed);
properties.children,
|widget, mut state, properties| {
update_stashed_for_widget(
global_state,
widget,
state.reborrow_mut(),
properties,
stashed,
);
parent_state.merge_up(state.item);
},
);
@ -322,8 +389,14 @@ fn update_stashed_for_widget(
pub(crate) fn run_update_stashed_pass(root: &mut RenderRoot) {
let _span = info_span!("update_stashed").entered();
let (root_widget, root_state) = root.widget_arena.get_pair_mut(root.root.id());
update_stashed_for_widget(&mut root.global_state, root_widget, root_state, false);
let (root_widget, root_state, root_properties) = root.widget_arena.get_all_mut(root.root.id());
update_stashed_for_widget(
&mut root.global_state,
root_widget,
root_state,
root_properties,
false,
);
}
// ----------------
@ -341,9 +414,15 @@ fn update_focus_chain_for_widget(
global_state: &mut RenderRootState,
mut widget: ArenaMut<'_, Box<dyn Widget>>,
mut state: ArenaMut<'_, WidgetState>,
mut properties: ArenaMut<'_, AnyMap>,
parent_focus_chain: &mut Vec<WidgetId>,
) {
let _span = enter_span(global_state, widget.reborrow(), state.reborrow());
let _span = enter_span(
global_state,
widget.reborrow(),
state.reborrow(),
properties.reborrow(),
);
let id = state.item.id;
if !state.item.needs_update_focus_chain {
@ -365,11 +444,13 @@ fn update_focus_chain_for_widget(
id,
widget.reborrow_mut(),
state.children,
|widget, mut state| {
properties.children,
|widget, mut state, properties| {
update_focus_chain_for_widget(
global_state,
widget,
state.reborrow_mut(),
properties,
&mut parent_state.focus_chain,
);
parent_state.merge_up(state.item);
@ -394,11 +475,12 @@ pub(crate) fn run_update_focus_chain_pass(root: &mut RenderRoot) {
let _span = info_span!("update_focus_chain").entered();
let mut dummy_focus_chain = Vec::new();
let (root_widget, mut root_state) = root.widget_arena.get_pair_mut(root.root.id());
let (root_widget, root_state, root_properties) = root.widget_arena.get_all_mut(root.root.id());
update_focus_chain_for_widget(
&mut root.global_state,
root_widget,
root_state.reborrow_mut(),
root_state,
root_properties,
&mut dummy_focus_chain,
);
}
@ -483,11 +565,11 @@ pub(crate) fn run_update_focus_pass(root: &mut RenderRoot) {
widget_id: WidgetId,
focused_set: &HashSet<WidgetId>,
) {
run_targeted_update_pass(root, Some(widget_id), |widget, ctx| {
run_targeted_update_pass(root, Some(widget_id), |widget, ctx, props| {
let has_focused = focused_set.contains(&ctx.widget_id());
if ctx.widget_state.has_focus_target != has_focused {
widget.update(ctx, &Update::ChildFocusChanged(has_focused));
widget.update(ctx, props, &Update::ChildFocusChanged(has_focused));
}
ctx.widget_state.has_focus_target = has_focused;
});
@ -524,13 +606,13 @@ pub(crate) fn run_update_focus_pass(root: &mut RenderRoot) {
if prev_focused != next_focused {
// We send FocusChange event to widget that lost and the widget that gained focus.
// We also request accessibility, because build_access_node() depends on the focus state.
run_single_update_pass(root, prev_focused, |widget, ctx| {
widget.update(ctx, &Update::FocusChanged(false));
run_single_update_pass(root, prev_focused, |widget, ctx, props| {
widget.update(ctx, props, &Update::FocusChanged(false));
ctx.widget_state.request_accessibility = true;
ctx.widget_state.needs_accessibility = true;
});
run_single_update_pass(root, next_focused, |widget, ctx| {
widget.update(ctx, &Update::FocusChanged(true));
run_single_update_pass(root, next_focused, |widget, ctx, props| {
widget.update(ctx, props, &Update::FocusChanged(true));
ctx.widget_state.request_accessibility = true;
ctx.widget_state.needs_accessibility = true;
});
@ -568,9 +650,9 @@ pub(crate) fn run_update_scroll_pass(root: &mut RenderRoot) {
let mut target_rect = rect;
// TODO - Run top-down instead of bottom-up.
run_targeted_update_pass(root, Some(target), |widget, ctx| {
run_targeted_update_pass(root, Some(target), |widget, ctx, props| {
let event = Update::RequestPanToChild(rect);
widget.update(ctx, &event);
widget.update(ctx, props, &event);
// TODO - We should run the compose method after this, so
// translations are updated and the rect passed to parents
@ -660,11 +742,11 @@ pub(crate) fn run_update_pointer_pass(root: &mut RenderRoot) {
widget_id: WidgetId,
hovered_set: &HashSet<WidgetId>,
) {
run_targeted_update_pass(root, Some(widget_id), |widget, ctx| {
run_targeted_update_pass(root, Some(widget_id), |widget, ctx, props| {
let has_hovered = hovered_set.contains(&ctx.widget_id());
if ctx.widget_state.has_hovered != has_hovered {
widget.update(ctx, &Update::ChildHoveredChanged(has_hovered));
widget.update(ctx, props, &Update::ChildHoveredChanged(has_hovered));
}
ctx.widget_state.has_hovered = has_hovered;
});
@ -690,13 +772,13 @@ pub(crate) fn run_update_pointer_pass(root: &mut RenderRoot) {
}
if prev_hovered_widget != next_hovered_widget {
run_single_update_pass(root, prev_hovered_widget, |widget, ctx| {
run_single_update_pass(root, prev_hovered_widget, |widget, ctx, props| {
ctx.widget_state.is_hovered = false;
widget.update(ctx, &Update::HoveredChanged(false));
widget.update(ctx, props, &Update::HoveredChanged(false));
});
run_single_update_pass(root, next_hovered_widget, |widget, ctx| {
run_single_update_pass(root, next_hovered_widget, |widget, ctx, props| {
ctx.widget_state.is_hovered = true;
widget.update(ctx, &Update::HoveredChanged(true));
widget.update(ctx, props, &Update::HoveredChanged(true));
});
}
@ -710,13 +792,14 @@ pub(crate) fn run_update_pointer_pass(root: &mut RenderRoot) {
.or(next_hovered_widget);
let new_icon = if let (Some(icon_source), Some(pos)) = (icon_source, pointer_pos) {
let (widget, state) = root.widget_arena.get_pair(icon_source);
let (widget, state, properties) = root.widget_arena.get_all(icon_source);
let ctx = QueryCtx {
global_state: &root.global_state,
widget_state_children: state.children,
widget_children: widget.children,
widget_state: state.item,
properties_children: properties.children,
};
if state.item.is_disabled {

View File

@ -0,0 +1,33 @@
// Copyright 2025 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0
//! Types and logic commonly used across widgets.
//!
//! See [properties documentation](crate::doc::doc_03_implementing_container_widget) for details.
#![allow(
missing_docs,
reason = "A lot of properties and especially their fields are self-explanatory."
)]
use std::any::TypeId;
use vello::peniko::color::{AlphaColor, Srgb};
use crate::core::UpdateCtx;
// TODO - Split out into files.
/// The background color of a widget.
#[derive(Clone, Copy, Debug)]
pub struct BackgroundColor {
pub color: AlphaColor<Srgb>,
}
impl BackgroundColor {
pub(crate) fn prop_changed(ctx: &mut UpdateCtx<'_>, property_type: TypeId) {
if property_type == TypeId::of::<Self>() {
ctx.request_paint_only();
}
}
}

View File

@ -8,6 +8,7 @@
//! Note: Some of these types are undocumented. They're meant to help maintainers of
//! Masonry, not to be user-facing.
use std::any::TypeId;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
@ -20,24 +21,27 @@ use vello::Scene;
use crate::AsAny;
use crate::core::{
AccessCtx, AccessEvent, BoxConstraints, ComposeCtx, EventCtx, LayoutCtx, PaintCtx,
PointerEvent, QueryCtx, RegisterCtx, TextEvent, Update, UpdateCtx, Widget, WidgetId, WidgetPod,
WidgetRef, find_widget_at_pos,
PointerEvent, PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, TextEvent, Update,
UpdateCtx, Widget, WidgetId, WidgetPod, WidgetRef, find_widget_at_pos,
};
use crate::kurbo::{Point, Size};
use crate::widgets::SizedBox;
use cursor_icon::CursorIcon;
pub type PointerEventFn<S> = dyn FnMut(&mut S, &mut EventCtx, &PointerEvent);
pub type TextEventFn<S> = dyn FnMut(&mut S, &mut EventCtx, &TextEvent);
pub type AccessEventFn<S> = dyn FnMut(&mut S, &mut EventCtx, &AccessEvent);
pub type AnimFrameFn<S> = dyn FnMut(&mut S, &mut UpdateCtx, u64);
pub type PointerEventFn<S> =
dyn FnMut(&mut S, &mut EventCtx, &mut PropertiesMut<'_>, &PointerEvent);
pub type TextEventFn<S> = dyn FnMut(&mut S, &mut EventCtx, &mut PropertiesMut<'_>, &TextEvent);
pub type AccessEventFn<S> = dyn FnMut(&mut S, &mut EventCtx, &mut PropertiesMut<'_>, &AccessEvent);
pub type AnimFrameFn<S> = dyn FnMut(&mut S, &mut UpdateCtx, &mut PropertiesMut<'_>, u64);
pub type RegisterChildrenFn<S> = dyn FnMut(&mut S, &mut RegisterCtx);
pub type UpdateFn<S> = dyn FnMut(&mut S, &mut UpdateCtx, &Update);
pub type LayoutFn<S> = dyn FnMut(&mut S, &mut LayoutCtx, &BoxConstraints) -> Size;
pub type UpdateFn<S> = dyn FnMut(&mut S, &mut UpdateCtx, &mut PropertiesMut<'_>, &Update);
pub type PropertyChangeFn<S> = dyn FnMut(&mut S, &mut UpdateCtx, TypeId);
pub type LayoutFn<S> =
dyn FnMut(&mut S, &mut LayoutCtx, &mut PropertiesMut<'_>, &BoxConstraints) -> Size;
pub type ComposeFn<S> = dyn FnMut(&mut S, &mut ComposeCtx);
pub type PaintFn<S> = dyn FnMut(&mut S, &mut PaintCtx, &mut Scene);
pub type PaintFn<S> = dyn FnMut(&mut S, &mut PaintCtx, &PropertiesRef<'_>, &mut Scene);
pub type RoleFn<S> = dyn Fn(&S) -> Role;
pub type AccessFn<S> = dyn FnMut(&mut S, &mut AccessCtx, &mut Node);
pub type AccessFn<S> = dyn FnMut(&mut S, &mut AccessCtx, &PropertiesRef<'_>, &mut Node);
pub type ChildrenFn<S> = dyn Fn(&S) -> SmallVec<[WidgetId; 16]>;
#[cfg(FALSE)]
@ -57,6 +61,7 @@ pub struct ModularWidget<S> {
on_anim_frame: Option<Box<AnimFrameFn<S>>>,
register_children: Option<Box<RegisterChildrenFn<S>>>,
update: Option<Box<UpdateFn<S>>>,
property_change: Option<Box<PropertyChangeFn<S>>>,
layout: Option<Box<LayoutFn<S>>>,
compose: Option<Box<ComposeFn<S>>>,
paint: Option<Box<PaintFn<S>>>,
@ -120,6 +125,8 @@ pub enum Record {
RC,
/// Update
U(Update),
/// Property change.
PC(TypeId),
/// Layout. Records the size returned by the layout method.
Layout(Size),
/// Compose.
@ -167,6 +174,7 @@ impl<S> ModularWidget<S> {
on_anim_frame: None,
register_children: None,
update: None,
property_change: None,
layout: None,
compose: None,
paint: None,
@ -207,7 +215,7 @@ impl<S> ModularWidget<S> {
/// See [`Widget::on_pointer_event`]
pub fn pointer_event_fn(
mut self,
f: impl FnMut(&mut S, &mut EventCtx, &PointerEvent) + 'static,
f: impl FnMut(&mut S, &mut EventCtx, &mut PropertiesMut<'_>, &PointerEvent) + 'static,
) -> Self {
self.on_pointer_event = Some(Box::new(f));
self
@ -216,7 +224,7 @@ impl<S> ModularWidget<S> {
/// See [`Widget::on_text_event`]
pub fn text_event_fn(
mut self,
f: impl FnMut(&mut S, &mut EventCtx, &TextEvent) + 'static,
f: impl FnMut(&mut S, &mut EventCtx, &mut PropertiesMut<'_>, &TextEvent) + 'static,
) -> Self {
self.on_text_event = Some(Box::new(f));
self
@ -225,14 +233,17 @@ impl<S> ModularWidget<S> {
/// See [`Widget::on_access_event`]
pub fn access_event_fn(
mut self,
f: impl FnMut(&mut S, &mut EventCtx, &AccessEvent) + 'static,
f: impl FnMut(&mut S, &mut EventCtx, &mut PropertiesMut<'_>, &AccessEvent) + 'static,
) -> Self {
self.on_access_event = Some(Box::new(f));
self
}
/// See [`Widget::on_anim_frame`]
pub fn anim_frame_fn(mut self, f: impl FnMut(&mut S, &mut UpdateCtx, u64) + 'static) -> Self {
pub fn anim_frame_fn(
mut self,
f: impl FnMut(&mut S, &mut UpdateCtx, &mut PropertiesMut<'_>, u64) + 'static,
) -> Self {
self.on_anim_frame = Some(Box::new(f));
self
}
@ -247,15 +258,27 @@ impl<S> ModularWidget<S> {
}
/// See [`Widget::update`]
pub fn update_fn(mut self, f: impl FnMut(&mut S, &mut UpdateCtx, &Update) + 'static) -> Self {
pub fn update_fn(
mut self,
f: impl FnMut(&mut S, &mut UpdateCtx, &mut PropertiesMut<'_>, &Update) + 'static,
) -> Self {
self.update = Some(Box::new(f));
self
}
/// See [`Widget::property_changed`]
pub fn property_change_fn(
mut self,
f: impl FnMut(&mut S, &mut UpdateCtx, TypeId) + 'static,
) -> Self {
self.property_change = Some(Box::new(f));
self
}
/// See [`Widget::layout`]
pub fn layout_fn(
mut self,
f: impl FnMut(&mut S, &mut LayoutCtx, &BoxConstraints) -> Size + 'static,
f: impl FnMut(&mut S, &mut LayoutCtx, &mut PropertiesMut<'_>, &BoxConstraints) -> Size + 'static,
) -> Self {
self.layout = Some(Box::new(f));
self
@ -268,7 +291,10 @@ impl<S> ModularWidget<S> {
}
/// See [`Widget::paint`]
pub fn paint_fn(mut self, f: impl FnMut(&mut S, &mut PaintCtx, &mut Scene) + 'static) -> Self {
pub fn paint_fn(
mut self,
f: impl FnMut(&mut S, &mut PaintCtx, &PropertiesRef<'_>, &mut Scene) + 'static,
) -> Self {
self.paint = Some(Box::new(f));
self
}
@ -280,7 +306,10 @@ impl<S> ModularWidget<S> {
}
/// See [`Widget::accessibility`]
pub fn access_fn(mut self, f: impl FnMut(&mut S, &mut AccessCtx, &mut Node) + 'static) -> Self {
pub fn access_fn(
mut self,
f: impl FnMut(&mut S, &mut AccessCtx, &PropertiesRef<'_>, &mut Node) + 'static,
) -> Self {
self.access = Some(Box::new(f));
self
}
@ -297,27 +326,42 @@ impl<S> ModularWidget<S> {
#[warn(clippy::missing_trait_methods)]
impl<S: 'static> Widget for ModularWidget<S> {
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) {
fn on_pointer_event(
&mut self,
ctx: &mut EventCtx,
props: &mut PropertiesMut<'_>,
event: &PointerEvent,
) {
if let Some(f) = self.on_pointer_event.as_mut() {
f(&mut self.state, ctx, event);
f(&mut self.state, ctx, props, event);
}
}
fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent) {
fn on_text_event(
&mut self,
ctx: &mut EventCtx,
props: &mut PropertiesMut<'_>,
event: &TextEvent,
) {
if let Some(f) = self.on_text_event.as_mut() {
f(&mut self.state, ctx, event);
f(&mut self.state, ctx, props, event);
}
}
fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) {
fn on_access_event(
&mut self,
ctx: &mut EventCtx,
props: &mut PropertiesMut<'_>,
event: &AccessEvent,
) {
if let Some(f) = self.on_access_event.as_mut() {
f(&mut self.state, ctx, event);
f(&mut self.state, ctx, props, event);
}
}
fn on_anim_frame(&mut self, ctx: &mut UpdateCtx, interval: u64) {
fn on_anim_frame(&mut self, ctx: &mut UpdateCtx, props: &mut PropertiesMut<'_>, interval: u64) {
if let Some(f) = self.on_anim_frame.as_mut() {
f(&mut self.state, ctx, interval);
f(&mut self.state, ctx, props, interval);
}
}
@ -327,17 +371,28 @@ impl<S: 'static> Widget for ModularWidget<S> {
}
}
fn update(&mut self, ctx: &mut UpdateCtx, event: &Update) {
fn update(&mut self, ctx: &mut UpdateCtx, props: &mut PropertiesMut<'_>, event: &Update) {
if let Some(f) = self.update.as_mut() {
f(&mut self.state, ctx, event);
f(&mut self.state, ctx, props, event);
}
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn property_changed(&mut self, ctx: &mut UpdateCtx, property_type: TypeId) {
if let Some(f) = self.property_change.as_mut() {
f(&mut self.state, ctx, property_type);
}
}
fn layout(
&mut self,
ctx: &mut LayoutCtx,
props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
let Self { state, layout, .. } = self;
layout
.as_mut()
.map(|f| f(state, ctx, bc))
.map(|f| f(state, ctx, props, bc))
.unwrap_or_else(|| Size::new(100., 100.))
}
@ -355,15 +410,15 @@ impl<S: 'static> Widget for ModularWidget<S> {
}
}
fn accessibility(&mut self, ctx: &mut AccessCtx, node: &mut Node) {
fn accessibility(&mut self, ctx: &mut AccessCtx, props: &PropertiesRef<'_>, node: &mut Node) {
if let Some(f) = self.access.as_mut() {
f(&mut self.state, ctx, node);
f(&mut self.state, ctx, props, node);
}
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, props: &PropertiesRef<'_>, scene: &mut Scene) {
if let Some(f) = self.paint.as_mut() {
f(&mut self.state, ctx, scene);
f(&mut self.state, ctx, props, scene);
}
}
@ -402,9 +457,17 @@ impl<S: 'static> Widget for ModularWidget<S> {
fn find_widget_at_pos<'c>(
&'c self,
ctx: QueryCtx<'c>,
props: PropertiesRef<'c>,
pos: Point,
) -> Option<WidgetRef<'c, dyn Widget>> {
find_widget_at_pos(&WidgetRef { widget: self, ctx }, pos)
find_widget_at_pos(
&WidgetRef {
widget: self,
properties: props,
ctx,
},
pos,
)
}
fn type_name(&self) -> &'static str {
@ -438,7 +501,7 @@ impl ReplaceChild {
impl Widget for ReplaceChild {
#[cfg(FALSE)]
fn on_event(&mut self, ctx: &mut EventCtx, event: &Event) {
fn on_event(&mut self, ctx: &mut EventCtx, _props: &mut PropertiesMut<'_>, event: &Event) {
#[cfg(FALSE)]
if let Event::Command(cmd) = event {
if cmd.is(REPLACE_CHILD) {
@ -450,31 +513,62 @@ impl Widget for ReplaceChild {
self.child.on_event(ctx, event)
}
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {}
fn on_pointer_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &PointerEvent,
) {
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.child);
}
fn update(&mut self, _ctx: &mut UpdateCtx, _event: &Update) {}
fn update(&mut self, _ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, _event: &Update) {}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn property_changed(&mut self, _ctx: &mut UpdateCtx, _property_type: TypeId) {}
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
ctx.run_layout(&mut self.child, bc)
}
fn compose(&mut self, _ctx: &mut ComposeCtx) {}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut Scene) {}
fn paint(&mut self, _ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, _scene: &mut Scene) {}
fn accessibility_role(&self) -> Role {
Role::GenericContainer
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) {}
fn accessibility(
&mut self,
_ctx: &mut AccessCtx,
_props: &PropertiesRef<'_>,
_node: &mut Node,
) {
}
fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
todo!()
@ -516,24 +610,39 @@ impl Recording {
#[warn(clippy::missing_trait_methods)]
impl<W: Widget> Widget for Recorder<W> {
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) {
fn on_pointer_event(
&mut self,
ctx: &mut EventCtx,
props: &mut PropertiesMut<'_>,
event: &PointerEvent,
) {
self.recording.push(Record::PE(event.clone()));
self.child.on_pointer_event(ctx, event);
self.child.on_pointer_event(ctx, props, event);
}
fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent) {
fn on_text_event(
&mut self,
ctx: &mut EventCtx,
props: &mut PropertiesMut<'_>,
event: &TextEvent,
) {
self.recording.push(Record::TE(event.clone()));
self.child.on_text_event(ctx, event);
self.child.on_text_event(ctx, props, event);
}
fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) {
fn on_access_event(
&mut self,
ctx: &mut EventCtx,
props: &mut PropertiesMut<'_>,
event: &AccessEvent,
) {
self.recording.push(Record::AE(event.clone()));
self.child.on_access_event(ctx, event);
self.child.on_access_event(ctx, props, event);
}
fn on_anim_frame(&mut self, ctx: &mut UpdateCtx, interval: u64) {
fn on_anim_frame(&mut self, ctx: &mut UpdateCtx, props: &mut PropertiesMut<'_>, interval: u64) {
self.recording.push(Record::AF(interval));
self.child.on_anim_frame(ctx, interval);
self.child.on_anim_frame(ctx, props, interval);
}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
@ -541,13 +650,23 @@ impl<W: Widget> Widget for Recorder<W> {
self.child.register_children(ctx);
}
fn update(&mut self, ctx: &mut UpdateCtx, event: &Update) {
fn update(&mut self, ctx: &mut UpdateCtx, props: &mut PropertiesMut<'_>, event: &Update) {
self.recording.push(Record::U(event.clone()));
self.child.update(ctx, event);
self.child.update(ctx, props, event);
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
let size = self.child.layout(ctx, bc);
fn property_changed(&mut self, ctx: &mut UpdateCtx, property_type: TypeId) {
self.recording.push(Record::PC(property_type));
self.child.property_changed(ctx, property_type);
}
fn layout(
&mut self,
ctx: &mut LayoutCtx,
props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
let size = self.child.layout(ctx, props, bc);
self.recording.push(Record::Layout(size));
size
}
@ -557,18 +676,18 @@ impl<W: Widget> Widget for Recorder<W> {
self.child.compose(ctx);
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, props: &PropertiesRef<'_>, scene: &mut Scene) {
self.recording.push(Record::Paint);
self.child.paint(ctx, scene);
self.child.paint(ctx, props, scene);
}
fn accessibility_role(&self) -> Role {
self.child.accessibility_role()
}
fn accessibility(&mut self, ctx: &mut AccessCtx, node: &mut Node) {
fn accessibility(&mut self, ctx: &mut AccessCtx, props: &PropertiesRef<'_>, node: &mut Node) {
self.recording.push(Record::Access);
self.child.accessibility(ctx, node);
self.child.accessibility(ctx, props, node);
}
fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
@ -602,9 +721,10 @@ impl<W: Widget> Widget for Recorder<W> {
fn find_widget_at_pos<'c>(
&'c self,
ctx: QueryCtx<'c>,
props: PropertiesRef<'c>,
pos: Point,
) -> Option<WidgetRef<'c, dyn Widget>> {
self.child.find_widget_at_pos(ctx, pos)
self.child.find_widget_at_pos(ctx, props, pos)
}
fn type_name(&self) -> &'static str {

View File

@ -14,8 +14,8 @@ use tracing::{Span, trace_span};
use vello::Scene;
use crate::core::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent, QueryCtx,
RegisterCtx, TextEvent, Widget, WidgetId, WidgetPod,
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent,
PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, TextEvent, Widget, WidgetId, WidgetPod,
};
use crate::kurbo::{Rect, Size};
use crate::util::UnitPoint;
@ -86,17 +86,40 @@ impl Align {
// --- MARK: IMPL WIDGET ---
impl Widget for Align {
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {}
fn on_pointer_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &PointerEvent,
) {
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.child);
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
let size = ctx.run_layout(&mut self.child, &bc.loosen());
log_size_warnings(size);
@ -137,13 +160,19 @@ impl Widget for Align {
my_size
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut Scene) {}
fn paint(&mut self, _ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, _scene: &mut Scene) {}
fn accessibility_role(&self) -> Role {
Role::GenericContainer
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) {}
fn accessibility(
&mut self,
_ctx: &mut AccessCtx,
_props: &PropertiesRef<'_>,
_node: &mut Node,
) {
}
fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
smallvec![self.child.id()]

View File

@ -10,8 +10,8 @@ use vello::Scene;
use crate::core::{
AccessCtx, AccessEvent, Action, ArcStr, BoxConstraints, EventCtx, LayoutCtx, PaintCtx,
PointerButton, PointerEvent, QueryCtx, TextEvent, Update, UpdateCtx, Widget, WidgetId,
WidgetMut, WidgetPod,
PointerButton, PointerEvent, PropertiesMut, PropertiesRef, QueryCtx, TextEvent, Update,
UpdateCtx, Widget, WidgetId, WidgetMut, WidgetPod,
};
use crate::kurbo::{Insets, Size};
use crate::theme;
@ -86,7 +86,12 @@ impl Button {
// --- MARK: IMPL WIDGET ---
impl Widget for Button {
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) {
fn on_pointer_event(
&mut self,
ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
event: &PointerEvent,
) {
match event {
PointerEvent::PointerDown(_, _) => {
if !ctx.is_disabled() {
@ -108,9 +113,20 @@ impl Widget for Button {
}
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) {
fn on_access_event(
&mut self,
ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
event: &AccessEvent,
) {
if ctx.target() == ctx.widget_id() {
match event.action {
accesskit::Action::Click => {
@ -121,7 +137,7 @@ impl Widget for Button {
}
}
fn update(&mut self, ctx: &mut UpdateCtx, event: &Update) {
fn update(&mut self, ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, event: &Update) {
match event {
Update::HoveredChanged(_) | Update::FocusChanged(_) | Update::DisabledChanged(_) => {
ctx.request_paint_only();
@ -134,7 +150,12 @@ impl Widget for Button {
ctx.register_child(&mut self.label);
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
let padding = Size::new(LABEL_INSETS.x_value(), LABEL_INSETS.y_value());
let label_bc = bc.shrink(padding).loosen();
@ -158,7 +179,7 @@ impl Widget for Button {
button_size
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, scene: &mut Scene) {
let is_active = ctx.is_pointer_capture_target() && !ctx.is_disabled();
let is_hovered = ctx.is_hovered();
let size = ctx.size();
@ -197,7 +218,7 @@ impl Widget for Button {
Role::Button
}
fn accessibility(&mut self, ctx: &mut AccessCtx, node: &mut Node) {
fn accessibility(&mut self, ctx: &mut AccessCtx, _props: &PropertiesRef<'_>, node: &mut Node) {
// IMPORTANT: We don't want to merge this code in practice, because
// the child label already has a 'name' property.
// This is more of a proof of concept of `get_raw_ref()`.

View File

@ -11,8 +11,8 @@ use vello::kurbo::{Affine, BezPath, Cap, Join, Size, Stroke};
use crate::core::{
AccessCtx, AccessEvent, Action, ArcStr, BoxConstraints, EventCtx, LayoutCtx, PaintCtx,
PointerEvent, QueryCtx, RegisterCtx, TextEvent, Update, UpdateCtx, Widget, WidgetId, WidgetMut,
WidgetPod,
PointerEvent, PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, TextEvent, Update,
UpdateCtx, Widget, WidgetId, WidgetMut, WidgetPod,
};
use crate::theme;
use crate::util::{UnitPoint, fill_lin_gradient, stroke};
@ -66,7 +66,12 @@ impl Checkbox {
// --- MARK: IMPL WIDGET ---
impl Widget for Checkbox {
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) {
fn on_pointer_event(
&mut self,
ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
event: &PointerEvent,
) {
match event {
PointerEvent::PointerDown(_, _) => {
if !ctx.is_disabled() {
@ -89,9 +94,20 @@ impl Widget for Checkbox {
}
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) {
fn on_access_event(
&mut self,
ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
event: &AccessEvent,
) {
if ctx.target() == ctx.widget_id() {
match event.action {
accesskit::Action::Click => {
@ -105,7 +121,7 @@ impl Widget for Checkbox {
}
}
fn update(&mut self, ctx: &mut UpdateCtx, event: &Update) {
fn update(&mut self, ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, event: &Update) {
match event {
Update::HoveredChanged(_) | Update::FocusChanged(_) | Update::DisabledChanged(_) => {
ctx.request_paint_only();
@ -119,7 +135,12 @@ impl Widget for Checkbox {
ctx.register_child(&mut self.label);
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
let x_padding = theme::WIDGET_CONTROL_COMPONENT_PADDING;
let check_size = theme::BASIC_WIDGET_HEIGHT;
@ -137,7 +158,7 @@ impl Widget for Checkbox {
our_size
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, scene: &mut Scene) {
let check_size = theme::BASIC_WIDGET_HEIGHT;
let border_width = 1.;
@ -193,7 +214,7 @@ impl Widget for Checkbox {
Role::CheckBox
}
fn accessibility(&mut self, ctx: &mut AccessCtx, node: &mut Node) {
fn accessibility(&mut self, ctx: &mut AccessCtx, _props: &PropertiesRef<'_>, node: &mut Node) {
// IMPORTANT: We don't want to merge this code in practice, because
// the child label already has a 'name' property.
// This is more of a proof of concept of `get_raw_ref()`.

View File

@ -11,8 +11,8 @@ use vello::kurbo::common::FloatExt;
use vello::kurbo::{Affine, Line, Stroke, Vec2};
use crate::core::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent, QueryCtx,
TextEvent, Widget, WidgetId, WidgetMut, WidgetPod,
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent,
PropertiesMut, PropertiesRef, QueryCtx, TextEvent, Widget, WidgetId, WidgetMut, WidgetPod,
};
use crate::kurbo::{Point, Rect, Size};
@ -911,11 +911,29 @@ fn new_flex_child(params: FlexParams, widget: WidgetPod<dyn Widget>) -> Child {
// --- MARK: IMPL WIDGET---
impl Widget for Flex {
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {}
fn on_pointer_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &PointerEvent,
) {
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn register_children(&mut self, ctx: &mut crate::core::RegisterCtx) {
for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) {
@ -923,7 +941,12 @@ impl Widget for Flex {
}
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
// we loosen our constraints when passing to children.
let loosened_bc = bc.loosen();
@ -1177,7 +1200,7 @@ impl Widget for Flex {
my_size
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, scene: &mut Scene) {
// paint the baseline if we're debugging layout
if ctx.debug_paint_enabled() && ctx.baseline_offset() != 0.0 {
let color = ctx.debug_color();
@ -1193,7 +1216,13 @@ impl Widget for Flex {
Role::GenericContainer
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) {}
fn accessibility(
&mut self,
_ctx: &mut AccessCtx,
_props: &PropertiesRef<'_>,
_node: &mut Node,
) {
}
fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
self.children

View File

@ -8,8 +8,8 @@ use vello::Scene;
use vello::kurbo::{Affine, Line, Stroke};
use crate::core::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent, QueryCtx,
TextEvent, Widget, WidgetId, WidgetMut, WidgetPod,
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent,
PropertiesMut, PropertiesRef, QueryCtx, TextEvent, Widget, WidgetId, WidgetMut, WidgetPod,
};
use crate::kurbo::{Point, Size};
@ -242,11 +242,29 @@ impl Grid {
// --- MARK: IMPL WIDGET---
impl Widget for Grid {
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {}
fn on_pointer_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &PointerEvent,
) {
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn register_children(&mut self, ctx: &mut crate::core::RegisterCtx) {
for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) {
@ -254,7 +272,12 @@ impl Widget for Grid {
}
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
let total_size = bc.max();
if !total_size.is_finite() {
debug_panic!(
@ -279,7 +302,7 @@ impl Widget for Grid {
total_size
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, scene: &mut Scene) {
// paint the baseline if we're debugging layout
if ctx.debug_paint_enabled() && ctx.baseline_offset() != 0.0 {
let color = ctx.debug_color();
@ -295,7 +318,13 @@ impl Widget for Grid {
Role::GenericContainer
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) {}
fn accessibility(
&mut self,
_ctx: &mut AccessCtx,
_props: &PropertiesRef<'_>,
_node: &mut Node,
) {
}
fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
self.children

View File

@ -13,7 +13,8 @@ use vello::peniko::{BlendMode, Image as ImageBuf};
use crate::core::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, ObjectFit, PaintCtx, PointerEvent,
QueryCtx, RegisterCtx, TextEvent, Update, UpdateCtx, Widget, WidgetId, WidgetMut,
PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, TextEvent, Update, UpdateCtx, Widget,
WidgetId, WidgetMut,
};
use crate::kurbo::Size;
@ -71,17 +72,40 @@ impl Image {
// --- MARK: IMPL WIDGET ---
impl Widget for Image {
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {}
fn on_pointer_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &PointerEvent,
) {
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn register_children(&mut self, _ctx: &mut RegisterCtx) {}
fn update(&mut self, _ctx: &mut UpdateCtx, _event: &Update) {}
fn update(&mut self, _ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, _event: &Update) {}
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
_ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
// If either the width or height is constrained calculate a value so that the image fits
// in the size exactly. If it is unconstrained by both width and height take the size of
// the image.
@ -112,7 +136,7 @@ impl Widget for Image {
}
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, scene: &mut Scene) {
let image_size = Size::new(self.image_data.width as f64, self.image_data.height as f64);
let transform = self.object_fit.affine_to_fill(ctx.size(), image_size);
@ -126,7 +150,12 @@ impl Widget for Image {
Role::Image
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) {
fn accessibility(
&mut self,
_ctx: &mut AccessCtx,
_props: &PropertiesRef<'_>,
_node: &mut Node,
) {
// TODO - Handle alt text and such.
}

View File

@ -18,8 +18,8 @@ use vello::peniko::{BlendMode, Brush};
use crate::core::{
AccessCtx, AccessEvent, ArcStr, BoxConstraints, BrushIndex, EventCtx, LayoutCtx, PaintCtx,
PointerEvent, QueryCtx, RegisterCtx, StyleProperty, StyleSet, TextEvent, Update, UpdateCtx,
Widget, WidgetId, WidgetMut, default_styles, render_text,
PointerEvent, PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, StyleProperty, StyleSet,
TextEvent, Update, UpdateCtx, Widget, WidgetId, WidgetMut, default_styles, render_text,
};
use crate::theme;
@ -311,19 +311,37 @@ impl Label {
// --- MARK: IMPL WIDGET ---
impl Widget for Label {
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {}
fn on_pointer_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &PointerEvent,
) {
}
fn accepts_pointer_interaction(&self) -> bool {
false
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn register_children(&mut self, _ctx: &mut RegisterCtx) {}
fn update(&mut self, ctx: &mut UpdateCtx, event: &Update) {
fn update(&mut self, ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, event: &Update) {
match event {
Update::DisabledChanged(_) => {
if self.disabled_brush.is_some() {
@ -334,7 +352,12 @@ impl Widget for Label {
}
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
let available_width = if bc.max().width.is_finite() {
Some(bc.max().width as f32 - 2. * LABEL_X_PADDING as f32)
} else {
@ -397,7 +420,7 @@ impl Widget for Label {
bc.constrain(label_size)
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, scene: &mut Scene) {
if self.line_break_mode == LineBreaking::Clip {
let clip_rect = ctx.size().to_rect();
scene.push_layer(BlendMode::default(), 1., Affine::IDENTITY, &clip_rect);
@ -422,7 +445,7 @@ impl Widget for Label {
Role::Label
}
fn accessibility(&mut self, ctx: &mut AccessCtx, node: &mut Node) {
fn accessibility(&mut self, ctx: &mut AccessCtx, _props: &PropertiesRef<'_>, node: &mut Node) {
let window_origin = ctx.window_origin();
self.accessibility.build_nodes(
self.text.as_ref(),

View File

@ -13,8 +13,8 @@ use vello::kurbo::{Point, Rect, Size, Vec2};
use crate::core::{
AccessCtx, AccessEvent, BoxConstraints, ComposeCtx, EventCtx, FromDynWidget, LayoutCtx,
PaintCtx, PointerEvent, QueryCtx, RegisterCtx, TextEvent, Update, UpdateCtx, Widget, WidgetId,
WidgetMut, WidgetPod,
PaintCtx, PointerEvent, PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, TextEvent, Update,
UpdateCtx, Widget, WidgetId, WidgetMut, WidgetPod,
};
use crate::widgets::{Axis, ScrollBar};
@ -260,7 +260,12 @@ impl<W: Widget + FromDynWidget + ?Sized> Portal<W> {
// --- MARK: IMPL WIDGET ---
impl<W: Widget + FromDynWidget + ?Sized> Widget for Portal<W> {
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) {
fn on_pointer_event(
&mut self,
ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
event: &PointerEvent,
) {
const SCROLLING_SPEED: f64 = 10.0;
let portal_size = ctx.size();
@ -321,10 +326,22 @@ impl<W: Widget + FromDynWidget + ?Sized> Widget for Portal<W> {
}
// TODO - handle Home/End keys, etc
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
// TODO - Handle scroll-related events?
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.child);
@ -332,7 +349,7 @@ impl<W: Widget + FromDynWidget + ?Sized> Widget for Portal<W> {
ctx.register_child(&mut self.scrollbar_vertical);
}
fn update(&mut self, ctx: &mut UpdateCtx, event: &Update) {
fn update(&mut self, ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, event: &Update) {
match event {
Update::RequestPanToChild(target) => {
let portal_size = ctx.size();
@ -361,7 +378,12 @@ impl<W: Widget + FromDynWidget + ?Sized> Widget for Portal<W> {
}
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
// TODO - How Portal handles BoxConstraints is due for a rework
let min_child_size = if self.must_fill { bc.min() } else { Size::ZERO };
let max_child_size = bc.max();
@ -432,13 +454,13 @@ impl<W: Widget + FromDynWidget + ?Sized> Widget for Portal<W> {
ctx.set_child_scroll_translation(&mut self.child, Vec2::new(0.0, -self.viewport_pos.y));
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut Scene) {}
fn paint(&mut self, _ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, _scene: &mut Scene) {}
fn accessibility_role(&self) -> Role {
Role::GenericContainer
}
fn accessibility(&mut self, ctx: &mut AccessCtx, node: &mut Node) {
fn accessibility(&mut self, ctx: &mut AccessCtx, _props: &PropertiesRef<'_>, node: &mut Node) {
// TODO - Double check this code
// Not sure about these values
if false {

View File

@ -10,7 +10,8 @@ use vello::Scene;
use crate::core::{
AccessCtx, AccessEvent, ArcStr, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent,
QueryCtx, RegisterCtx, TextEvent, Update, UpdateCtx, Widget, WidgetId, WidgetMut, WidgetPod,
PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, TextEvent, Update, UpdateCtx, Widget,
WidgetId, WidgetMut, WidgetPod,
};
use crate::kurbo::{Point, Size};
use crate::theme;
@ -90,19 +91,42 @@ fn clamp_progress(progress: &mut Option<f64>) {
// --- MARK: IMPL WIDGET ---
impl Widget for ProgressBar {
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {}
fn on_pointer_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &PointerEvent,
) {
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.label);
}
fn update(&mut self, _ctx: &mut UpdateCtx, _event: &Update) {}
fn update(&mut self, _ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, _event: &Update) {}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
const DEFAULT_WIDTH: f64 = 400.;
// TODO: Clearer constraints here
let label_size = ctx.run_layout(&mut self.label, &bc.loosen());
@ -121,7 +145,7 @@ impl Widget for ProgressBar {
final_size
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, scene: &mut Scene) {
let border_width = 1.;
let rect = ctx
@ -163,7 +187,7 @@ impl Widget for ProgressBar {
Role::ProgressIndicator
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, node: &mut Node) {
fn accessibility(&mut self, _ctx: &mut AccessCtx, _props: &PropertiesRef<'_>, node: &mut Node) {
node.set_value(self.value_accessibility());
if let Some(value) = self.progress {
node.set_numeric_value(value * 100.0);

View File

@ -10,8 +10,9 @@ use vello::Scene;
use vello::kurbo::{Point, Rect, Size};
use crate::core::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent, QueryCtx,
RegisterCtx, TextEvent, Update, UpdateCtx, Widget, WidgetId, WidgetMut, WidgetPod,
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent,
PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, TextEvent, Update, UpdateCtx, Widget,
WidgetId, WidgetMut, WidgetPod,
};
use crate::widgets::{Padding, TextArea};
@ -111,19 +112,42 @@ impl Prose {
// --- MARK: IMPL WIDGET ---
impl Widget for Prose {
fn on_pointer_event(&mut self, _: &mut EventCtx, _: &PointerEvent) {}
fn on_pointer_event(
&mut self,
_: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_: &PointerEvent,
) {
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.text);
}
fn update(&mut self, _ctx: &mut UpdateCtx, _event: &Update) {}
fn update(&mut self, _ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, _event: &Update) {}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
// TODO: Set minimum to deal with alignment
let size = ctx.run_layout(&mut self.text, bc);
ctx.place_child(&mut self.text, Point::ORIGIN);
@ -135,7 +159,7 @@ impl Widget for Prose {
size
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut Scene) {
fn paint(&mut self, _ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, _scene: &mut Scene) {
// All painting is handled by the child
}
@ -143,7 +167,13 @@ impl Widget for Prose {
Role::GenericContainer
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) {}
fn accessibility(
&mut self,
_ctx: &mut AccessCtx,
_props: &PropertiesRef<'_>,
_node: &mut Node,
) {
}
fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
smallvec![self.text.id()]

View File

@ -9,7 +9,8 @@ use vello::kurbo::Point;
use crate::core::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, FromDynWidget, LayoutCtx, PaintCtx,
PointerEvent, QueryCtx, RegisterCtx, TextEvent, Widget, WidgetId, WidgetMut, WidgetPod,
PointerEvent, PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, TextEvent, Widget, WidgetId,
WidgetMut, WidgetPod,
};
use crate::kurbo::Size;
@ -40,27 +41,56 @@ impl<W: Widget + FromDynWidget + ?Sized> RootWidget<W> {
}
impl<W: Widget + FromDynWidget + ?Sized> Widget for RootWidget<W> {
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_pointer_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &PointerEvent,
) {
}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.pod);
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
let size = ctx.run_layout(&mut self.pod, bc);
ctx.place_child(&mut self.pod, Point::ORIGIN);
size
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut Scene) {}
fn paint(&mut self, _ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, _scene: &mut Scene) {}
fn accessibility_role(&self) -> Role {
Role::Window
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) {}
fn accessibility(
&mut self,
_ctx: &mut AccessCtx,
_props: &PropertiesRef<'_>,
_node: &mut Node,
) {
}
fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
smallvec![self.pod.id()]

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1fec814423f9b67cb5be91276af1986b93aa94c7690f6618887e1667954214a4
size 5055

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d8ad2917e49963e047743424abae12cedd4d1787870cfddcb1208b251861baf3
size 5029

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:55b6d1b0cad21653601bd5c8b1324812e0550c76567bf0f71592702ce037db4c
size 5055

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6024649cc7f1d2716150dbba737ecfd9319635e41126d9da9668f35d8fb16332
size 4371

View File

@ -11,7 +11,8 @@ use vello::kurbo::Rect;
use crate::core::{
AccessCtx, AccessEvent, AllowRawMut, BoxConstraints, EventCtx, LayoutCtx, PaintCtx,
PointerEvent, QueryCtx, RegisterCtx, TextEvent, Update, UpdateCtx, Widget, WidgetId, WidgetMut,
PointerEvent, PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, TextEvent, Update,
UpdateCtx, Widget, WidgetId, WidgetMut,
};
use crate::kurbo::{Point, Size};
use crate::theme;
@ -121,7 +122,12 @@ impl ScrollBar {
// --- MARK: IMPL WIDGET ---
impl Widget for ScrollBar {
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) {
fn on_pointer_event(
&mut self,
ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
event: &PointerEvent,
) {
match event {
PointerEvent::PointerDown(_, _) => {
ctx.capture_pointer();
@ -162,17 +168,33 @@ impl Widget for ScrollBar {
}
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
// TODO - Handle scroll-related events?
}
fn register_children(&mut self, _ctx: &mut RegisterCtx) {}
fn update(&mut self, _ctx: &mut UpdateCtx, _event: &Update) {}
fn update(&mut self, _ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, _event: &Update) {}
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
_ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
// TODO - handle resize
let scrollbar_width = theme::SCROLLBAR_WIDTH;
@ -185,7 +207,7 @@ impl Widget for ScrollBar {
.into()
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, scene: &mut Scene) {
let radius = theme::SCROLLBAR_RADIUS;
let edge_width = theme::SCROLLBAR_EDGE_WIDTH;
let cursor_padding = theme::SCROLLBAR_PAD;
@ -210,7 +232,12 @@ impl Widget for ScrollBar {
Role::ScrollBar
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) {
fn accessibility(
&mut self,
_ctx: &mut AccessCtx,
_props: &PropertiesRef<'_>,
_node: &mut Node,
) {
// TODO
// Use set_scroll_x/y_min/max?
}

View File

@ -3,6 +3,8 @@
//! A widget with predefined size.
use std::any::TypeId;
use accesskit::{Node, Role};
use smallvec::{SmallVec, smallvec};
use tracing::{Span, trace_span, warn};
@ -11,10 +13,12 @@ use vello::kurbo::{Affine, RoundedRectRadii};
use vello::peniko::{Brush, Fill};
use crate::core::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent, QueryCtx,
RegisterCtx, TextEvent, Widget, WidgetId, WidgetMut, WidgetPod,
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent,
PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, TextEvent, UpdateCtx, Widget, WidgetId,
WidgetMut, WidgetPod,
};
use crate::kurbo::{Point, Size};
use crate::properties::BackgroundColor;
use crate::util::stroke;
// FIXME - Improve all doc in this module ASAP.
@ -453,11 +457,29 @@ impl SizedBox {
// --- MARK: IMPL WIDGET ---
impl Widget for SizedBox {
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {}
fn on_pointer_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &PointerEvent,
) {
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
if let Some(ref mut child) = self.child {
@ -465,7 +487,16 @@ impl Widget for SizedBox {
}
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn property_changed(&mut self, ctx: &mut UpdateCtx, property_type: TypeId) {
BackgroundColor::prop_changed(ctx, property_type);
}
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
// Shrink constraints by border offset
let border_width = match &self.border {
Some(border) => border.width,
@ -510,17 +541,25 @@ impl Widget for SizedBox {
size
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, props: &PropertiesRef<'_>, scene: &mut Scene) {
let corner_radius = self.corner_radius;
if let Some(background) = self.background.as_mut() {
// TODO - Handle properties more gracefully.
// This is more of a proof of concept.
let background = self.background.clone().or_else(|| {
props
.get::<BackgroundColor>()
.map(|background| background.color.into())
});
if let Some(background) = background {
let panel = ctx.size().to_rounded_rect(corner_radius);
trace_span!("paint background").in_scope(|| {
scene.fill(
Fill::NonZero,
Affine::IDENTITY,
&*background,
&background,
Some(Affine::IDENTITY),
&panel,
);
@ -542,7 +581,13 @@ impl Widget for SizedBox {
Role::GenericContainer
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) {}
fn accessibility(
&mut self,
_ctx: &mut AccessCtx,
_props: &PropertiesRef<'_>,
_node: &mut Node,
) {
}
fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
if let Some(child) = &self.child {
@ -709,4 +754,44 @@ mod tests {
}
// TODO - add screenshot tests for different brush types
// --- MARK: PROP TESTS ---
#[test]
fn background_brush_property() {
let widget = SizedBox::empty().width(40.).height(40.).rounded(20.);
let mut harness = TestHarness::create(widget);
harness.edit_root_widget(|mut sized_box| {
let brush = BackgroundColor {
color: palette::css::RED,
};
sized_box.insert_prop(brush);
});
assert_render_snapshot!(harness, "background_brush_red");
harness.edit_root_widget(|mut sized_box| {
let brush = BackgroundColor {
color: palette::css::GREEN,
};
*sized_box.get_prop_mut().unwrap() = brush;
});
assert_render_snapshot!(harness, "background_brush_green");
harness.edit_root_widget(|mut sized_box| {
let brush = BackgroundColor {
color: palette::css::BLUE,
};
sized_box.prop_entry().and_modify(|entry| {
*entry = brush;
});
});
assert_render_snapshot!(harness, "background_brush_blue");
harness.edit_root_widget(|mut sized_box| {
sized_box.remove_prop::<BackgroundColor>();
});
assert_render_snapshot!(harness, "background_brush_removed");
}
}

View File

@ -12,8 +12,9 @@ use vello::Scene;
use vello::kurbo::{Affine, Cap, Line, Stroke};
use crate::core::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent, QueryCtx,
RegisterCtx, TextEvent, Update, UpdateCtx, Widget, WidgetId, WidgetMut,
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent,
PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, TextEvent, Update, UpdateCtx, Widget,
WidgetId, WidgetMut,
};
use crate::kurbo::{Point, Size, Vec2};
use crate::peniko::Color;
@ -73,13 +74,36 @@ impl Spinner {
// --- MARK: IMPL WIDGET ---
impl Widget for Spinner {
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {}
fn on_pointer_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &PointerEvent,
) {
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn on_anim_frame(&mut self, ctx: &mut UpdateCtx, interval: u64) {
fn on_anim_frame(
&mut self,
ctx: &mut UpdateCtx,
_props: &mut PropertiesMut<'_>,
interval: u64,
) {
self.t += (interval as f64) * 1e-9;
if self.t >= 1.0 {
self.t = self.t.rem_euclid(1.0);
@ -90,7 +114,7 @@ impl Widget for Spinner {
fn register_children(&mut self, _ctx: &mut RegisterCtx) {}
fn update(&mut self, ctx: &mut UpdateCtx, event: &Update) {
fn update(&mut self, ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, event: &Update) {
match event {
Update::WidgetAdded => {
ctx.request_anim_frame();
@ -99,7 +123,12 @@ impl Widget for Spinner {
}
}
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
_ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
if bc.is_width_bounded() && bc.is_height_bounded() {
bc.max()
} else {
@ -110,7 +139,7 @@ impl Widget for Spinner {
}
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, scene: &mut Scene) {
let t = self.t;
let (width, height) = (ctx.size().width, ctx.size().height);
let center = Point::new(width / 2.0, height / 2.0);
@ -141,7 +170,13 @@ impl Widget for Spinner {
Role::Unknown
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) {}
fn accessibility(
&mut self,
_ctx: &mut AccessCtx,
_props: &PropertiesRef<'_>,
_node: &mut Node,
) {
}
fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
SmallVec::new()

View File

@ -10,7 +10,8 @@ use vello::Scene;
use crate::core::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerButton,
PointerEvent, QueryCtx, RegisterCtx, TextEvent, Widget, WidgetId, WidgetMut, WidgetPod,
PointerEvent, PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, TextEvent, Widget, WidgetId,
WidgetMut, WidgetPod,
};
use crate::kurbo::{Line, Point, Rect, Size};
use crate::peniko::Color;
@ -367,7 +368,12 @@ impl Split {
// --- MARK: IMPL WIDGET ---
impl Widget for Split {
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) {
fn on_pointer_event(
&mut self,
ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
event: &PointerEvent,
) {
if self.draggable {
match event {
PointerEvent::PointerDown(PointerButton::Primary, state) => {
@ -403,16 +409,33 @@ impl Widget for Split {
}
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.child1);
ctx.register_child(&mut self.child2);
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
match self.split_axis {
Axis::Horizontal => {
if !bc.is_width_bounded() {
@ -510,7 +533,7 @@ impl Widget for Split {
my_size
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, scene: &mut Scene) {
// TODO - Paint differently if the bar is draggable and hovered.
if self.solid {
self.paint_solid_bar(ctx, scene);
@ -537,7 +560,13 @@ impl Widget for Split {
Role::Splitter
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) {}
fn accessibility(
&mut self,
_ctx: &mut AccessCtx,
_props: &PropertiesRef<'_>,
_node: &mut Node,
) {
}
fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
smallvec![self.child1.id(), self.child2.id()]

View File

@ -45,7 +45,7 @@ fn layout_insets() {
let [child_id, parent_id] = widget_ids();
let child_widget = ModularWidget::new(()).layout_fn(|_, ctx, _| {
let child_widget = ModularWidget::new(()).layout_fn(|_, ctx, _, _| {
// this widget paints twenty points above below its layout bounds
ctx.set_paint_insets(Insets::uniform_xy(0., 20.));
Size::new(BOX_WIDTH, BOX_WIDTH)

View File

@ -14,7 +14,7 @@ fn make_parent_widget<W: Widget>(child: W) -> ModularWidget<WidgetPod<W>> {
.register_children_fn(move |child, ctx| {
ctx.register_child(child);
})
.layout_fn(move |child, ctx, bc| {
.layout_fn(move |child, ctx, _, bc| {
let size = ctx.run_layout(child, bc);
ctx.place_child(child, Point::ZERO);
size
@ -26,7 +26,7 @@ fn make_parent_widget<W: Widget>(child: W) -> ModularWidget<WidgetPod<W>> {
#[should_panic(expected = "not visited in method on_text_event")]
#[test]
fn check_forget_to_recurse_text_event() {
let widget = make_parent_widget(Flex::row()).text_event_fn(|_child, _ctx, _event| {
let widget = make_parent_widget(Flex::row()).text_event_fn(|_child, _ctx, _, _event| {
// We forget to call child.on_text_event();
});
@ -101,7 +101,7 @@ fn check_register_invalid_child() {
ignore = "This test doesn't work without debug assertions (i.e. in release mode). See https://github.com/linebender/xilem/issues/477"
)]
fn check_pointer_capture_outside_pointer_down() {
let widget = ModularWidget::new(()).pointer_event_fn(|_, ctx, _event| {
let widget = ModularWidget::new(()).pointer_event_fn(|_, ctx, _, _event| {
ctx.capture_pointer();
});
@ -120,7 +120,7 @@ fn check_pointer_capture_text_event() {
let id = WidgetId::next();
let widget = ModularWidget::new(())
.accepts_focus(true)
.text_event_fn(|_, ctx, _event| {
.text_event_fn(|_, ctx, _, _event| {
ctx.capture_pointer();
})
.with_id(id);
@ -137,7 +137,7 @@ fn check_pointer_capture_text_event() {
ignore = "This test doesn't work without debug assertions (i.e. in release mode). See https://github.com/linebender/xilem/issues/477"
)]
fn check_forget_to_recurse_layout() {
let widget = make_parent_widget(Flex::row()).layout_fn(|_child, _ctx, _| {
let widget = make_parent_widget(Flex::row()).layout_fn(|_child, _ctx, _, _| {
// We forget to call ctx.run_layout();
Size::ZERO
});
@ -152,7 +152,7 @@ fn check_forget_to_recurse_layout() {
ignore = "This test doesn't work without debug assertions (i.e. in release mode). See https://github.com/linebender/xilem/issues/477"
)]
fn check_forget_to_call_place_child() {
let widget = make_parent_widget(Flex::row()).layout_fn(|child, ctx, bc| {
let widget = make_parent_widget(Flex::row()).layout_fn(|child, ctx, _, bc| {
// We call ctx.run_layout(), but forget place_child
ctx.run_layout(child, bc)
});
@ -168,11 +168,11 @@ fn check_forget_to_call_place_child() {
#[test]
fn allow_non_recurse_event_handled() {
let widget = make_parent_widget(Flex::row())
.pointer_event_fn(|_child, ctx, _event| {
.pointer_event_fn(|_child, ctx, _, _event| {
// Event handled, we don't need to recurse
ctx.set_handled();
})
.text_event_fn(|_child, ctx, _event| {
.text_event_fn(|_child, ctx, _, _event| {
// Event handled, we don't need to recurse
ctx.set_handled();
});
@ -185,12 +185,12 @@ fn allow_non_recurse_event_handled() {
#[test]
fn allow_non_recurse_cursor_oob() {
let widget = make_parent_widget(Flex::row())
.pointer_event_fn(|child, ctx, event| {
.pointer_event_fn(|child, ctx, _, event| {
if !matches!(event, PointerEvent::PointerMove(_)) {
child.on_pointer_event(ctx, event);
}
})
.layout_fn(|child, ctx, bc| {
.layout_fn(|child, ctx, _, bc| {
let _size = ctx.run_layout(child, bc);
ctx.place_child(child, Point::ZERO);
Size::new(6000.0, 6000.0)
@ -207,7 +207,7 @@ fn allow_non_recurse_oob_paint() {
.paint_fn(|_child, _ctx, _| {
// We forget to call child.paint();
})
.layout_fn(|child, ctx, bc| {
.layout_fn(|child, ctx, _, bc| {
let _size = ctx.run_layout(child, bc);
ctx.place_child(child, Point::new(500.0, 500.0));
Size::new(600.0, 600.0)
@ -242,7 +242,7 @@ fn check_forget_children_changed() {
child.lifecycle(ctx, event);
}
})
.layout_fn(|child, ctx, bc| {
.layout_fn(|child, ctx, _, bc| {
if let Some(child) = child {
let size = ctx.run_layout(child, bc);
ctx.place_child(child, Point::ZERO);
@ -274,7 +274,7 @@ fn check_forget_children_changed() {
#[should_panic]
#[test]
fn check_recurse_event_twice() {
let widget = make_parent_widget(Flex::row()).pointer_event_fn(|child, ctx, event| {
let widget = make_parent_widget(Flex::row()).pointer_event_fn(|child, ctx, _, event| {
child.on_pointer_event(ctx, event);
child.on_pointer_event(ctx, event);
});
@ -299,7 +299,7 @@ fn check_recurse_lifecycle_twice() {
#[should_panic]
#[test]
fn check_recurse_layout_twice() {
let widget = make_parent_widget(Flex::row()).layout_fn(|child, ctx, bc| {
let widget = make_parent_widget(Flex::row()).layout_fn(|child, ctx, _, bc| {
let size = ctx.run_layout(child, bc);
let _ = ctx.run_layout(child, bc);
ctx.place_child(child, Point::ZERO);
@ -328,12 +328,12 @@ fn check_recurse_paint_twice() {
#[test]
fn check_layout_stashed() {
let widget = make_parent_widget(Flex::row())
.update_fn(|child, ctx, event| {
.update_fn(|child, ctx, _, event| {
if matches!(event, Update::WidgetAdded) {
ctx.set_stashed(child, true);
}
})
.layout_fn(|child, ctx, bc| {
.layout_fn(|child, ctx, _, bc| {
let size = ctx.run_layout(child, bc);
ctx.place_child(child, Point::ZERO);
size
@ -351,7 +351,7 @@ fn check_layout_stashed() {
#[test]
fn check_paint_rect_includes_children() {
use crate::widgets::Label;
let widget = make_parent_widget(Label::new("Hello world")).layout_fn(|child, ctx, bc| {
let widget = make_parent_widget(Label::new("Hello world")).layout_fn(|child, ctx, _, bc| {
let _size = ctx.run_layout(child, bc);
ctx.place_child(child, Point::ZERO);
Size::ZERO

View File

@ -19,8 +19,9 @@ use winit::keyboard::{Key, NamedKey};
use crate::core::{
AccessCtx, AccessEvent, BoxConstraints, BrushIndex, EventCtx, LayoutCtx, PaintCtx,
PointerButton, PointerEvent, QueryCtx, RegisterCtx, StyleProperty, TextEvent, Update,
UpdateCtx, Widget, WidgetId, WidgetMut, default_styles, render_text,
PointerButton, PointerEvent, PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx,
StyleProperty, TextEvent, Update, UpdateCtx, Widget, WidgetId, WidgetMut, default_styles,
render_text,
};
use crate::widgets::Padding;
use crate::{palette, theme};
@ -507,7 +508,12 @@ impl<const EDITABLE: bool> TextArea<EDITABLE> {
// --- MARK: IMPL WIDGET ---
impl<const EDITABLE: bool> Widget for TextArea<EDITABLE> {
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) {
fn on_pointer_event(
&mut self,
ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
event: &PointerEvent,
) {
if self.editor.is_composing() {
return;
}
@ -567,7 +573,12 @@ impl<const EDITABLE: bool> Widget for TextArea<EDITABLE> {
}
}
fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent) {
fn on_text_event(
&mut self,
ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
event: &TextEvent,
) {
match event {
TextEvent::KeyboardKey(key_event, modifiers_state) => {
if !key_event.state.is_pressed() || self.editor.is_composing() {
@ -836,7 +847,12 @@ impl<const EDITABLE: bool> Widget for TextArea<EDITABLE> {
EDITABLE
}
fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) {
fn on_access_event(
&mut self,
ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
event: &AccessEvent,
) {
if event.action == accesskit::Action::SetTextSelection {
if self.editor.is_composing() {
return;
@ -859,7 +875,7 @@ impl<const EDITABLE: bool> Widget for TextArea<EDITABLE> {
fn register_children(&mut self, _ctx: &mut RegisterCtx) {}
fn update(&mut self, ctx: &mut UpdateCtx, event: &Update) {
fn update(&mut self, ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, event: &Update) {
match event {
Update::FocusChanged(_) => {
ctx.request_render();
@ -872,7 +888,12 @@ impl<const EDITABLE: bool> Widget for TextArea<EDITABLE> {
}
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
// Shrink constraints by padding inset
let padding_size = Size::new(
self.padding.leading + self.padding.trailing,
@ -914,7 +935,7 @@ impl<const EDITABLE: bool> Widget for TextArea<EDITABLE> {
bc.constrain(area_size)
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, scene: &mut Scene) {
let layout = if let Some(layout) = self.editor.try_layout() {
layout
} else {
@ -968,7 +989,7 @@ impl<const EDITABLE: bool> Widget for TextArea<EDITABLE> {
}
}
fn accessibility(&mut self, ctx: &mut AccessCtx, node: &mut Node) {
fn accessibility(&mut self, ctx: &mut AccessCtx, _props: &PropertiesRef<'_>, node: &mut Node) {
if !EDITABLE {
node.set_read_only();
}

View File

@ -10,8 +10,9 @@ use vello::Scene;
use vello::kurbo::{Affine, Insets, Point, Rect, Size, Stroke};
use crate::core::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent, QueryCtx,
RegisterCtx, TextEvent, Update, UpdateCtx, Widget, WidgetId, WidgetMut, WidgetPod,
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent,
PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, TextEvent, Update, UpdateCtx, Widget,
WidgetId, WidgetMut, WidgetPod,
};
use crate::peniko::Color;
use crate::widgets::{Padding, TextArea};
@ -110,19 +111,42 @@ impl Textbox {
// --- MARK: IMPL WIDGET ---
impl Widget for Textbox {
fn on_pointer_event(&mut self, _: &mut EventCtx, _: &PointerEvent) {}
fn on_pointer_event(
&mut self,
_: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_: &PointerEvent,
) {
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.text);
}
fn update(&mut self, _ctx: &mut UpdateCtx, _event: &Update) {}
fn update(&mut self, _ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, _event: &Update) {}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
let margin = TEXTBOX_MARGIN;
// Shrink constraints by padding inset
let margin_size = Size::new(margin.leading + margin.trailing, margin.top + margin.bottom);
@ -137,7 +161,7 @@ impl Widget for Textbox {
size + margin_size
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
fn paint(&mut self, ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, scene: &mut Scene) {
let size = ctx.size();
let outline_rect = size.to_rect().inset(Insets::new(
-TEXTBOX_MARGIN.leading,
@ -158,7 +182,13 @@ impl Widget for Textbox {
Role::GenericContainer
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) {}
fn accessibility(
&mut self,
_ctx: &mut AccessCtx,
_props: &PropertiesRef<'_>,
_node: &mut Node,
) {
}
fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
smallvec![self.text.id()]

View File

@ -13,8 +13,8 @@ use vello::kurbo::{Point, Size};
use crate::core::{
AccessCtx, AccessEvent, ArcStr, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent,
QueryCtx, RegisterCtx, StyleProperty, TextEvent, Update, UpdateCtx, Widget, WidgetId,
WidgetMut, WidgetPod,
PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, StyleProperty, TextEvent, Update,
UpdateCtx, Widget, WidgetId, WidgetMut, WidgetPod,
};
use crate::parley::style::FontWeight;
use crate::widgets::Label;
@ -173,23 +173,46 @@ impl VariableLabel {
// --- MARK: IMPL WIDGET ---
impl Widget for VariableLabel {
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {}
fn on_pointer_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &PointerEvent,
) {
}
fn accepts_pointer_interaction(&self) -> bool {
false
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn update(&mut self, _ctx: &mut UpdateCtx, _event: &Update) {}
fn update(&mut self, _ctx: &mut UpdateCtx, _props: &mut PropertiesMut<'_>, _event: &Update) {}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.label);
}
fn on_anim_frame(&mut self, ctx: &mut UpdateCtx, interval: u64) {
fn on_anim_frame(
&mut self,
ctx: &mut UpdateCtx,
_props: &mut PropertiesMut<'_>,
interval: u64,
) {
let millis = (interval as f64 / 1_000_000.) as f32;
let result = self.weight.advance(millis);
let new_weight = self.weight.value;
@ -212,19 +235,30 @@ impl Widget for VariableLabel {
}
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
let size = ctx.run_layout(&mut self.label, bc);
ctx.place_child(&mut self.label, Point::ORIGIN);
size
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut Scene) {}
fn paint(&mut self, _ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, _scene: &mut Scene) {}
fn accessibility_role(&self) -> Role {
Role::GenericContainer
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) {}
fn accessibility(
&mut self,
_ctx: &mut AccessCtx,
_props: &PropertiesRef<'_>,
_node: &mut Node,
) {
}
fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
smallvec![self.label.id()]

View File

@ -8,8 +8,8 @@ use smallvec::SmallVec;
use tracing::trace_span;
use crate::core::{
AccessCtx, BoxConstraints, LayoutCtx, PaintCtx, QueryCtx, RegisterCtx, Widget, WidgetId,
WidgetMut, WidgetPod,
AccessCtx, BoxConstraints, LayoutCtx, PaintCtx, PropertiesMut, PropertiesRef, QueryCtx,
RegisterCtx, Widget, WidgetId, WidgetMut, WidgetPod,
};
use crate::kurbo::{Point, Size};
use crate::vello::Scene;
@ -298,7 +298,12 @@ impl ZStack {
// --- MARK: IMPL WIDGET---
impl Widget for ZStack {
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
// First pass: calculate the smallest bounds needed to layout the children.
let mut max_size = bc.min();
let loosened_bc = bc.loosen();
@ -341,7 +346,7 @@ impl Widget for ZStack {
max_size
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut Scene) {}
fn paint(&mut self, _ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, _scene: &mut Scene) {}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
for child in self.children.iter_mut().map(|x| &mut x.widget) {
@ -361,7 +366,13 @@ impl Widget for ZStack {
Role::GenericContainer
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) {}
fn accessibility(
&mut self,
_ctx: &mut AccessCtx,
_props: &PropertiesRef<'_>,
_node: &mut Node,
) {
}
fn make_trace_span(&self, ctx: &QueryCtx<'_>) -> tracing::Span {
trace_span!("ZStack", id = ctx.widget_id().trace())

View File

@ -4,7 +4,8 @@
use accesskit::{Node, Role};
use masonry::core::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, FromDynWidget, LayoutCtx, PaintCtx,
PointerEvent, QueryCtx, RegisterCtx, TextEvent, Widget, WidgetId, WidgetMut, WidgetPod,
PointerEvent, PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx, TextEvent, Widget, WidgetId,
WidgetMut, WidgetPod,
};
use masonry::kurbo::{Point, Size};
use smallvec::{SmallVec, smallvec};
@ -68,27 +69,56 @@ impl DynWidget {
/// Forward all events to the child widget.
impl Widget for DynWidget {
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_pointer_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &PointerEvent,
) {
}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.inner);
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
let size = ctx.run_layout(&mut self.inner, bc);
ctx.place_child(&mut self.inner, Point::ORIGIN);
size
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut Scene) {}
fn paint(&mut self, _ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, _scene: &mut Scene) {}
fn accessibility_role(&self) -> Role {
Role::GenericContainer
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) {}
fn accessibility(
&mut self,
_ctx: &mut AccessCtx,
_props: &PropertiesRef<'_>,
_node: &mut Node,
) {
}
fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
smallvec![self.inner.id()]

View File

@ -6,7 +6,8 @@
use accesskit::{Node, Role};
use masonry::core::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, FromDynWidget, LayoutCtx, PaintCtx,
PointerEvent, RegisterCtx, TextEvent, Widget, WidgetId, WidgetPod,
PointerEvent, PropertiesMut, PropertiesRef, RegisterCtx, TextEvent, Widget, WidgetId,
WidgetPod,
};
use masonry::kurbo::{Point, Size};
use smallvec::{SmallVec, smallvec};
@ -182,9 +183,27 @@ impl<
I: Widget + FromDynWidget + ?Sized,
> Widget for OneOfWidget<A, B, C, D, E, F, G, H, I>
{
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn on_pointer_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &PointerEvent,
) {
}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(
&mut self,
_ctx: &mut EventCtx,
_props: &mut PropertiesMut<'_>,
_event: &AccessEvent,
) {
}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
match self {
@ -200,7 +219,12 @@ impl<
}
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
match self {
Self::A(w) => {
let size = ctx.run_layout(w, bc);
@ -250,13 +274,19 @@ impl<
}
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut Scene) {}
fn paint(&mut self, _ctx: &mut PaintCtx, _props: &PropertiesRef<'_>, _scene: &mut Scene) {}
fn accessibility_role(&self) -> Role {
Role::GenericContainer
}
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) {}
fn accessibility(
&mut self,
_ctx: &mut AccessCtx,
_props: &PropertiesRef<'_>,
_node: &mut Node,
) {
}
fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
match self {