//! Create interactive, native cross-platform applications for WGPU. mod state; pub use state::State; use crate::clipboard::{self, Clipboard}; use crate::conversion; use crate::mouse; use crate::renderer; use crate::settings; use crate::widget::operation; use crate::window; use crate::{ Command, Debug, Element, Error, Executor, Proxy, Renderer, Runtime, Settings, Size, Subscription, }; use iced_futures::futures::channel::mpsc; use iced_futures::futures::{self, FutureExt}; use iced_graphics::compositor; use iced_native::user_interface::{self, UserInterface}; pub use iced_native::application::{Appearance, StyleSheet}; use std::collections::HashMap; use std::mem::ManuallyDrop; use std::time::Instant; #[cfg(feature = "trace")] pub use crate::Profiler; #[cfg(feature = "trace")] use tracing::{info_span, instrument::Instrument}; /// This is a wrapper around the `Application::Message` associate type /// to allows the `shell` to create internal messages, while still having /// the current user-specified custom messages. #[derive(Debug)] pub enum Event { /// An [`Application`] generated message Application(Message), /// A message which spawns a new window. NewWindow { /// The [window::Id] of the newly spawned [`Window`]. id: window::Id, /// The [settings::Window] of the newly spawned [`Window`]. settings: settings::Window, /// The title of the newly spawned [`Window`]. title: String, }, /// Close a window. CloseWindow(window::Id), /// A message for when the window has finished being created. WindowCreated(window::Id, winit::window::Window), } /// An interactive, native, cross-platform, multi-windowed application. /// /// This trait is the main entrypoint of multi-window Iced. Once implemented, you can run /// your GUI application by simply calling [`run`]. It will run in /// its own window. /// /// An [`Application`] can execute asynchronous actions by returning a /// [`Command`] in some of its methods. /// /// When using an [`Application`] with the `debug` feature enabled, a debug view /// can be toggled by pressing `F12`. pub trait Application: Sized where ::Theme: StyleSheet, { /// The data needed to initialize your [`Application`]. type Flags; /// The graphics backend to use to draw the [`Program`]. type Renderer: Renderer; /// The type of __messages__ your [`Program`] will produce. type Message: std::fmt::Debug + Send; /// Handles a __message__ and updates the state of the [`Program`]. /// /// This is where you define your __update logic__. All the __messages__, /// produced by either user interactions or commands, will be handled by /// this method. /// /// Any [`Command`] returned will be executed immediately in the /// background by shells. fn update(&mut self, message: Self::Message) -> Command; /// Returns the widgets to display for the `window` in the [`Program`]. /// /// These widgets can produce __messages__ based on user interaction. fn view( &self, window: window::Id, ) -> Element<'_, Self::Message, Self::Renderer>; /// Initializes the [`Application`] with the flags provided to /// [`run`] as part of the [`Settings`]. /// /// Here is where you should return the initial state of your app. /// /// Additionally, you can return a [`Command`] if you need to perform some /// async action in the background on startup. This is useful if you want to /// load state from a file, perform an initial HTTP request, etc. fn new(flags: Self::Flags) -> (Self, Command); /// Returns the current title of each window of the [`Application`]. /// /// This title can be dynamic! The runtime will automatically update the /// title of your application when necessary. fn title(&self, window: window::Id) -> String; /// Returns the current [`Theme`] of the [`Application`]. fn theme(&self, window: window::Id) -> ::Theme; /// Returns the [`Style`] variation of the [`Theme`]. fn style( &self, ) -> <::Theme as StyleSheet>::Style { Default::default() } /// Returns the event `Subscription` for the current state of the /// application. /// /// The messages produced by the `Subscription` will be handled by /// [`update`](#tymethod.update). /// /// A `Subscription` will be kept alive as long as you keep returning it! /// /// By default, it returns an empty subscription. fn subscription(&self) -> Subscription { Subscription::none() } /// Returns the scale factor of the window of the [`Application`]. /// /// It can be used to dynamically control the size of the UI at runtime /// (i.e. zooming). /// /// For instance, a scale factor of `2.0` will make widgets twice as big, /// while a scale factor of `0.5` will shrink them to half their size. /// /// By default, it returns `1.0`. #[allow(unused_variables)] fn scale_factor(&self, window: window::Id) -> f64 { 1.0 } /// Returns whether the [`Application`] should be terminated. /// /// By default, it returns `false`. fn should_exit(&self) -> bool { false } /// Returns the `Self::Message` that should be processed when a `window` is requested to /// be closed. fn close_requested(&self, window: window::Id) -> Self::Message; } /// Runs an [`Application`] with an executor, compositor, and the provided /// settings. pub fn run( settings: Settings, compositor_settings: C::Settings, ) -> Result<(), Error> where A: Application + 'static, E: Executor + 'static, C: iced_graphics::window::Compositor + 'static, ::Theme: StyleSheet, { use futures::task; use futures::Future; use winit::event_loop::EventLoopBuilder; #[cfg(feature = "trace")] let _guard = Profiler::init(); let mut debug = Debug::new(); debug.startup_started(); #[cfg(feature = "trace")] let _ = info_span!("Application", "RUN").entered(); let event_loop = EventLoopBuilder::with_user_event().build(); let proxy = event_loop.create_proxy(); let runtime = { let proxy = Proxy::new(event_loop.create_proxy()); let executor = E::new().map_err(Error::ExecutorCreationFailed)?; Runtime::new(executor, proxy) }; let (application, init_command) = { let flags = settings.flags; runtime.enter(|| A::new(flags)) }; let builder = settings.window.into_builder( &application.title(window::Id::MAIN), event_loop.primary_monitor(), settings.id, ); log::info!("Window builder: {:#?}", builder); let window = builder .build(&event_loop) .map_err(Error::WindowCreationFailed)?; let windows: HashMap = HashMap::from([(window::Id::MAIN, window)]); let window = windows.values().next().expect("No window found"); #[cfg(target_arch = "wasm32")] { use winit::platform::web::WindowExtWebSys; let canvas = window.canvas(); let window = web_sys::window().unwrap(); let document = window.document().unwrap(); let body = document.body().unwrap(); let _ = body .append_child(&canvas) .expect("Append canvas to HTML body"); } let (compositor, renderer) = C::new(compositor_settings, Some(&window))?; let (mut event_sender, event_receiver) = mpsc::unbounded(); let (control_sender, mut control_receiver) = mpsc::unbounded(); let mut instance = Box::pin({ let run_instance = run_instance::( application, compositor, renderer, runtime, proxy, debug, event_receiver, control_sender, init_command, windows, settings.exit_on_close_request, ); #[cfg(feature = "trace")] let run_instance = run_instance.instrument(info_span!("Application", "LOOP")); run_instance }); let mut context = task::Context::from_waker(task::noop_waker_ref()); platform::run(event_loop, move |event, event_loop, control_flow| { use winit::event_loop::ControlFlow; if let ControlFlow::ExitWithCode(_) = control_flow { return; } let event = match event { winit::event::Event::WindowEvent { event: winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. }, window_id, } => Some(winit::event::Event::WindowEvent { event: winit::event::WindowEvent::Resized(*new_inner_size), window_id, }), winit::event::Event::UserEvent(Event::NewWindow { id, settings, title, }) => { let window = settings .into_builder(&title, event_loop.primary_monitor(), None) .build(event_loop) .expect("Failed to build window"); Some(winit::event::Event::UserEvent(Event::WindowCreated( id, window, ))) } _ => event.to_static(), }; if let Some(event) = event { event_sender.start_send(event).expect("Send event"); let poll = instance.as_mut().poll(&mut context); match poll { task::Poll::Pending => { if let Ok(Some(flow)) = control_receiver.try_next() { *control_flow = flow; } } task::Poll::Ready(_) => { *control_flow = ControlFlow::Exit; } }; } }) } async fn run_instance( mut application: A, mut compositor: C, mut renderer: A::Renderer, mut runtime: Runtime>, Event>, mut proxy: winit::event_loop::EventLoopProxy>, mut debug: Debug, mut event_receiver: mpsc::UnboundedReceiver< winit::event::Event<'_, Event>, >, mut control_sender: mpsc::UnboundedSender, init_command: Command, mut windows: HashMap, _exit_on_close_request: bool, ) where A: Application + 'static, E: Executor + 'static, C: iced_graphics::window::Compositor + 'static, ::Theme: StyleSheet, { use iced_futures::futures::stream::StreamExt; use winit::event; use winit::event_loop::ControlFlow; let mut clipboard = Clipboard::connect(windows.values().next().expect("No window found")); let mut caches = HashMap::new(); let mut window_ids: HashMap<_, _> = windows .iter() .map(|(&id, window)| (window.id(), id)) .collect(); let mut states = HashMap::new(); let mut surfaces = HashMap::new(); let mut interfaces = ManuallyDrop::new(HashMap::new()); for (&id, window) in windows.keys().zip(windows.values()) { let mut surface = compositor.create_surface(window); let state = State::new(&application, id, window); let physical_size = state.physical_size(); compositor.configure_surface( &mut surface, physical_size.width, physical_size.height, ); let user_interface = build_user_interface( &application, user_interface::Cache::default(), &mut renderer, state.logical_size(), &mut debug, id, ); let _ = states.insert(id, state); let _ = surfaces.insert(id, surface); let _ = interfaces.insert(id, user_interface); let _ = caches.insert(id, user_interface::Cache::default()); } run_command( &application, &mut caches, &states, &mut renderer, init_command, &mut runtime, &mut clipboard, &mut proxy, &mut debug, &windows, || compositor.fetch_information(), ); runtime.track(application.subscription().map(Event::Application)); let mut mouse_interaction = mouse::Interaction::default(); let mut events = Vec::new(); let mut messages = Vec::new(); let mut redraw_pending = false; debug.startup_finished(); 'main: while let Some(event) = event_receiver.next().await { match event { event::Event::NewEvents(start_cause) => { redraw_pending = matches!( start_cause, event::StartCause::Init | event::StartCause::Poll | event::StartCause::ResumeTimeReached { .. } ); } event::Event::MainEventsCleared => { for id in states.keys().copied().collect::>() { // Partition events into only events for this window let (filtered, remaining): (Vec<_>, Vec<_>) = events.iter().cloned().partition( |(window_id, _event): &( Option, iced_native::event::Event, )| { *window_id == Some(id) || *window_id == None }, ); // Only retain events which have not been processed for next iteration events.retain(|el| remaining.contains(el)); let window_events: Vec<_> = filtered .into_iter() .map(|(_id, event)| event) .collect(); if !redraw_pending && window_events.is_empty() && messages.is_empty() { continue; } // Process winit events for window debug.event_processing_started(); let cursor_position = states.get(&id).unwrap().cursor_position(); let (interface_state, statuses) = { let user_interface = interfaces.get_mut(&id).unwrap(); user_interface.update( &window_events, cursor_position, &mut renderer, &mut clipboard, &mut messages, ) }; for event in window_events.into_iter().zip(statuses.into_iter()) { runtime.broadcast(event); } debug.event_processing_finished(); // Update application with app messages // Unless we implement some kind of diffing, we must redraw all windows as we // cannot know what changed. if !messages.is_empty() || matches!( interface_state, user_interface::State::Outdated, ) { let mut cached_interfaces: HashMap<_, _> = ManuallyDrop::into_inner(interfaces) .drain() .map( |(id, interface): ( window::Id, UserInterface<'_, _, _>, )| { (id, interface.into_cache()) }, ) .collect(); // Update application update( &mut application, &mut cached_interfaces, &states, &mut renderer, &mut runtime, &mut clipboard, &mut proxy, &mut debug, &mut messages, &windows, || compositor.fetch_information(), ); // synchronize window states with application states. for (id, state) in states.iter_mut() { state.synchronize( &application, *id, windows .get(id) .expect("No window found with ID."), ); } interfaces = ManuallyDrop::new(build_user_interfaces( &application, &mut renderer, &mut debug, &states, cached_interfaces, )); if application.should_exit() { break 'main; } } // TODO: Avoid redrawing all the time by forcing widgets to // request redraws on state changes // // Then, we can use the `interface_state` here to decide if a redraw // is needed right away, or simply wait until a specific time. let redraw_event = iced_native::Event::Window( id, window::Event::RedrawRequested(Instant::now()), ); let (interface_state, _) = interfaces.get_mut(&id).unwrap().update( &[redraw_event.clone()], cursor_position, &mut renderer, &mut clipboard, &mut messages, ); debug.draw_started(); let new_mouse_interaction = { let state = states.get(&id).unwrap(); interfaces.get_mut(&id).unwrap().draw( &mut renderer, state.theme(), &renderer::Style { text_color: state.text_color(), }, state.cursor_position(), ) }; debug.draw_finished(); let window = windows.get(&id).unwrap(); if new_mouse_interaction != mouse_interaction { window.set_cursor_icon(conversion::mouse_interaction( new_mouse_interaction, )); mouse_interaction = new_mouse_interaction; } for window in windows.values() { window.request_redraw(); } runtime.broadcast(( redraw_event.clone(), crate::event::Status::Ignored, )); let _ = control_sender.start_send(match interface_state { user_interface::State::Updated { redraw_request: Some(redraw_request), } => match redraw_request { window::RedrawRequest::NextFrame => { ControlFlow::Poll } window::RedrawRequest::At(at) => { ControlFlow::WaitUntil(at) } }, _ => ControlFlow::Wait, }); redraw_pending = false; } } event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( event::MacOS::ReceivedUrl(url), )) => { use iced_native::event; events.push(( None, iced_native::Event::PlatformSpecific( event::PlatformSpecific::MacOS( event::MacOS::ReceivedUrl(url), ), ), )); } event::Event::UserEvent(event) => match event { Event::Application(message) => { messages.push(message); } Event::WindowCreated(id, window) => { let mut surface = compositor.create_surface(&window); let state = State::new(&application, id, &window); let physical_size = state.physical_size(); compositor.configure_surface( &mut surface, physical_size.width, physical_size.height, ); let user_interface = build_user_interface( &application, user_interface::Cache::default(), &mut renderer, state.logical_size(), &mut debug, id, ); let _ = states.insert(id, state); let _ = surfaces.insert(id, surface); let _ = interfaces.insert(id, user_interface); let _ = window_ids.insert(window.id(), id); let _ = windows.insert(id, window); let _ = caches.insert(id, user_interface::Cache::default()); } Event::CloseWindow(id) => { if let Some(window) = windows.get(&id) { if window_ids.remove(&window.id()).is_none() { log::error!("Failed to remove window with id {:?} from window_ids.", window.id()); } } else { log::error!( "Could not find window with id {:?} in windows.", id ); } if states.remove(&id).is_none() { log::error!( "Failed to remove window {:?} from states.", id ); } if interfaces.remove(&id).is_none() { log::error!( "Failed to remove window {:?} from interfaces.", id ); } if windows.remove(&id).is_none() { log::error!( "Failed to remove window {:?} from windows.", id ); } if surfaces.remove(&id).is_none() { log::error!( "Failed to remove window {:?} from surfaces.", id ); } if windows.is_empty() { log::info!( "All windows are closed. Terminating program." ); break 'main; } else { log::info!("Remaining windows: {:?}", windows.len()); } } Event::NewWindow { .. } => unreachable!(), }, event::Event::RedrawRequested(id) => { #[cfg(feature = "trace")] let _ = info_span!("Application", "FRAME").entered(); let state = window_ids .get(&id) .and_then(|id| states.get_mut(id)) .unwrap(); let surface = window_ids .get(&id) .and_then(|id| surfaces.get_mut(id)) .unwrap(); let physical_size = state.physical_size(); if physical_size.width == 0 || physical_size.height == 0 { continue; } debug.render_started(); if state.viewport_changed() { let mut user_interface = window_ids .get(&id) .and_then(|id| interfaces.remove(id)) .unwrap(); let logical_size = state.logical_size(); debug.layout_started(); user_interface = user_interface.relayout(logical_size, &mut renderer); debug.layout_finished(); debug.draw_started(); let new_mouse_interaction = { let state = &state; user_interface.draw( &mut renderer, state.theme(), &renderer::Style { text_color: state.text_color(), }, state.cursor_position(), ) }; let window = window_ids .get(&id) .and_then(|id| windows.get(id)) .unwrap(); if new_mouse_interaction != mouse_interaction { window.set_cursor_icon(conversion::mouse_interaction( new_mouse_interaction, )); mouse_interaction = new_mouse_interaction; } debug.draw_finished(); let _ = interfaces .insert(*window_ids.get(&id).unwrap(), user_interface); compositor.configure_surface( surface, physical_size.width, physical_size.height, ); } match compositor.present( &mut renderer, surface, state.viewport(), state.background_color(), &debug.overlay(), ) { Ok(()) => { debug.render_finished(); // TODO: Handle animations! // Maybe we can use `ControlFlow::WaitUntil` for this. } Err(error) => match error { // This is an unrecoverable error. compositor::SurfaceError::OutOfMemory => { panic!("{:?}", error); } _ => { debug.render_finished(); log::error!("Error {error:?} when presenting surface."); // Try rendering windows again next frame. for window in windows.values() { window.request_redraw(); } } }, } } event::Event::WindowEvent { event: window_event, window_id, } => { if let (Some(window), Some(state)) = ( window_ids.get(&window_id).and_then(|id| windows.get(id)), window_ids .get(&window_id) .and_then(|id| states.get_mut(id)), ) { if crate::application::requests_exit(&window_event, state.modifiers()) { if let Some(id) = window_ids.get(&window_id).cloned() { let message = application.close_requested(id); messages.push(message); } } state.update(window, &window_event, &mut debug); if let Some(event) = conversion::window_event( *window_ids.get(&window_id).unwrap(), &window_event, state.scale_factor(), state.modifiers(), ) { events .push((window_ids.get(&window_id).cloned(), event)); } } else { log::error!( "Could not find window or state for id: {window_id:?}" ); } } _ => {} } } // Manually drop the user interfaces drop(ManuallyDrop::into_inner(interfaces)); } /// Builds a window's [`UserInterface`] for the [`Application`]. pub fn build_user_interface<'a, A: Application>( application: &'a A, cache: user_interface::Cache, renderer: &mut A::Renderer, size: Size, debug: &mut Debug, id: window::Id, ) -> UserInterface<'a, A::Message, A::Renderer> where ::Theme: StyleSheet, { #[cfg(feature = "trace")] let view_span = info_span!("Application", "VIEW").entered(); debug.view_started(); let view = application.view(id); #[cfg(feature = "trace")] let _ = view_span.exit(); debug.view_finished(); #[cfg(feature = "trace")] let layout_span = info_span!("Application", "LAYOUT").entered(); debug.layout_started(); let user_interface = UserInterface::build(view, size, cache, renderer); #[cfg(feature = "trace")] let _ = layout_span.exit(); debug.layout_finished(); user_interface } /// Updates an [`Application`] by feeding it messages, spawning any /// resulting [`Command`], and tracking its [`Subscription`]. pub fn update( application: &mut A, caches: &mut HashMap, states: &HashMap>, renderer: &mut A::Renderer, runtime: &mut Runtime>, Event>, clipboard: &mut Clipboard, proxy: &mut winit::event_loop::EventLoopProxy>, debug: &mut Debug, messages: &mut Vec, windows: &HashMap, graphics_info: impl FnOnce() -> compositor::Information + Copy, ) where A: Application + 'static, ::Theme: StyleSheet, { for message in messages.drain(..) { #[cfg(feature = "trace")] let update_span = info_span!("Application", "UPDATE").entered(); debug.log_message(&message); debug.update_started(); let command = runtime.enter(|| application.update(message)); #[cfg(feature = "trace")] let _ = update_span.exit(); debug.update_finished(); run_command( application, caches, states, renderer, command, runtime, clipboard, proxy, debug, windows, graphics_info, ); } let subscription = application.subscription().map(Event::Application); runtime.track(subscription); } /// Runs the actions of a [`Command`]. pub fn run_command( application: &A, caches: &mut HashMap, states: &HashMap>, renderer: &mut A::Renderer, command: Command, runtime: &mut Runtime>, Event>, clipboard: &mut Clipboard, proxy: &mut winit::event_loop::EventLoopProxy>, debug: &mut Debug, windows: &HashMap, _graphics_info: impl FnOnce() -> compositor::Information + Copy, ) where A: Application + 'static, E: Executor, ::Theme: StyleSheet, { use iced_native::command; use iced_native::system; use iced_native::window; for action in command.actions() { match action { command::Action::Future(future) => { runtime.spawn(Box::pin(future.map(Event::Application))); } command::Action::Clipboard(action) => match action { clipboard::Action::Read(tag) => { let message = tag(clipboard.read()); proxy .send_event(Event::Application(message)) .expect("Send message to event loop"); } clipboard::Action::Write(contents) => { clipboard.write(contents); } }, command::Action::Window(id, action) => match action { window::Action::Spawn { settings } => { proxy .send_event(Event::NewWindow { id, settings: settings.into(), title: application.title(id), }) .expect("Send message to event loop"); } window::Action::Close => { proxy .send_event(Event::CloseWindow(id)) .expect("Send message to event loop"); } window::Action::Drag => { let window = windows.get(&id).expect("No window found"); let _res = window.drag_window(); } window::Action::Resize { width, height } => { let window = windows.get(&id).expect("No window found"); window.set_inner_size(winit::dpi::LogicalSize { width, height, }); } window::Action::Move { x, y } => { let window = windows.get(&id).expect("No window found"); window.set_outer_position(winit::dpi::LogicalPosition { x, y, }); } window::Action::ChangeMode(mode) => { let window = windows.get(&id).expect("No window found"); window.set_visible(conversion::visible(mode)); window.set_fullscreen(conversion::fullscreen( window.current_monitor(), mode, )); } window::Action::FetchMode(tag) => { let window = windows.get(&id).expect("No window found"); let mode = if window.is_visible().unwrap_or(true) { conversion::mode(window.fullscreen()) } else { window::Mode::Hidden }; proxy .send_event(Event::Application(tag(mode))) .expect("Send message to event loop"); } window::Action::Maximize(value) => { let window = windows.get(&id).expect("No window found!"); window.set_maximized(value); } window::Action::Minimize(value) => { let window = windows.get(&id).expect("No window found!"); window.set_minimized(value); } window::Action::ToggleMaximize => { let window = windows.get(&id).expect("No window found!"); window.set_maximized(!window.is_maximized()); } window::Action::ToggleDecorations => { let window = windows.get(&id).expect("No window found!"); window.set_decorations(!window.is_decorated()); } window::Action::RequestUserAttention(attention_type) => { let window = windows.get(&id).expect("No window found!"); window.request_user_attention( attention_type.map(conversion::user_attention), ); } window::Action::GainFocus => { let window = windows.get(&id).expect("No window found!"); window.focus_window(); } window::Action::ChangeAlwaysOnTop(on_top) => { let window = windows.get(&id).expect("No window found!"); window.set_always_on_top(on_top); } window::Action::FetchId(tag) => { let window = windows.get(&id).expect("No window found!"); proxy .send_event(Event::Application(tag(window.id().into()))) .expect("Send message to event loop.") } }, command::Action::System(action) => match action { system::Action::QueryInformation(_tag) => { #[cfg(feature = "system")] { let graphics_info = _graphics_info(); let proxy = proxy.clone(); let _ = std::thread::spawn(move || { let information = crate::system::information(graphics_info); let message = _tag(information); proxy .send_event(Event::Application(message)) .expect("Send message to event loop") }); } } }, command::Action::Widget(action) => { let mut current_caches = std::mem::take(caches); let mut current_operation = Some(action.into_operation()); let mut user_interfaces = build_user_interfaces( application, renderer, debug, states, current_caches, ); while let Some(mut operation) = current_operation.take() { for user_interface in user_interfaces.values_mut() { user_interface.operate(renderer, operation.as_mut()); match operation.finish() { operation::Outcome::None => {} operation::Outcome::Some(message) => { proxy .send_event(Event::Application(message)) .expect("Send message to event loop"); } operation::Outcome::Chain(next) => { current_operation = Some(next); } } } } let user_interfaces: HashMap<_, _> = user_interfaces .drain() .map(|(id, interface)| (id, interface.into_cache())) .collect(); current_caches = user_interfaces; *caches = current_caches; } } } } /// Build the user interfaces for every window. pub fn build_user_interfaces<'a, A>( application: &'a A, renderer: &mut A::Renderer, debug: &mut Debug, states: &HashMap>, mut cached_user_interfaces: HashMap, ) -> HashMap< window::Id, UserInterface< 'a, ::Message, ::Renderer, >, > where A: Application + 'static, ::Theme: StyleSheet, { let mut interfaces = HashMap::new(); for (id, cache) in cached_user_interfaces.drain() { let state = &states.get(&id).unwrap(); let user_interface = build_user_interface( application, cache, renderer, state.logical_size(), debug, id, ); let _ = interfaces.insert(id, user_interface); } interfaces } #[cfg(not(target_arch = "wasm32"))] mod platform { pub fn run( mut event_loop: winit::event_loop::EventLoop, event_handler: F, ) -> Result<(), super::Error> where F: 'static + FnMut( winit::event::Event<'_, T>, &winit::event_loop::EventLoopWindowTarget, &mut winit::event_loop::ControlFlow, ), { use winit::platform::run_return::EventLoopExtRunReturn; let _ = event_loop.run_return(event_handler); Ok(()) } } #[cfg(target_arch = "wasm32")] mod platform { pub fn run( event_loop: winit::event_loop::EventLoop, event_handler: F, ) -> ! where F: 'static + FnMut( winit::event::Event<'_, T>, &winit::event_loop::EventLoopWindowTarget, &mut winit::event_loop::ControlFlow, ), { event_loop.run(event_handler) } }