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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions crates/freya-components/tests/on_visible.rs
Original file line number Diff line number Diff line change
@@ -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::<State<i32>>();

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);
}
2 changes: 2 additions & 0 deletions crates/freya-core/src/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ use crate::{
PointerEventData,
SizedEventData,
TouchEventData,
VisibleEventData,
WheelEventData,
},
name::EventName,
Expand Down Expand Up @@ -430,6 +431,7 @@ pub enum EventHandlerType {
Mouse(EventHandler<Event<MouseEventData>>),
Keyboard(EventHandler<Event<KeyboardEventData>>),
Sized(EventHandler<Event<SizedEventData>>),
Visible(EventHandler<Event<VisibleEventData>>),
Wheel(EventHandler<Event<WheelEventData>>),
Touch(EventHandler<Event<TouchEventData>>),
Pointer(EventHandler<Event<PointerEventData>>),
Expand Down
10 changes: 10 additions & 0 deletions crates/freya-core/src/elements/extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ use crate::{
KeyboardEventData,
MouseEventData,
SizedEventData,
VisibleEventData,
WheelEventData,
},
name::EventName,
Expand Down Expand Up @@ -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<EventHandler<Event<VisibleEventData>>>) -> 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:
Expand Down
17 changes: 17 additions & 0 deletions crates/freya-core/src/events/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -286,6 +302,7 @@ pub enum EventType {
Mouse(MouseEventData),
Keyboard(KeyboardEventData),
Sized(SizedEventData),
Visible(VisibleEventData),
Wheel(WheelEventData),
Touch(TouchEventData),
Pointer(PointerEventData),
Expand Down
2 changes: 2 additions & 0 deletions crates/freya-core/src/events/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ pub enum EventName {

Sized,

Visible,

FileDrop,

ImePreedit,
Expand Down
1 change: 1 addition & 0 deletions crates/freya-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down
16 changes: 16 additions & 0 deletions crates/freya-core/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
42 changes: 42 additions & 0 deletions crates/freya-core/src/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use freya_engine::prelude::{
};
use futures_channel::mpsc::UnboundedSender;
use itertools::Itertools;
use ragnarok::NodesState;
use rustc_hash::{
FxHashMap,
FxHashSet,
Expand Down Expand Up @@ -52,6 +53,7 @@ use crate::{
data::{
EventType,
SizedEventData,
VisibleEventData,
},
emittable::EmmitableEvent,
name::EventName,
Expand Down Expand Up @@ -573,12 +575,14 @@ impl Tree {
Some(*current)
}

#[allow(clippy::too_many_arguments)]
pub fn measure_layout(
&mut self,
size: Size2D,
font_collection: &mut FontCollection,
font_manager: &FontMgr,
events_sender: &UnboundedSender<EventsChunk>,
nodes_state: &mut NodesState<NodeId>,
scale_factor: f64,
fallback_fonts: &[Cow<'static, str>],
) {
Expand Down Expand Up @@ -610,11 +614,49 @@ 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<EmmitableEvent>,
nodes_state: &mut NodesState<NodeId>,
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 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(layout_node.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();
Expand Down
Loading
Loading