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>,
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>>>,
render_transform_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
affects_render_subscriptions: RefCell<Vec<(&'static FerroProperty, Rc<dyn IDisposable>)>>,
}
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::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) {
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);
}
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 expected_count = visual_children.len();
if child_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;
for c in 0..visual_children.len() {
let expected = match &sorted_children {
Some(sorted) => sorted[c].0.composition_visual(),
None => visual_children[c].composition_visual(),
};
if !same(&composition_children.get(c), expected) {
mismatch = true;
break;
}
}
if let Some(child_visual) = &child_visual {
if !Rc::ptr_eq(&composition_children.get(composition_children.count() - 1), child_visual) {
mismatch = true;
}
}
if !mismatch {
return;
}
}
composition_children.clear();
match &sorted_children {
Some(sorted) => {
for (child, _) in sorted {
if let Some(composition_child) = child.composition_visual() {
composition_children.add((*composition_child).clone());
}
}
}
None => {
for child in visual_children.iter() {
if let Some(composition_child) = child.composition_visual() {
composition_children.add((*composition_child).clone());
}
}
}
}
if let Some(child_visual) = child_visual {
composition_children.add(child_visual);
}
}
fn synchronize_composition_properties(this: &Self) {
let Some(comp) = this.composition_visual() else { return };
let bounds = this.bounds();
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());
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);
}
comp.set_transform_matrix(render_transform);
}
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 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),
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),
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()
}
#[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;
}
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());
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 => {}
}
}
}