use crate::collections::{FerroList, NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs, ResetBehavior};
use crate::media::{
can_value_affect_render, subscribe_invalidated, CacheMode, DrawingContext, FlowDirection, Geometry, IBrush, IEffect,
ITransform, RenderOptions, TextOptions, Transform,
};
use crate::rendering::composition::{CompositionDrawListVisual, CompositionVisual, Compositor};
use crate::reactive::{Disposable, IDisposable};
use crate::rendering::IPresentationSource;
use crate::utilities::HandlerList;
use crate::{
ferro_class, ferro_property, instantiate, AttachedProperty, DirectProperty, FerroObjectImpl,
FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Nullable, ObjectType, Rect, Ref,
RelativePoint, StyledElement,
StyledElementImpl, StyledElementImplExt, StyledProperty, StyledPropertyOptions, Upcast,
VisualTreeAttachmentEventArgs, WeakRef,
};
use std::cell::{Cell, OnceCell, RefCell};
use std::rc::Rc;
#[repr(C)]
pub struct Visual {
base: StyledElement,
bounds: Cell<Rect>,
presentation_source: RefCell<Option<Rc<dyn IPresentationSource>>>,
visual_parent: RefCell<Option<WeakRef<Visual>>>,
has_mirror_transform: Cell<bool>,
has_render_transform: Cell<bool>,
has_manipulation_transform: Cell<bool>,
is_effectively_visible: Cell<bool>,
has_non_uniform_z_index_children: Cell<bool>,
visual_children: OnceCell<FerroList<Ref<Visual>>>,
attached_to_visual_tree: HandlerList<dyn Fn(&VisualTreeAttachmentEventArgs)>,
detached_from_visual_tree: HandlerList<dyn Fn(&VisualTreeAttachmentEventArgs)>,
is_effectively_visible_changed: HandlerList<dyn Fn()>,
render_options: Cell<RenderOptions>,
text_options: Cell<TextOptions>,
composition_visual: RefCell<Option<CompositionDrawListVisual>>,
child_composition_visual: RefCell<Option<Rc<CompositionVisual>>>,
composition_extras: RefCell<Option<Box<CompositionExtras>>>,
render_transform_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
affects_render_subscriptions: RefCell<Vec<(&'static FerroProperty, Rc<dyn IDisposable>)>>,
}
#[derive(Default)]
struct CompositionExtras {
translation: Option<crate::Vector3D>,
hide_requests: u32,
render_suppression_requests: u32,
show_requests: u32,
layer_visual: Option<Rc<CompositionVisual>>,
unloading_visuals: Vec<Rc<CompositionVisual>>,
facades: composition_facades::CompositionFacades,
lights: Option<FerroList<Ref<crate::media::XamlLight>>>,
lights_subscriptions: Vec<Rc<dyn IDisposable>>,
light_target_ids: Vec<String>,
light_target_subscription: Option<Rc<dyn IDisposable>>,
manipulation_transform: Option<crate::Matrix>,
theme_shadow: Option<ThemeShadowState>,
}
#[derive(Default)]
struct ThemeShadowState {
popup: Option<WeakRef<Visual>>,
delegates_to_child: bool,
visual: Option<Rc<crate::rendering::composition::ThemeShadowVisual>>,
}
mod composition_facades;
mod theme_shadow;
#[cfg(test)]
mod composition_facades_tests;
pub use composition_facades::{ScalarTransition, Vector3Transition, Vector3TransitionComponents};
ferro_class! {
Visual: StyledElement, virtuals VisualImpl: StyledElementImpl {
fn render(this, context: &mut DrawingContext);
fn on_attached_to_visual_tree_core(this, e: &VisualTreeAttachmentEventArgs);
fn on_detached_from_visual_tree_core(this, e: &VisualTreeAttachmentEventArgs);
fn on_attached_to_visual_tree(this, e: &VisualTreeAttachmentEventArgs);
fn on_detached_from_visual_tree(this, e: &VisualTreeAttachmentEventArgs);
fn on_visual_parent_changed(this, old_parent: Option<&Ref<Visual>>, new_parent: Option<&Ref<Visual>>);
fn bypass_flow_direction_policies(this) -> bool;
fn invalidate_mirror_transform(this);
fn clip_to_bounds_radius(this) -> Option<crate::CornerRadius>;
fn custom_hit_test(this, point: crate::Point) -> Option<bool>;
fn custom_hit_test_geometry(this, geometry: &Ref<crate::media::Geometry>) -> Option<crate::media::IntersectionResult>;
fn ensure_initialized_for_visual_brush(this);
fn create_composition_visual(this, compositor: &Rc<Compositor>) -> CompositionDrawListVisual;
fn detach_from_compositor(this);
fn synchronize_composition_child_visuals(this);
fn synchronize_composition_properties(this);
}
}
crate::ferro_thread_local! {
static ROOTED_VISUAL_CHILDREN_COUNT: Cell<i32> = const { Cell::new(0) };
}
crate::ferro_class_info!(Visual { new: Visual::new });
crate::ferro_thread_local! {
static GEOMETRY_VERSION: Cell<u64> = const { Cell::new(0) };
}
#[inline]
pub(crate) fn geometry_version() -> u64 {
GEOMETRY_VERSION.with(Cell::get)
}
#[inline]
fn bump_geometry_version() {
GEOMETRY_VERSION.with(|version| version.set(version.get().wrapping_add(1)));
}
crate::ferro_overrides! { impl FerroObjectImpl for Visual {
fn constructed(this: &Self) {
Self::parent_constructed(this);
this.disable_transitions();
}
fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
Self::parent_on_property_changed(this, change);
if change.property() == Self::is_visible_property().as_property() {
let parent_state = this.visual_parent().is_none_or(|p| p.is_effectively_visible());
this.update_is_effectively_visible(parent_state);
} else if change.property() == Self::shadow_property().as_property() {
let old = change.get_old_value::<Option<Ref<crate::media::Shadow>>>().flatten();
let new = change.get_new_value::<Option<Ref<crate::media::Shadow>>>();
this.on_shadow_changed(old.as_ref(), new.as_ref());
} else if change.property() == crate::styling::ThemeVariant::actual_theme_variant_property().as_property() {
if this.may_cast_theme_shadow() {
this.invalidate_visual();
}
} else if change.property() == Self::flow_direction_property().as_property() {
this.invalidate_mirror_transform();
if let Some(children) = this.visual_children.get() {
for child in children.snapshot().iter() {
child.invalidate_mirror_transform();
}
}
}
}
} }
impl StyledElementImpl for Visual {
fn logical_children_collection_changed(this: &Self, e: &NotifyCollectionChangedEventArgs<'_, Ref<StyledElement>>) {
Self::parent_logical_children_collection_changed(this, e);
if let Some(source) = this.presentation_source() {
source.renderer().recalculate_children(this);
}
}
fn on_templated_parent_control_theme_changed(this: &Self) {
Self::parent_on_templated_parent_control_theme_changed(this);
let templated_parent = this.templated_parent();
if let Some(children) = this.visual_children.get() {
for child in children.snapshot().iter() {
if child.templated_parent() == templated_parent {
child.on_templated_parent_control_theme_changed();
}
}
}
}
}
impl VisualImpl for Visual {
fn render(_this: &Self, _context: &mut DrawingContext) {}
fn on_attached_to_visual_tree_core(this: &Self, e: &VisualTreeAttachmentEventArgs) {
*this.presentation_source.borrow_mut() = Some(e.presentation_source().clone());
ROOTED_VISUAL_CHILDREN_COUNT.with(|count| count.set(count.get() + 1));
if let Some(render_transform) = this.render_transform() {
if let Some(mutable_transform) = render_transform.as_mutable_transform() {
let weak = this.to_ref().downgrade();
let subscription = mutable_transform.changed(Rc::new(move || {
if let Some(visual) = weak.upgrade() {
visual.render_transform_value_changed();
}
}));
this.set_render_transform_subscription(Some(subscription));
}
}
this.enable_transitions();
let renderer = e.presentation_source().renderer();
if let Some(compositor) = renderer.compositor() {
this.attach_to_compositor(&compositor);
}
this.invalidate_mirror_transform();
let parent = this.visual_parent();
this.update_is_effectively_visible(parent.as_ref().is_none_or(|p| p.is_effectively_visible()));
this.on_attached_to_visual_tree(e);
if !this.attached_to_visual_tree.is_empty() {
for (_, handler) in this.attached_to_visual_tree.snapshot().iter() {
handler(e);
}
}
this.invalidate_visual();
if let Some(parent) = &parent {
renderer.recalculate_children(parent);
if this.z_index() != 0 {
parent.has_non_uniform_z_index_children.set(true);
}
}
if let Some(children) = this.visual_children.get() {
let this_ref = this.to_ref();
for child in children.snapshot().iter() {
let attached_here = child
.presentation_source()
.is_some_and(|s| Rc::ptr_eq(&s, e.presentation_source()));
if !attached_here && child.visual_parent().is_some_and(|p| p == this_ref) {
child.on_attached_to_visual_tree_core(e);
}
}
}
}
fn on_detached_from_visual_tree_core(this: &Self, e: &VisualTreeAttachmentEventArgs) {
ROOTED_VISUAL_CHILDREN_COUNT.with(|count| count.set(count.get() - 1));
this.set_render_transform_subscription(None);
this.disable_transitions();
this.update_is_effectively_visible(true);
this.on_detached_from_visual_tree(e);
this.detach_from_compositor();
if !this.detached_from_visual_tree.is_empty() {
for (_, handler) in this.detached_from_visual_tree.snapshot().iter() {
handler(e);
}
}
if let Some(source) = this.presentation_source() {
source.renderer().add_dirty(this);
}
*this.presentation_source.borrow_mut() = None;
if let Some(children) = this.visual_children.get() {
for child in children.snapshot().iter() {
if child.presentation_source().is_some() {
child.on_detached_from_visual_tree_core(e);
}
}
}
}
fn on_attached_to_visual_tree(_this: &Self, _e: &VisualTreeAttachmentEventArgs) {}
fn on_detached_from_visual_tree(_this: &Self, _e: &VisualTreeAttachmentEventArgs) {}
fn on_visual_parent_changed(this: &Self, old_parent: Option<&Ref<Visual>>, new_parent: Option<&Ref<Visual>>) {
this.raise_direct_property_changed(
Self::visual_parent_property(),
&old_parent.cloned(),
&new_parent.cloned(),
);
}
fn bypass_flow_direction_policies(_this: &Self) -> bool {
false
}
fn clip_to_bounds_radius(_this: &Self) -> Option<crate::CornerRadius> {
None
}
fn custom_hit_test(_this: &Self, _point: crate::Point) -> Option<bool> {
None
}
fn custom_hit_test_geometry(
_this: &Self,
_geometry: &Ref<crate::media::Geometry>,
) -> Option<crate::media::IntersectionResult> {
None
}
fn ensure_initialized_for_visual_brush(_this: &Self) {}
fn create_composition_visual(this: &Self, compositor: &Rc<Compositor>) -> CompositionDrawListVisual {
CompositionDrawListVisual::new(compositor, this)
}
fn detach_from_compositor(this: &Self) {
this.release_theme_shadow_visual();
let composition_visual = this.composition_visual.borrow_mut().take();
if let Some(composition_visual) = composition_visual {
if let Some(child) = this.child_composition_visual() {
composition_visual.children().remove(&child);
}
if let Some(layer) = this.composition_layer_visual() {
composition_visual.children().remove(&layer);
}
composition_visual.set_draw_list(None);
composition_visual.set_opacity_mask(None);
composition_visual.children().clear();
}
}
fn synchronize_composition_child_visuals(this: &Self) {
let Some(composition_visual) = this.composition_visual() else { return };
let composition_children = composition_visual.children();
let visual_children: Rc<Vec<Ref<Visual>>> =
this.visual_children_snapshot().unwrap_or_else(|| Rc::new(Vec::new()));
let mut sorted_children: Option<Vec<(Ref<Visual>, usize)>> = None;
if this.has_non_uniform_z_index_children() && visual_children.len() > 1 {
let mut sorted: Vec<(Ref<Visual>, usize)> =
visual_children.iter().cloned().enumerate().map(|(index, visual)| (visual, index)).collect();
sorted.sort_by(|lhs, rhs| lhs.0.z_index().cmp(&rhs.0.z_index()).then(lhs.1.cmp(&rhs.1)));
sorted_children = Some(sorted);
}
let mut child_visual = this.child_composition_visual();
if child_visual.as_ref().is_some_and(|child| !Rc::ptr_eq(child.compositor(), composition_visual.compositor())) {
child_visual = None;
}
let mut layer_visual = this.composition_layer_visual();
if layer_visual.as_ref().is_some_and(|layer| !Rc::ptr_eq(layer.compositor(), composition_visual.compositor())) {
layer_visual = None;
}
let unloading_visuals: Vec<Rc<CompositionVisual>> = this
.read_composition_extras(|extras| extras.unloading_visuals.clone())
.unwrap_or_default()
.into_iter()
.filter(|unloading| Rc::ptr_eq(unloading.compositor(), composition_visual.compositor()))
.collect();
let child_count = visual_children.len();
let child_at = |c: usize| -> &Ref<Visual> {
match &sorted_children {
Some(sorted) => &sorted[c].0,
None => &visual_children[c],
}
};
let shadows: Option<Vec<Option<Rc<CompositionVisual>>>> =
if visual_children.iter().any(|child| child.may_cast_theme_shadow()) {
Some((0..child_count).map(|c| child_at(c).ensure_theme_shadow_composition_visual()).collect())
} else {
None
};
let shadow_at = |c: usize| -> Option<&Rc<CompositionVisual>> {
shadows.as_ref().and_then(|shadows| shadows[c].as_ref())
};
let shadow_count = shadows.as_ref().map_or(0, |shadows| shadows.iter().flatten().count());
let mut expected_count = child_count + shadow_count + unloading_visuals.len();
if child_visual.is_some() {
expected_count += 1;
}
if layer_visual.is_some() {
expected_count += 1;
}
let same = |a: &Rc<CompositionVisual>, b: Option<CompositionDrawListVisual>| {
b.is_some_and(|b| Rc::ptr_eq(a, &b))
};
if composition_children.count() == expected_count {
let mut mismatch = false;
let mut index = 0;
for c in 0..child_count {
if let Some(shadow) = shadow_at(c) {
if !Rc::ptr_eq(&composition_children.get(index), shadow) {
mismatch = true;
break;
}
index += 1;
}
if !same(&composition_children.get(index), child_at(c).composition_visual()) {
mismatch = true;
break;
}
index += 1;
}
if !mismatch {
for unloading in &unloading_visuals {
if !Rc::ptr_eq(&composition_children.get(index), unloading) {
mismatch = true;
}
index += 1;
}
if let Some(child_visual) = &child_visual {
if !Rc::ptr_eq(&composition_children.get(index), child_visual) {
mismatch = true;
}
index += 1;
}
if let Some(layer_visual) = &layer_visual {
if !Rc::ptr_eq(&composition_children.get(index), layer_visual) {
mismatch = true;
}
}
}
if !mismatch {
return;
}
}
composition_children.clear();
for c in 0..child_count {
if let Some(shadow) = shadow_at(c) {
composition_children.add(shadow.clone());
}
if let Some(composition_child) = child_at(c).composition_visual() {
composition_children.add((*composition_child).clone());
}
}
for unloading in unloading_visuals {
composition_children.add(unloading);
}
if let Some(child_visual) = child_visual {
composition_children.add(child_visual);
}
if let Some(layer_visual) = layer_visual {
composition_children.add(layer_visual);
}
}
fn synchronize_composition_properties(this: &Self) {
let Some(comp) = this.composition_visual() else { return };
let bounds = this.bounds();
this.apply_composition_transitions(&comp, false);
comp.set_offset(crate::Vector3D::new(bounds.x, bounds.y, 0.0));
comp.set_size(crate::Vector::new(bounds.width, bounds.height));
comp.set_visible(
(this.is_visible() || this.is_shown_by_composition())
&& !this.is_hidden_by_composition()
&& !this.is_render_suppressed(),
);
comp.set_opacity(this.opacity() as f32);
comp.set_clip_to_bounds(this.clip_to_bounds());
comp.set_clip(this.clip().and_then(|clip| clip.platform_impl()));
let opacity_mask = this.opacity_mask();
let same_mask = match (comp.opacity_mask(), &opacity_mask) {
(Some(a), Some(b)) => *a == **b,
(None, None) => true,
_ => false,
};
if !same_mask {
comp.set_opacity_mask(opacity_mask);
}
let cache_mode = this.cache_mode().map(|cache_mode| cache_mode.get_for_compositor(comp.compositor()));
let same_cache_mode = match (comp.cache_mode(), &cache_mode) {
(Some(a), Some(b)) => Rc::ptr_eq(&a, b),
(None, None) => true,
_ => false,
};
if !same_cache_mode {
comp.set_cache_mode(cache_mode);
}
let effect = this.effect();
if !crate::media::EffectExtensions::effect_equals(comp.effect().as_deref(), effect.as_deref()) {
comp.set_effect(effect.as_ref().map(crate::media::EffectExtensions::to_immutable));
}
comp.set_render_options(this.render_options());
comp.set_text_options(this.text_options());
let mut render_transform = crate::Matrix::IDENTITY;
if this.has_mirror_transform() {
render_transform = crate::Matrix::new(-1.0, 0.0, 0.0, 1.0, bounds.width, 0.0);
}
if let Some(transform) = this.render_transform() {
let origin = this.render_transform_origin().to_pixels(crate::Size::new(bounds.width, bounds.height));
let offset = crate::Matrix::create_translation(origin.x, origin.y);
render_transform = render_transform * ((-offset) * transform.value() * offset);
}
if let Some(facade) = this.composition_facade_transform_matrix() {
render_transform = render_transform * facade;
}
if let Some(manipulation) = this.manipulation_transform() {
render_transform = render_transform * manipulation;
}
comp.set_transform_matrix(render_transform);
if let Some(translation) = this.composition_translation() {
comp.set_translation(translation);
}
this.synchronize_composition_facades(&comp);
this.apply_composition_transitions(&comp, true);
this.update_theme_shadow_visual();
}
fn invalidate_mirror_transform(this: &Self) {
let flow_direction = this.flow_direction();
let mut parent_flow_direction = FlowDirection::LeftToRight;
let bypass = this.bypass_flow_direction_policies();
let mut parent_bypass = false;
if let Some(parent) = this.visual_parent() {
parent_flow_direction = parent.flow_direction();
parent_bypass = parent.bypass_flow_direction_policies();
}
let this_should_be_mirrored = flow_direction == FlowDirection::RightToLeft && !bypass;
let parent_should_be_mirrored = parent_flow_direction == FlowDirection::RightToLeft && !parent_bypass;
this.set_has_mirror_transform(this_should_be_mirrored != parent_should_be_mirrored);
}
}
impl Drop for Visual {
fn drop(&mut self) {
for (_, subscription) in self.affects_render_subscriptions.get_mut().drain(..) {
subscription.dispose();
}
}
}
crate::ferro_properties! { impl Visual {
ferro_property!(
pub fn bounds_property() -> DirectProperty<Visual, Rect> {
FerroProperty::register_direct::<Visual, _>("Bounds", |o| o.bounds(), None, Rect::default())
}
);
ferro_property!(
pub fn clip_to_bounds_property() -> StyledProperty<bool> {
FerroProperty::register::<Visual, _>("ClipToBounds", false)
}
);
ferro_property!(
pub fn clip_property() -> StyledProperty<Option<Ref<Geometry>>> {
FerroProperty::register::<Visual, _>("Clip", None)
}
);
ferro_property!(
pub fn shadow_property() -> StyledProperty<Option<Ref<crate::media::Shadow>>> {
FerroProperty::register::<Visual, _>("Shadow", None)
}
);
ferro_property!(
pub fn translation_property() -> DirectProperty<Visual, crate::Vector3D> {
FerroProperty::register_direct::<Visual, _>(
"Translation",
|o| o.translation(),
Some(|o, value| o.set_translation(value)),
crate::Vector3D::default(),
)
}
);
ferro_property!(
pub fn cache_mode_property() -> StyledProperty<Option<Ref<CacheMode>>> {
FerroProperty::register::<Visual, _>("CacheMode", None)
}
);
ferro_property!(
pub fn effect_property() -> StyledProperty<Option<Rc<dyn IEffect>>> {
FerroProperty::register::<Visual, _>("Effect", None)
}
);
ferro_property!(
pub fn opacity_mask_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
FerroProperty::register::<Visual, _>("OpacityMask", None)
}
);
ferro_property!(
pub fn render_transform_property() -> StyledProperty<Option<Rc<dyn ITransform>>> {
FerroProperty::register::<Visual, _>("RenderTransform", None)
}
);
ferro_property!(
pub fn render_transform_origin_property() -> StyledProperty<RelativePoint> {
FerroProperty::register::<Visual, _>("RenderTransformOrigin", RelativePoint::CENTER)
}
);
ferro_property!(
pub fn is_visible_property() -> StyledProperty<bool> {
FerroProperty::register::<Visual, _>("IsVisible", true)
}
);
ferro_property!(
pub fn opacity_property() -> StyledProperty<f64> {
FerroProperty::register::<Visual, _>("Opacity", 1.0)
}
);
ferro_property!(
pub fn has_mirror_transform_property() -> DirectProperty<Visual, bool> {
FerroProperty::register_direct::<Visual, _>("HasMirrorTransform", |o| o.has_mirror_transform(), None, false)
}
);
ferro_property!(
pub fn flow_direction_property() -> AttachedProperty<FlowDirection> {
FerroProperty::register_attached_with::<Visual, Visual, _>(
"FlowDirection",
StyledPropertyOptions::new(FlowDirection::LeftToRight).inherits(true),
)
}
);
ferro_property!(
pub fn visual_parent_property() -> DirectProperty<Visual, Option<Ref<Visual>>> {
FerroProperty::register_direct::<Visual, _>("VisualParent", |o| o.visual_parent(), None, None)
}
);
ferro_property!(
pub fn z_index_property() -> StyledProperty<i32> {
FerroProperty::register::<Visual, _>("ZIndex", 0)
}
);
} }
impl Visual {
fn static_constructor() {
Self::affects_render::<Visual>(&[
Self::bounds_property().as_property(),
Self::clip_property().as_property(),
Self::clip_to_bounds_property().as_property(),
Self::is_visible_property().as_property(),
Self::opacity_property().as_property(),
Self::opacity_mask_property().as_property(),
Self::effect_property().as_property(),
Self::has_mirror_transform_property().as_property(),
]);
Self::render_transform_property().changed().subscribe(Self::render_transform_changed);
Self::clip_to_bounds_property().changed().subscribe(|_| bump_geometry_version());
Self::render_transform_origin_property().changed().subscribe(|_| bump_geometry_version());
Self::z_index_property().changed().subscribe(|e| {
let Some(sender) = e.sender().downcast_ref::<Visual>() else { return };
let parent = sender.visual_parent();
if let Some(parent) = &parent {
if sender.z_index() != 0 {
parent.has_non_uniform_z_index_children.set(true);
}
}
sender.invalidate_visual();
if let Some(parent) = &parent {
if let Some(source) = parent.presentation_source() {
source.renderer().recalculate_children(parent);
}
}
});
}
pub fn construct() -> Self {
Self {
base: StyledElement::construct(),
bounds: Cell::new(Rect::default()),
presentation_source: RefCell::new(None),
visual_parent: RefCell::new(None),
has_mirror_transform: Cell::new(false),
has_render_transform: Cell::new(false),
has_manipulation_transform: Cell::new(false),
is_effectively_visible: Cell::new(true),
has_non_uniform_z_index_children: Cell::new(false),
visual_children: OnceCell::new(),
attached_to_visual_tree: HandlerList::new(),
detached_from_visual_tree: HandlerList::new(),
is_effectively_visible_changed: HandlerList::new(),
render_options: Cell::new(RenderOptions::default()),
text_options: Cell::new(TextOptions::default()),
composition_visual: RefCell::new(None),
child_composition_visual: RefCell::new(None),
composition_extras: RefCell::new(None),
render_transform_subscription: RefCell::new(None),
affects_render_subscriptions: RefCell::new(Vec::new()),
}
}
pub fn new() -> Ref<Self> {
instantiate(Self::construct())
}
pub fn attached_to_visual_tree(
&self,
handler: impl Fn(&VisualTreeAttachmentEventArgs) + 'static,
) -> Rc<dyn IDisposable> {
let token = self.attached_to_visual_tree.add(Rc::new(handler));
let weak = self.to_ref().downgrade();
Disposable::create(move || {
if let Some(this) = weak.upgrade() {
this.attached_to_visual_tree.remove(token);
}
})
}
pub fn detached_from_visual_tree(
&self,
handler: impl Fn(&VisualTreeAttachmentEventArgs) + 'static,
) -> Rc<dyn IDisposable> {
let token = self.detached_from_visual_tree.add(Rc::new(handler));
let weak = self.to_ref().downgrade();
Disposable::create(move || {
if let Some(this) = weak.upgrade() {
this.detached_from_visual_tree.remove(token);
}
})
}
pub fn is_effectively_visible_changed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
let token = self.is_effectively_visible_changed.add(Rc::new(handler));
let weak = self.to_ref().downgrade();
Disposable::create(move || {
if let Some(this) = weak.upgrade() {
this.is_effectively_visible_changed.remove(token);
}
})
}
#[inline]
pub fn bounds(&self) -> Rect {
self.bounds.get()
}
pub fn set_bounds(&self, value: Rect) {
if self.bounds.get() != value {
bump_geometry_version();
}
self.set_and_raise_cell(Self::bounds_property(), &self.bounds, value);
}
pub fn clip_to_bounds(&self) -> bool {
self.get_value(Self::clip_to_bounds_property())
}
pub fn set_clip_to_bounds(&self, value: bool) {
self.set_value(Self::clip_to_bounds_property(), value)
}
pub fn clip(&self) -> Option<Ref<Geometry>> {
self.get_value(Self::clip_property())
}
pub fn set_clip(&self, value: impl Into<Nullable<Geometry>>) {
self.set_value(Self::clip_property(), value.into().0)
}
pub fn cache_mode(&self) -> Option<Ref<CacheMode>> {
self.get_value(Self::cache_mode_property())
}
pub fn set_cache_mode(&self, value: impl Into<Nullable<CacheMode>>) {
self.set_value(Self::cache_mode_property(), value.into().0)
}
pub fn effect(&self) -> Option<Rc<dyn IEffect>> {
self.get_value(Self::effect_property())
}
pub fn set_effect(&self, value: Option<Rc<dyn IEffect>>) {
self.set_value(Self::effect_property(), value)
}
pub fn opacity_mask(&self) -> Option<Rc<dyn IBrush>> {
self.get_value(Self::opacity_mask_property())
}
pub fn set_opacity_mask(&self, value: Option<Rc<dyn IBrush>>) {
self.set_value(Self::opacity_mask_property(), value)
}
pub fn render_transform(&self) -> Option<Rc<dyn ITransform>> {
self.get_value(Self::render_transform_property())
}
pub fn set_render_transform(&self, value: Option<Rc<dyn ITransform>>) {
self.set_value(Self::render_transform_property(), value)
}
pub fn render_transform_origin(&self) -> RelativePoint {
self.get_value(Self::render_transform_origin_property())
}
pub fn set_render_transform_origin(&self, value: RelativePoint) {
self.set_value(Self::render_transform_origin_property(), value)
}
#[inline]
pub fn is_effectively_visible(&self) -> bool {
self.is_effectively_visible.get()
}
pub fn is_visible(&self) -> bool {
self.get_value(Self::is_visible_property())
}
pub fn set_is_visible(&self, value: bool) {
self.set_value(Self::is_visible_property(), value)
}
pub fn opacity(&self) -> f64 {
self.get_value(Self::opacity_property())
}
pub fn set_opacity(&self, value: f64) {
self.set_value(Self::opacity_property(), value)
}
#[inline]
pub(crate) fn has_render_transform(&self) -> bool {
self.has_render_transform.get()
}
pub fn manipulation_transform(&self) -> Option<crate::Matrix> {
if !self.has_manipulation_transform.get() {
return None;
}
self.read_composition_extras(|extras| extras.manipulation_transform).flatten()
}
pub fn set_manipulation_transform(&self, value: Option<crate::Matrix>) {
let value = value.filter(|matrix| !matrix.is_identity());
if self.manipulation_transform() == value {
return;
}
self.has_manipulation_transform.set(value.is_some());
self.with_composition_extras(|extras| extras.manipulation_transform = value);
bump_geometry_version();
self.invalidate_visual();
}
#[inline]
pub fn has_mirror_transform(&self) -> bool {
self.has_mirror_transform.get()
}
pub fn set_has_mirror_transform(&self, value: bool) {
self.set_and_raise_cell(Self::has_mirror_transform_property(), &self.has_mirror_transform, value);
}
pub fn flow_direction(&self) -> FlowDirection {
self.get_value(Self::flow_direction_property())
}
pub fn set_flow_direction(&self, value: FlowDirection) {
self.set_value(Self::flow_direction_property(), value)
}
pub fn get_flow_direction(visual: &Visual) -> FlowDirection {
visual.get_value(Self::flow_direction_property())
}
pub fn set_flow_direction_on(visual: &Visual, value: FlowDirection) {
visual.set_value(Self::flow_direction_property(), value)
}
pub fn z_index(&self) -> i32 {
self.get_value(Self::z_index_property())
}
pub fn set_z_index(&self, value: i32) {
self.set_value(Self::z_index_property(), value)
}
pub fn visual_children(&self) -> &FerroList<Ref<Visual>> {
self.visual_children.get_or_init(|| {
let list = FerroList::new();
list.set_reset_behavior(ResetBehavior::Remove);
let weak = self.to_ref().downgrade();
let validator_owner = weak.clone();
list.set_validate(Some(Rc::new(move |item: &Ref<Visual>| {
if let Some(parent) = item.visual_parent() {
let owner = validator_owner.upgrade();
panic!(
"The control {:?} already has a visual parent {:?} while trying to add it as a child of {:?}.",
item, parent, owner
);
}
})));
list.add_collection_changed(Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, Ref<Visual>>| {
if let Some(this) = weak.upgrade() {
this.visual_children_changed(e);
}
}));
list
})
}
#[inline]
pub fn visual_children_count(&self) -> usize {
self.visual_children.get().map_or(0, FerroList::count)
}
#[inline]
pub fn visual_children_snapshot(&self) -> Option<Rc<Vec<Ref<Visual>>>> {
self.visual_children.get().map(FerroList::snapshot)
}
#[inline]
pub fn presentation_source(&self) -> Option<Rc<dyn IPresentationSource>> {
self.presentation_source.borrow().clone()
}
pub fn visual_root(&self) -> Option<Ref<Visual>> {
self.presentation_source().and_then(|s| s.root_visual())
}
#[inline]
pub(crate) fn render_options(&self) -> RenderOptions {
self.render_options.get()
}
pub(crate) fn set_render_options(&self, value: RenderOptions) {
self.render_options.set(value);
self.invalidate_visual();
}
#[inline]
pub(crate) fn text_options(&self) -> TextOptions {
self.text_options.get()
}
pub(crate) fn set_text_options(&self, value: TextOptions) {
self.text_options.set(value);
self.invalidate_visual();
}
pub fn composition_visual(&self) -> Option<CompositionDrawListVisual> {
self.composition_visual.borrow().clone()
}
pub fn child_composition_visual(&self) -> Option<Rc<CompositionVisual>> {
self.child_composition_visual.borrow().clone()
}
pub fn set_child_composition_visual(&self, value: Option<Rc<CompositionVisual>>) {
*self.child_composition_visual.borrow_mut() = value;
}
fn with_composition_extras<R>(&self, f: impl FnOnce(&mut CompositionExtras) -> R) -> R {
let mut extras = self.composition_extras.borrow_mut();
f(extras.get_or_insert_with(Default::default))
}
fn read_composition_extras<R>(&self, f: impl FnOnce(&CompositionExtras) -> R) -> Option<R> {
self.composition_extras.borrow().as_deref().map(f)
}
pub fn lights(&self) -> FerroList<Ref<crate::media::XamlLight>> {
if let Some(lights) = self.read_composition_extras(|extras| extras.lights.clone()).flatten() {
return lights;
}
let lights: FerroList<Ref<crate::media::XamlLight>> = FerroList::new();
let subscriptions = crate::media::xaml_light::track_lights(self, &lights);
self.with_composition_extras(|extras| {
extras.lights = Some(lights.clone());
extras.lights_subscriptions = subscriptions;
});
lights
}
pub fn has_lights(&self) -> bool {
self.read_composition_extras(|extras| extras.lights.as_ref().is_some_and(|lights| lights.count() > 0))
.unwrap_or(false)
}
pub fn light_target_ids(&self) -> Vec<String> {
self.read_composition_extras(|extras| extras.light_target_ids.clone()).unwrap_or_default()
}
pub(crate) fn add_light_target_id(&self, id: &str) -> bool {
let added = self.with_composition_extras(|extras| {
if extras.light_target_ids.iter().any(|existing| existing == id) {
return false;
}
extras.light_target_ids.push(id.to_owned());
true
});
if added && self.read_composition_extras(|extras| extras.light_target_subscription.is_none()).unwrap_or(true) {
let subscription = crate::media::xaml_light::track_light_target(self);
self.with_composition_extras(|extras| extras.light_target_subscription = Some(subscription));
}
added
}
pub(crate) fn remove_light_target_id(&self, id: &str) -> bool {
self.read_composition_extras(|extras| extras.light_target_ids.iter().any(|existing| existing == id))
.unwrap_or(false)
&& self.with_composition_extras(|extras| {
extras.light_target_ids.retain(|existing| existing != id);
true
})
}
pub fn translation(&self) -> crate::Vector3D {
self.composition_translation().unwrap_or_default()
}
pub fn set_translation(&self, value: crate::Vector3D) {
let old = self.translation();
self.with_composition_extras(|extras| extras.translation = Some(value));
self.invalidate_visual();
if old != value {
self.raise_direct_property_changed(Self::translation_property(), &old, &value);
}
}
fn composition_translation(&self) -> Option<crate::Vector3D> {
self.read_composition_extras(|extras| extras.translation).flatten()
}
pub fn is_hidden_by_composition(&self) -> bool {
self.read_composition_extras(|extras| extras.hide_requests > 0).unwrap_or(false)
}
pub fn add_composition_hide_request(&self) {
self.with_composition_extras(|extras| extras.hide_requests += 1);
self.invalidate_visual();
}
pub fn remove_composition_hide_request(&self) {
self.with_composition_extras(|extras| extras.hide_requests = extras.hide_requests.saturating_sub(1));
self.invalidate_visual();
}
pub fn is_render_suppressed(&self) -> bool {
self.read_composition_extras(|extras| extras.render_suppression_requests > 0)
.unwrap_or(false)
}
pub fn add_render_suppression_request(&self) {
let changed = self.with_composition_extras(|extras| {
let previous = extras.render_suppression_requests;
extras.render_suppression_requests = previous
.checked_add(1)
.expect("Render suppression request count exhausted");
previous == 0
});
if changed {
self.invalidate_visual();
}
}
pub fn remove_render_suppression_request(&self) {
if !self.is_render_suppressed() {
return;
}
let changed = self.with_composition_extras(|extras| {
extras.render_suppression_requests -= 1;
extras.render_suppression_requests == 0
});
if changed {
self.invalidate_visual();
}
}
pub fn is_shown_by_composition(&self) -> bool {
self.read_composition_extras(|extras| extras.show_requests > 0).unwrap_or(false)
}
pub fn add_composition_show_request(&self) {
self.with_composition_extras(|extras| extras.show_requests += 1);
self.invalidate_visual();
}
pub fn remove_composition_show_request(&self) {
self.with_composition_extras(|extras| extras.show_requests = extras.show_requests.saturating_sub(1));
self.invalidate_visual();
}
pub fn composition_layer_visual(&self) -> Option<Rc<CompositionVisual>> {
self.read_composition_extras(|extras| extras.layer_visual.clone()).flatten()
}
pub(crate) fn add_composition_unloading_visual(&self, visual: Rc<CompositionVisual>) {
self.with_composition_extras(|extras| extras.unloading_visuals.push(visual));
if let Some(source) = self.presentation_source() {
source.renderer().recalculate_children(self);
}
}
pub(crate) fn remove_composition_unloading_visual(&self, visual: &Rc<CompositionVisual>) {
self.with_composition_extras(|extras| extras.unloading_visuals.retain(|v| !Rc::ptr_eq(v, visual)));
if let Some(own) = self.composition_visual() {
own.children().remove(visual);
}
if let Some(source) = self.presentation_source() {
source.renderer().recalculate_children(self);
}
}
pub fn set_composition_layer_visual(&self, value: Option<Rc<CompositionVisual>>) {
let old = self.with_composition_extras(|extras| std::mem::replace(&mut extras.layer_visual, value.clone()));
if let (Some(old), Some(own)) = (&old, self.composition_visual()) {
if !value.as_ref().is_some_and(|new| Rc::ptr_eq(new, old)) {
own.children().remove(old);
}
}
if let Some(source) = self.presentation_source() {
source.renderer().recalculate_children(self);
}
}
pub fn attach_to_compositor(&self, compositor: &Rc<Compositor>) -> CompositionDrawListVisual {
let current = self.composition_visual();
match current {
Some(current) if Rc::ptr_eq(current.compositor(), compositor) => current,
_ => {
let created = self.create_composition_visual(compositor);
*self.composition_visual.borrow_mut() = Some(created.clone());
self.composition_visual_created(&created);
created
}
}
}
pub fn has_non_uniform_z_index_children(&self) -> bool {
self.has_non_uniform_z_index_children.get()
}
#[inline]
pub fn is_attached_to_visual_tree(&self) -> bool {
self.presentation_source.borrow().is_some()
}
#[inline]
pub fn visual_parent(&self) -> Option<Ref<Visual>> {
self.visual_parent.borrow().as_ref().and_then(WeakRef::upgrade)
}
pub(crate) fn rooted_visual_children_count() -> i32 {
ROOTED_VISUAL_CHILDREN_COUNT.with(Cell::get)
}
pub fn invalidate_visual(&self) {
if let Some(source) = self.presentation_source() {
source.renderer().add_dirty(self);
}
}
pub fn affects_render<T: ObjectType + Upcast<Visual>>(properties: &[&'static FerroProperty]) {
for property in properties {
if can_value_affect_render(property.property_type()) {
property.changed().subscribe(|e| {
if let Some(sender) = e.sender().downcast_ref::<T>() {
let visual: &Visual = sender.upcast();
visual.update_affects_render_subscription(e);
visual.invalidate_visual();
}
});
} else {
property.changed().subscribe(|e| {
if let Some(sender) = e.sender().downcast_ref::<T>() {
let visual: &Visual = sender.upcast();
visual.invalidate_visual();
}
});
}
}
}
fn update_affects_render_subscription(&self, e: &FerroPropertyChangedEventArgs<'_>) {
let property = e.property();
let old = {
let mut subscriptions = self.affects_render_subscriptions.borrow_mut();
let position = subscriptions.iter().position(|(p, _)| *p == property);
position.map(|position| subscriptions.swap_remove(position))
};
if let Some((_, subscription)) = old {
subscription.dispose();
}
let weak = self.to_ref().downgrade();
let subscription = subscribe_invalidated(
e.new_value(),
Rc::new(move || {
if let Some(target) = weak.upgrade() {
target.invalidate_visual();
}
}),
);
if let Some(subscription) = subscription {
self.affects_render_subscriptions.borrow_mut().push((property, subscription));
}
}
fn render_transform_changed(e: &FerroPropertyChangedEventArgs<'_>) {
let Some(sender) = e.sender().downcast_ref::<Visual>() else { return };
let (_, new_value) = e.get_old_and_new_value::<Option<Rc<dyn ITransform>>>();
sender.has_render_transform.set(new_value.is_some());
bump_geometry_version();
if sender.visual_root().is_some() {
let subscription = new_value.as_ref().and_then(|new_value| {
let new_transform = new_value.as_any().downcast_ref::<Transform>()?;
let weak = sender.to_ref().downgrade();
Some(new_transform.changed(move || {
if let Some(visual) = weak.upgrade() {
visual.render_transform_value_changed();
}
}))
});
sender.set_render_transform_subscription(subscription);
sender.invalidate_visual();
}
}
fn render_transform_value_changed(&self) {
bump_geometry_version();
self.invalidate_visual();
}
fn set_render_transform_subscription(&self, value: Option<Rc<dyn IDisposable>>) {
let previous = self.render_transform_subscription.replace(value);
if let Some(previous) = previous {
previous.dispose();
}
}
pub fn set_presentation_source_for_root_visual(&self, presentation_source: Option<Rc<dyn IPresentationSource>>) {
let current = self.presentation_source();
let same = match (¤t, &presentation_source) {
(Some(a), Some(b)) => Rc::ptr_eq(a, b),
(None, None) => true,
_ => false,
};
if same {
return;
}
if let Some(current) = current {
assert!(
presentation_source.is_none(),
"Visual is already attached to a presentation source. Only one presentation source can be \
attached to a visual tree."
);
self.on_detached_from_visual_tree_core(&VisualTreeAttachmentEventArgs::new(None, current));
}
*self.presentation_source.borrow_mut() = presentation_source.clone();
if let Some(source) = presentation_source {
let e = VisualTreeAttachmentEventArgs::new(None, source);
self.on_attached_to_visual_tree_core(&e);
}
}
fn update_is_effectively_visible(&self, parent_state: bool) {
let is_effectively_visible = parent_state && self.is_visible();
if self.is_effectively_visible.get() == is_effectively_visible {
return;
}
self.is_effectively_visible.set(is_effectively_visible);
if !self.is_effectively_visible_changed.is_empty() {
for (_, handler) in self.is_effectively_visible_changed.snapshot().iter() {
handler();
}
}
if let Some(children) = self.visual_children.get() {
for child in children.snapshot().iter() {
child.update_is_effectively_visible(is_effectively_visible);
}
}
}
fn set_visual_parent(&self, value: Option<&Ref<Visual>>) {
let old = self.visual_parent();
if old.as_ref() == value {
return;
}
*self.visual_parent.borrow_mut() = value.map(Ref::downgrade);
bump_geometry_version();
if let (Some(source), Some(old)) = (self.presentation_source(), &old) {
let e = VisualTreeAttachmentEventArgs::new(Some(old.clone()), source);
self.on_detached_from_visual_tree_core(&e);
}
if let Some(parent) = value {
if let Some(source) = parent.presentation_source() {
let e = VisualTreeAttachmentEventArgs::new(Some(parent.clone()), source);
self.on_attached_to_visual_tree_core(&e);
}
}
self.on_visual_parent_changed(old.as_ref(), value);
}
fn visual_children_changed(&self, e: &NotifyCollectionChangedEventArgs<'_, Ref<Visual>>) {
match e.action {
NotifyCollectionChangedAction::Add => {
let this = self.to_ref();
for visual in e.new_items {
visual.set_visual_parent(Some(&this));
}
}
NotifyCollectionChangedAction::Remove => {
for visual in e.old_items {
visual.set_visual_parent(None);
}
}
NotifyCollectionChangedAction::Replace => {
for visual in e.old_items {
visual.set_visual_parent(None);
}
let this = self.to_ref();
for visual in e.new_items {
visual.set_visual_parent(Some(&this));
}
}
NotifyCollectionChangedAction::Move | NotifyCollectionChangedAction::Reset => {}
}
}
}