//! Create interactive, native cross-platform applications for WGPU. mod state; mod window_manager; pub use state::State; use crate::conversion; use crate::core; use crate::core::mouse; use crate::core::renderer; use crate::core::theme; use crate::core::time::Instant; use crate::core::widget::operation; use crate::core::window; use crate::core::{Element, Point, Size}; use crate::futures::futures::channel::mpsc; use crate::futures::futures::channel::oneshot; use crate::futures::futures::task; use crate::futures::futures::{Future, StreamExt}; use crate::futures::subscription::{self, Subscription}; use crate::futures::{Executor, Runtime}; use crate::graphics; use crate::graphics::{Compositor, compositor}; use crate::runtime::Debug; use crate::runtime::user_interface::{self, UserInterface}; use crate::runtime::{self, Action, Task}; use crate::{Clipboard, Error, Proxy, Settings}; use window_manager::WindowManager; use rustc_hash::FxHashMap; use std::borrow::Cow; use std::mem::ManuallyDrop; use std::sync::Arc; /// 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. /// /// A [`Program`] can execute asynchronous actions by returning a /// [`Task`] in some of its methods. /// /// When using a [`Program`] with the `debug` feature enabled, a debug view /// can be toggled by pressing `F12`. pub trait Program where Self: Sized, Self::Theme: theme::Base, { /// The type of __messages__ your [`Program`] will produce. type Message: std::fmt::Debug + Send; /// The theme used to draw the [`Program`]. type Theme; /// The [`Executor`] that will run commands and subscriptions. /// /// The [default executor] can be a good starting point! /// /// [`Executor`]: Self::Executor /// [default executor]: crate::futures::backend::default::Executor type Executor: Executor; /// The graphics backend to use to draw the [`Program`]. type Renderer: core::Renderer + core::text::Renderer; /// The data needed to initialize your [`Program`]. type Flags; /// Initializes the [`Program`] 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 [`Task`] 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, Task); /// Returns the current title of the [`Program`]. /// /// This title can be dynamic! The runtime will automatically update the /// title of your application when necessary. fn title(&self, window: window::Id) -> String; /// 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 [`Task`] returned will be executed immediately in the background by the /// runtime. fn update(&mut self, message: Self::Message) -> Task; /// Returns the widgets to display in the [`Program`] for the `window`. /// /// These widgets can produce __messages__ based on user interaction. fn view( &self, window: window::Id, ) -> Element<'_, Self::Message, Self::Theme, Self::Renderer>; /// Returns the current `Theme` of the [`Program`]. fn theme(&self, window: window::Id) -> Self::Theme; /// Returns the `Style` variation of the `Theme`. fn style(&self, theme: &Self::Theme) -> theme::Style { theme::Base::base(theme) } /// 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 [`Program`]. /// /// 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 } } /// Runs a [`Program`] with an executor, compositor, and the provided /// settings. pub fn run( settings: Settings, graphics_settings: graphics::Settings, window_settings: Option, flags: P::Flags, ) -> Result<(), Error> where P: Program + 'static, C: Compositor + 'static, P::Theme: theme::Base, { use winit::event_loop::EventLoop; let mut debug = Debug::new(); debug.startup_started(); let event_loop = EventLoop::with_user_event() .build() .expect("Create event loop"); let (proxy, worker) = Proxy::new(event_loop.create_proxy()); let mut runtime = { let executor = P::Executor::new().map_err(Error::ExecutorCreationFailed)?; executor.spawn(worker); Runtime::new(executor, proxy.clone()) }; let (program, task) = runtime.enter(|| P::new(flags)); let is_daemon = window_settings.is_none(); let task = if let Some(window_settings) = window_settings { let mut task = Some(task); let (_id, open) = runtime::window::open(window_settings); open.then(move |_| task.take().unwrap_or(Task::none())) } else { task }; if let Some(stream) = runtime::task::into_stream(task) { runtime.run(stream); } runtime.track(subscription::into_recipes( runtime.enter(|| program.subscription().map(Action::Output)), )); let (event_sender, event_receiver) = mpsc::unbounded(); let (control_sender, control_receiver) = mpsc::unbounded(); let instance = Box::pin(run_instance::( program, runtime, proxy.clone(), debug, event_receiver, control_sender, is_daemon, graphics_settings, settings.fonts, )); let context = task::Context::from_waker(task::noop_waker_ref()); struct Runner { instance: std::pin::Pin>, context: task::Context<'static>, id: Option, sender: mpsc::UnboundedSender>>, receiver: mpsc::UnboundedReceiver, error: Option, #[cfg(target_arch = "wasm32")] canvas: Option, } let runner = Runner { instance, context, id: settings.id, sender: event_sender, receiver: control_receiver, error: None, #[cfg(target_arch = "wasm32")] canvas: None, }; impl winit::application::ApplicationHandler> for Runner where Message: std::fmt::Debug, F: Future, { fn resumed( &mut self, _event_loop: &winit::event_loop::ActiveEventLoop, ) { } fn new_events( &mut self, event_loop: &winit::event_loop::ActiveEventLoop, cause: winit::event::StartCause, ) { self.process_event( event_loop, Event::EventLoopAwakened(winit::event::Event::NewEvents(cause)), ); } fn window_event( &mut self, event_loop: &winit::event_loop::ActiveEventLoop, window_id: winit::window::WindowId, event: winit::event::WindowEvent, ) { #[cfg(target_os = "windows")] let is_move_or_resize = matches!( event, winit::event::WindowEvent::Resized(_) | winit::event::WindowEvent::Moved(_) ); self.process_event( event_loop, Event::EventLoopAwakened(winit::event::Event::WindowEvent { window_id, event, }), ); // TODO: Remove when unnecessary // On Windows, we emulate an `AboutToWait` event after every `Resized` event // since the event loop does not resume during resize interaction. // More details: https://github.com/rust-windowing/winit/issues/3272 #[cfg(target_os = "windows")] { if is_move_or_resize { self.process_event( event_loop, Event::EventLoopAwakened( winit::event::Event::AboutToWait, ), ); } } } fn user_event( &mut self, event_loop: &winit::event_loop::ActiveEventLoop, action: Action, ) { self.process_event( event_loop, Event::EventLoopAwakened(winit::event::Event::UserEvent( action, )), ); } fn received_url( &mut self, event_loop: &winit::event_loop::ActiveEventLoop, url: String, ) { self.process_event( event_loop, Event::EventLoopAwakened( winit::event::Event::PlatformSpecific( winit::event::PlatformSpecific::MacOS( winit::event::MacOS::ReceivedUrl(url), ), ), ), ); } fn about_to_wait( &mut self, event_loop: &winit::event_loop::ActiveEventLoop, ) { self.process_event( event_loop, Event::EventLoopAwakened(winit::event::Event::AboutToWait), ); } } impl Runner where F: Future, { fn process_event( &mut self, event_loop: &winit::event_loop::ActiveEventLoop, event: Event>, ) { if event_loop.exiting() { return; } self.sender.start_send(event).expect("Send event"); loop { let poll = self.instance.as_mut().poll(&mut self.context); match poll { task::Poll::Pending => match self.receiver.try_next() { Ok(Some(control)) => match control { Control::ChangeFlow(flow) => { use winit::event_loop::ControlFlow; match (event_loop.control_flow(), flow) { ( ControlFlow::WaitUntil(current), ControlFlow::WaitUntil(new), ) if current < new => {} ( ControlFlow::WaitUntil(target), ControlFlow::Wait, ) if target > Instant::now() => {} _ => { event_loop.set_control_flow(flow); } } } Control::CreateWindow { id, settings, title, monitor, on_open, } => { let exit_on_close_request = settings.exit_on_close_request; let visible = settings.visible; #[cfg(target_arch = "wasm32")] let target = settings.platform_specific.target.clone(); let window_attributes = conversion::window_attributes( settings, &title, monitor .or(event_loop.primary_monitor()), self.id.clone(), ) .with_visible(false); #[cfg(target_arch = "wasm32")] let window_attributes = { use winit::platform::web::WindowAttributesExtWebSys; window_attributes .with_canvas(self.canvas.take()) }; log::info!( "Window attributes for id `{id:#?}`: {window_attributes:#?}" ); // On macOS, the `position` in `WindowAttributes` represents the "inner" // position of the window; while on other platforms it's the "outer" position. // We fix the inconsistency on macOS by positioning the window after creation. #[cfg(target_os = "macos")] let mut window_attributes = window_attributes; #[cfg(target_os = "macos")] let position = window_attributes.position.take(); let window = event_loop .create_window(window_attributes) .expect("Create window"); #[cfg(target_os = "macos")] if let Some(position) = position { window.set_outer_position(position); } #[cfg(target_arch = "wasm32")] { use winit::platform::web::WindowExtWebSys; let canvas = window .canvas() .expect("Get window canvas"); let _ = canvas.set_attribute( "style", "display: block; width: 100%; height: 100%", ); let window = web_sys::window().unwrap(); let document = window.document().unwrap(); let body = document.body().unwrap(); let target = target.and_then(|target| { body.query_selector(&format!( "#{target}" )) .ok() .unwrap_or(None) }); match target { Some(node) => { let _ = node .replace_with_with_node_1( &canvas, ) .expect(&format!( "Could not replace #{}", node.id() )); } None => { let _ = body .append_child(&canvas) .expect( "Append canvas to HTML body", ); } }; } self.process_event( event_loop, Event::WindowCreated { id, window: Arc::new(window), exit_on_close_request, make_visible: visible, on_open, }, ); } Control::Exit => { event_loop.exit(); } Control::Crash(error) => { self.error = Some(error); event_loop.exit(); } }, _ => { break; } }, task::Poll::Ready(_) => { event_loop.exit(); break; } }; } } } #[cfg(not(target_arch = "wasm32"))] { let mut runner = runner; let _ = event_loop.run_app(&mut runner); runner.error.map(Err).unwrap_or(Ok(())) } #[cfg(target_arch = "wasm32")] { use winit::platform::web::EventLoopExtWebSys; let _ = event_loop.spawn_app(runner); Ok(()) } } #[derive(Debug)] enum Event { WindowCreated { id: window::Id, window: Arc, exit_on_close_request: bool, make_visible: bool, on_open: oneshot::Sender, }, EventLoopAwakened(winit::event::Event), } #[derive(Debug)] enum Control { ChangeFlow(winit::event_loop::ControlFlow), Exit, Crash(Error), CreateWindow { id: window::Id, settings: window::Settings, title: String, monitor: Option, on_open: oneshot::Sender, }, } async fn run_instance( mut program: P, mut runtime: Runtime, Action>, mut proxy: Proxy, mut debug: Debug, mut event_receiver: mpsc::UnboundedReceiver>>, mut control_sender: mpsc::UnboundedSender, is_daemon: bool, graphics_settings: graphics::Settings, default_fonts: Vec>, ) where P: Program + 'static, C: Compositor + 'static, P::Theme: theme::Base, { use winit::event; use winit::event_loop::ControlFlow; let mut window_manager = WindowManager::new(); let mut is_window_opening = !is_daemon; let mut compositor = None; let mut events = Vec::new(); let mut messages = Vec::new(); let mut actions = 0; let mut ui_caches = FxHashMap::default(); let mut user_interfaces = ManuallyDrop::new(FxHashMap::default()); let mut clipboard = Clipboard::unconnected(); let mut compositor_receiver: Option> = None; debug.startup_finished(); loop { let event = if compositor_receiver.is_some() { let compositor_receiver = compositor_receiver.take().expect("Waiting for compositor"); match compositor_receiver.await { Ok(Ok((new_compositor, event))) => { compositor = Some(new_compositor); Some(event) } Ok(Err(error)) => { control_sender .start_send(Control::Crash( Error::GraphicsCreationFailed(error), )) .expect("Send control action"); break; } Err(error) => { panic!("Compositor initialization failed: {error}") } } // Empty the queue if possible } else if let Ok(event) = event_receiver.try_next() { event } else { event_receiver.next().await }; let Some(event) = event else { break; }; match event { Event::WindowCreated { id, window, exit_on_close_request, make_visible, on_open, } => { if compositor.is_none() { let (compositor_sender, new_compositor_receiver) = oneshot::channel(); compositor_receiver = Some(new_compositor_receiver); let create_compositor = { let default_fonts = default_fonts.clone(); async move { let mut compositor = C::new(graphics_settings, window.clone()).await; if let Ok(compositor) = &mut compositor { for font in default_fonts { compositor.load_font(font.clone()); } } compositor_sender .send(compositor.map(|compositor| { ( compositor, Event::WindowCreated { id, window, exit_on_close_request, make_visible, on_open, }, ) })) .ok() .expect("Send compositor"); } }; #[cfg(not(target_arch = "wasm32"))] crate::futures::futures::executor::block_on( create_compositor, ); #[cfg(target_arch = "wasm32")] { wasm_bindgen_futures::spawn_local(create_compositor); } continue; } let window = window_manager.insert( id, window, &program, compositor .as_mut() .expect("Compositor must be initialized"), exit_on_close_request, ); let logical_size = window.state.logical_size(); let _ = user_interfaces.insert( id, build_user_interface( &program, user_interface::Cache::default(), &mut window.renderer, logical_size, &mut debug, id, ), ); let _ = ui_caches.insert(id, user_interface::Cache::default()); if make_visible { window.raw.set_visible(true); } events.push(( id, core::Event::Window(window::Event::Opened { position: window.position(), size: window.size(), }), )); if clipboard.window_id().is_none() { clipboard = Clipboard::connect(window.raw.clone()); } let _ = on_open.send(id); is_window_opening = false; } Event::EventLoopAwakened(event) => { match event { event::Event::NewEvents(event::StartCause::Init) => { for (_id, window) in window_manager.iter_mut() { window.raw.request_redraw(); } } event::Event::NewEvents( event::StartCause::ResumeTimeReached { .. }, ) => { let now = Instant::now(); for (_id, window) in window_manager.iter_mut() { if let Some(redraw_at) = window.redraw_at { if redraw_at <= now { window.raw.request_redraw(); window.redraw_at = None; } } } if let Some(redraw_at) = window_manager.redraw_at() { let _ = control_sender.start_send(Control::ChangeFlow( ControlFlow::WaitUntil(redraw_at), )); } else { let _ = control_sender.start_send( Control::ChangeFlow(ControlFlow::Wait), ); } } event::Event::PlatformSpecific( event::PlatformSpecific::MacOS( event::MacOS::ReceivedUrl(url), ), ) => { runtime.broadcast( subscription::Event::PlatformSpecific( subscription::PlatformSpecific::MacOS( subscription::MacOS::ReceivedUrl(url), ), ), ); } event::Event::UserEvent(action) => { run_action( action, &program, &mut compositor, &mut events, &mut messages, &mut clipboard, &mut control_sender, &mut debug, &mut user_interfaces, &mut window_manager, &mut ui_caches, &mut is_window_opening, ); actions += 1; } event::Event::WindowEvent { window_id: id, event: event::WindowEvent::RedrawRequested, .. } => { let Some(compositor) = &mut compositor else { continue; }; let Some((id, window)) = window_manager.get_mut_alias(id) else { continue; }; let physical_size = window.state.physical_size(); if physical_size.width == 0 || physical_size.height == 0 { continue; } if window.viewport_version != window.state.viewport_version() { let logical_size = window.state.logical_size(); debug.layout_started(); let ui = user_interfaces .remove(&id) .expect("Remove user interface"); let _ = user_interfaces.insert( id, ui.relayout(logical_size, &mut window.renderer), ); debug.layout_finished(); compositor.configure_surface( &mut window.surface, physical_size.width, physical_size.height, ); window.viewport_version = window.state.viewport_version(); } let redraw_event = core::Event::Window( window::Event::RedrawRequested(Instant::now()), ); let cursor = window.state.cursor(); let ui = user_interfaces .get_mut(&id) .expect("Get user interface"); let (ui_state, _) = ui.update( &[redraw_event.clone()], cursor, &mut window.renderer, &mut clipboard, &mut messages, ); debug.draw_started(); let new_mouse_interaction = ui.draw( &mut window.renderer, window.state.theme(), &renderer::Style { text_color: window.state.text_color(), }, cursor, ); debug.draw_finished(); if new_mouse_interaction != window.mouse_interaction { window.raw.set_cursor( conversion::mouse_interaction( new_mouse_interaction, ), ); window.mouse_interaction = new_mouse_interaction; } runtime.broadcast(subscription::Event::Interaction { window: id, event: redraw_event, status: core::event::Status::Ignored, }); if let user_interface::State::Updated { redraw_request, input_method, } = ui_state { window.request_redraw(redraw_request); window.request_input_method(input_method); } window.draw_preedit(); debug.render_started(); match compositor.present( &mut window.renderer, &mut window.surface, window.state.viewport(), window.state.background_color(), &debug.overlay(), ) { Ok(()) => { debug.render_finished(); } 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 all windows again next frame. for (_id, window) in window_manager.iter_mut() { window.raw.request_redraw(); } } }, } } event::Event::WindowEvent { event: window_event, window_id, } => { if !is_daemon && matches!( window_event, winit::event::WindowEvent::Destroyed ) && !is_window_opening && window_manager.is_empty() { control_sender .start_send(Control::Exit) .expect("Send control action"); continue; } let Some((id, window)) = window_manager.get_mut_alias(window_id) else { continue; }; if matches!( window_event, winit::event::WindowEvent::Resized(_) ) { window.raw.request_redraw(); } if matches!( window_event, winit::event::WindowEvent::CloseRequested ) && window.exit_on_close_request { run_action( Action::Window(runtime::window::Action::Close( id, )), &program, &mut compositor, &mut events, &mut messages, &mut clipboard, &mut control_sender, &mut debug, &mut user_interfaces, &mut window_manager, &mut ui_caches, &mut is_window_opening, ); } else { window.state.update( &window.raw, &window_event, &mut debug, ); if let Some(event) = conversion::window_event( window_event, window.state.scale_factor(), window.state.modifiers(), ) { events.push((id, event)); } } } event::Event::AboutToWait => { if actions > 0 { proxy.free_slots(actions); actions = 0; } if events.is_empty() && messages.is_empty() && window_manager.is_idle() { continue; } debug.event_processing_started(); let mut uis_stale = false; for (id, window) in window_manager.iter_mut() { let mut window_events = vec![]; events.retain(|(window_id, event)| { if *window_id == id { window_events.push(event.clone()); false } else { true } }); if window_events.is_empty() && messages.is_empty() { continue; } let (ui_state, statuses) = user_interfaces .get_mut(&id) .expect("Get user interface") .update( &window_events, window.state.cursor(), &mut window.renderer, &mut clipboard, &mut messages, ); #[cfg(feature = "unconditional-rendering")] window.request_redraw( window::RedrawRequest::NextFrame, ); match ui_state { user_interface::State::Updated { redraw_request: _redraw_request, .. } => { #[cfg(not( feature = "unconditional-rendering" ))] window.request_redraw(_redraw_request); } user_interface::State::Outdated => { uis_stale = true; } } for (event, status) in window_events .into_iter() .zip(statuses.into_iter()) { runtime.broadcast( subscription::Event::Interaction { window: id, event, status, }, ); } } for (id, event) in events.drain(..) { runtime.broadcast( subscription::Event::Interaction { window: id, event, status: core::event::Status::Ignored, }, ); } debug.event_processing_finished(); if !messages.is_empty() || uis_stale { let cached_interfaces: FxHashMap< window::Id, user_interface::Cache, > = ManuallyDrop::into_inner(user_interfaces) .drain() .map(|(id, ui)| (id, ui.into_cache())) .collect(); update( &mut program, &mut runtime, &mut debug, &mut messages, ); for (id, window) in window_manager.iter_mut() { window.state.synchronize( &program, id, &window.raw, ); window.raw.request_redraw(); } user_interfaces = ManuallyDrop::new(build_user_interfaces( &program, &mut debug, &mut window_manager, cached_interfaces, )); } if let Some(redraw_at) = window_manager.redraw_at() { let _ = control_sender.start_send(Control::ChangeFlow( ControlFlow::WaitUntil(redraw_at), )); } else { let _ = control_sender.start_send( Control::ChangeFlow(ControlFlow::Wait), ); } } _ => {} } } } } let _ = ManuallyDrop::into_inner(user_interfaces); } /// Builds a window's [`UserInterface`] for the [`Program`]. fn build_user_interface<'a, P: Program>( program: &'a P, cache: user_interface::Cache, renderer: &mut P::Renderer, size: Size, debug: &mut Debug, id: window::Id, ) -> UserInterface<'a, P::Message, P::Theme, P::Renderer> where P::Theme: theme::Base, { debug.view_started(); let view = program.view(id); debug.view_finished(); debug.layout_started(); let user_interface = UserInterface::build(view, size, cache, renderer); debug.layout_finished(); user_interface } fn update( program: &mut P, runtime: &mut Runtime, Action>, debug: &mut Debug, messages: &mut Vec, ) where P::Theme: theme::Base, { for message in messages.drain(..) { debug.log_message(&message); debug.update_started(); let task = runtime.enter(|| program.update(message)); debug.update_finished(); if let Some(stream) = runtime::task::into_stream(task) { runtime.run(stream); } } let subscription = runtime.enter(|| program.subscription()); runtime.track(subscription::into_recipes(subscription.map(Action::Output))); } fn run_action( action: Action, program: &P, compositor: &mut Option, events: &mut Vec<(window::Id, core::Event)>, messages: &mut Vec, clipboard: &mut Clipboard, control_sender: &mut mpsc::UnboundedSender, debug: &mut Debug, interfaces: &mut FxHashMap< window::Id, UserInterface<'_, P::Message, P::Theme, P::Renderer>, >, window_manager: &mut WindowManager, ui_caches: &mut FxHashMap, is_window_opening: &mut bool, ) where P: Program, C: Compositor + 'static, P::Theme: theme::Base, { use crate::runtime::clipboard; use crate::runtime::system; use crate::runtime::window; match action { Action::Output(message) => { messages.push(message); } Action::Clipboard(action) => match action { clipboard::Action::Read { target, channel } => { let _ = channel.send(clipboard.read(target)); } clipboard::Action::Write { target, contents } => { clipboard.write(target, contents); } }, Action::Window(action) => match action { window::Action::Open(id, settings, channel) => { let monitor = window_manager.last_monitor(); control_sender .start_send(Control::CreateWindow { id, settings, title: program.title(id), monitor, on_open: channel, }) .expect("Send control action"); *is_window_opening = true; } window::Action::Close(id) => { let _ = ui_caches.remove(&id); let _ = interfaces.remove(&id); if let Some(window) = window_manager.remove(id) { if clipboard.window_id() == Some(window.raw.id()) { *clipboard = window_manager .first() .map(|window| window.raw.clone()) .map(Clipboard::connect) .unwrap_or_else(Clipboard::unconnected); } events.push(( id, core::Event::Window(core::window::Event::Closed), )); } if window_manager.is_empty() { *compositor = None; } } window::Action::GetOldest(channel) => { let id = window_manager.iter_mut().next().map(|(id, _window)| id); let _ = channel.send(id); } window::Action::GetLatest(channel) => { let id = window_manager.iter_mut().last().map(|(id, _window)| id); let _ = channel.send(id); } window::Action::Drag(id) => { if let Some(window) = window_manager.get_mut(id) { let _ = window.raw.drag_window(); } } window::Action::DragResize(id, direction) => { if let Some(window) = window_manager.get_mut(id) { let _ = window.raw.drag_resize_window( conversion::resize_direction(direction), ); } } window::Action::Resize(id, size) => { if let Some(window) = window_manager.get_mut(id) { let _ = window.raw.request_inner_size( winit::dpi::LogicalSize { width: size.width, height: size.height, }, ); } } window::Action::SetMinSize(id, size) => { if let Some(window) = window_manager.get_mut(id) { window.raw.set_min_inner_size(size.map(|size| { winit::dpi::LogicalSize { width: size.width, height: size.height, } })); } } window::Action::SetMaxSize(id, size) => { if let Some(window) = window_manager.get_mut(id) { window.raw.set_max_inner_size(size.map(|size| { winit::dpi::LogicalSize { width: size.width, height: size.height, } })); } } window::Action::SetResizeIncrements(id, increments) => { if let Some(window) = window_manager.get_mut(id) { window.raw.set_resize_increments(increments.map(|size| { winit::dpi::LogicalSize { width: size.width, height: size.height, } })); } } window::Action::SetResizable(id, resizable) => { if let Some(window) = window_manager.get_mut(id) { window.raw.set_resizable(resizable); } } window::Action::GetSize(id, channel) => { if let Some(window) = window_manager.get_mut(id) { let size = window .raw .inner_size() .to_logical(window.raw.scale_factor()); let _ = channel.send(Size::new(size.width, size.height)); } } window::Action::GetMaximized(id, channel) => { if let Some(window) = window_manager.get_mut(id) { let _ = channel.send(window.raw.is_maximized()); } } window::Action::Maximize(id, maximized) => { if let Some(window) = window_manager.get_mut(id) { window.raw.set_maximized(maximized); } } window::Action::GetMinimized(id, channel) => { if let Some(window) = window_manager.get_mut(id) { let _ = channel.send(window.raw.is_minimized()); } } window::Action::Minimize(id, minimized) => { if let Some(window) = window_manager.get_mut(id) { window.raw.set_minimized(minimized); } } window::Action::GetPosition(id, channel) => { if let Some(window) = window_manager.get(id) { let position = window .raw .outer_position() .map(|position| { let position = position .to_logical::(window.raw.scale_factor()); Point::new(position.x, position.y) }) .ok(); let _ = channel.send(position); } } window::Action::GetScaleFactor(id, channel) => { if let Some(window) = window_manager.get_mut(id) { let scale_factor = window.raw.scale_factor(); let _ = channel.send(scale_factor as f32); } } window::Action::Move(id, position) => { if let Some(window) = window_manager.get_mut(id) { window.raw.set_outer_position( winit::dpi::LogicalPosition { x: position.x, y: position.y, }, ); } } window::Action::SetMode(id, mode) => { if let Some(window) = window_manager.get_mut(id) { window.raw.set_visible(conversion::visible(mode)); window.raw.set_fullscreen(conversion::fullscreen( window.raw.current_monitor(), mode, )); } } window::Action::SetIcon(id, icon) => { if let Some(window) = window_manager.get_mut(id) { window.raw.set_window_icon(conversion::icon(icon)); } } window::Action::GetMode(id, channel) => { if let Some(window) = window_manager.get_mut(id) { let mode = if window.raw.is_visible().unwrap_or(true) { conversion::mode(window.raw.fullscreen()) } else { core::window::Mode::Hidden }; let _ = channel.send(mode); } } window::Action::ToggleMaximize(id) => { if let Some(window) = window_manager.get_mut(id) { window.raw.set_maximized(!window.raw.is_maximized()); } } window::Action::ToggleDecorations(id) => { if let Some(window) = window_manager.get_mut(id) { window.raw.set_decorations(!window.raw.is_decorated()); } } window::Action::RequestUserAttention(id, attention_type) => { if let Some(window) = window_manager.get_mut(id) { window.raw.request_user_attention( attention_type.map(conversion::user_attention), ); } } window::Action::GainFocus(id) => { if let Some(window) = window_manager.get_mut(id) { window.raw.focus_window(); } } window::Action::SetLevel(id, level) => { if let Some(window) = window_manager.get_mut(id) { window .raw .set_window_level(conversion::window_level(level)); } } window::Action::ShowSystemMenu(id) => { if let Some(window) = window_manager.get_mut(id) { if let mouse::Cursor::Available(point) = window.state.cursor() { window.raw.show_window_menu( winit::dpi::LogicalPosition { x: point.x, y: point.y, }, ); } } } window::Action::GetRawId(id, channel) => { if let Some(window) = window_manager.get_mut(id) { let _ = channel.send(window.raw.id().into()); } } window::Action::RunWithHandle(id, f) => { use window::raw_window_handle::HasWindowHandle; if let Some(handle) = window_manager .get_mut(id) .and_then(|window| window.raw.window_handle().ok()) { f(handle); } } window::Action::Screenshot(id, channel) => { if let Some(window) = window_manager.get_mut(id) { if let Some(compositor) = compositor { let bytes = compositor.screenshot( &mut window.renderer, window.state.viewport(), window.state.background_color(), &debug.overlay(), ); let _ = channel.send(core::window::Screenshot::new( bytes, window.state.physical_size(), window.state.viewport().scale_factor(), )); } } } window::Action::EnableMousePassthrough(id) => { if let Some(window) = window_manager.get_mut(id) { let _ = window.raw.set_cursor_hittest(false); } } window::Action::DisableMousePassthrough(id) => { if let Some(window) = window_manager.get_mut(id) { let _ = window.raw.set_cursor_hittest(true); } } }, Action::System(action) => match action { system::Action::QueryInformation(_channel) => { #[cfg(feature = "system")] { if let Some(compositor) = compositor { let graphics_info = compositor.fetch_information(); let _ = std::thread::spawn(move || { let information = crate::system::information(graphics_info); let _ = _channel.send(information); }); } } } }, Action::Widget(operation) => { let mut current_operation = Some(operation); while let Some(mut operation) = current_operation.take() { for (id, ui) in interfaces.iter_mut() { if let Some(window) = window_manager.get_mut(*id) { ui.operate(&window.renderer, operation.as_mut()); } } match operation.finish() { operation::Outcome::None => {} operation::Outcome::Some(()) => {} operation::Outcome::Chain(next) => { current_operation = Some(next); } } } } Action::LoadFont { bytes, channel } => { if let Some(compositor) = compositor { // TODO: Error handling (?) compositor.load_font(bytes.clone()); let _ = channel.send(Ok(())); } } Action::Exit => { control_sender .start_send(Control::Exit) .expect("Send control action"); } } } /// Build the user interface for every window. pub fn build_user_interfaces<'a, P: Program, C>( program: &'a P, debug: &mut Debug, window_manager: &mut WindowManager, mut cached_user_interfaces: FxHashMap, ) -> FxHashMap> where C: Compositor, P::Theme: theme::Base, { cached_user_interfaces .drain() .filter_map(|(id, cache)| { let window = window_manager.get_mut(id)?; Some(( id, build_user_interface( program, cache, &mut window.renderer, window.state.logical_size(), debug, id, ), )) }) .collect() } /// Returns true if the provided event should cause a [`Program`] to /// exit. pub fn user_force_quit( event: &winit::event::WindowEvent, _modifiers: winit::keyboard::ModifiersState, ) -> bool { match event { #[cfg(target_os = "macos")] winit::event::WindowEvent::KeyboardInput { event: winit::event::KeyEvent { logical_key: winit::keyboard::Key::Character(c), state: winit::event::ElementState::Pressed, .. }, .. } if c == "q" && _modifiers.super_key() => true, _ => false, } }