use crate::draw;
use crate::event::{self, Event, Key, LoopEvent, Update};
use crate::frame::{Frame, RawFrame};
use crate::geom;
use crate::state;
use crate::time::DurationF64;
use crate::wgpu;
use crate::window::{self, Window};
use find_folder;
use instant::Instant;
use std::cell::{RefCell, RefMut};
use std::collections::HashMap;
use std::future::Future;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::atomic::{self, AtomicBool};
use std::sync::Arc;
use std::time::Duration;
use std::{self, future};
use wgpu_upstream::InstanceDescriptor;
use winit;
use winit::event_loop::ControlFlow;
pub type ModelFn<Model> = fn(&App) -> Model;
pub type EventFn<Model, Event> = fn(&App, &mut Model, Event);
pub type UpdateFn<Model> = fn(&App, &mut Model, Update);
pub type ViewFn<Model> = fn(&App, &Model, Frame);
pub type SketchViewFn = fn(&App, Frame);
pub type ExitFn<Model> = fn(&App, Model);
enum View<Model = ()> {
WithModel(ViewFn<Model>),
Sketch(SketchViewFn),
}
pub struct Builder<M = (), E = Event> {
model: Box<dyn FnOnce(&App) -> Box<dyn Future<Output = M> + '_>>,
config: Config,
event: Option<EventFn<M, E>>,
update: Option<UpdateFn<M>>,
default_view: Option<View<M>>,
exit: Option<ExitFn<M>>,
create_default_window: bool,
default_window_size: Option<DefaultWindowSize>,
capture_frame_timeout: Option<Option<Duration>>,
max_capture_frame_jobs: Option<u32>,
backends: wgpu::Backends,
}
pub struct SketchBuilder<E = Event> {
builder: Builder<(), E>,
}
enum DefaultWindowSize {
Logical(winit::dpi::LogicalSize<u32>),
Fullscreen,
}
fn default_model(_: &App) -> () {
()
}
pub struct App {
config: RefCell<Config>,
default_window_size: Option<DefaultWindowSize>,
max_capture_frame_jobs: u32,
capture_frame_timeout: Option<Duration>,
pub(crate) event_loop_window_target: Option<EventLoopWindowTarget>,
pub(crate) event_loop_proxy: Proxy,
pub(crate) windows: RefCell<HashMap<window::Id, Window>>,
backends: wgpu::Backends,
instance: wgpu::Instance,
adapters: wgpu::AdapterMap,
draw_state: DrawState,
pub(crate) focused_window: RefCell<Option<window::Id>>,
pub mouse: state::Mouse,
pub keys: state::Keys,
pub duration: state::Time,
pub time: f32,
}
#[derive(Debug)]
struct Config {
loop_mode: LoopMode,
exit_on_escape: bool,
fullscreen_on_shortcut: bool,
}
#[derive(Debug)]
struct DrawState {
draw: RefCell<draw::Draw>,
renderers: RefCell<HashMap<window::Id, RefCell<draw::Renderer>>>,
}
#[derive(Clone)]
pub struct Proxy {
event_loop_proxy: winit::event_loop::EventLoopProxy<()>,
wakeup_queued: Arc<AtomicBool>,
}
struct LoopState {
updates_since_event: u64,
loop_start: Instant,
last_update: Instant,
total_updates: u64,
}
#[derive(Clone, Debug, PartialEq)]
pub enum LoopMode {
RefreshSync,
Rate {
update_interval: Duration,
},
Wait,
NTimes {
number_of_updates: usize,
},
}
impl<M> Builder<M, Event>
where
M: 'static,
{
pub const DEFAULT_BACKENDS: wgpu::Backends = wgpu::DEFAULT_BACKENDS;
pub fn new(model: ModelFn<M>) -> Self {
Self::new_async(move |app| Box::new(future::ready(model(app))))
}
pub fn new_async(
model: impl FnOnce(&App) -> Box<dyn Future<Output = M> + '_> + 'static,
) -> Self {
Builder {
model: Box::new(model),
config: Config::default(),
event: None,
update: None,
default_view: None,
exit: None,
create_default_window: false,
default_window_size: None,
max_capture_frame_jobs: None,
capture_frame_timeout: None,
backends: Self::DEFAULT_BACKENDS,
}
}
#[cfg_attr(rustfmt, rustfmt_skip)]
pub fn event<E>(self, event: EventFn<M, E>) -> Builder<M, E>
where
E: LoopEvent,
{
let Builder {
model,
config,
update,
default_view,
exit,
create_default_window,
default_window_size,
max_capture_frame_jobs,
capture_frame_timeout,
backends,
..
} = self;
Builder {
model,
config,
event: Some(event),
update,
default_view,
exit,
create_default_window,
default_window_size,
max_capture_frame_jobs,
capture_frame_timeout,
backends
}
}
}
impl<M, E> Builder<M, E>
where
M: 'static,
E: LoopEvent,
{
pub const DEFAULT_CAPTURE_FRAME_TIMEOUT: Duration = Duration::from_secs(10);
pub fn view(mut self, view: ViewFn<M>) -> Self {
self.default_view = Some(View::WithModel(view));
self
}
pub fn update(mut self, update: UpdateFn<M>) -> Self {
self.update = Some(update);
self
}
pub fn simple_window(mut self, view: ViewFn<M>) -> Self {
self.default_view = Some(View::WithModel(view));
self.create_default_window = true;
self
}
pub fn exit(mut self, exit: ExitFn<M>) -> Self {
self.exit = Some(exit);
self
}
pub fn size(mut self, width: u32, height: u32) -> Self {
let size = winit::dpi::LogicalSize { width, height };
self.default_window_size = Some(DefaultWindowSize::Logical(size));
self
}
pub fn fullscreen(mut self) -> Self {
self.default_window_size = Some(DefaultWindowSize::Fullscreen);
self
}
pub fn loop_mode(mut self, mode: LoopMode) -> Self {
self.config.loop_mode = mode;
self
}
pub fn max_capture_frame_jobs(mut self, max_jobs: u32) -> Self {
assert!(
max_jobs >= 1,
"must allow for at least one capture frame job at a time"
);
self.max_capture_frame_jobs = Some(max_jobs);
self
}
pub fn capture_frame_timeout(mut self, timeout: Option<std::time::Duration>) -> Self {
self.capture_frame_timeout = Some(timeout);
self
}
pub fn backends(mut self, backends: wgpu::Backends) -> Self {
self.backends = backends;
self
}
pub fn run(self) {
let rt = Self::build_runtime();
rt.block_on(self.run_async())
}
#[cfg(not(target_arch = "wasm32"))]
fn build_runtime() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("failed to create tokio runtime")
}
#[cfg(target_arch = "wasm32")]
fn build_runtime() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("failed to create tokio runtime")
}
pub async fn run_async(self) {
let event_loop = winit::event_loop::EventLoop::new();
let event_loop_proxy = event_loop.create_proxy();
let wakeup_queued = Arc::new(AtomicBool::new(false));
let event_loop_proxy = Proxy {
event_loop_proxy,
wakeup_queued,
};
let max_capture_frame_jobs = self
.max_capture_frame_jobs
.unwrap_or(num_cpus::get() as u32);
let capture_frame_timeout = self
.capture_frame_timeout
.unwrap_or(Some(Self::DEFAULT_CAPTURE_FRAME_TIMEOUT));
let event_loop_window_target = Some(EventLoopWindowTarget::Owned(event_loop));
let app = App::new(
self.config,
event_loop_proxy,
event_loop_window_target,
self.default_window_size,
max_capture_frame_jobs,
capture_frame_timeout,
self.backends,
);
if self.create_default_window {
let window_id = app
.new_window()
.build_async()
.await
.expect("could not build default app window");
*app.focused_window.borrow_mut() = Some(window_id);
}
let model = Pin::from((self.model)(&app)).await;
if app.focused_window.borrow().is_none() {
if let Some(id) = app.windows.borrow().keys().next() {
*app.focused_window.borrow_mut() = Some(id.clone());
}
}
run_loop(
app,
model,
self.event,
self.update,
self.default_view,
self.exit,
);
}
}
impl<E> SketchBuilder<E>
where
E: LoopEvent,
{
pub fn loop_mode(mut self, mode: LoopMode) -> Self {
self.builder = self.builder.loop_mode(mode);
self
}
pub fn size(mut self, width: u32, height: u32) -> Self {
self.builder = self.builder.size(width, height);
self
}
pub fn run(self) {
self.builder.run()
}
}
impl Builder<(), Event> {
pub fn sketch(view: SketchViewFn) -> SketchBuilder<Event> {
let mut builder = Builder::new(default_model);
builder.default_view = Some(View::Sketch(view));
builder.create_default_window = true;
SketchBuilder { builder }
}
}
fn update_interval(fps: f64) -> Duration {
assert!(fps > 0.0);
const NANOSEC_PER_SEC: f64 = 1_000_000_000.0;
let interval_nanosecs = NANOSEC_PER_SEC / fps;
let secs = (interval_nanosecs / NANOSEC_PER_SEC) as u64;
let nanosecs = (interval_nanosecs % NANOSEC_PER_SEC) as u32;
Duration::new(secs, nanosecs)
}
impl LoopMode {
pub const DEFAULT_RATE_FPS: f64 = 60.0;
pub const UPDATES_PER_WAIT_EVENT: u32 = 3;
pub fn refresh_sync() -> Self {
LoopMode::RefreshSync
}
pub fn rate_fps(fps: f64) -> Self {
let update_interval = update_interval(fps);
LoopMode::Rate { update_interval }
}
pub fn wait() -> Self {
LoopMode::Wait
}
pub fn loop_ntimes(number_of_updates: usize) -> Self {
LoopMode::NTimes { number_of_updates }
}
pub fn loop_once() -> Self {
Self::loop_ntimes(1)
}
}
impl Default for LoopMode {
fn default() -> Self {
LoopMode::refresh_sync()
}
}
impl Default for Config {
fn default() -> Self {
let loop_mode = Default::default();
let exit_on_escape = App::DEFAULT_EXIT_ON_ESCAPE;
let fullscreen_on_shortcut = App::DEFAULT_FULLSCREEN_ON_SHORTCUT;
Config {
loop_mode,
exit_on_escape,
fullscreen_on_shortcut,
}
}
}
impl App {
pub const ASSETS_DIRECTORY_NAME: &'static str = "assets";
pub const DEFAULT_EXIT_ON_ESCAPE: bool = true;
pub const DEFAULT_FULLSCREEN_ON_SHORTCUT: bool = true;
fn new(
config: Config,
event_loop_proxy: Proxy,
event_loop_window_target: Option<EventLoopWindowTarget>,
default_window_size: Option<DefaultWindowSize>,
max_capture_frame_jobs: u32,
capture_frame_timeout: Option<Duration>,
backends: wgpu::Backends,
) -> Self {
let instance = wgpu::Instance::new(InstanceDescriptor {
backends,
..Default::default()
});
let adapters = Default::default();
let windows = RefCell::new(HashMap::new());
let draw = RefCell::new(draw::Draw::default());
let config = RefCell::new(config);
let renderers = RefCell::new(Default::default());
let draw_state = DrawState { draw, renderers };
let focused_window = RefCell::new(None);
let mouse = state::Mouse::new();
let keys = state::Keys::default();
let duration = state::Time::default();
let time = duration.since_start.secs() as _;
let app = App {
event_loop_proxy,
event_loop_window_target,
default_window_size,
max_capture_frame_jobs,
capture_frame_timeout,
focused_window,
backends,
instance,
adapters,
windows,
config,
draw_state,
mouse,
keys,
duration,
time,
};
app
}
pub fn available_monitors(&self) -> Vec<winit::monitor::MonitorHandle> {
match self.event_loop_window_target {
Some(EventLoopWindowTarget::Owned(ref event_loop)) => {
event_loop.available_monitors().collect()
}
_ => {
let windows = self.windows.borrow();
match windows.values().next() {
None => vec![],
Some(window) => window.window.available_monitors().collect(),
}
}
}
}
pub fn primary_monitor(&self) -> Option<winit::monitor::MonitorHandle> {
match self.event_loop_window_target {
Some(EventLoopWindowTarget::Owned(ref event_loop)) => event_loop.primary_monitor(),
_ => {
let windows = self.windows.borrow();
match windows.values().next() {
None => unimplemented!(
"yet to implement a way to get `primary_monitor` if neither \
event loop or window can be safely accessed"
),
Some(window) => window.window.primary_monitor(),
}
}
}
}
pub fn assets_path(&self) -> Result<PathBuf, find_folder::Error> {
find_assets_path()
}
pub fn project_path(&self) -> Result<PathBuf, find_folder::Error> {
find_project_path()
}
pub fn new_window(&self) -> window::Builder {
let builder = window::Builder::new(self);
let builder = match self.default_window_size {
Some(DefaultWindowSize::Fullscreen) => builder.fullscreen(),
Some(DefaultWindowSize::Logical(size)) => builder.size(size.width, size.height),
None => builder,
};
builder
.max_capture_frame_jobs(self.max_capture_frame_jobs)
.capture_frame_timeout(self.capture_frame_timeout)
}
pub fn window_count(&self) -> usize {
self.windows.borrow().len()
}
pub fn window(&self, id: window::Id) -> Option<std::cell::Ref<Window>> {
let windows = self.windows.borrow();
if !windows.contains_key(&id) {
None
} else {
Some(std::cell::Ref::map(windows, |ws| &ws[&id]))
}
}
pub fn window_id(&self) -> window::Id {
self.focused_window
.borrow()
.expect("called `App::window_id` but there is no window currently in focus")
}
pub fn window_ids(&self) -> Vec<window::Id> {
let windows = self.windows.borrow();
windows.keys().cloned().collect()
}
pub fn window_rect(&self) -> geom::Rect<f32> {
self.main_window().rect()
}
pub fn main_window(&self) -> std::cell::Ref<Window> {
self.window(self.window_id())
.expect("no window for focused id")
}
pub fn backends(&self) -> wgpu::Backends {
self.backends
}
pub fn instance(&self) -> &wgpu::Instance {
&self.instance
}
pub fn wgpu_adapters(&self) -> &wgpu::AdapterMap {
&self.adapters
}
pub fn exit_on_escape(&self) -> bool {
self.config.borrow().exit_on_escape
}
pub fn set_exit_on_escape(&self, b: bool) {
self.config.borrow_mut().exit_on_escape = b;
}
pub fn fullscreen_on_shortcut(&self) -> bool {
self.config.borrow().fullscreen_on_shortcut
}
pub fn set_fullscreen_on_shortcut(&self, b: bool) {
self.config.borrow_mut().fullscreen_on_shortcut = b;
}
pub fn loop_mode(&self) -> LoopMode {
self.config.borrow().loop_mode.clone()
}
pub fn set_loop_mode(&self, mode: LoopMode) {
self.config.borrow_mut().loop_mode = mode;
}
pub fn create_proxy(&self) -> Proxy {
self.event_loop_proxy.clone()
}
pub fn draw(&self) -> draw::Draw {
let draw = self.draw_state.draw.borrow_mut();
draw.reset();
draw.clone()
}
pub fn elapsed_frames(&self) -> u64 {
self.main_window().frame_count
}
pub fn fps(&self) -> f32 {
self.duration.updates_per_second()
}
pub fn exe_name(&self) -> std::io::Result<String> {
let string = std::env::current_exe()?
.file_stem()
.expect("exe path contained no file stem")
.to_string_lossy()
.to_string();
Ok(string)
}
pub fn quit(&self) {
self.windows.borrow_mut().clear();
}
}
impl Proxy {
pub fn wakeup(&self) -> Result<(), winit::event_loop::EventLoopClosed<()>> {
if !self.wakeup_queued.load(atomic::Ordering::SeqCst) {
self.event_loop_proxy.send_event(())?;
self.wakeup_queued.store(true, atomic::Ordering::SeqCst);
}
Ok(())
}
}
impl draw::Draw {
pub fn to_frame(&self, app: &App, frame: &Frame) -> Result<(), draw::renderer::DrawError> {
let window_id = frame.window_id();
let window = app
.window(window_id)
.expect("no window to draw to for `Draw`'s window_id");
let renderers = app.draw_state.renderers.borrow_mut();
let renderer = RefMut::map(renderers, |renderers| {
renderers.entry(window_id).or_insert_with(|| {
let device = window.device();
let frame_dims: [u32; 2] = window.tracked_state.physical_size.into();
let scale_factor = window.tracked_state.scale_factor as f32;
let msaa_samples = window.msaa_samples();
let target_format = crate::frame::Frame::TEXTURE_FORMAT;
let renderer = draw::RendererBuilder::new().build(
device,
frame_dims,
scale_factor,
msaa_samples,
target_format,
);
RefCell::new(renderer)
})
});
let scale_factor = window.tracked_state.scale_factor as _;
let mut renderer = renderer.borrow_mut();
renderer.render_to_frame(window.device(), self, scale_factor, frame);
Ok(())
}
}
impl<'a> wgpu::WithDeviceQueuePair for &'a crate::app::App {
fn with_device_queue_pair<F, O>(self, f: F) -> O
where
F: FnOnce(&wgpu::Device, &wgpu::Queue) -> O,
{
self.main_window().with_device_queue_pair(f)
}
}
pub fn find_assets_path() -> Result<PathBuf, find_folder::Error> {
let exe_path = std::env::current_exe()?;
find_folder::Search::ParentsThenKids(5, 3)
.of(exe_path
.parent()
.expect("executable has no parent directory to search")
.into())
.for_folder(App::ASSETS_DIRECTORY_NAME)
}
pub fn find_project_path() -> Result<PathBuf, find_folder::Error> {
let exe_path = std::env::current_exe()?;
let mut path = exe_path.parent().expect("exe has no parent directory");
while let Some(parent) = path.parent() {
path = parent;
if path.join("Cargo").with_extension("toml").exists() {
return Ok(path.to_path_buf());
}
}
Err(find_folder::Error::NotFound)
}
pub(crate) enum EventLoopWindowTarget {
Owned(winit::event_loop::EventLoop<()>),
Pointer(*const winit::event_loop::EventLoopWindowTarget<()>),
}
impl EventLoopWindowTarget {
pub(crate) fn as_ref(&self) -> &winit::event_loop::EventLoopWindowTarget<()> {
match *self {
EventLoopWindowTarget::Owned(ref event_loop) => &**event_loop,
EventLoopWindowTarget::Pointer(ptr) => {
unsafe { &*ptr as &winit::event_loop::EventLoopWindowTarget<()> }
}
}
}
}
fn run_loop<M, E>(
mut app: App,
model: M,
event_fn: Option<EventFn<M, E>>,
update_fn: Option<UpdateFn<M>>,
default_view: Option<View<M>>,
exit_fn: Option<ExitFn<M>>,
) where
M: 'static,
E: LoopEvent,
{
let loop_start = Instant::now();
let mut model = Some(model);
let event_loop = match app.event_loop_window_target.take() {
Some(EventLoopWindowTarget::Owned(event_loop)) => event_loop,
_ => unreachable!("the app should always own the event loop at this point"),
};
let mut loop_state = LoopState {
updates_since_event: 0,
loop_start,
last_update: loop_start,
total_updates: 0,
};
event_loop.run(move |mut event, event_loop_window_target, control_flow| {
app.event_loop_window_target = Some(EventLoopWindowTarget::Pointer(
event_loop_window_target as *const _,
));
let mut exit = false;
match event {
winit::event::Event::MainEventsCleared => {
if let Some(model) = model.as_mut() {
let loop_mode = app.loop_mode();
let now = Instant::now();
let mut do_update = |loop_state: &mut LoopState| {
apply_update(&mut app, model, event_fn, update_fn, loop_state, now);
};
match loop_mode {
LoopMode::NTimes { number_of_updates }
if loop_state.total_updates >= number_of_updates as u64 => {}
LoopMode::Wait if loop_state.updates_since_event > 0 => {}
_ => {
do_update(&mut loop_state);
},
}
}
}
winit::event::Event::RedrawRequested(window_id) => {
if let Some(model) = model.as_mut() {
let (mut surface_tex_result, nth_frame) = {
let mut windows = app.windows.borrow_mut();
let window = windows
.get_mut(&window_id)
.expect("no window for `RedrawRequest`");
let texture = window.surface.get_current_texture();
let nth_frame = window.frame_count;
(texture, nth_frame)
};
if let Err(e) = &surface_tex_result {
match e {
wgpu::SurfaceError::Lost => {
let mut windows = app.windows.borrow_mut();
let window = windows
.get_mut(&window_id)
.expect("no window for `RedrawRequest`");
window
.reconfigure_surface(window.tracked_state.physical_size.into());
surface_tex_result = window.surface.get_current_texture();
}
wgpu::SurfaceError::Outdated => {} wgpu::SurfaceError::Timeout => {} wgpu::SurfaceError::OutOfMemory => {
panic!("out of memory acquiring the surface frame: {}", e);
}
}
}
if let Ok(surface_tex) = surface_tex_result {
let surface_texture = &surface_tex
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let windows = app.windows.borrow();
let window = windows
.get(&window_id)
.expect("failed to find window for redraw request");
let frame_data = &window.frame_data;
let sf = window.tracked_state.scale_factor;
let (w, h) = window
.tracked_state
.physical_size
.to_logical::<f32>(sf)
.into();
let window_rect = geom::Rect::from_w_h(w, h);
let raw_frame = RawFrame::new_empty(
window.device_queue_pair().clone(),
window_id,
nth_frame,
surface_texture,
window.surface_conf.format,
window_rect,
);
if window.is_invalidated {
if let Some(data) = frame_data {
raw_frame.clear(&data.render.texture_view(), window.clear_color);
}
}
let window_view = window.user_functions.view.clone();
match window_view {
Some(window::View::Sketch(view)) => {
let data = frame_data.as_ref().expect("missing `frame_data`");
let frame =
Frame::new_empty(raw_frame, &data.render, &data.capture);
view(&app, frame);
}
Some(window::View::WithModel(view)) => {
let data = frame_data.as_ref().expect("missing `frame_data`");
let frame =
Frame::new_empty(raw_frame, &data.render, &data.capture);
let view = view.to_fn_ptr::<M>().expect(
"unexpected model argument given to window view function",
);
(*view)(&app, model, frame);
}
Some(window::View::WithModelRaw(raw_view)) => {
let raw_view = raw_view.to_fn_ptr::<M>().expect(
"unexpected model argument given to window raw_view function",
);
(*raw_view)(&app, &model, raw_frame);
}
None => match default_view {
Some(View::Sketch(view)) => {
let data = frame_data.as_ref().expect("missing `frame_data`");
let frame =
Frame::new_empty(raw_frame, &data.render, &data.capture);
view(&app, frame);
}
Some(View::WithModel(view)) => {
let data = frame_data.as_ref().expect("missing `frame_data`");
let frame =
Frame::new_empty(raw_frame, &data.render, &data.capture);
view(&app, &model, frame);
}
None => raw_frame.submit(),
},
}
surface_tex.present();
drop(windows);
let mut windows = app.windows.borrow_mut();
let window = windows
.get_mut(&window_id)
.expect("no window for redraw request ID");
window.is_invalidated = false;
window.frame_count += 1;
}
}
}
winit::event::Event::RedrawEventsCleared => {
app.wgpu_adapters().clear_inactive_adapters_and_devices();
}
winit::event::Event::WindowEvent { .. }
| winit::event::Event::DeviceEvent { .. }
| winit::event::Event::UserEvent(_)
| winit::event::Event::Suspended
| winit::event::Event::Resumed => {
loop_state.updates_since_event = 0;
if let winit::event::Event::UserEvent(_) = event {
app.event_loop_proxy.wakeup_queued.store(false, atomic::Ordering::SeqCst);
}
}
winit::event::Event::NewEvents(_)
| winit::event::Event::LoopDestroyed => {}
}
if let winit::event::Event::WindowEvent {
ref mut event,
window_id,
} = event
{
match event {
winit::event::WindowEvent::Resized(new_inner_size) => {
let mut windows = app.windows.borrow_mut();
if let Some(window) = windows.get_mut(&window_id) {
window.reconfigure_surface(new_inner_size.clone().into());
}
}
winit::event::WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size,
} => {
let mut windows = app.windows.borrow_mut();
if let Some(window) = windows.get_mut(&window_id) {
window.tracked_state.scale_factor = *scale_factor;
window.reconfigure_surface(new_inner_size.clone().into());
}
}
_ => (),
}
}
if let Some(model) = model.as_mut() {
exit |= process_and_emit_winit_event::<M, E>(&mut app, model, event_fn, &event);
}
let loop_mode = app.loop_mode();
*control_flow = match loop_mode {
LoopMode::Wait => ControlFlow::Wait,
LoopMode::NTimes { number_of_updates }
if loop_state.total_updates >= number_of_updates as u64 =>
{
ControlFlow::Wait
}
_ => ControlFlow::Poll,
};
if exit {
if let Some(model) = model.take() {
if let Some(exit_fn) = exit_fn {
exit_fn(&app, model);
}
}
*control_flow = ControlFlow::Exit;
return;
}
});
#[allow(unreachable_code)]
{
app.event_loop_window_target.take();
}
}
fn apply_update<M, E>(
app: &mut App,
model: &mut M,
event_fn: Option<EventFn<M, E>>,
update_fn: Option<UpdateFn<M>>,
loop_state: &mut LoopState,
now: Instant,
) where
M: 'static,
E: LoopEvent,
{
let since_last = now.duration_since(loop_state.last_update);
let since_start = now.duration_since(loop_state.loop_start);
app.duration.since_prev_update = since_last;
app.duration.since_start = since_start;
app.time = since_start.secs() as _;
let update = crate::event::Update {
since_start,
since_last,
};
if let Some(event_fn) = event_fn {
let event = E::from(update.clone());
event_fn(app, model, event);
}
if let Some(update_fn) = update_fn {
update_fn(app, model, update);
}
loop_state.last_update = now;
loop_state.total_updates += 1;
loop_state.updates_since_event += 1;
let windows = app.windows.borrow();
for window in windows.values() {
window.window.request_redraw();
}
}
fn should_toggle_fullscreen(
winit_event: &winit::event::WindowEvent,
mods: &winit::event::ModifiersState,
) -> bool {
let input = match *winit_event {
winit::event::WindowEvent::KeyboardInput { ref input, .. } => match input.state {
event::ElementState::Pressed => input,
_ => return false,
},
_ => return false,
};
let key = match input.virtual_keycode {
None => return false,
Some(k) => k,
};
if cfg!(target_os = "linux") {
if *mods == winit::event::ModifiersState::empty() {
if let Key::F11 = key {
return true;
}
}
} else if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
if mods.logo() {
if let Key::F = key {
return true;
}
}
}
false
}
fn process_and_emit_winit_event<'a, M, E>(
app: &mut App,
model: &mut M,
event_fn: Option<EventFn<M, E>>,
winit_event: &winit::event::Event<'a, ()>,
) -> bool
where
M: 'static,
E: LoopEvent,
{
let mut exit_on_escape = false;
let mut removed_window = None;
if let winit::event::Event::WindowEvent {
window_id,
ref event,
} = *winit_event
{
if app.exit_on_escape() {
if let winit::event::WindowEvent::KeyboardInput { input, .. } = *event {
if let Some(Key::Escape) = input.virtual_keycode {
exit_on_escape = true;
}
}
}
fn remove_related_window_state(app: &App, window_id: &window::Id) -> Option<Window> {
app.draw_state.renderers.borrow_mut().remove(window_id);
app.windows.borrow_mut().remove(window_id)
}
if let winit::event::WindowEvent::Destroyed = *event {
removed_window = remove_related_window_state(app, &window_id);
} else if let winit::event::WindowEvent::CloseRequested = *event {
removed_window = remove_related_window_state(app, &window_id);
} else {
let (win_w, win_h, scale_factor) = match app.window(window_id) {
Some(win) => {
if app.fullscreen_on_shortcut() {
if should_toggle_fullscreen(event, &app.keys.mods) {
if win.is_fullscreen() {
win.set_fullscreen(false);
} else {
win.set_fullscreen(true);
}
}
}
let sf = win.tracked_state.scale_factor;
let (w, h) = win.tracked_state.physical_size.to_logical::<f32>(sf).into();
(w, h, sf)
}
None => (0.0, 0.0, 1.0),
};
let tx = |x: geom::scalar::Default| x - win_w as geom::scalar::Default / 2.0;
let ty = |y: geom::scalar::Default| -(y - win_h as geom::scalar::Default / 2.0);
if *app.focused_window.borrow() != Some(window_id) {
if app.window(window_id).is_some() {
*app.focused_window.borrow_mut() = Some(window_id);
}
}
match *event {
winit::event::WindowEvent::CursorMoved { position, .. } => {
let (x, y) = position.to_logical::<f32>(scale_factor).into();
let x = tx(x);
let y = ty(y);
app.mouse.x = x;
app.mouse.y = y;
app.mouse.window = Some(window_id);
}
winit::event::WindowEvent::MouseInput { state, button, .. } => {
match state {
event::ElementState::Pressed => {
let p = app.mouse.position();
app.mouse.buttons.press(button, p);
}
event::ElementState::Released => {
app.mouse.buttons.release(button);
}
}
app.mouse.window = Some(window_id);
}
winit::event::WindowEvent::KeyboardInput { input, .. } => {
if let Some(key) = input.virtual_keycode {
match input.state {
event::ElementState::Pressed => {
app.keys.down.keys.insert(key);
}
event::ElementState::Released => {
app.keys.down.keys.remove(&key);
}
}
}
}
_ => (),
}
}
}
if let winit::event::Event::WindowEvent { event, .. } = winit_event {
if let winit::event::WindowEvent::ModifiersChanged(new_mods) = event {
app.keys.mods = new_mods.clone();
}
}
if let Some(event_fn) = event_fn {
if let Some(event) = E::from_winit_event(winit_event, app) {
event_fn(&app, model, event);
}
}
if let winit::event::Event::WindowEvent {
window_id,
ref event,
} = *winit_event
{
if let Some(raw_window_event_fn) = {
let windows = app.windows.borrow();
windows
.get(&window_id)
.and_then(|w| w.user_functions.raw_event.clone())
.or_else(|| {
removed_window
.as_ref()
.and_then(|w| w.user_functions.raw_event.clone())
})
} {
let raw_window_event_fn = raw_window_event_fn
.to_fn_ptr::<M>()
.expect("unexpected model argument given to window event function");
(*raw_window_event_fn)(&app, model, event);
}
let (win_w, win_h, scale_factor) = {
let windows = app.windows.borrow();
windows
.get(&window_id)
.map(|w| {
let sf = w.tracked_state.scale_factor;
let (w, h) = w.tracked_state.physical_size.to_logical::<f64>(sf).into();
(w, h, sf)
})
.unwrap_or((0.0, 0.0, 1.0))
};
if let Some(simple) =
event::WindowEvent::from_winit_window_event(event, win_w, win_h, scale_factor)
{
if let Some(window_event_fn) = {
let windows = app.windows.borrow();
windows
.get(&window_id)
.and_then(|w| w.user_functions.event.clone())
.or_else(|| {
removed_window
.as_ref()
.and_then(|w| w.user_functions.event.clone())
})
} {
let window_event_fn = window_event_fn
.to_fn_ptr::<M>()
.expect("unexpected model argument given to window event function");
(*window_event_fn)(&app, model, simple.clone());
}
macro_rules! call_user_function {
($fn_name:ident $(,$arg:expr)*) => {{
if let Some(event_fn) = {
let windows = app.windows.borrow();
windows
.get(&window_id)
.and_then(|w| w.user_functions.$fn_name.clone())
.or_else(|| {
removed_window
.as_ref()
.and_then(|w| w.user_functions.$fn_name.clone())
})
} {
let event_fn = event_fn
.to_fn_ptr::<M>()
.unwrap_or_else(|| {
panic!(
"unexpected model argument given to {} function",
stringify!($fn_name),
);
});
(*event_fn)(&app, model, $($arg),*);
}
}};
}
match simple {
event::WindowEvent::KeyPressed(key) => call_user_function!(key_pressed, key),
event::WindowEvent::KeyReleased(key) => call_user_function!(key_released, key),
event::WindowEvent::ReceivedCharacter(char) => {
call_user_function!(received_character, char)
}
event::WindowEvent::MouseMoved(pos) => call_user_function!(mouse_moved, pos),
event::WindowEvent::MousePressed(button) => {
call_user_function!(mouse_pressed, button)
}
event::WindowEvent::MouseReleased(button) => {
call_user_function!(mouse_released, button)
}
event::WindowEvent::MouseEntered => call_user_function!(mouse_entered),
event::WindowEvent::MouseExited => call_user_function!(mouse_exited),
event::WindowEvent::MouseWheel(amount, phase) => {
call_user_function!(mouse_wheel, amount, phase)
}
event::WindowEvent::Moved(pos) => call_user_function!(moved, pos),
event::WindowEvent::Resized(size) => call_user_function!(resized, size),
event::WindowEvent::Touch(touch) => call_user_function!(touch, touch),
event::WindowEvent::TouchPressure(pressure) => {
call_user_function!(touchpad_pressure, pressure)
}
event::WindowEvent::HoveredFile(path) => call_user_function!(hovered_file, path),
event::WindowEvent::HoveredFileCancelled => {
call_user_function!(hovered_file_cancelled)
}
event::WindowEvent::DroppedFile(path) => call_user_function!(dropped_file, path),
event::WindowEvent::Focused => call_user_function!(focused),
event::WindowEvent::Unfocused => call_user_function!(unfocused),
event::WindowEvent::Closed => call_user_function!(closed),
}
}
}
let loop_destroyed = match winit_event {
winit::event::Event::LoopDestroyed => true,
_ => false,
};
let exit = if loop_destroyed || exit_on_escape || app.windows.borrow().is_empty() {
true
} else {
false
};
exit
}