Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions masonry/examples/calc_masonry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use std::str::FromStr;

use masonry::core::{
ErasedAction, NewWidget, Properties, Property, StyleProperty, Widget, WidgetId, WidgetOptions,
ErasedAction, NewWidget, Properties, Property, StyleProperty, Widget, WidgetId,
};
use masonry::dpi::LogicalSize;
use masonry::peniko::Color;
Expand Down Expand Up @@ -197,7 +197,7 @@ fn op_button_with_label(op: char, label: String) -> NewWidget<Button> {
NewWidget::new_with(
button,
WidgetId::next(),
WidgetOptions::default(),
false,
Properties::new()
.with(Background::Color(BLUE))
.with(ActiveBackground(Background::Color(LIGHT_BLUE)))
Expand Down Expand Up @@ -225,7 +225,7 @@ fn digit_button(digit: u8) -> NewWidget<Button> {
NewWidget::new_with(
button,
WidgetId::next(),
WidgetOptions::default(),
false,
Properties::new()
.with(Background::Color(GRAY))
.with(ActiveBackground(Background::Color(LIGHT_GRAY)))
Expand Down
8 changes: 1 addition & 7 deletions masonry/examples/grid_masonry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use masonry::TextAlign;
use masonry::core::{
ErasedAction, NewWidget, PointerButton, Properties, StyleProperty, Widget as _, WidgetId,
WidgetOptions,
};
use masonry::dpi::LogicalSize;
use masonry::peniko::Color;
Expand Down Expand Up @@ -71,12 +70,7 @@ pub fn make_grid(grid_spacing: f64) -> NewWidget<Grid> {
let props = Properties::new()
.with(BorderColor::new(Color::from_rgb8(40, 40, 80)))
.with(BorderWidth::all(1.0));
let label = SizedBox::new(NewWidget::new_with(
label,
WidgetId::next(),
WidgetOptions::default(),
props,
));
let label = SizedBox::new(NewWidget::new_with(label, WidgetId::next(), false, props));

let button_inputs = vec![
GridParams {
Expand Down
6 changes: 2 additions & 4 deletions masonry/src/tests/compose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

use assert_matches::assert_matches;
use masonry_core::core::{ChildrenIds, NewWidget, Widget, WidgetPod, WidgetTag};
use masonry_core::core::{ChildrenIds, NewWidget, Transform, Widget, WidgetPod, WidgetTag};
use masonry_testing::{ModularWidget, Record, TestHarness, TestWidgetExt};
use vello::kurbo::{Affine, Point, Size, Vec2};

Expand Down Expand Up @@ -63,9 +63,7 @@ fn request_compose() {
assert_matches!(harness.take_records_of(parent_tag)[..], [Record::Compose]);

harness.edit_widget(parent_tag, |mut parent| {
parent
.ctx
.set_transform(Affine::translate(Vec2::new(7., 7.)));
parent.insert_prop(Transform::from(Affine::translate(Vec2::new(7., 7.))));
});

// Origin should be "parent_origin + pos + scroll_offset"
Expand Down
76 changes: 34 additions & 42 deletions masonry_core/src/core/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ use vello::kurbo::{Affine, Insets, Point, Rect, Size, Vec2};

use crate::app::{MutateCallback, RenderRootSignal, RenderRootState};
use crate::core::{
AllowRawMut, BoxConstraints, BrushIndex, DefaultProperties, ErasedAction, FromDynWidget,
NewWidget, PropertiesMut, PropertiesRef, ResizeDirection, Widget, WidgetArenaNode, WidgetId,
WidgetMut, WidgetPod, WidgetRef, WidgetState,
AllowRawMut, BoxConstraints, BrushIndex, ChangedProperties, DefaultProperties, ErasedAction,
FromDynWidget, NewWidget, PropertiesMut, PropertiesRef, ResizeDirection, Transform, Widget,
WidgetArenaNode, WidgetId, WidgetMut, WidgetPod, WidgetRef, WidgetState,
};
use crate::debug_panic;
use crate::passes::layout::{place_widget, run_layout_on};
use crate::peniko::Color;
use crate::util::{TypeSet, get_debug_color};
use crate::util::get_debug_color;

// Note - Most methods defined in this file revolve around `WidgetState` fields.
// Consider reading `WidgetState` documentation (especially the documented naming scheme)
Expand Down Expand Up @@ -54,7 +54,7 @@ pub struct MutateCtx<'a> {
pub(crate) parent_widget_state: Option<&'a mut WidgetState>,
pub(crate) widget_state: &'a mut WidgetState,
pub(crate) properties: PropertiesMut<'a>,
pub(crate) changed_properties: &'a mut TypeSet,
pub(crate) changed_properties: &'a mut ChangedProperties,
pub(crate) children: ArenaMutList<'a, WidgetArenaNode>,
pub(crate) default_properties: &'a DefaultProperties,
}
Expand Down Expand Up @@ -190,12 +190,6 @@ impl_context_method!(
.item
.state
}

#[allow(dead_code, reason = "Copy-pasted for some types that don't need it")]
/// The current (local) transform of this widget.
pub fn transform(&self) -> Affine {
self.widget_state.transform
}
}
);

Expand Down Expand Up @@ -279,15 +273,6 @@ impl MutateCtx<'_> {
default_properties: self.default_properties,
}
}

/// Whether the (local) transform of this widget has been modified since
/// the last time this widget's transformation was resolved.
///
/// This is exposed for Xilem, and is more likely to change or be removed
/// in major releases of Masonry.
pub fn transform_has_changed(&self) -> bool {
self.widget_state.transform_changed
}
}

// --- MARK: WIDGET_REF
Expand Down Expand Up @@ -588,9 +573,17 @@ impl LayoutCtx<'_> {
);
}

let child_state = self.get_child_state_mut(child);
let child_node = self
.children
.item_mut(child.id())
.expect("place_child: child not found")
.item;

place_widget(child_state, origin);
place_widget(
&mut child_node.state,
&mut child_node.changed_properties,
origin,
);

self.widget_state.local_paint_rect = self
.widget_state
Expand Down Expand Up @@ -780,10 +773,14 @@ impl ComposeCtx<'_> {

let translation = translation.round();

let child = self.get_child_state_mut(child);
if translation != child.scroll_translation {
child.scroll_translation = translation;
child.transform_changed = true;
let child_node = self
.children
.item_mut(child.id())
.expect("No child widget exists")
.item;
if translation != child_node.state.scroll_translation {
child_node.state.scroll_translation = translation;
child_node.changed_properties.mark_as_changed::<Transform>();
}
}

Expand Down Expand Up @@ -812,10 +809,14 @@ impl ComposeCtx<'_> {
);
}

let child = self.get_child_state_mut(child);
if translation != child.scroll_translation {
child.scroll_translation = translation;
child.transform_changed = true;
let child_node = self
.children
.item_mut(child.id())
.expect("No child widget exists")
.item;
if translation != child_node.state.scroll_translation {
child_node.state.scroll_translation = translation;
child_node.changed_properties.mark_as_changed::<Transform>();
}
}
}
Expand Down Expand Up @@ -1186,15 +1187,6 @@ impl_context_method!(MutateCtx<'_>, EventCtx<'_>, UpdateCtx<'_>, RawCtx<'_>, {
self.widget_state.needs_update_disabled = true;
self.widget_state.is_explicitly_disabled = disabled;
}

/// Set the transform for this widget.
///
/// It behaves similarly as CSS transforms
pub fn set_transform(&mut self, transform: Affine) {
self.widget_state.transform = transform;
self.widget_state.transform_changed = true;
self.request_compose();
}
});

// --- MARK: OTHER METHODS
Expand Down Expand Up @@ -1441,7 +1433,7 @@ impl RegisterCtx<'_> {
let Some(NewWidget {
widget,
id,
options,
disabled,
properties,
tag,
action_type,
Expand All @@ -1460,7 +1452,7 @@ impl RegisterCtx<'_> {
let state = WidgetState::new(
id,
widget.short_type_name(),
options,
disabled,
action_type,
#[cfg(debug_assertions)]
action_type_name,
Expand All @@ -1481,7 +1473,7 @@ impl RegisterCtx<'_> {
widget: widget.as_box_dyn(),
state,
properties: properties.map,
changed_properties: TypeSet::default(),
changed_properties: ChangedProperties::default(),
};
self.children.insert(id, node);
}
Expand Down
5 changes: 3 additions & 2 deletions masonry_core/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ pub use events::{
AccessEvent, Handled, Ime, ResizeDirection, TextEvent, Update, WindowEvent, WindowTheme,
};
pub use properties::{
DefaultProperties, HasProperty, Properties, PropertiesMut, PropertiesRef, Property,
ChangedProperties, DefaultProperties, GlobalProperty, HasProperty, Properties, PropertiesMut,
PropertiesRef, Property, Transform,
};
pub use text::{ArcStr, BrushIndex, StyleProperty, StyleSet, render_text};
pub use widget::find_widget_under_pointer;
pub use widget::{AllowRawMut, AsDynWidget, ChildrenIds, FromDynWidget, Widget, WidgetId};
pub use widget_mut::WidgetMut;
pub use widget_pod::{NewWidget, WidgetOptions, WidgetPod};
pub use widget_pod::{NewWidget, WidgetPod};
pub use widget_ref::WidgetRef;
pub use widget_tag::WidgetTag;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use std::collections::HashMap;
use std::default::Default;

use crate::core::Widget;
use crate::util::AnyMap;
use crate::util::{AnyMap, TypeSet};

mod transform;
pub use transform::Transform;

/// A marker trait that indicates that a type is intended to be used as a widget's property.
///
Expand All @@ -33,6 +36,9 @@ pub trait Property: Default + Send + Sync + 'static {
fn static_default() -> &'static Self;
}

/// A marker trait for properties that can be used on *any* [`Widget`]
pub trait GlobalProperty: Property {}

// TODO - Implement Debug.
/// A collection of [properties](Property) that a widget can be created with.
#[derive(Default)]
Expand Down Expand Up @@ -76,6 +82,8 @@ pub struct DefaultProperties {
/// This is not used directly by Masonry Core, but is instead provided for the convenience of external crates.
pub trait HasProperty<P: Property> {}

impl<P: GlobalProperty, W: Widget> HasProperty<P> for W {}

impl Properties {
/// Create an empty collection of properties.
pub fn new() -> Self {
Expand Down Expand Up @@ -111,6 +119,34 @@ impl Properties {
}
}

/// When properties of a widget were modified this should also be reflected by [`ChangedProperties::mark_as_changed`]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// When properties of a widget were modified this should also be reflected by [`ChangedProperties::mark_as_changed`]
/// When properties of a widget were modified this should also be reflected by [`ChangedProperties::mark_as_changed`].

#[derive(Default, Debug)]
pub struct ChangedProperties {
set: TypeSet,
}

impl ChangedProperties {
/// Mark that the property `P` has changed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Mark that the property `P` has changed
/// Mark that the property `P` has changed.

etc.

pub fn mark_as_changed<P: Property>(&mut self) {
self.set.insert(TypeId::of::<P>());
}

/// Whether the property `P` has changed
pub fn has_changed<P: Property>(&self) -> bool {
self.set.contains(&TypeId::of::<P>())
}

/// Clears all marked as changed properties
pub fn clear(&mut self) {
self.set.clear();
}

/// Clears all marked as changed properties
pub fn clear_property<P: Property>(&mut self) {
self.set.remove(&TypeId::of::<P>());
}
}

macro_rules! impl_props_from_tuple {
(
$(
Expand Down
30 changes: 30 additions & 0 deletions masonry_core/src/core/properties/transform.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2025 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0

use vello::kurbo::Affine;

use crate::core::{GlobalProperty, Property};

/// The local transform of a widget
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The local transform of a widget
/// The local transform of a widget.

#[derive(Clone, Copy, Default, Debug, PartialEq)]
pub struct Transform {
/// Local transform of the widget
pub transform: Affine,
}

impl Property for Transform {
fn static_default() -> &'static Self {
static DEFAULT: Transform = Transform {
transform: Affine::IDENTITY,
};
&DEFAULT
}
}

impl GlobalProperty for Transform {}

impl From<Affine> for Transform {
fn from(transform: Affine) -> Self {
Self { transform }
}
}
4 changes: 2 additions & 2 deletions masonry_core/src/core/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use vello::kurbo::{Point, Size};
use crate::core::{
AccessCtx, AccessEvent, BoxConstraints, ComposeCtx, CursorIcon, EventCtx, LayoutCtx, NewWidget,
PaintCtx, PointerEvent, Properties, PropertiesMut, PropertiesRef, QueryCtx, RegisterCtx,
TextEvent, Update, UpdateCtx, WidgetOptions, WidgetRef,
TextEvent, Update, UpdateCtx, WidgetRef,
};

/// A unique identifier for a single [`Widget`].
Expand Down Expand Up @@ -430,7 +430,7 @@ pub trait Widget: AsDynWidget + Any {
where
Self: Sized,
{
NewWidget::new_with(self, WidgetId::next(), WidgetOptions::default(), props)
NewWidget::new_with(self, WidgetId::next(), false, props)
}
}

Expand Down
6 changes: 3 additions & 3 deletions masonry_core/src/core/widget_arena.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

use tree_arena::{ArenaMut, ArenaRef, TreeArena};

use crate::core::{Widget, WidgetId, WidgetState};
use crate::util::{AnyMap, TypeSet};
use crate::core::{ChangedProperties, Widget, WidgetId, WidgetState};
use crate::util::AnyMap;

pub(crate) struct WidgetArena {
pub(crate) nodes: TreeArena<WidgetArenaNode>,
Expand All @@ -14,7 +14,7 @@ pub(crate) struct WidgetArenaNode {
pub(crate) widget: Box<dyn Widget>,
pub(crate) state: WidgetState,
pub(crate) properties: AnyMap,
pub(crate) changed_properties: TypeSet,
pub(crate) changed_properties: ChangedProperties,
}

impl WidgetArena {
Expand Down
Loading
Loading