use std::{
num::NonZeroU32,
path::PathBuf,
};
use dioxus_core::VirtualDom;
use freya_core::{
accessibility::AccessibilityFocusDirection,
dom::SafeDOM,
events::{
EventName,
PlatformEvent,
},
prelude::{
EventMessage,
NavigationMode,
},
};
use freya_elements::events::{
map_winit_key,
map_winit_modifiers,
map_winit_physical_key,
Code,
Key,
};
use glutin::prelude::{
GlSurface,
PossiblyCurrentGlContext,
};
use torin::geometry::CursorPoint;
use winit::{
application::ApplicationHandler,
event::{
ElementState,
Ime,
KeyEvent,
MouseButton,
MouseScrollDelta,
StartCause,
Touch,
TouchPhase,
WindowEvent,
},
event_loop::{
EventLoop,
EventLoopProxy,
},
keyboard::ModifiersState,
};
use crate::{
devtools::Devtools,
window_state::{
create_surface,
CreatedState,
NotCreatedState,
WindowState,
},
HoveredNode,
LaunchConfig,
};
const WHEEL_SPEED_MODIFIER: f32 = 53.0;
pub struct DesktopRenderer<'a, State: Clone + 'static> {
pub(crate) event_loop_proxy: EventLoopProxy<EventMessage>,
pub(crate) state: WindowState<'a, State>,
pub(crate) hovered_node: HoveredNode,
pub(crate) cursor_pos: CursorPoint,
pub(crate) mouse_state: ElementState,
pub(crate) modifiers_state: ModifiersState,
pub(crate) dropped_file_path: Option<PathBuf>,
}
impl<'a, State: Clone + 'static> DesktopRenderer<'a, State> {
pub fn launch(
vdom: VirtualDom,
sdom: SafeDOM,
config: LaunchConfig<State>,
devtools: Option<Devtools>,
hovered_node: HoveredNode,
) {
let event_loop = EventLoop::<EventMessage>::with_user_event()
.build()
.expect("Failed to create event loop.");
let proxy = event_loop.create_proxy();
#[cfg(feature = "hot-reload")]
{
use std::process::exit;
let proxy = proxy.clone();
dioxus_hot_reload::connect(move |msg| match msg {
dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => {
let _ = proxy.send_event(EventMessage::UpdateTemplate(template));
}
dioxus_hot_reload::HotReloadMsg::Shutdown => exit(0),
dioxus_hot_reload::HotReloadMsg::UpdateAsset(_) => {}
});
}
let mut desktop_renderer =
DesktopRenderer::new(vdom, sdom, config, devtools, hovered_node, proxy);
event_loop.run_app(&mut desktop_renderer).unwrap();
}
pub fn new(
vdom: VirtualDom,
sdom: SafeDOM,
config: LaunchConfig<'a, State>,
devtools: Option<Devtools>,
hovered_node: HoveredNode,
proxy: EventLoopProxy<EventMessage>,
) -> Self {
DesktopRenderer {
state: WindowState::NotCreated(NotCreatedState {
sdom,
devtools,
vdom,
config,
}),
hovered_node,
event_loop_proxy: proxy,
cursor_pos: CursorPoint::default(),
mouse_state: ElementState::Released,
modifiers_state: ModifiersState::default(),
dropped_file_path: None,
}
}
fn send_event(&mut self, event: PlatformEvent) {
let scale_factor = self.scale_factor();
self.state
.created_state()
.app
.send_event(event, scale_factor);
}
fn scale_factor(&self) -> f64 {
match &self.state {
WindowState::Created(CreatedState { window, .. }) => window.scale_factor(),
_ => 0.0,
}
}
pub fn run_on_setup(&mut self) {
let state = self.state.created_state();
if let Some(on_setup) = &state.window_config.on_setup {
(on_setup)(&mut state.window)
}
}
pub fn run_on_exit(&mut self) {
let state = self.state.created_state();
if let Some(on_exit) = &state.window_config.on_exit {
(on_exit)(&mut state.window)
}
}
}
impl<'a, State: Clone> ApplicationHandler<EventMessage> for DesktopRenderer<'a, State> {
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
if !self.state.has_been_created() {
self.state.create(event_loop, &self.event_loop_proxy);
self.run_on_setup();
}
}
fn new_events(
&mut self,
_event_loop: &winit::event_loop::ActiveEventLoop,
cause: winit::event::StartCause,
) {
if cause == StartCause::Init {
self.event_loop_proxy
.send_event(EventMessage::PollVDOM)
.ok();
}
}
fn user_event(&mut self, event_loop: &winit::event_loop::ActiveEventLoop, event: EventMessage) {
let scale_factor = self.scale_factor();
let CreatedState { window, app, .. } = self.state.created_state();
match event {
EventMessage::FocusAccessibilityNode(id) => {
app.focus_node(id, window);
}
EventMessage::RequestRerender => {
window.request_redraw();
}
EventMessage::RemeasureTextGroup(text_id) => {
app.measure_text_group(text_id, scale_factor);
}
EventMessage::Accessibility(accesskit_winit::WindowEvent::ActionRequested(request)) => {
if accesskit::Action::Focus == request.action {
app.focus_node(request.target, window);
}
}
EventMessage::Accessibility(accesskit_winit::WindowEvent::InitialTreeRequested) => {
app.accessibility.process_initial_tree();
}
EventMessage::SetCursorIcon(icon) => window.set_cursor(icon),
EventMessage::FocusPrevAccessibilityNode => {
app.set_navigation_mode(NavigationMode::Keyboard);
app.focus_next_node(AccessibilityFocusDirection::Backward, window);
}
EventMessage::FocusNextAccessibilityNode => {
app.set_navigation_mode(NavigationMode::Keyboard);
app.focus_next_node(AccessibilityFocusDirection::Forward, window);
}
EventMessage::WithWindow(use_window) => (use_window)(window),
EventMessage::QueueFocusAccessibilityNode(node_id) => {
app.queue_focus_node(node_id);
}
EventMessage::ExitApp => event_loop.exit(),
EventMessage::PlatformEvent(platform_event) => self.send_event(platform_event),
ev => {
if let EventMessage::UpdateTemplate(template) = ev {
app.vdom_replace_template(template);
}
if matches!(ev, EventMessage::PollVDOM)
|| matches!(ev, EventMessage::UpdateTemplate(_))
{
app.poll_vdom(window);
}
}
}
}
fn window_event(
&mut self,
event_loop: &winit::event_loop::ActiveEventLoop,
_window_id: winit::window::WindowId,
event: winit::event::WindowEvent,
) {
let scale_factor = self.scale_factor();
let CreatedState {
gr_context,
surface,
gl_surface,
gl_context,
window,
app,
window_config,
fb_info,
num_samples,
stencil_size,
is_window_focused,
..
} = self.state.created_state();
app.accessibility
.process_accessibility_event(&event, window);
match event {
WindowEvent::ThemeChanged(theme) => {
app.platform_sender.send_modify(|state| {
state.preferred_theme = theme.into();
});
}
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::Ime(Ime::Commit(text)) => {
self.send_event(PlatformEvent::Keyboard {
name: EventName::KeyDown,
key: Key::Character(text),
code: Code::Unidentified,
modifiers: map_winit_modifiers(self.modifiers_state),
});
}
WindowEvent::RedrawRequested => {
app.platform_sender.send_if_modified(|state| {
let scale_factor_is_different = state.scale_factor == scale_factor;
state.scale_factor = scale_factor;
scale_factor_is_different
});
if app.measure_layout_on_next_render {
app.process_layout(window.inner_size(), scale_factor);
app.process_accessibility(window);
app.measure_layout_on_next_render = false;
}
surface.canvas().clear(window_config.background);
app.render(&self.hovered_node, surface.canvas(), window);
app.event_loop_tick();
window.pre_present_notify();
gr_context.flush_and_submit();
gl_surface.swap_buffers(gl_context).unwrap();
}
WindowEvent::MouseInput { state, button, .. } => {
app.set_navigation_mode(NavigationMode::NotKeyboard);
self.mouse_state = state;
let name = match state {
ElementState::Pressed => EventName::MouseDown,
ElementState::Released => match button {
MouseButton::Middle => EventName::MiddleClick,
MouseButton::Right => EventName::RightClick,
MouseButton::Left => EventName::Click,
_ => EventName::PointerUp,
},
};
self.send_event(PlatformEvent::Mouse {
name,
cursor: self.cursor_pos,
button: Some(button),
});
}
WindowEvent::MouseWheel { delta, phase, .. } => {
if TouchPhase::Moved == phase {
let scroll_data = {
match delta {
MouseScrollDelta::LineDelta(x, y) => (
(x * WHEEL_SPEED_MODIFIER) as f64,
(y * WHEEL_SPEED_MODIFIER) as f64,
),
MouseScrollDelta::PixelDelta(pos) => (pos.x, pos.y),
}
};
self.send_event(PlatformEvent::Wheel {
name: EventName::Wheel,
scroll: CursorPoint::from(scroll_data),
cursor: self.cursor_pos,
});
}
}
WindowEvent::ModifiersChanged(modifiers) => {
self.modifiers_state = modifiers.state();
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
physical_key,
logical_key,
state,
..
},
..
} => {
if !*is_window_focused {
return;
}
let name = match state {
ElementState::Pressed => EventName::KeyDown,
ElementState::Released => EventName::KeyUp,
};
self.send_event(PlatformEvent::Keyboard {
name,
key: map_winit_key(&logical_key),
code: map_winit_physical_key(&physical_key),
modifiers: map_winit_modifiers(self.modifiers_state),
})
}
WindowEvent::CursorLeft { .. } => {
if self.mouse_state == ElementState::Released {
self.cursor_pos = CursorPoint::new(-1.0, -1.0);
self.send_event(PlatformEvent::Mouse {
name: EventName::MouseOver,
cursor: self.cursor_pos,
button: None,
});
}
}
WindowEvent::CursorMoved { position, .. } => {
self.cursor_pos = CursorPoint::from((position.x, position.y));
self.send_event(PlatformEvent::Mouse {
name: EventName::MouseOver,
cursor: self.cursor_pos,
button: None,
});
if let Some(dropped_file_path) = self.dropped_file_path.take() {
self.send_event(PlatformEvent::File {
name: EventName::FileDrop,
file_path: Some(dropped_file_path),
cursor: self.cursor_pos,
});
}
}
WindowEvent::Touch(Touch {
location,
phase,
id,
force,
..
}) => {
self.cursor_pos = CursorPoint::from((location.x, location.y));
let name = match phase {
TouchPhase::Cancelled => EventName::TouchCancel,
TouchPhase::Ended => EventName::TouchEnd,
TouchPhase::Moved => EventName::TouchMove,
TouchPhase::Started => EventName::TouchStart,
};
self.send_event(PlatformEvent::Touch {
name,
location: self.cursor_pos,
finger_id: id,
phase,
force,
});
}
WindowEvent::Resized(size) => {
*surface =
create_surface(window, *fb_info, gr_context, *num_samples, *stencil_size);
gl_surface.resize(
gl_context,
NonZeroU32::new(size.width.max(1)).unwrap(),
NonZeroU32::new(size.height.max(1)).unwrap(),
);
window.request_redraw();
app.resize(window);
}
WindowEvent::DroppedFile(file_path) => {
self.dropped_file_path = Some(file_path);
}
WindowEvent::HoveredFile(file_path) => {
self.send_event(PlatformEvent::File {
name: EventName::GlobalFileHover,
file_path: Some(file_path),
cursor: self.cursor_pos,
});
}
WindowEvent::HoveredFileCancelled => {
self.send_event(PlatformEvent::File {
name: EventName::GlobalFileHoverCancelled,
file_path: None,
cursor: self.cursor_pos,
});
}
WindowEvent::Focused(is_focused) => {
*is_window_focused = is_focused;
}
_ => {}
}
}
fn exiting(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
self.run_on_exit();
}
}
impl<T: Clone> Drop for DesktopRenderer<'_, T> {
fn drop(&mut self) {
if let WindowState::Created(CreatedState {
gl_context,
gl_surface,
gr_context,
..
}) = &mut self.state
{
if !gl_context.is_current() && gl_context.make_current(gl_surface).is_err() {
gr_context.abandon();
}
}
}
}