From 372e2c83b1ac3375a23b94e3bec2b6cf0d1aa23b Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sun, 5 Jul 2026 15:55:17 +0200 Subject: [PATCH 1/5] feat(core): `on_visible` --- crates/freya-components/tests/on_visible.rs | 47 ++++ crates/freya-core/src/element.rs | 2 + crates/freya-core/src/elements/extensions.rs | 10 + crates/freya-core/src/events/data.rs | 17 ++ crates/freya-core/src/events/name.rs | 2 + crates/freya-core/src/runner.rs | 16 ++ crates/freya-core/src/tree.rs | 42 ++++ crates/freya-core/tests/visible.rs | 202 ++++++++++++++++++ crates/freya-testing/src/lib.rs | 2 + crates/freya-winit/src/renderer.rs | 1 + crates/freya-winit/src/window.rs | 5 +- crates/freya/src/_docs/events.rs | 2 +- crates/ragnarok/src/nodes_state.rs | 11 + .../component_virtual_scrollview_infinite.rs | 41 ++++ examples/event_on_visible.rs | 32 +++ 15 files changed, 429 insertions(+), 3 deletions(-) create mode 100644 crates/freya-components/tests/on_visible.rs create mode 100644 crates/freya-core/tests/visible.rs create mode 100644 examples/component_virtual_scrollview_infinite.rs create mode 100644 examples/event_on_visible.rs diff --git a/crates/freya-components/tests/on_visible.rs b/crates/freya-components/tests/on_visible.rs new file mode 100644 index 000000000..a9dbf26fb --- /dev/null +++ b/crates/freya-components/tests/on_visible.rs @@ -0,0 +1,47 @@ +use freya::prelude::*; +use freya_testing::prelude::*; + +#[test] +pub fn on_visible_with_scrollview() { + fn on_visible_app() -> impl IntoElement { + let mut count = use_consume::>(); + + ScrollView::new() + .child(rect().height(Size::px(600.)).width(Size::px(200.))) + .child( + rect() + .height(Size::px(200.)) + .width(Size::px(200.)) + .on_visible(move |_| *count.write() += 1), + ) + } + + let (mut test, count) = TestingRunner::new( + on_visible_app, + (500., 500.).into(), + |runner| runner.provide_root_context(|| State::create(0i32)), + 1., + ); + test.sync_and_update(); + + // The target sits at 600..800, below the 500px tall viewport + assert_eq!(*count.peek(), 0); + + // Scrolling it into view fires the event + test.scroll((250., 250.), (0., -300.)); + test.sync_and_update(); + assert_eq!(*count.peek(), 1); + + // Scrolling further while it stays visible does not fire again + test.scroll((250., 250.), (0., -50.)); + test.sync_and_update(); + assert_eq!(*count.peek(), 1); + + // Scrolling it fully out and back in fires again + test.scroll((250., 250.), (0., 350.)); + test.sync_and_update(); + assert_eq!(*count.peek(), 1); + test.scroll((250., 250.), (0., -300.)); + test.sync_and_update(); + assert_eq!(*count.peek(), 2); +} diff --git a/crates/freya-core/src/element.rs b/crates/freya-core/src/element.rs index 554eed1a3..290b2c903 100644 --- a/crates/freya-core/src/element.rs +++ b/crates/freya-core/src/element.rs @@ -43,6 +43,7 @@ use crate::{ PointerEventData, SizedEventData, TouchEventData, + VisibleEventData, WheelEventData, }, name::EventName, @@ -430,6 +431,7 @@ pub enum EventHandlerType { Mouse(EventHandler>), Keyboard(EventHandler>), Sized(EventHandler>), + Visible(EventHandler>), Wheel(EventHandler>), Touch(EventHandler>), Pointer(EventHandler>), diff --git a/crates/freya-core/src/elements/extensions.rs b/crates/freya-core/src/elements/extensions.rs index 6fbda8220..0aa4c8049 100644 --- a/crates/freya-core/src/elements/extensions.rs +++ b/crates/freya-core/src/elements/extensions.rs @@ -51,6 +51,7 @@ use crate::{ KeyboardEventData, MouseEventData, SizedEventData, + VisibleEventData, WheelEventData, }, name::EventName, @@ -306,6 +307,15 @@ pub trait EventHandlersExt: Sized { self } + /// Fires when the element becomes visible, even partially, inside the viewports of its clipping ancestors. + fn on_visible(mut self, on_visible: impl Into>>) -> Self { + self.get_event_handlers().insert( + EventName::Visible, + EventHandlerType::Visible(on_visible.into()), + ); + self + } + /// This is generally the best event in which to run "press" logic, this might be called `onClick`, `onActivate`, or `onConnect` in other platforms. /// /// Gets triggered when: diff --git a/crates/freya-core/src/events/data.rs b/crates/freya-core/src/events/data.rs index 1066297a9..cb8674a8e 100644 --- a/crates/freya-core/src/events/data.rs +++ b/crates/freya-core/src/events/data.rs @@ -139,6 +139,22 @@ impl SizedEventData { } } +/// Data of a Visible event. +#[derive(Debug, Clone, PartialEq, Default)] +pub struct VisibleEventData { + pub area: Area, +} + +impl VisibleEventData { + pub fn new(area: Area) -> Self { + Self { area } + } + + pub fn div(&mut self, rhs: f32) { + self.area = self.area.div(rhs); + } +} + #[derive(Debug, Clone, PartialEq, Copy)] pub enum WheelSource { Device, @@ -286,6 +302,7 @@ pub enum EventType { Mouse(MouseEventData), Keyboard(KeyboardEventData), Sized(SizedEventData), + Visible(VisibleEventData), Wheel(WheelEventData), Touch(TouchEventData), Pointer(PointerEventData), diff --git a/crates/freya-core/src/events/name.rs b/crates/freya-core/src/events/name.rs index a7130d0bb..2e4b026d9 100644 --- a/crates/freya-core/src/events/name.rs +++ b/crates/freya-core/src/events/name.rs @@ -41,6 +41,8 @@ pub enum EventName { Sized, + Visible, + FileDrop, ImePreedit, diff --git a/crates/freya-core/src/runner.rs b/crates/freya-core/src/runner.rs index 70052855b..5eccb7485 100644 --- a/crates/freya-core/src/runner.rs +++ b/crates/freya-core/src/runner.rs @@ -417,6 +417,22 @@ impl Runner { } } } + EventType::Visible(data) => { + let event_handlers = element.events_handlers(); + if let Some(event_handlers) = event_handlers { + match event_handlers.get(&event_name) { + Some(EventHandlerType::Visible(handler)) => { + handler.call(Event { + data: data.clone(), + propagate: propagate.clone(), + default: default.clone(), + }); + } + Some(_) => unreachable!(), + _ => {} + } + } + } EventType::Wheel(data) => { let event_handlers = element.events_handlers(); if let Some(event_handlers) = event_handlers { diff --git a/crates/freya-core/src/tree.rs b/crates/freya-core/src/tree.rs index 15fbdafc5..0109fd8ca 100644 --- a/crates/freya-core/src/tree.rs +++ b/crates/freya-core/src/tree.rs @@ -16,6 +16,7 @@ use freya_engine::prelude::{ }; use futures_channel::mpsc::UnboundedSender; use itertools::Itertools; +use ragnarok::NodesState; use rustc_hash::{ FxHashMap, FxHashSet, @@ -52,6 +53,7 @@ use crate::{ data::{ EventType, SizedEventData, + VisibleEventData, }, emittable::EmmitableEvent, name::EventName, @@ -579,6 +581,7 @@ impl Tree { font_collection: &mut FontCollection, font_manager: &FontMgr, events_sender: &UnboundedSender, + nodes_state: &mut NodesState, scale_factor: f64, fallback_fonts: &[Cow<'static, str>], ) { @@ -610,11 +613,50 @@ impl Tree { &mut Some(layout_adapter), &mut tree_adapter, ); + self.measure_visible_events(&mut events, nodes_state, scale_factor); events_sender .unbounded_send(EventsChunk::Batch(events)) .unwrap(); } + /// Measure all the Visible listeners, emitting events for those that just became visible. + fn measure_visible_events( + &self, + events: &mut Vec, + nodes_state: &mut NodesState, + scale_factor: f64, + ) { + let mut visible_nodes = FxHashSet::default(); + let listeners = self.listeners.get(&EventName::Visible); + for node_id in listeners.into_iter().flatten() { + let visible_area = self + .layout + .get(node_id) + .zip(self.effect_state.get(node_id)) + .filter(|(layout_node, effect_state)| { + !layout_node.hidden && effect_state.is_visible(&self.layout, &layout_node.area) + }) + .map(|(layout_node, _)| layout_node.area); + + let Some(area) = visible_area else { + continue; + }; + visible_nodes.insert(*node_id); + if !nodes_state.is_visible(*node_id) { + let mut data = VisibleEventData::new(area); + data.div(scale_factor as f32); + events.push(EmmitableEvent { + node_id: *node_id, + name: EventName::Visible, + data: EventType::Visible(data), + bubbles: false, + source_event: EventName::Visible, + }); + } + } + nodes_state.set_visible_nodes(visible_nodes); + } + pub fn print_ascii(&self, node_id: NodeId, prefix: String, last: bool) { let height = self.heights.get(&node_id).unwrap(); let layer = self.layer_state.get(&node_id).unwrap(); diff --git a/crates/freya-core/tests/visible.rs b/crates/freya-core/tests/visible.rs new file mode 100644 index 000000000..a90f415b1 --- /dev/null +++ b/crates/freya-core/tests/visible.rs @@ -0,0 +1,202 @@ +use freya_core::prelude::*; +use freya_testing::TestingRunner; +use torin::prelude::Area; + +#[test] +fn visible_fires_once_on_first_layout() { + fn app() -> impl IntoElement { + let mut count = use_consume::>(); + let mut area = use_consume::>>(); + + rect() + .width(Size::px(50.)) + .height(Size::px(50.)) + .on_visible(move |e: Event| { + *count.write() += 1; + area.set(Some(e.area)); + }) + } + + let (mut test, (count, area)) = TestingRunner::new( + app, + (100., 100.).into(), + |runner| { + ( + runner.provide_root_context(|| State::create(0i32)), + runner.provide_root_context(|| State::create(Option::::None)), + ) + }, + 1., + ); + test.sync_and_update(); + assert_eq!(*count.peek(), 1); + assert_eq!( + *area.peek(), + Some(Area::new((0., 0.).into(), (50., 50.).into())) + ); + + // Further updates must not fire it again + test.sync_and_update(); + test.sync_and_update(); + assert_eq!(*count.peek(), 1); +} + +#[test] +fn visible_fires_when_entering_clip_viewport() { + fn app() -> impl IntoElement { + let offset = use_consume::>(); + let mut count = use_consume::>(); + + rect() + .width(Size::px(100.)) + .height(Size::px(100.)) + .overflow(Overflow::Clip) + .offset_y(*offset.read()) + .child(rect().width(Size::px(100.)).height(Size::px(200.))) + .child( + rect() + .width(Size::px(50.)) + .height(Size::px(50.)) + .on_visible(move |_| *count.write() += 1), + ) + } + + let (mut test, (offset, count)) = TestingRunner::new( + app, + (100., 100.).into(), + |runner| { + ( + runner.provide_root_context(|| State::create(0.0f32)), + runner.provide_root_context(|| State::create(0i32)), + ) + }, + 1., + ); + test.sync_and_update(); + + // The target sits at 200..250, outside of the 100px tall clipped parent + assert_eq!(*count.peek(), 0); + + // Scroll the target into view + *offset.write() = -150.; + test.sync_and_update(); + test.sync_and_update(); + assert_eq!(*count.peek(), 1); + + // Scrolling it out does not fire, entering again does + *offset.write() = 0.; + test.sync_and_update(); + test.sync_and_update(); + assert_eq!(*count.peek(), 1); + *offset.write() = -150.; + test.sync_and_update(); + test.sync_and_update(); + assert_eq!(*count.peek(), 2); +} + +#[test] +fn visible_fires_even_when_slightly_visible() { + fn app() -> impl IntoElement { + let offset = use_consume::>(); + let mut count = use_consume::>(); + + rect() + .width(Size::px(100.)) + .height(Size::px(100.)) + .overflow(Overflow::Clip) + .offset_y(*offset.read()) + .child(rect().width(Size::px(100.)).height(Size::px(200.))) + .child( + rect() + .width(Size::px(50.)) + .height(Size::px(50.)) + .on_visible(move |_| *count.write() += 1), + ) + } + + let (mut test, (offset, count)) = TestingRunner::new( + app, + (100., 100.).into(), + |runner| { + ( + runner.provide_root_context(|| State::create(0.0f32)), + runner.provide_root_context(|| State::create(0i32)), + ) + }, + 1., + ); + test.sync_and_update(); + + // The target top edge exactly touches the viewport bottom, still not visible + *offset.write() = -100.; + test.sync_and_update(); + test.sync_and_update(); + assert_eq!(*count.peek(), 0); + + // One more pixel makes it partially visible + *offset.write() = -101.; + test.sync_and_update(); + test.sync_and_update(); + assert_eq!(*count.peek(), 1); +} + +#[test] +fn visible_area_is_scaled_back_to_logical_pixels() { + fn app() -> impl IntoElement { + let mut area = use_consume::>>(); + + rect() + .width(Size::px(50.)) + .height(Size::px(50.)) + .on_visible(move |e: Event| area.set(Some(e.area))) + } + + let (mut test, area) = TestingRunner::new( + app, + (200., 200.).into(), + |runner| runner.provide_root_context(|| State::create(Option::::None)), + 2., + ); + test.sync_and_update(); + assert_eq!( + *area.peek(), + Some(Area::new((0., 0.).into(), (50., 50.).into())) + ); +} + +#[test] +fn visible_fires_again_when_remounted() { + fn app() -> impl IntoElement { + let show = use_consume::>(); + let mut count = use_consume::>(); + + rect().expanded().maybe_child((*show.read()).then(|| { + rect() + .width(Size::px(50.)) + .height(Size::px(50.)) + .on_visible(move |_| *count.write() += 1) + })) + } + + let (mut test, (show, count)) = TestingRunner::new( + app, + (100., 100.).into(), + |runner| { + ( + runner.provide_root_context(|| State::create(true)), + runner.provide_root_context(|| State::create(0i32)), + ) + }, + 1., + ); + test.sync_and_update(); + assert_eq!(*count.peek(), 1); + + // Unmount and mount again + *show.write() = false; + test.sync_and_update(); + *show.write() = true; + test.sync_and_update(); + test.sync_and_update(); + assert_eq!(*count.peek(), 2); +} diff --git a/crates/freya-testing/src/lib.rs b/crates/freya-testing/src/lib.rs index 6e90c496f..a24f2b763 100644 --- a/crates/freya-testing/src/lib.rs +++ b/crates/freya-testing/src/lib.rs @@ -310,6 +310,7 @@ impl TestingRunner { &mut self.font_collection, &self.font_manager, &self.events_sender, + &mut self.nodes_state, self.scale_factor, &self.default_fonts, ); @@ -365,6 +366,7 @@ impl TestingRunner { &mut self.font_collection, &self.font_manager, &self.events_sender, + &mut self.nodes_state, self.scale_factor, &self.default_fonts, ); diff --git a/crates/freya-winit/src/renderer.rs b/crates/freya-winit/src/renderer.rs index 8dcb383b1..20b3e8ed6 100644 --- a/crates/freya-winit/src/renderer.rs +++ b/crates/freya-winit/src/renderer.rs @@ -745,6 +745,7 @@ impl ApplicationHandler for WinitRenderer { &mut self.font_collection, &self.font_manager, &app.events_sender, + &mut app.nodes_state, scale_factor, &self.fallback_fonts, ); diff --git a/crates/freya-winit/src/window.rs b/crates/freya-winit/src/window.rs index 7fd94c6ea..4234a3c86 100644 --- a/crates/freya-winit/src/window.rs +++ b/crates/freya-winit/src/window.rs @@ -269,6 +269,8 @@ impl AppWindow { PluginHandle::new(event_loop_proxy), ); + let mut nodes_state = NodesState::default(); + let mutations = runner.sync_and_update(); tree.apply_mutations(mutations); tree.measure_layout( @@ -280,12 +282,11 @@ impl AppWindow { font_collection, font_manager, &events_sender, + &mut nodes_state, window.scale_factor(), fallback_fonts, ); - let nodes_state = NodesState::default(); - let accessibility_adapter = Adapter::with_event_loop_proxy(active_event_loop, &window, event_loop_proxy.clone()); diff --git a/crates/freya/src/_docs/events.rs b/crates/freya/src/_docs/events.rs index 2a463d097..8b43c2f86 100644 --- a/crates/freya/src/_docs/events.rs +++ b/crates/freya/src/_docs/events.rs @@ -29,7 +29,7 @@ //! `on_pointer_move`, `on_pointer_enter`, `on_pointer_leave`, `on_pointer_over`, `on_pointer_out`. //! - Keyboard: `on_key_down`, `on_key_up`. //! - Wheel: `on_wheel`. -//! - Layout: `on_sized`. +//! - Layout: `on_sized`, `on_visible`. //! - Files: `on_file_drop`. //! //! Each handler receives an [`Event`](freya_core::prelude::Event) where `D` is the payload diff --git a/crates/ragnarok/src/nodes_state.rs b/crates/ragnarok/src/nodes_state.rs index cb011ca44..1a5c7f6a5 100644 --- a/crates/ragnarok/src/nodes_state.rs +++ b/crates/ragnarok/src/nodes_state.rs @@ -16,6 +16,7 @@ use crate::{ pub struct NodesState { pressed_nodes: FxHashSet, hovered_nodes: FxHashSet, + visible_nodes: FxHashSet, entered_node: Option, } @@ -24,6 +25,7 @@ impl Default for NodesState { Self { pressed_nodes: FxHashSet::default(), hovered_nodes: FxHashSet::default(), + visible_nodes: FxHashSet::default(), entered_node: None, } } @@ -232,6 +234,15 @@ impl NodesState { pub fn is_pressed(&self, key: Key) -> bool { self.pressed_nodes.contains(&key) } + + pub fn is_visible(&self, key: Key) -> bool { + self.visible_nodes.contains(&key) + } + + /// Replace the visible nodes with the result of a new visibility pass. + pub fn set_visible_nodes(&mut self, visible_nodes: FxHashSet) { + self.visible_nodes = visible_nodes; + } } #[derive(Clone, Debug, PartialEq)] diff --git a/examples/component_virtual_scrollview_infinite.rs b/examples/component_virtual_scrollview_infinite.rs new file mode 100644 index 000000000..ec358e2a7 --- /dev/null +++ b/examples/component_virtual_scrollview_infinite.rs @@ -0,0 +1,41 @@ +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] +use freya::prelude::*; + +fn main() { + launch(LaunchConfig::new().with_window(WindowConfig::new(app))) +} + +fn app() -> impl IntoElement { + let mut length = use_state(|| 30usize); + + rect() + .expanded() + .child(format!("{} items", *length.read())) + .child( + VirtualScrollView::new(move |i, _| { + let is_last = i == *length.read() - 1; + rect() + .key(i) + .height(Size::px(50.)) + .padding(4.) + .maybe(is_last, |el| el.on_visible(move |_| *length.write() += 10)) + .child( + rect() + .width(Size::fill()) + .height(Size::fill()) + .padding(4.) + .corner_radius(8.) + .color((255, 255, 255)) + .background((0, 119, 182)) + .child(format!("Item {i}")), + ) + .into() + }) + .length(*length.read()) + .item_size(50.) + .expanded(), + ) +} diff --git a/examples/event_on_visible.rs b/examples/event_on_visible.rs new file mode 100644 index 000000000..05518cedb --- /dev/null +++ b/examples/event_on_visible.rs @@ -0,0 +1,32 @@ +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] +use freya::prelude::*; + +fn main() { + launch(LaunchConfig::new().with_window(WindowConfig::new(app))) +} + +fn app() -> impl IntoElement { + let mut times_shown = use_state(|| 0); + + rect() + .expanded() + .child(label().text(match *times_shown.read() { + 0 => "Scroll down to reveal the blue rect".to_string(), + times => format!("The blue rect has become visible {times} times"), + })) + .child( + ScrollView::new() + .expanded() + .child(rect().height(Size::px(1000.))) + .child( + rect() + .on_visible(move |_| *times_shown.write() += 1) + .width(Size::px(200.)) + .height(Size::px(200.)) + .background((0, 119, 182)), + ), + ) +} From 38c58e6c88c381ac91fec4f53902c486fa1ba962 Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sun, 5 Jul 2026 16:01:33 +0200 Subject: [PATCH 2/5] fix example --- crates/freya-core/src/lib.rs | 1 + examples/feature_embedded.rs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/crates/freya-core/src/lib.rs b/crates/freya-core/src/lib.rs index 0c85c0fd9..0d896235c 100644 --- a/crates/freya-core/src/lib.rs +++ b/crates/freya-core/src/lib.rs @@ -164,6 +164,7 @@ pub mod prelude { /// Used by renderers such as freya-testing, freya-winit or just integration crates. pub mod integration { + pub use ragnarok::NodesState; pub use rustc_hash::*; pub use crate::{ diff --git a/examples/feature_embedded.rs b/examples/feature_embedded.rs index 61e4b3ca8..50d53a47b 100644 --- a/examples/feature_embedded.rs +++ b/examples/feature_embedded.rs @@ -65,6 +65,7 @@ struct EmbeddedFreya { events_sender: UnboundedSender, events_receiver: UnboundedReceiver, + nodes_state: NodesState, size: Size2D, scale_factor: f64, @@ -93,6 +94,7 @@ impl EmbeddedFreya { default_fonts: default_fonts(), events_sender, events_receiver, + nodes_state: NodesState::default(), size, scale_factor, } @@ -112,6 +114,7 @@ impl EmbeddedFreya { &mut self.font_collection, &self.font_manager, &self.events_sender, + &mut self.nodes_state, self.scale_factor, &self.default_fonts, ); From 86ce663bbbe58a09fd0f42b01478418efbbceb80 Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sun, 5 Jul 2026 16:07:00 +0200 Subject: [PATCH 3/5] lint --- crates/freya-core/src/tree.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/freya-core/src/tree.rs b/crates/freya-core/src/tree.rs index 0109fd8ca..aaa2c6941 100644 --- a/crates/freya-core/src/tree.rs +++ b/crates/freya-core/src/tree.rs @@ -575,6 +575,7 @@ impl Tree { Some(*current) } + #[allow(clippy::too_many_arguments)] pub fn measure_layout( &mut self, size: Size2D, From 2f5f748e238f002e53e1a189011175b6f0a6e93c Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sun, 5 Jul 2026 16:17:59 +0200 Subject: [PATCH 4/5] tidy up --- crates/freya-core/tests/visible.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/freya-core/tests/visible.rs b/crates/freya-core/tests/visible.rs index a90f415b1..63dc931af 100644 --- a/crates/freya-core/tests/visible.rs +++ b/crates/freya-core/tests/visible.rs @@ -1,6 +1,9 @@ use freya_core::prelude::*; use freya_testing::TestingRunner; -use torin::prelude::Area; +use torin::{ + prelude::Area, + size::Size, +}; #[test] fn visible_fires_once_on_first_layout() { @@ -61,7 +64,7 @@ fn visible_fires_when_entering_clip_viewport() { ) } - let (mut test, (offset, count)) = TestingRunner::new( + let (mut test, (mut offset, count)) = TestingRunner::new( app, (100., 100.).into(), |runner| { @@ -114,7 +117,7 @@ fn visible_fires_even_when_slightly_visible() { ) } - let (mut test, (offset, count)) = TestingRunner::new( + let (mut test, (mut offset, count)) = TestingRunner::new( app, (100., 100.).into(), |runner| { @@ -178,7 +181,7 @@ fn visible_fires_again_when_remounted() { })) } - let (mut test, (show, count)) = TestingRunner::new( + let (mut test, (mut show, count)) = TestingRunner::new( app, (100., 100.).into(), |runner| { From 101faebda91590f7b7790fe8c663ccd69383714e Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sun, 5 Jul 2026 16:22:10 +0200 Subject: [PATCH 5/5] tidy up --- crates/freya-core/src/tree.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/crates/freya-core/src/tree.rs b/crates/freya-core/src/tree.rs index aaa2c6941..cc80325d3 100644 --- a/crates/freya-core/src/tree.rs +++ b/crates/freya-core/src/tree.rs @@ -630,21 +630,20 @@ impl Tree { let mut visible_nodes = FxHashSet::default(); let listeners = self.listeners.get(&EventName::Visible); for node_id in listeners.into_iter().flatten() { - let visible_area = self - .layout - .get(node_id) - .zip(self.effect_state.get(node_id)) - .filter(|(layout_node, effect_state)| { - !layout_node.hidden && effect_state.is_visible(&self.layout, &layout_node.area) - }) - .map(|(layout_node, _)| layout_node.area); - - let Some(area) = visible_area else { + let Some(layout_node) = self.layout.get(node_id) else { continue; }; + let is_visible = !layout_node.hidden + && self.effect_state.get(node_id).is_none_or(|effect_state| { + effect_state.is_visible(&self.layout, &layout_node.area) + }); + if !is_visible { + continue; + } + visible_nodes.insert(*node_id); if !nodes_state.is_visible(*node_id) { - let mut data = VisibleEventData::new(area); + let mut data = VisibleEventData::new(layout_node.area); data.div(scale_factor as f32); events.push(EmmitableEvent { node_id: *node_id,