#![allow(clippy::unneeded_field_pattern)] mod audio; mod custom_event; mod executor; mod input; mod navigator; mod storage; mod task; use crate::custom_event::RuffleEvent; use crate::executor::GlutinAsyncExecutor; use ruffle_core::{ backend::audio::{AudioBackend, NullAudioBackend}, Player, }; use ruffle_render_wgpu::WgpuRenderBackend; use std::path::PathBuf; use std::sync::Arc; use std::time::Instant; use structopt::StructOpt; use crate::storage::DiskStorageBackend; use ruffle_core::tag_utils::SwfMovie; use std::rc::Rc; use winit::dpi::{LogicalSize, PhysicalPosition}; use winit::event::{ElementState, MouseButton, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::{Icon, WindowBuilder}; #[derive(StructOpt, Debug)] #[structopt(name = "basic")] struct Opt { #[structopt(name = "FILE", parse(from_os_str))] input_path: PathBuf, } fn main() { win32_hide_console(); env_logger::init(); let opt = Opt::from_args(); let ret = run_player(opt.input_path); if let Err(e) = ret { eprintln!("Fatal error:\n{}", e); std::process::exit(-1); } } fn run_player(input_path: PathBuf) -> Result<(), Box> { let movie = SwfMovie::from_path(&input_path)?; let movie_size = LogicalSize::new(movie.width(), movie.height()); let icon_bytes = include_bytes!("../assets/favicon-32.rgba"); let icon = Icon::from_rgba(icon_bytes.to_vec(), 32, 32)?; let event_loop: EventLoop = EventLoop::with_user_event(); let window = Rc::new( WindowBuilder::new() .with_title(format!( "Ruffle - {}", input_path.file_name().unwrap_or_default().to_string_lossy() )) .with_window_icon(Some(icon)) .with_inner_size(movie_size) .build(&event_loop)?, ); let viewport_size = movie_size.to_physical(window.scale_factor()); let audio: Box = match audio::CpalAudioBackend::new() { Ok(audio) => Box::new(audio), Err(e) => { log::error!("Unable to create audio device: {}", e); Box::new(NullAudioBackend::new()) } }; let renderer = Box::new(WgpuRenderBackend::for_window( window.as_ref(), (viewport_size.width, viewport_size.height), )?); let (executor, chan) = GlutinAsyncExecutor::new(event_loop.create_proxy()); let navigator = Box::new(navigator::ExternalNavigatorBackend::with_base_path( input_path .parent() .unwrap_or_else(|| std::path::Path::new("")), chan, event_loop.create_proxy(), )); //TODO: actually implement this backend type let input = Box::new(input::WinitInputBackend::new(window.clone())); let storage = Box::new(DiskStorageBackend::new( input_path.file_name().unwrap_or_default().as_ref(), )); let player = Player::new(renderer, audio, navigator, input, storage)?; player.lock().unwrap().set_root_movie(Arc::new(movie)); player.lock().unwrap().set_is_playing(true); // Desktop player will auto-play. player .lock() .unwrap() .set_viewport_dimensions(viewport_size.width, viewport_size.height); let mut mouse_pos = PhysicalPosition::new(0.0, 0.0); let mut time = Instant::now(); let mut next_frame_time = Instant::now(); loop { // Poll UI events event_loop.run(move |event, _window_target, control_flow| { match event { winit::event::Event::LoopDestroyed => { player.lock().unwrap().flush_shared_objects(); return; } // Core loop winit::event::Event::MainEventsCleared => { let new_time = Instant::now(); let dt = new_time.duration_since(time).as_micros(); if dt > 0 { time = new_time; let mut player_lock = player.lock().unwrap(); player_lock.tick(dt as f64 / 1000.0); next_frame_time = new_time + player_lock.time_til_next_frame(); if player_lock.needs_render() { window.request_redraw(); } } } // Render winit::event::Event::RedrawRequested(_) => player.lock().unwrap().render(), winit::event::Event::WindowEvent { event, .. } => match event { WindowEvent::Resized(size) => { let mut player_lock = player.lock().unwrap(); player_lock.set_viewport_dimensions(size.width, size.height); player_lock .renderer_mut() .set_viewport_dimensions(size.width, size.height); window.request_redraw(); } WindowEvent::CursorMoved { position, .. } => { let mut player_lock = player.lock().unwrap(); mouse_pos = position; let event = ruffle_core::PlayerEvent::MouseMove { x: position.x, y: position.y, }; player_lock.handle_event(event); if player_lock.needs_render() { window.request_redraw(); } } WindowEvent::MouseInput { button: MouseButton::Left, state: pressed, .. } => { let mut player_lock = player.lock().unwrap(); let event = if pressed == ElementState::Pressed { ruffle_core::PlayerEvent::MouseDown { x: mouse_pos.x, y: mouse_pos.y, } } else { ruffle_core::PlayerEvent::MouseUp { x: mouse_pos.x, y: mouse_pos.y, } }; player_lock.handle_event(event); if player_lock.needs_render() { window.request_redraw(); } } WindowEvent::CursorLeft { .. } => { let mut player_lock = player.lock().unwrap(); player_lock.handle_event(ruffle_core::PlayerEvent::MouseLeft); if player_lock.needs_render() { window.request_redraw(); } } WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::KeyboardInput { .. } | WindowEvent::ReceivedCharacter(_) => { let mut player_lock = player.lock().unwrap(); if let Some(event) = player_lock .input_mut() .downcast_mut::() .unwrap() .handle_event(event) { player_lock.handle_event(event); if player_lock.needs_render() { window.request_redraw(); } } } _ => (), }, winit::event::Event::UserEvent(RuffleEvent::TaskPoll) => executor .lock() .expect("active executor reference") .poll_all(), _ => (), } // After polling events, sleep the event loop until the next event or the next frame. if *control_flow != ControlFlow::Exit { *control_flow = ControlFlow::WaitUntil(next_frame_time); } }); } } /// Hides the Win32 console if we were not launched from the command line. fn win32_hide_console() { #[cfg(windows)] unsafe { use winapi::um::{wincon::*, winuser::*}; // If we have a console, and we are the exclusive process using that console, // then we were not launched from the command-line; hide the console to act like a GUI app. let hwnd = GetConsoleWindow(); if !hwnd.is_null() { let mut pids = [0; 2]; let num_pids = GetConsoleProcessList(pids.as_mut_ptr(), 2); let is_exclusive = num_pids <= 1; if is_exclusive { ShowWindow(hwnd, SW_HIDE); } } } }