From 75d3c978beae20498c5cb6bd8bc31beff6fb8b7e Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Tue, 17 Feb 2026 17:00:39 +0100 Subject: [PATCH] Add concept of "attached layers" to Masonry --- masonry_core/src/app/render_root.rs | 6 ++- masonry_core/src/core/contexts.rs | 78 ++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/masonry_core/src/app/render_root.rs b/masonry_core/src/app/render_root.rs index f6a768d37..3bcd92835 100644 --- a/masonry_core/src/app/render_root.rs +++ b/masonry_core/src/app/render_root.rs @@ -1,7 +1,7 @@ // Copyright 2019 the Xilem Authors and the Druid Authors // SPDX-License-Identifier: Apache-2.0 -use std::any::Any; +use std::any::{Any, TypeId}; use std::collections::HashMap; use std::sync::Arc; @@ -144,6 +144,9 @@ pub(crate) struct RenderRootState { pub(crate) widget_tags: HashMap, + /// Map of layers attached to widgets, keyed by the attached widget id, then the type of the layer root. + pub(crate) attached_layers: HashMap>, + /// Whether data set in the pointer pass has been invalidated. pub(crate) needs_pointer_pass: bool, @@ -355,6 +358,7 @@ impl RenderRoot { last_sent_ime_area: INVALID_IME_AREA, scene_cache: HashMap::new(), widget_tags: HashMap::new(), + attached_layers: HashMap::new(), needs_pointer_pass: false, trace: PassTracing::from_env(), inspector_state: InspectorState { diff --git a/masonry_core/src/core/contexts.rs b/masonry_core/src/core/contexts.rs index 518906bf5..945962ed2 100644 --- a/masonry_core/src/core/contexts.rs +++ b/masonry_core/src/core/contexts.rs @@ -3,7 +3,7 @@ //! The context types that are passed into various widget methods. -use std::any::Any; +use std::any::{Any, TypeId}; use std::collections::hash_map::Entry; use accesskit::{NodeId, TreeUpdate}; @@ -1501,6 +1501,12 @@ impl_context_method!(MutateCtx<'_>, EventCtx<'_>, UpdateCtx<'_>, RawCtx<'_>, { } global_state.scene_cache.remove(&state.id); + + if let Some(layers) = global_state.attached_layers.remove(&state.id) { + for (_, layer_id) in layers { + global_state.emit_signal(RenderRootSignal::RemoveLayer(layer_id)); + } + } } let id = child.id(); @@ -1804,11 +1810,81 @@ impl_context_method!( )); } + /// Creates a new [layer] at a specified `position`, and ties it to the current widget. + /// + /// The layer will be removed when the current widget is removed from the tree. + /// A widget can only have one layer of any given type `W` at a time: if a widget creates two layers of the same type, the old layer will be removed. + /// + /// The given `position` must be in the window's coordinate space. + /// + /// # Panics + /// + /// If [`W::as_layer()`](Widget::as_layer) returns `None`. + /// + /// [layer]: crate::doc::masonry_concepts#layers + pub fn create_attached_layer( + &mut self, + layer_type: LayerType, + mut fallback_widget: NewWidget, + position: Point, + ) { + trace!("create_attached_layer"); + + if fallback_widget.widget.as_layer().is_none() { + debug_panic!( + "cannot create layer of type {} - `Widget::as_layer()` returned None", + fallback_widget.widget.short_type_name() + ); + return; + } + + // FIXME - Add LayerId type and use that instead. + let layer_id = fallback_widget.id; + + let layers = self + .global_state + .attached_layers + .entry(self.widget_id()) + .or_default(); + if let Some(prev) = layers.insert(TypeId::of::(), layer_id) { + self.global_state + .emit_signal(RenderRootSignal::RemoveLayer(prev)); + } + self.global_state.emit_signal(RenderRootSignal::NewLayer( + layer_type, + fallback_widget.erased(), + position, + )); + } + + /// Returns the attached layer created by this widget with the given type `W`. + /// + /// See [`Self::create_attached_layer`] for more details. + pub fn get_attached_layer(&self) -> Option { + self.global_state + .attached_layers + .get(&self.widget_id())? + .get(&TypeId::of::()) + .copied() + } + /// Removes the layer with the specified widget as root. pub fn remove_layer(&mut self, root_widget_id: WidgetId) { trace!("remove_layer"); self.global_state .emit_signal(RenderRootSignal::RemoveLayer(root_widget_id)); + + let Some(layers) = self.global_state.attached_layers.get_mut(&self.widget_id()) else { + return; + }; + + let layer_type_id = layers + .iter() + .find(|(_, id)| **id == root_widget_id) + .map(|(type_id, _)| *type_id); + if let Some(type_id) = layer_type_id { + layers.remove(&type_id); + } } /// Repositions the layer with the specified widget as root.