use std::sync::Arc;
use dioxus_core::{
Template,
VirtualDom,
};
use freya_core::prelude::*;
use freya_engine::prelude::*;
use freya_native_core::prelude::NodeImmutableDioxusExt;
use futures_task::Waker;
use futures_util::Future;
use pin_utils::pin_mut;
use tokio::{
select,
sync::{
broadcast,
mpsc,
watch,
},
};
use torin::geometry::Area;
use winit::{
dpi::PhysicalSize,
event_loop::EventLoopProxy,
window::Window,
};
use crate::{
accessibility::AccessKitManager,
devtools::Devtools,
size::WinitSize,
winit_waker::winit_waker,
EmbeddedFonts,
HoveredNode,
};
pub struct Application {
pub(crate) sdom: SafeDOM,
pub(crate) vdom: VirtualDom,
pub(crate) compositor: Compositor,
pub(crate) events: EventsQueue,
pub(crate) vdom_waker: Waker,
pub(crate) proxy: EventLoopProxy<EventMessage>,
pub(crate) devtools: Option<Devtools>,
pub(crate) event_emitter: EventEmitter,
pub(crate) event_receiver: EventReceiver,
pub(crate) nodes_state: NodesState,
pub(crate) platform_sender: NativePlatformSender,
pub(crate) platform_receiver: NativePlatformReceiver,
pub(crate) accessibility: AccessKitManager,
pub(crate) font_collection: FontCollection,
pub(crate) font_mgr: FontMgr,
pub(crate) ticker_sender: broadcast::Sender<()>,
pub(crate) plugins: PluginsManager,
pub(crate) measure_layout_on_next_render: bool,
pub(crate) init_accessibility_on_next_render: bool,
pub(crate) default_fonts: Vec<String>,
}
impl Application {
#[allow(clippy::too_many_arguments)]
pub fn new(
sdom: SafeDOM,
vdom: VirtualDom,
proxy: &EventLoopProxy<EventMessage>,
devtools: Option<Devtools>,
window: &Window,
fonts_config: EmbeddedFonts,
plugins: PluginsManager,
default_fonts: Vec<String>,
) -> Self {
let accessibility = AccessKitManager::new(window, proxy.clone());
let mut font_collection = FontCollection::new();
let def_mgr = FontMgr::default();
let mut provider = TypefaceFontProvider::new();
for (font_name, font_data) in fonts_config {
let ft_type = def_mgr.new_from_data(font_data, None).unwrap();
provider.register_typeface(ft_type, Some(font_name));
}
let font_mgr: FontMgr = provider.into();
font_collection.set_default_font_manager(def_mgr, None);
font_collection.set_dynamic_font_manager(font_mgr.clone());
let (event_emitter, event_receiver) = mpsc::unbounded_channel();
let (platform_sender, platform_receiver) = watch::channel(NativePlatformState {
focused_id: ACCESSIBILITY_ROOT_ID,
preferred_theme: window.theme().map(|theme| theme.into()).unwrap_or_default(),
navigation_mode: NavigationMode::default(),
information: PlatformInformation::from_winit(window),
scale_factor: window.scale_factor(),
});
let mut app = Self {
sdom,
vdom,
events: EventsQueue::new(),
vdom_waker: winit_waker(proxy),
proxy: proxy.clone(),
devtools,
event_emitter,
event_receiver,
nodes_state: NodesState::default(),
accessibility,
platform_sender,
platform_receiver,
font_collection,
font_mgr,
ticker_sender: broadcast::channel(5).0,
plugins,
measure_layout_on_next_render: false,
init_accessibility_on_next_render: false,
default_fonts,
compositor: Compositor::default(),
};
app.plugins.send(
PluginEvent::WindowCreated(window),
PluginHandle::new(&app.proxy),
);
app
}
pub fn provide_vdom_contexts<State: 'static>(&mut self, app_state: Option<State>) {
if let Some(state) = app_state {
self.vdom.insert_any_root_context(Box::new(state));
}
self.vdom
.insert_any_root_context(Box::new(self.proxy.clone()));
self.vdom
.insert_any_root_context(Box::new(self.platform_receiver.clone()));
self.vdom
.insert_any_root_context(Box::new(Arc::new(self.ticker_sender.subscribe())));
self.vdom
.insert_any_root_context(Box::new(self.sdom.get().accessibility_generator().clone()));
}
pub fn init_doms<State: 'static>(&mut self, scale_factor: f32, app_state: Option<State>) {
self.plugins.send(
PluginEvent::StartedUpdatingDOM,
PluginHandle::new(&self.proxy),
);
self.provide_vdom_contexts(app_state);
self.sdom.get_mut().init_dom(&mut self.vdom, scale_factor);
self.plugins.send(
PluginEvent::FinishedUpdatingDOM,
PluginHandle::new(&self.proxy),
);
}
pub fn render_mutations(&mut self, scale_factor: f32) -> (bool, bool) {
self.plugins.send(
PluginEvent::StartedUpdatingDOM,
PluginHandle::new(&self.proxy),
);
let (repaint, relayout) = self
.sdom
.get_mut()
.render_mutations(&mut self.vdom, scale_factor);
self.plugins.send(
PluginEvent::FinishedUpdatingDOM,
PluginHandle::new(&self.proxy),
);
if repaint {
if let Some(devtools) = &self.devtools {
devtools.update(&self.sdom.get());
}
}
(repaint, relayout)
}
pub fn poll_vdom(&mut self, window: &Window) {
let mut cx = std::task::Context::from_waker(&self.vdom_waker);
{
let fut = async {
select! {
Some(events) = self.event_receiver.recv() => {
let fdom = self.sdom.get();
let rdom = fdom.rdom();
for event in events {
if let Some(element_id) = rdom
.get(event.node_id)
.and_then(|node| node.mounted_id())
{
let name = event.name.into();
let data = event.data.any();
self.vdom
.handle_event(name, data, element_id, event.bubbles);
self.vdom.process_events();
}
}
},
_ = self.vdom.wait_for_work() => {},
}
};
pin_mut!(fut);
match fut.poll(&mut cx) {
std::task::Poll::Ready(_) => {
self.proxy.send_event(EventMessage::PollVDOM).ok();
}
std::task::Poll::Pending => return,
}
}
let (must_repaint, must_relayout) = self.render_mutations(window.scale_factor() as f32);
if must_relayout {
self.measure_layout_on_next_render = true;
}
if must_relayout || must_repaint {
window.request_redraw();
}
}
pub fn process_events(&mut self, scale_factor: f64) {
let focus_id = self.accessibility.focused_node_id();
process_events(
&self.sdom.get(),
&mut self.events,
&self.event_emitter,
&mut self.nodes_state,
scale_factor,
focus_id,
)
}
pub fn init_accessibility(&mut self) {
{
let fdom = self.sdom.get();
let rdom = fdom.rdom();
let layout = fdom.layout();
let mut dirty_accessibility_tree = fdom.accessibility_dirty_nodes();
self.accessibility
.init_accessibility(rdom, &layout, &mut dirty_accessibility_tree);
}
}
pub fn process_accessibility(&mut self, window: &Window) {
let fdom = self.sdom.get();
let rdom = fdom.rdom();
let layout = fdom.layout();
let mut dirty_accessibility_tree = fdom.accessibility_dirty_nodes();
self.accessibility.process_updates(
rdom,
&layout,
&self.platform_sender,
window,
&mut dirty_accessibility_tree,
);
}
pub fn send_event(&mut self, event: PlatformEvent, scale_factor: f64) {
self.events.push(event);
self.process_events(scale_factor);
}
pub fn vdom_replace_template(&mut self, template: Template) {
self.vdom.replace_template(template);
}
pub fn render(
&mut self,
hovered_node: &HoveredNode,
background: Color,
surface: &mut Surface,
dirty_surface: &mut Surface,
window: &Window,
scale_factor: f64,
) {
self.plugins.send(
PluginEvent::BeforeRender {
canvas: surface.canvas(),
font_collection: &self.font_collection,
freya_dom: &self.sdom.get(),
},
PluginHandle::new(&self.proxy),
);
self.start_render(
hovered_node,
background,
surface,
dirty_surface,
window.inner_size(),
scale_factor as f32,
);
self.plugins.send(
PluginEvent::AfterRender {
canvas: surface.canvas(),
font_collection: &self.font_collection,
freya_dom: &self.sdom.get(),
},
PluginHandle::new(&self.proxy),
);
}
pub fn resize(&mut self, window: &Window) {
self.measure_layout_on_next_render = true;
self.init_accessibility_on_next_render = true;
self.compositor.reset();
self.sdom
.get()
.compositor_dirty_area()
.unite_or_insert(&Area::new(
(0.0, 0.0).into(),
window.inner_size().to_torin(),
));
self.sdom.get().layout().reset();
self.platform_sender.send_modify(|state| {
state.information = PlatformInformation::from_winit(window);
})
}
pub fn measure_text_group(&self, text_measurement: TextGroupMeasurement, scale_factor: f64) {
self.sdom
.get()
.measure_paragraphs(text_measurement, scale_factor);
}
pub fn focus_node(&mut self, node_id: AccessibilityId, window: &Window) {
let fdom = self.sdom.get();
let layout = fdom.layout();
self.accessibility
.focus_node(node_id, &self.platform_sender, window, &layout)
}
pub fn focus_next_node(&mut self, direction: AccessibilityFocusStrategy, window: &Window) {
let fdom = self.sdom.get();
let rdom = fdom.rdom();
let layout = fdom.layout();
self.accessibility
.focus_next_node(rdom, direction, &self.platform_sender, window, &layout);
}
pub fn event_loop_tick(&self) {
self.ticker_sender.send(()).ok();
}
pub fn set_navigation_mode(&mut self, navigation_mode: NavigationMode) {
self.platform_sender.send_modify(|state| {
state.navigation_mode = navigation_mode;
})
}
pub fn process_layout(&mut self, window_size: PhysicalSize<u32>, scale_factor: f64) {
{
let fdom = self.sdom.get();
self.plugins.send(
PluginEvent::StartedLayout(&fdom.layout()),
PluginHandle::new(&self.proxy),
);
process_layout(
&fdom,
Area::from_size(window_size.to_torin()),
&mut self.font_collection,
scale_factor as f32,
&self.default_fonts,
);
self.plugins.send(
PluginEvent::FinishedLayout(&fdom.layout()),
PluginHandle::new(&self.proxy),
);
}
if let Some(devtools) = &self.devtools {
devtools.update(&self.sdom.get())
}
#[cfg(debug_assertions)]
{
let fdom = self.sdom.get();
tracing::info!(
"Processed {} layers and {} group of paragraph elements",
fdom.layers().len(),
fdom.paragraphs().len()
);
}
}
pub fn start_render(
&mut self,
hovered_node: &HoveredNode,
background: Color,
surface: &mut Surface,
dirty_surface: &mut Surface,
window_size: PhysicalSize<u32>,
scale_factor: f32,
) {
let fdom = self.sdom.get();
let hovered_node = hovered_node
.as_ref()
.and_then(|hovered_node| *hovered_node.lock().unwrap());
let mut render_pipeline = RenderPipeline {
canvas_area: Area::from_size(window_size.to_torin()),
rdom: fdom.rdom(),
compositor_dirty_area: &mut fdom.compositor_dirty_area(),
compositor_dirty_nodes: &mut fdom.compositor_dirty_nodes(),
compositor_cache: &mut fdom.compositor_cache(),
layers: &mut fdom.layers(),
layout: &mut fdom.layout(),
background,
surface,
dirty_surface,
compositor: &mut self.compositor,
scale_factor,
selected_node: hovered_node,
font_collection: &mut self.font_collection,
font_manager: &self.font_mgr,
default_fonts: &self.default_fonts,
};
render_pipeline.run();
}
}