use std::sync::{Arc, Mutex};
use std::time::Duration;
use accesskit::NodeId as AccessibilityId;
use dioxus_core::VirtualDom;
use freya_common::EventMessage;
use freya_core::prelude::*;
use freya_dom::prelude::SafeDOM;
use freya_engine::prelude::FontCollection;
use freya_hooks::PlatformInformation;
use tokio::sync::broadcast;
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
use tokio::time::{interval, timeout};
use torin::geometry::{Area, Size2D};
use winit::window::CursorIcon;
use crate::test_node::TestNode;
use crate::test_utils::TestUtils;
use crate::{TestingConfig, SCALE_FACTOR};
pub struct TestingHandler {
pub(crate) vdom: VirtualDom,
pub(crate) utils: TestUtils,
pub(crate) event_emitter: EventEmitter,
pub(crate) event_receiver: EventReceiver,
pub(crate) platform_event_emitter: UnboundedSender<EventMessage>,
pub(crate) platform_event_receiver: UnboundedReceiver<EventMessage>,
pub(crate) events_queue: EventsQueue,
pub(crate) nodes_state: NodesState,
pub(crate) font_collection: FontCollection,
pub(crate) accessibility_manager: SharedAccessibilityManager,
pub(crate) config: TestingConfig,
pub(crate) ticker_sender: broadcast::Sender<()>,
pub(crate) navigation_state: NavigatorState,
pub(crate) platform_information: Arc<Mutex<PlatformInformation>>,
pub(crate) cursor_icon: CursorIcon,
}
impl TestingHandler {
pub(crate) fn init_dom(&mut self) {
self.provide_vdom_contexts();
let sdom = self.utils.sdom();
let mut fdom = sdom.get();
fdom.init_dom(&mut self.vdom, SCALE_FACTOR as f32);
}
pub fn config(&mut self) -> &mut TestingConfig {
&mut self.config
}
fn provide_vdom_contexts(&mut self) {
self.vdom
.insert_any_root_context(Box::new(self.platform_event_emitter.clone()));
self.vdom
.insert_any_root_context(Box::new(Arc::new(self.ticker_sender.subscribe())));
self.vdom
.insert_any_root_context(Box::new(self.navigation_state.clone()));
self.vdom
.insert_any_root_context(Box::new(self.platform_information.clone()));
}
pub async fn wait_for_update(&mut self) -> (bool, bool) {
self.wait_for_work(self.config.size());
let mut ticker = if self.config.event_loop_ticker {
Some(interval(Duration::from_millis(16)))
} else {
None
};
loop {
let platform_ev = self.platform_event_receiver.try_recv();
let vdom_ev = self.event_receiver.try_recv();
if vdom_ev.is_err() && platform_ev.is_err() {
break;
}
if let Ok(ev) = platform_ev {
match ev {
EventMessage::RequestRerender => {
if let Some(ticker) = ticker.as_mut() {
ticker.tick().await;
self.ticker_sender.send(()).unwrap();
timeout(self.config.vdom_timeout(), self.vdom.wait_for_work())
.await
.ok();
}
}
EventMessage::FocusAccessibilityNode(node_id) => {
self.accessibility_manager
.lock()
.unwrap()
.set_focus_with_update(node_id);
}
EventMessage::SetCursorIcon(icon) => {
self.cursor_icon = icon;
}
_ => {}
}
}
if let Ok(ev) = vdom_ev {
self.vdom
.handle_event(ev.name.into(), ev.data.any(), ev.element_id, ev.bubbles);
self.vdom.process_events();
}
}
timeout(self.config.vdom_timeout(), self.vdom.wait_for_work())
.await
.ok();
let (must_repaint, must_relayout) = self
.utils
.sdom()
.get_mut()
.render_mutations(&mut self.vdom, SCALE_FACTOR as f32);
self.wait_for_work(self.config.size());
self.ticker_sender.send(()).unwrap();
(must_repaint, must_relayout)
}
pub fn wait_for_work(&mut self, size: Size2D) {
self.utils.sdom().get_mut().layout().reset();
let (layers, viewports) = process_layout(
&self.utils.sdom().get(),
Area {
origin: (0.0, 0.0).into(),
size,
},
&mut self.font_collection,
SCALE_FACTOR as f32,
);
*self.utils.layers().lock().unwrap() = layers;
*self.utils.viewports().lock().unwrap() = viewports;
let dom = &self.utils.sdom().get_mut();
process_accessibility(
&self.utils.layers().lock().unwrap(),
&dom.layout(),
dom.rdom(),
&mut self.accessibility_manager.lock().unwrap(),
);
process_events(
dom,
&self.utils.layers().lock().unwrap(),
&mut self.events_queue,
&self.event_emitter,
&mut self.nodes_state,
&self.utils.viewports().lock().unwrap(),
SCALE_FACTOR,
);
}
pub fn push_event(&mut self, event: PlatformEvent) {
self.events_queue.push(event);
}
pub fn root(&mut self) -> TestNode {
let root_id = {
let sdom = self.utils.sdom();
let fdom = sdom.get();
let rdom = fdom.rdom();
rdom.root_id()
};
self.utils.get_node_by_id(root_id)
}
pub fn focus_id(&self) -> AccessibilityId {
self.accessibility_manager.lock().unwrap().focused_id
}
pub fn resize(&mut self, size: Size2D) {
self.config.size = size;
self.platform_information.lock().unwrap().window_size = size;
}
pub fn cursor_icon(&self) -> CursorIcon {
self.cursor_icon
}
pub fn sdom(&self) -> &SafeDOM {
self.utils.sdom()
}
}