use crate::app::memory::release_freed_pages;
use crate::app::snapshot::SnapshotRequest;
use crate::app::surface::{Surface, SurfaceOptions};
use crate::app::wake::Wake;
use crate::view::window::Window as UiWindow;
use std::time::{Duration, Instant};
use winit::application::ApplicationHandler;
use winit::event::{StartCause, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop, EventLoopProxy};
use winit::window::WindowId;
const RELIEF_DELAY: Duration = Duration::from_secs(3);
const SNAPSHOT_WARMUP: u32 = 2;
type DropHandler = Box<dyn FnMut(&mut UiWindow, std::path::PathBuf)>;
pub struct AppOptions {
pub window: SurfaceOptions,
on_drop: Option<DropHandler>,
#[cfg(target_os = "android")]
android_app: Option<AndroidApp>,
}
#[cfg(target_os = "android")]
pub use winit::platform::android::activity::AndroidApp;
impl AppOptions {
pub fn new(title: impl Into<String>) -> Self {
Self {
window: SurfaceOptions { title: title.into(), ..SurfaceOptions::default() },
on_drop: None,
#[cfg(target_os = "android")]
android_app: None,
}
}
#[cfg(target_os = "android")]
pub fn android_app(mut self, app: AndroidApp) -> Self {
self.android_app = Some(app);
self
}
pub fn size(mut self, width: f64, height: f64) -> Self {
self.window.size = (width, height);
self
}
pub fn theme(mut self, theme: crate::view::theme::Theme) -> Self {
self.window.theme = theme;
self
}
pub fn on_file_drop(mut self, handler: impl FnMut(&mut UiWindow, std::path::PathBuf) + 'static) -> Self {
self.on_drop = Some(Box::new(handler));
self
}
pub fn run_ui<F, R>(self, build: F) -> Result<(), Box<dyn std::error::Error>>
where
F: FnMut(&mut crate::view::ui::Ui) -> R + 'static,
R: crate::view::element::IntoElement + 'static,
{
run(self, move |window| {
window.set_root(crate::app::closure::FnApp::new(build));
})
}
}
type Build = Box<dyn FnOnce(&mut UiWindow)>;
struct Runner {
proxy: EventLoopProxy<Wake>,
options: Option<AppOptions>,
build: Option<Build>,
on_drop: Option<DropHandler>,
surface: Option<Surface>,
parked: Option<UiWindow>,
snapshot: Option<SnapshotRequest>,
last_relief: Instant,
relief_due: bool,
}
pub fn run(
#[allow(unused_mut)] mut options: AppOptions,
build: impl FnOnce(&mut UiWindow) + 'static,
) -> Result<(), Box<dyn std::error::Error>> {
#[allow(unused_mut)]
let mut builder = EventLoop::<Wake>::with_user_event();
#[cfg(target_os = "android")]
if let Some(app) = options.android_app.take() {
use winit::platform::android::EventLoopBuilderExtAndroid;
builder.with_android_app(app);
}
#[cfg(target_os = "ios")]
crate::app::ios_scene::register();
let event_loop = builder.build()?;
let mut runner = Runner {
proxy: event_loop.create_proxy(),
options: Some(options),
build: Some(Box::new(build)),
on_drop: None,
surface: None,
parked: None,
snapshot: SnapshotRequest::from_environment(),
last_relief: Instant::now(),
relief_due: false,
};
event_loop.run_app(&mut runner)?;
Ok(())
}
impl ApplicationHandler<Wake> for Runner {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
event_loop.set_control_flow(ControlFlow::Wait);
if let Some(ui) = self.parked.take() {
match Surface::reopen(event_loop, &self.proxy, ui) {
Ok(surface) => {
surface.request_redraw();
self.surface = Some(surface);
}
Err(error) => {
crate::app::console::warn(format_args!("no se pudo reabrir la ventana: {error}"));
event_loop.exit();
}
}
return;
}
if let Some(surface) = self.surface.as_mut() {
if surface.handle(WindowEvent::Occluded(false)) {
surface.request_redraw();
}
return;
}
let (Some(mut options), Some(build)) = (self.options.take(), self.build.take()) else { return };
self.on_drop = options.on_drop.take();
options.window.system_clipboard &= self.snapshot.is_none();
match Surface::create(event_loop, &self.proxy, options.window) {
Ok(mut surface) => {
build(&mut surface.ui);
surface.request_redraw();
self.surface = Some(surface);
}
Err(error) => {
crate::app::console::warn(format_args!("no se pudo abrir la ventana: {error}"));
event_loop.exit();
}
}
}
fn suspended(&mut self, _: &ActiveEventLoop) {
if cfg!(target_os = "android") {
self.parked = self.surface.take().map(Surface::into_ui);
} else if let Some(surface) = self.surface.as_mut() {
surface.handle(WindowEvent::Occluded(true));
}
release_freed_pages();
}
fn memory_warning(&mut self, _: &ActiveEventLoop) {
if let Some(surface) = self.surface.as_mut() {
surface.ui.shrink_caches();
surface.renderer.shrink_caches();
surface.request_redraw();
}
release_freed_pages();
}
fn new_events(&mut self, _: &ActiveEventLoop, cause: StartCause) {
let (StartCause::ResumeTimeReached { .. }, Some(surface)) = (cause, self.surface.as_mut()) else { return };
let long_press = surface.touch.tick(Instant::now());
if surface.dispatch(&long_press) | surface.ui.run_pending() {
surface.request_redraw();
}
}
fn user_event(&mut self, _: &ActiveEventLoop, event: Wake) {
let Some(surface) = self.surface.as_mut() else { return };
self.relief_due = true;
match event {
Wake::Access(event) => surface.access_event(event.window_event),
Wake::Inbox(_) => {
if surface.ui.run_pending() {
surface.request_redraw();
}
}
}
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
let Some(surface) = self.surface.as_mut() else { return };
if surface.wants_frame() {
surface.request_redraw();
}
if self.relief_due && self.last_relief.elapsed() >= RELIEF_DELAY {
self.last_relief = Instant::now();
self.relief_due = false;
release_freed_pages();
}
let relief = self.relief_due.then(|| self.last_relief + RELIEF_DELAY);
let mut deadline =
[surface.ui.next_deadline(), surface.touch.next_deadline(), relief].into_iter().flatten().min();
if let Some(request) = &self.snapshot {
deadline = deadline.into_iter().chain(Some(request.deadline())).min();
if request.ready() {
surface.request_redraw();
}
}
event_loop.set_control_flow(deadline.map_or(ControlFlow::Wait, ControlFlow::WaitUntil));
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) {
let Some(surface) = self.surface.as_mut() else { return };
if !matches!(event, WindowEvent::RedrawRequested) || surface.frames_drawn <= 1 {
self.relief_due = true;
}
surface.access.process_event(&surface.window, &event);
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => {
if !surface.visibility.is_drawable() {
return;
}
if let Err(error) = surface.redraw() {
crate::app::console::warn(format_args!("{error}"));
}
let Some(request) = &self.snapshot else { return };
if surface.frames_drawn == SNAPSHOT_WARMUP {
for keystroke in &request.keystrokes {
surface.press(keystroke);
surface.ui.run_pending();
}
}
if surface.frames_drawn <= SNAPSHOT_WARMUP + 1 {
surface.request_redraw();
} else if request.ready() {
surface.save_snapshot(request);
event_loop.exit();
}
}
WindowEvent::DroppedFile(path) => {
if let Some(handler) = self.on_drop.as_mut() {
handler(&mut surface.ui, path);
surface.ui.run_pending();
surface.request_redraw();
}
}
other => {
if surface.handle(other) {
surface.request_redraw();
}
}
}
}
}