diff options
author | 2024-06-19 01:53:40 +0200 | |
---|---|---|
committer | 2024-06-19 01:53:40 +0200 | |
commit | 341c9a3c12aa9d327ef1d8f168ea0adb9b5ad10b (patch) | |
tree | 6a37f29352b1768911b6d42c220f22ebecc202ee /winit | |
parent | 368b15f70896b387ae3f072e807b289b36b2d103 (diff) | |
download | iced-341c9a3c12aa9d327ef1d8f168ea0adb9b5ad10b.tar.gz iced-341c9a3c12aa9d327ef1d8f168ea0adb9b5ad10b.tar.bz2 iced-341c9a3c12aa9d327ef1d8f168ea0adb9b5ad10b.zip |
Introduce `daemon` API and unify shell runtimes
Diffstat (limited to 'winit')
-rw-r--r-- | winit/Cargo.toml | 2 | ||||
-rw-r--r-- | winit/src/application.rs | 1082 | ||||
-rw-r--r-- | winit/src/application/state.rs | 221 | ||||
-rw-r--r-- | winit/src/lib.rs | 15 | ||||
-rw-r--r-- | winit/src/program.rs (renamed from winit/src/multi_window.rs) | 340 | ||||
-rw-r--r-- | winit/src/program/state.rs (renamed from winit/src/multi_window/state.rs) | 32 | ||||
-rw-r--r-- | winit/src/program/window_manager.rs (renamed from winit/src/multi_window/window_manager.rs) | 60 | ||||
-rw-r--r-- | winit/src/proxy.rs | 13 | ||||
-rw-r--r-- | winit/src/settings.rs | 12 |
9 files changed, 254 insertions, 1523 deletions
diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 6d3dddde..68368aa1 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -17,7 +17,7 @@ workspace = true default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] debug = ["iced_runtime/debug"] system = ["sysinfo"] -application = [] +program = [] x11 = ["winit/x11"] wayland = ["winit/wayland"] wayland-dlopen = ["winit/wayland-dlopen"] diff --git a/winit/src/application.rs b/winit/src/application.rs deleted file mode 100644 index a93878ea..00000000 --- a/winit/src/application.rs +++ /dev/null @@ -1,1082 +0,0 @@ -//! Create interactive, native cross-platform applications. -mod state; - -pub use state::State; - -use crate::conversion; -use crate::core; -use crate::core::mouse; -use crate::core::renderer; -use crate::core::time::Instant; -use crate::core::widget::operation; -use crate::core::window; -use crate::core::{Color, Event, Point, Size, Theme}; -use crate::futures::futures; -use crate::futures::subscription::{self, Subscription}; -use crate::futures::{Executor, Runtime}; -use crate::graphics; -use crate::graphics::compositor::{self, Compositor}; -use crate::runtime::clipboard; -use crate::runtime::program::Program; -use crate::runtime::user_interface::{self, UserInterface}; -use crate::runtime::{Action, Debug, Task}; -use crate::{Clipboard, Error, Proxy, Settings}; - -use futures::channel::mpsc; -use futures::channel::oneshot; - -use std::borrow::Cow; -use std::mem::ManuallyDrop; -use std::sync::Arc; - -/// An interactive, native cross-platform application. -/// -/// This trait is the main entrypoint of 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 -/// [`Task`] 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: Program -where - Self::Theme: DefaultStyle, -{ - /// The data needed to initialize your [`Application`]. - type Flags; - - /// 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 [`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<Self::Message>); - - /// Returns the current title of the [`Application`]. - /// - /// This title can be dynamic! The runtime will automatically update the - /// title of your application when necessary. - fn title(&self) -> String; - - /// Returns the current `Theme` of the [`Application`]. - fn theme(&self) -> Self::Theme; - - /// Returns the `Style` variation of the `Theme`. - fn style(&self, theme: &Self::Theme) -> Appearance { - theme.default_style() - } - - /// 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<Self::Message> { - Subscription::none() - } - - /// Returns the scale factor 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`. - fn scale_factor(&self) -> f64 { - 1.0 - } -} - -/// The appearance of an application. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Appearance { - /// The background [`Color`] of the application. - pub background_color: Color, - - /// The default text [`Color`] of the application. - pub text_color: Color, -} - -/// The default style of an [`Application`]. -pub trait DefaultStyle { - /// Returns the default style of an [`Application`]. - fn default_style(&self) -> Appearance; -} - -impl DefaultStyle for Theme { - fn default_style(&self) -> Appearance { - default(self) - } -} - -/// The default [`Appearance`] of an [`Application`] with the built-in [`Theme`]. -pub fn default(theme: &Theme) -> Appearance { - let palette = theme.extended_palette(); - - Appearance { - background_color: palette.background.base.color, - text_color: palette.background.base.text, - } -} - -/// Runs an [`Application`] with an executor, compositor, and the provided -/// settings. -pub fn run<A, E, C>( - settings: Settings<A::Flags>, - graphics_settings: graphics::Settings, -) -> Result<(), Error> -where - A: Application + 'static, - E: Executor + 'static, - C: Compositor<Renderer = A::Renderer> + 'static, - A::Theme: DefaultStyle, -{ - use futures::task; - use futures::Future; - 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 = E::new().map_err(Error::ExecutorCreationFailed)?; - executor.spawn(worker); - - Runtime::new(executor, proxy.clone()) - }; - - let (application, task) = { - let flags = settings.flags; - - runtime.enter(|| A::new(flags)) - }; - - if let Some(stream) = task.into_stream() { - runtime.run(stream); - } - - let id = settings.id; - let title = application.title(); - - let (boot_sender, boot_receiver) = oneshot::channel(); - let (event_sender, event_receiver) = mpsc::unbounded(); - let (control_sender, control_receiver) = mpsc::unbounded(); - - let instance = Box::pin(run_instance::<A, E, C>( - application, - runtime, - proxy, - debug, - boot_receiver, - event_receiver, - control_sender, - settings.fonts, - )); - - let context = task::Context::from_waker(task::noop_waker_ref()); - - struct Runner<Message: 'static, F, C> { - instance: std::pin::Pin<Box<F>>, - context: task::Context<'static>, - boot: Option<BootConfig<C>>, - sender: mpsc::UnboundedSender<winit::event::Event<Action<Message>>>, - receiver: mpsc::UnboundedReceiver<winit::event_loop::ControlFlow>, - error: Option<Error>, - #[cfg(target_arch = "wasm32")] - is_booted: std::rc::Rc<std::cell::RefCell<bool>>, - #[cfg(target_arch = "wasm32")] - queued_events: Vec<winit::event::Event<Action<Message>>>, - } - - struct BootConfig<C> { - sender: oneshot::Sender<Boot<C>>, - id: Option<String>, - title: String, - window_settings: window::Settings, - graphics_settings: graphics::Settings, - } - - let runner = Runner { - instance, - context, - boot: Some(BootConfig { - sender: boot_sender, - id, - title, - window_settings: settings.window, - graphics_settings, - }), - sender: event_sender, - receiver: control_receiver, - error: None, - #[cfg(target_arch = "wasm32")] - is_booted: std::rc::Rc::new(std::cell::RefCell::new(false)), - #[cfg(target_arch = "wasm32")] - queued_events: Vec::new(), - }; - - impl<Message, F, C> winit::application::ApplicationHandler<Action<Message>> - for Runner<Message, F, C> - where - F: Future<Output = ()>, - C: Compositor + 'static, - { - fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { - let Some(BootConfig { - sender, - id, - title, - window_settings, - graphics_settings, - }) = self.boot.take() - else { - return; - }; - - let should_be_visible = window_settings.visible; - let exit_on_close_request = window_settings.exit_on_close_request; - - #[cfg(target_arch = "wasm32")] - let target = window_settings.platform_specific.target.clone(); - - let window_attributes = conversion::window_attributes( - window_settings, - &title, - event_loop.primary_monitor(), - id, - ) - .with_visible(false); - - log::debug!("Window attributes: {window_attributes:#?}"); - - let window = match event_loop.create_window(window_attributes) { - Ok(window) => Arc::new(window), - Err(error) => { - self.error = Some(Error::WindowCreationFailed(error)); - event_loop.exit(); - return; - } - }; - - let finish_boot = { - let window = window.clone(); - - async move { - let compositor = - C::new(graphics_settings, window.clone()).await?; - - sender - .send(Boot { - window, - compositor, - should_be_visible, - exit_on_close_request, - }) - .ok() - .expect("Send boot event"); - - Ok::<_, graphics::Error>(()) - } - }; - - #[cfg(not(target_arch = "wasm32"))] - if let Err(error) = futures::executor::block_on(finish_boot) { - self.error = Some(Error::GraphicsCreationFailed(error)); - event_loop.exit(); - } - - #[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"); - } - }; - - let is_booted = self.is_booted.clone(); - - wasm_bindgen_futures::spawn_local(async move { - finish_boot.await.expect("Finish boot!"); - - *is_booted.borrow_mut() = true; - }); - } - } - - fn new_events( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - cause: winit::event::StartCause, - ) { - if self.boot.is_some() { - return; - } - - self.process_event( - event_loop, - 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, - 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, - winit::event::Event::AboutToWait, - ); - } - } - } - - fn user_event( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - action: Action<Message>, - ) { - self.process_event( - event_loop, - winit::event::Event::UserEvent(action), - ); - } - - fn about_to_wait( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - ) { - self.process_event(event_loop, winit::event::Event::AboutToWait); - } - } - - impl<Message, F, C> Runner<Message, F, C> - where - F: Future<Output = ()>, - { - fn process_event( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - event: winit::event::Event<Action<Message>>, - ) { - // On Wasm, events may start being processed before the compositor - // boots up. We simply queue them and process them once ready. - #[cfg(target_arch = "wasm32")] - if !*self.is_booted.borrow() { - self.queued_events.push(event); - return; - } else if !self.queued_events.is_empty() { - let queued_events = std::mem::take(&mut self.queued_events); - - // This won't infinitely recurse, since we `mem::take` - for event in queued_events { - self.process_event(event_loop, event); - } - } - - if event_loop.exiting() { - return; - } - - self.sender.start_send(event).expect("Send event"); - - let poll = self.instance.as_mut().poll(&mut self.context); - - match poll { - task::Poll::Pending => { - if let Ok(Some(flow)) = self.receiver.try_next() { - event_loop.set_control_flow(flow); - } - } - task::Poll::Ready(_) => { - event_loop.exit(); - } - } - } - } - - #[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(()) - } -} - -struct Boot<C> { - window: Arc<winit::window::Window>, - compositor: C, - should_be_visible: bool, - exit_on_close_request: bool, -} - -async fn run_instance<A, E, C>( - mut application: A, - mut runtime: Runtime<E, Proxy<A::Message>, Action<A::Message>>, - mut proxy: Proxy<A::Message>, - mut debug: Debug, - mut boot: oneshot::Receiver<Boot<C>>, - mut event_receiver: mpsc::UnboundedReceiver< - winit::event::Event<Action<A::Message>>, - >, - mut control_sender: mpsc::UnboundedSender<winit::event_loop::ControlFlow>, - fonts: Vec<Cow<'static, [u8]>>, -) where - A: Application + 'static, - E: Executor + 'static, - C: Compositor<Renderer = A::Renderer> + 'static, - A::Theme: DefaultStyle, -{ - use futures::stream::StreamExt; - use winit::event; - use winit::event_loop::ControlFlow; - - let Boot { - window, - mut compositor, - should_be_visible, - exit_on_close_request, - } = boot.try_recv().ok().flatten().expect("Receive boot"); - - let mut renderer = compositor.create_renderer(); - - for font in fonts { - compositor.load_font(font); - } - - let mut state = State::new(&application, &window); - let mut viewport_version = state.viewport_version(); - let physical_size = state.physical_size(); - - let mut clipboard = Clipboard::connect(&window); - let cache = user_interface::Cache::default(); - let mut surface = compositor.create_surface( - window.clone(), - physical_size.width, - physical_size.height, - ); - let mut should_exit = false; - - if should_be_visible { - window.set_visible(true); - } - - runtime.track( - application - .subscription() - .map(Action::Output) - .into_recipes(), - ); - - let mut user_interface = ManuallyDrop::new(build_user_interface( - &application, - cache, - &mut renderer, - state.logical_size(), - &mut debug, - )); - - let mut mouse_interaction = mouse::Interaction::default(); - let mut events = Vec::new(); - let mut messages = Vec::new(); - let mut user_events = 0; - let mut redraw_pending = false; - - debug.startup_finished(); - - while let Some(event) = event_receiver.next().await { - match event { - event::Event::NewEvents( - event::StartCause::Init - | event::StartCause::ResumeTimeReached { .. }, - ) if !redraw_pending => { - window.request_redraw(); - redraw_pending = true; - } - 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, - &mut user_interface, - &mut compositor, - &mut surface, - &state, - &mut renderer, - &mut messages, - &mut clipboard, - &mut should_exit, - &mut debug, - &window, - ); - - user_events += 1; - } - event::Event::WindowEvent { - event: event::WindowEvent::RedrawRequested { .. }, - .. - } => { - let physical_size = state.physical_size(); - - if physical_size.width == 0 || physical_size.height == 0 { - continue; - } - - let current_viewport_version = state.viewport_version(); - - if viewport_version != current_viewport_version { - let logical_size = state.logical_size(); - - debug.layout_started(); - user_interface = ManuallyDrop::new( - ManuallyDrop::into_inner(user_interface) - .relayout(logical_size, &mut renderer), - ); - debug.layout_finished(); - - compositor.configure_surface( - &mut surface, - physical_size.width, - physical_size.height, - ); - - viewport_version = current_viewport_version; - } - - // 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 = Event::Window( - window::Event::RedrawRequested(Instant::now()), - ); - - let (interface_state, _) = user_interface.update( - &[redraw_event.clone()], - state.cursor(), - &mut renderer, - &mut clipboard, - &mut messages, - ); - - let _ = control_sender.start_send(match interface_state { - user_interface::State::Updated { - redraw_request: Some(redraw_request), - } => match redraw_request { - window::RedrawRequest::NextFrame => { - window.request_redraw(); - - ControlFlow::Wait - } - window::RedrawRequest::At(at) => { - ControlFlow::WaitUntil(at) - } - }, - _ => ControlFlow::Wait, - }); - - runtime.broadcast(subscription::Event::Interaction { - window: window::Id::MAIN, - event: redraw_event, - status: core::event::Status::Ignored, - }); - - debug.draw_started(); - let new_mouse_interaction = user_interface.draw( - &mut renderer, - state.theme(), - &renderer::Style { - text_color: state.text_color(), - }, - state.cursor(), - ); - redraw_pending = false; - debug.draw_finished(); - - if new_mouse_interaction != mouse_interaction { - window.set_cursor(conversion::mouse_interaction( - new_mouse_interaction, - )); - - mouse_interaction = new_mouse_interaction; - } - - debug.render_started(); - match compositor.present( - &mut renderer, - &mut 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(); - - // Try rendering again next frame. - window.request_redraw(); - } - }, - } - } - event::Event::WindowEvent { - event: window_event, - .. - } => { - if requests_exit(&window_event, state.modifiers()) - && exit_on_close_request - { - break; - } - - state.update(&window, &window_event, &mut debug); - - if let Some(event) = conversion::window_event( - window_event, - state.scale_factor(), - state.modifiers(), - ) { - events.push(event); - } - } - event::Event::AboutToWait => { - if events.is_empty() && messages.is_empty() { - continue; - } - - debug.event_processing_started(); - - let (interface_state, statuses) = user_interface.update( - &events, - state.cursor(), - &mut renderer, - &mut clipboard, - &mut messages, - ); - - debug.event_processing_finished(); - - for (event, status) in - events.drain(..).zip(statuses.into_iter()) - { - runtime.broadcast(subscription::Event::Interaction { - window: window::Id::MAIN, - event, - status, - }); - } - - if !messages.is_empty() - || matches!( - interface_state, - user_interface::State::Outdated - ) - { - let cache = - ManuallyDrop::into_inner(user_interface).into_cache(); - - // Update application - update( - &mut application, - &mut state, - &mut runtime, - &mut debug, - &mut messages, - &window, - ); - - user_interface = ManuallyDrop::new(build_user_interface( - &application, - cache, - &mut renderer, - state.logical_size(), - &mut debug, - )); - - if should_exit { - break; - } - - if user_events > 0 { - proxy.free_slots(user_events); - user_events = 0; - } - } - - if !redraw_pending { - window.request_redraw(); - redraw_pending = true; - } - } - _ => {} - } - } - - // Manually drop the user interface - drop(ManuallyDrop::into_inner(user_interface)); -} - -/// Returns true if the provided event should cause an [`Application`] to -/// exit. -pub fn requests_exit( - event: &winit::event::WindowEvent, - _modifiers: winit::keyboard::ModifiersState, -) -> bool { - use winit::event::WindowEvent; - - match event { - WindowEvent::CloseRequested => true, - #[cfg(target_os = "macos")] - 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, - } -} - -/// Builds a [`UserInterface`] for the provided [`Application`], logging -/// [`struct@Debug`] information accordingly. -pub fn build_user_interface<'a, A: Application>( - application: &'a A, - cache: user_interface::Cache, - renderer: &mut A::Renderer, - size: Size, - debug: &mut Debug, -) -> UserInterface<'a, A::Message, A::Theme, A::Renderer> -where - A::Theme: DefaultStyle, -{ - debug.view_started(); - let view = application.view(); - debug.view_finished(); - - debug.layout_started(); - let user_interface = UserInterface::build(view, size, cache, renderer); - debug.layout_finished(); - - user_interface -} - -/// Updates an [`Application`] by feeding it the provided messages, spawning any -/// resulting [`Task`], and tracking its [`Subscription`]. -pub fn update<A: Application, E: Executor>( - application: &mut A, - state: &mut State<A>, - runtime: &mut Runtime<E, Proxy<A::Message>, Action<A::Message>>, - debug: &mut Debug, - messages: &mut Vec<A::Message>, - window: &winit::window::Window, -) where - A::Theme: DefaultStyle, -{ - for message in messages.drain(..) { - debug.log_message(&message); - - debug.update_started(); - let task = runtime.enter(|| application.update(message)); - debug.update_finished(); - - if let Some(stream) = task.into_stream() { - runtime.run(stream); - } - } - - state.synchronize(application, window); - - let subscription = application.subscription(); - runtime.track(subscription.map(Action::Output).into_recipes()); -} - -/// Runs the actions of a [`Task`]. -pub fn run_action<A, C>( - action: Action<A::Message>, - user_interface: &mut UserInterface<'_, A::Message, A::Theme, C::Renderer>, - compositor: &mut C, - surface: &mut C::Surface, - state: &State<A>, - renderer: &mut A::Renderer, - messages: &mut Vec<A::Message>, - clipboard: &mut Clipboard, - should_exit: &mut bool, - debug: &mut Debug, - window: &winit::window::Window, -) where - A: Application, - C: Compositor<Renderer = A::Renderer> + 'static, - A::Theme: DefaultStyle, -{ - use crate::runtime::system; - use crate::runtime::window; - - match action { - 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::Close(_id) => { - *should_exit = true; - } - window::Action::Drag(_id) => { - let _res = window.drag_window(); - } - window::Action::Open { .. } => { - log::warn!( - "Spawning a window is only available with \ - multi-window applications." - ); - } - window::Action::Resize(_id, size) => { - let _ = window.request_inner_size(winit::dpi::LogicalSize { - width: size.width, - height: size.height, - }); - } - window::Action::FetchSize(_id, channel) => { - let size = - window.inner_size().to_logical(window.scale_factor()); - - let _ = channel.send(Size::new(size.width, size.height)); - } - window::Action::FetchMaximized(_id, channel) => { - let _ = channel.send(window.is_maximized()); - } - window::Action::Maximize(_id, maximized) => { - window.set_maximized(maximized); - } - window::Action::FetchMinimized(_id, channel) => { - let _ = channel.send(window.is_minimized()); - } - window::Action::Minimize(_id, minimized) => { - window.set_minimized(minimized); - } - window::Action::FetchPosition(_id, channel) => { - let position = window - .inner_position() - .map(|position| { - let position = - position.to_logical::<f32>(window.scale_factor()); - - Point::new(position.x, position.y) - }) - .ok(); - - let _ = channel.send(position); - } - window::Action::Move(_id, position) => { - window.set_outer_position(winit::dpi::LogicalPosition { - x: position.x, - y: position.y, - }); - } - window::Action::ChangeMode(_id, mode) => { - window.set_visible(conversion::visible(mode)); - window.set_fullscreen(conversion::fullscreen( - window.current_monitor(), - mode, - )); - } - window::Action::ChangeIcon(_id, icon) => { - window.set_window_icon(conversion::icon(icon)); - } - window::Action::FetchMode(_id, channel) => { - let mode = if window.is_visible().unwrap_or(true) { - conversion::mode(window.fullscreen()) - } else { - core::window::Mode::Hidden - }; - - let _ = channel.send(mode); - } - window::Action::ToggleMaximize(_id) => { - window.set_maximized(!window.is_maximized()); - } - window::Action::ToggleDecorations(_id) => { - window.set_decorations(!window.is_decorated()); - } - window::Action::RequestUserAttention(_id, user_attention) => { - window.request_user_attention( - user_attention.map(conversion::user_attention), - ); - } - window::Action::GainFocus(_id) => { - window.focus_window(); - } - window::Action::ChangeLevel(_id, level) => { - window.set_window_level(conversion::window_level(level)); - } - window::Action::ShowSystemMenu(_id) => { - if let mouse::Cursor::Available(point) = state.cursor() { - window.show_window_menu(winit::dpi::LogicalPosition { - x: point.x, - y: point.y, - }); - } - } - window::Action::FetchRawId(_id, channel) => { - let _ = channel.send(window.id().into()); - } - window::Action::RunWithHandle(_id, f) => { - use window::raw_window_handle::HasWindowHandle; - - if let Ok(handle) = window.window_handle() { - f(handle); - } - } - - window::Action::Screenshot(_id, channel) => { - let bytes = compositor.screenshot( - renderer, - surface, - state.viewport(), - state.background_color(), - &debug.overlay(), - ); - - let _ = channel.send(window::Screenshot::new( - bytes, - state.physical_size(), - state.viewport().scale_factor(), - )); - } - }, - Action::System(action) => match action { - system::Action::QueryInformation(_channel) => { - #[cfg(feature = "system")] - { - 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() { - user_interface.operate(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 } => { - // TODO: Error handling (?) - compositor.load_font(bytes); - - let _ = channel.send(Ok(())); - } - Action::Output(message) => { - messages.push(message); - } - } -} diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs deleted file mode 100644 index a0a06933..00000000 --- a/winit/src/application/state.rs +++ /dev/null @@ -1,221 +0,0 @@ -use crate::application; -use crate::conversion; -use crate::core::mouse; -use crate::core::{Color, Size}; -use crate::graphics::Viewport; -use crate::runtime::Debug; -use crate::Application; - -use std::marker::PhantomData; -use winit::event::{Touch, WindowEvent}; -use winit::window::Window; - -/// The state of a windowed [`Application`]. -#[allow(missing_debug_implementations)] -pub struct State<A: Application> -where - A::Theme: application::DefaultStyle, -{ - title: String, - scale_factor: f64, - viewport: Viewport, - viewport_version: usize, - cursor_position: Option<winit::dpi::PhysicalPosition<f64>>, - modifiers: winit::keyboard::ModifiersState, - theme: A::Theme, - appearance: application::Appearance, - application: PhantomData<A>, -} - -impl<A: Application> State<A> -where - A::Theme: application::DefaultStyle, -{ - /// Creates a new [`State`] for the provided [`Application`] and window. - pub fn new(application: &A, window: &Window) -> Self { - let title = application.title(); - let scale_factor = application.scale_factor(); - let theme = application.theme(); - let appearance = application.style(&theme); - - let viewport = { - let physical_size = window.inner_size(); - - Viewport::with_physical_size( - Size::new(physical_size.width, physical_size.height), - window.scale_factor() * scale_factor, - ) - }; - - Self { - title, - scale_factor, - viewport, - viewport_version: 0, - cursor_position: None, - modifiers: winit::keyboard::ModifiersState::default(), - theme, - appearance, - application: PhantomData, - } - } - - /// Returns the current [`Viewport`] of the [`State`]. - pub fn viewport(&self) -> &Viewport { - &self.viewport - } - - /// Returns the version of the [`Viewport`] of the [`State`]. - /// - /// The version is incremented every time the [`Viewport`] changes. - pub fn viewport_version(&self) -> usize { - self.viewport_version - } - - /// Returns the physical [`Size`] of the [`Viewport`] of the [`State`]. - pub fn physical_size(&self) -> Size<u32> { - self.viewport.physical_size() - } - - /// Returns the logical [`Size`] of the [`Viewport`] of the [`State`]. - pub fn logical_size(&self) -> Size<f32> { - self.viewport.logical_size() - } - - /// Returns the current scale factor of the [`Viewport`] of the [`State`]. - pub fn scale_factor(&self) -> f64 { - self.viewport.scale_factor() - } - - /// Returns the current cursor position of the [`State`]. - pub fn cursor(&self) -> mouse::Cursor { - self.cursor_position - .map(|cursor_position| { - conversion::cursor_position( - cursor_position, - self.viewport.scale_factor(), - ) - }) - .map(mouse::Cursor::Available) - .unwrap_or(mouse::Cursor::Unavailable) - } - - /// Returns the current keyboard modifiers of the [`State`]. - pub fn modifiers(&self) -> winit::keyboard::ModifiersState { - self.modifiers - } - - /// Returns the current theme of the [`State`]. - pub fn theme(&self) -> &A::Theme { - &self.theme - } - - /// Returns the current background [`Color`] of the [`State`]. - pub fn background_color(&self) -> Color { - self.appearance.background_color - } - - /// Returns the current text [`Color`] of the [`State`]. - pub fn text_color(&self) -> Color { - self.appearance.text_color - } - - /// Processes the provided window event and updates the [`State`] - /// accordingly. - pub fn update( - &mut self, - window: &Window, - event: &WindowEvent, - _debug: &mut Debug, - ) { - match event { - WindowEvent::Resized(new_size) => { - let size = Size::new(new_size.width, new_size.height); - - self.viewport = Viewport::with_physical_size( - size, - window.scale_factor() * self.scale_factor, - ); - - self.viewport_version = self.viewport_version.wrapping_add(1); - } - WindowEvent::ScaleFactorChanged { - scale_factor: new_scale_factor, - .. - } => { - let size = self.viewport.physical_size(); - - self.viewport = Viewport::with_physical_size( - size, - new_scale_factor * self.scale_factor, - ); - - self.viewport_version = self.viewport_version.wrapping_add(1); - } - WindowEvent::CursorMoved { position, .. } - | WindowEvent::Touch(Touch { - location: position, .. - }) => { - self.cursor_position = Some(*position); - } - WindowEvent::CursorLeft { .. } => { - self.cursor_position = None; - } - WindowEvent::ModifiersChanged(new_modifiers) => { - self.modifiers = new_modifiers.state(); - } - #[cfg(feature = "debug")] - WindowEvent::KeyboardInput { - event: - winit::event::KeyEvent { - logical_key: - winit::keyboard::Key::Named( - winit::keyboard::NamedKey::F12, - ), - state: winit::event::ElementState::Pressed, - .. - }, - .. - } => _debug.toggle(), - _ => {} - } - } - - /// Synchronizes the [`State`] with its [`Application`] and its respective - /// window. - /// - /// Normally an [`Application`] should be synchronized with its [`State`] - /// and window after calling [`crate::application::update`]. - pub fn synchronize(&mut self, application: &A, window: &Window) { - // Update window title - let new_title = application.title(); - - if self.title != new_title { - window.set_title(&new_title); - - self.title = new_title; - } - - // Update scale factor and size - let new_scale_factor = application.scale_factor(); - let new_size = window.inner_size(); - let current_size = self.viewport.physical_size(); - - if self.scale_factor != new_scale_factor - || (current_size.width, current_size.height) - != (new_size.width, new_size.height) - { - self.viewport = Viewport::with_physical_size( - Size::new(new_size.width, new_size.height), - window.scale_factor() * new_scale_factor, - ); - self.viewport_version = self.viewport_version.wrapping_add(1); - - self.scale_factor = new_scale_factor; - } - - // Update theme and appearance - self.theme = application.theme(); - self.appearance = application.style(&self.theme); - } -} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 3619cde8..3c11b72a 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -5,7 +5,7 @@ //! `iced_winit` offers some convenient abstractions on top of [`iced_runtime`] //! to quickstart development when using [`winit`]. //! -//! It exposes a renderer-agnostic [`Application`] trait that can be implemented +//! It exposes a renderer-agnostic [`Program`] trait that can be implemented //! and then run with a simple call. The use of this trait is optional. //! //! Additionally, a [`conversion`] module is available for users that decide to @@ -24,24 +24,23 @@ pub use iced_runtime::core; pub use iced_runtime::futures; pub use winit; -#[cfg(feature = "multi-window")] -pub mod multi_window; - -#[cfg(feature = "application")] -pub mod application; pub mod clipboard; pub mod conversion; pub mod settings; +#[cfg(feature = "program")] +pub mod program; + #[cfg(feature = "system")] pub mod system; mod error; mod proxy; -#[cfg(feature = "application")] -pub use application::Application; pub use clipboard::Clipboard; pub use error::Error; pub use proxy::Proxy; pub use settings::Settings; + +#[cfg(feature = "program")] +pub use program::Program; diff --git a/winit/src/multi_window.rs b/winit/src/program.rs index 8bd8a64d..28cd8e52 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/program.rs @@ -10,7 +10,7 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::widget::operation; use crate::core::window; -use crate::core::{Point, Size}; +use crate::core::{Color, Element, Point, Size, Theme}; use crate::futures::futures::channel::mpsc; use crate::futures::futures::channel::oneshot; use crate::futures::futures::executor; @@ -20,14 +20,12 @@ use crate::futures::subscription::{self, Subscription}; use crate::futures::{Executor, Runtime}; use crate::graphics; use crate::graphics::{compositor, Compositor}; -use crate::multi_window::window_manager::WindowManager; -use crate::runtime::multi_window::Program; use crate::runtime::user_interface::{self, UserInterface}; use crate::runtime::Debug; -use crate::runtime::{Action, Task}; +use crate::runtime::{self, Action, Task}; use crate::{Clipboard, Error, Proxy, Settings}; -pub use crate::application::{default, Appearance, DefaultStyle}; +use window_manager::WindowManager; use rustc_hash::FxHashMap; use std::mem::ManuallyDrop; @@ -40,19 +38,37 @@ use std::time::Instant; /// your GUI application by simply calling [`run`]. It will run in /// its own window. /// -/// An [`Application`] can execute asynchronous actions by returning a +/// A [`Program`] can execute asynchronous actions by returning a /// [`Task`] in some of its methods. /// -/// When using an [`Application`] with the `debug` feature enabled, a debug view +/// When using a [`Program`] with the `debug` feature enabled, a debug view /// can be toggled by pressing `F12`. -pub trait Application: Program +pub trait Program where + Self: Sized, Self::Theme: DefaultStyle, { - /// The data needed to initialize your [`Application`]. + /// 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 [`Application`] with the flags provided to + /// 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. @@ -62,13 +78,31 @@ where /// load state from a file, perform an initial HTTP request, etc. fn new(flags: Self::Flags) -> (Self, Task<Self::Message>); - /// Returns the current title of the [`Application`]. + /// 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; - /// Returns the current `Theme` of the [`Application`]. + /// 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<Self::Message>; + + /// 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`. @@ -89,7 +123,7 @@ where Subscription::none() } - /// Returns the scale factor of the window of the [`Application`]. + /// 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). @@ -104,17 +138,49 @@ where } } -/// Runs an [`Application`] with an executor, compositor, and the provided +/// The appearance of a program. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Appearance { + /// The background [`Color`] of the application. + pub background_color: Color, + + /// The default text [`Color`] of the application. + pub text_color: Color, +} + +/// The default style of a [`Program`]. +pub trait DefaultStyle { + /// Returns the default style of a [`Program`]. + fn default_style(&self) -> Appearance; +} + +impl DefaultStyle for Theme { + fn default_style(&self) -> Appearance { + default(self) + } +} + +/// The default [`Appearance`] of a [`Program`] with the built-in [`Theme`]. +pub fn default(theme: &Theme) -> Appearance { + let palette = theme.extended_palette(); + + Appearance { + background_color: palette.background.base.color, + text_color: palette.background.base.text, + } +} +/// Runs a [`Program`] with an executor, compositor, and the provided /// settings. -pub fn run<A, E, C>( - settings: Settings<A::Flags>, +pub fn run<P, C>( + settings: Settings, graphics_settings: graphics::Settings, + window_settings: Option<window::Settings>, + flags: P::Flags, ) -> Result<(), Error> where - A: Application + 'static, - E: Executor + 'static, - C: Compositor<Renderer = A::Renderer> + 'static, - A::Theme: DefaultStyle, + P: Program + 'static, + C: Compositor<Renderer = P::Renderer> + 'static, + P::Theme: DefaultStyle, { use winit::event_loop::EventLoop; @@ -128,30 +194,24 @@ where let (proxy, worker) = Proxy::new(event_loop.create_proxy()); let mut runtime = { - let executor = E::new().map_err(Error::ExecutorCreationFailed)?; + let executor = + P::Executor::new().map_err(Error::ExecutorCreationFailed)?; executor.spawn(worker); Runtime::new(executor, proxy.clone()) }; - let (application, task) = { - let flags = settings.flags; - - runtime.enter(|| A::new(flags)) - }; + let (application, task) = runtime.enter(|| P::new(flags)); if let Some(stream) = task.into_stream() { runtime.run(stream); } - let id = settings.id; - let title = application.title(window::Id::MAIN); - let (boot_sender, boot_receiver) = oneshot::channel(); let (event_sender, event_receiver) = mpsc::unbounded(); let (control_sender, control_receiver) = mpsc::unbounded(); - let instance = Box::pin(run_instance::<A, E, C>( + let instance = Box::pin(run_instance::<P, C>( application, runtime, proxy, @@ -166,6 +226,7 @@ where struct Runner<Message: 'static, F, C> { instance: std::pin::Pin<Box<F>>, context: task::Context<'static>, + id: Option<String>, boot: Option<BootConfig<C>>, sender: mpsc::UnboundedSender<Event<Message>>, receiver: mpsc::UnboundedReceiver<Control>, @@ -174,20 +235,17 @@ where struct BootConfig<C> { sender: oneshot::Sender<Boot<C>>, - id: Option<String>, - title: String, - window_settings: window::Settings, + window_settings: Option<window::Settings>, graphics_settings: graphics::Settings, } let mut runner = Runner { instance, context, + id: settings.id, boot: Some(BootConfig { sender: boot_sender, - id, - title, - window_settings: settings.window, + window_settings, graphics_settings, }), sender: event_sender, @@ -204,8 +262,6 @@ where fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { let Some(BootConfig { sender, - id, - title, window_settings, graphics_settings, }) = self.boot.take() @@ -213,20 +269,9 @@ where return; }; - let should_be_visible = window_settings.visible; - let exit_on_close_request = window_settings.exit_on_close_request; - - let window_attributes = conversion::window_attributes( - window_settings, - &title, - event_loop.primary_monitor(), - id, - ) - .with_visible(false); - - log::debug!("Window attributes: {window_attributes:#?}"); - - let window = match event_loop.create_window(window_attributes) { + let window = match event_loop.create_window( + winit::window::WindowAttributes::default().with_visible(false), + ) { Ok(window) => Arc::new(window), Err(error) => { self.error = Some(Error::WindowCreationFailed(error)); @@ -235,16 +280,17 @@ where } }; + let clipboard = Clipboard::connect(&window); + let finish_boot = async move { let compositor = C::new(graphics_settings, window.clone()).await?; sender .send(Boot { - window, compositor, - should_be_visible, - exit_on_close_request, + clipboard, + window_settings, }) .ok() .expect("Send boot event"); @@ -386,7 +432,12 @@ where let window = event_loop .create_window( conversion::window_attributes( - settings, &title, monitor, None, + settings, + &title, + monitor + .or(event_loop + .primary_monitor()), + self.id.clone(), ), ) .expect("Create window"); @@ -423,10 +474,9 @@ where } struct Boot<C> { - window: Arc<winit::window::Window>, compositor: C, - should_be_visible: bool, - exit_on_close_request: bool, + clipboard: Clipboard, + window_settings: Option<window::Settings>, } enum Event<Message: 'static> { @@ -449,62 +499,37 @@ enum Control { }, } -async fn run_instance<A, E, C>( - mut application: A, - mut runtime: Runtime<E, Proxy<A::Message>, Action<A::Message>>, - mut proxy: Proxy<A::Message>, +async fn run_instance<P, C>( + mut program: P, + mut runtime: Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>, + mut proxy: Proxy<P::Message>, mut debug: Debug, mut boot: oneshot::Receiver<Boot<C>>, - mut event_receiver: mpsc::UnboundedReceiver<Event<Action<A::Message>>>, + mut event_receiver: mpsc::UnboundedReceiver<Event<Action<P::Message>>>, mut control_sender: mpsc::UnboundedSender<Control>, ) where - A: Application + 'static, - E: Executor + 'static, - C: Compositor<Renderer = A::Renderer> + 'static, - A::Theme: DefaultStyle, + P: Program + 'static, + C: Compositor<Renderer = P::Renderer> + 'static, + P::Theme: DefaultStyle, { use winit::event; use winit::event_loop::ControlFlow; let Boot { - window: main_window, mut compositor, - should_be_visible, - exit_on_close_request, + mut clipboard, + window_settings, } = boot.try_recv().ok().flatten().expect("Receive boot"); let mut window_manager = WindowManager::new(); - let _ = window_manager.insert( - window::Id::MAIN, - main_window, - &application, - &mut compositor, - exit_on_close_request, - ); - - let main_window = window_manager - .get_mut(window::Id::MAIN) - .expect("Get main window"); - - if should_be_visible { - main_window.raw.set_visible(true); - } - - let mut clipboard = Clipboard::connect(&main_window.raw); - let mut events = { - vec![( - window::Id::MAIN, - core::Event::Window(window::Event::Opened { - position: main_window.position(), - size: main_window.size(), - }), - )] - }; + 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(build_user_interfaces( - &application, + &program, &mut debug, &mut window_manager, FxHashMap::from_iter([( @@ -513,15 +538,19 @@ async fn run_instance<A, E, C>( )]), )); - runtime.track( - application - .subscription() - .map(Action::Output) - .into_recipes(), - ); + runtime.track(program.subscription().map(Action::Output).into_recipes()); - let mut messages = Vec::new(); - let mut user_events = 0; + let is_daemon = window_settings.is_none(); + + if let Some(window_settings) = window_settings { + let (sender, _receiver) = oneshot::channel(); + + proxy.send_action(Action::Window(runtime::window::Action::Open( + window::Id::unique(), + window_settings, + sender, + ))); + } debug.startup_finished(); @@ -535,7 +564,7 @@ async fn run_instance<A, E, C>( let window = window_manager.insert( id, Arc::new(window), - &application, + &program, &mut compositor, exit_on_close_request, ); @@ -545,7 +574,7 @@ async fn run_instance<A, E, C>( let _ = user_interfaces.insert( id, build_user_interface( - &application, + &program, user_interface::Cache::default(), &mut window.renderer, logical_size, @@ -591,7 +620,7 @@ async fn run_instance<A, E, C>( event::Event::UserEvent(action) => { run_action( action, - &application, + &program, &mut compositor, &mut messages, &mut clipboard, @@ -601,7 +630,7 @@ async fn run_instance<A, E, C>( &mut window_manager, &mut ui_caches, ); - user_events += 1; + actions += 1; } event::Event::WindowEvent { window_id: id, @@ -782,6 +811,16 @@ async fn run_instance<A, E, C>( event: window_event, window_id, } => { + if !is_daemon + && matches!( + window_event, + winit::event::WindowEvent::Destroyed + ) + && window_manager.is_empty() + { + break 'main; + } + let Some((id, window)) = window_manager.get_mut_alias(window_id) else { @@ -801,10 +840,6 @@ async fn run_instance<A, E, C>( id, core::Event::Window(window::Event::Closed), )); - - if window_manager.is_empty() { - break 'main; - } } else { window.state.update( &window.raw, @@ -903,7 +938,7 @@ async fn run_instance<A, E, C>( // Update application update( - &mut application, + &mut program, &mut runtime, &mut debug, &mut messages, @@ -913,7 +948,7 @@ async fn run_instance<A, E, C>( // application update since we don't know what changed for (id, window) in window_manager.iter_mut() { window.state.synchronize( - &application, + &program, id, &window.raw, ); @@ -926,15 +961,15 @@ async fn run_instance<A, E, C>( // rebuild UIs with the synchronized states user_interfaces = ManuallyDrop::new(build_user_interfaces( - &application, + &program, &mut debug, &mut window_manager, cached_interfaces, )); - if user_events > 0 { - proxy.free_slots(user_events); - user_events = 0; + if actions > 0 { + proxy.free_slots(actions); + actions = 0; } } } @@ -947,17 +982,17 @@ async fn run_instance<A, E, C>( let _ = ManuallyDrop::into_inner(user_interfaces); } -/// Builds a window's [`UserInterface`] for the [`Application`]. -fn build_user_interface<'a, A: Application>( - application: &'a A, +/// Builds a window's [`UserInterface`] for the [`Program`]. +fn build_user_interface<'a, P: Program>( + application: &'a P, cache: user_interface::Cache, - renderer: &mut A::Renderer, + renderer: &mut P::Renderer, size: Size, debug: &mut Debug, id: window::Id, -) -> UserInterface<'a, A::Message, A::Theme, A::Renderer> +) -> UserInterface<'a, P::Message, P::Theme, P::Renderer> where - A::Theme: DefaultStyle, + P::Theme: DefaultStyle, { debug.view_started(); let view = application.view(id); @@ -970,13 +1005,13 @@ where user_interface } -fn update<A: Application, E: Executor>( - application: &mut A, - runtime: &mut Runtime<E, Proxy<A::Message>, Action<A::Message>>, +fn update<P: Program, E: Executor>( + application: &mut P, + runtime: &mut Runtime<E, Proxy<P::Message>, Action<P::Message>>, debug: &mut Debug, - messages: &mut Vec<A::Message>, + messages: &mut Vec<P::Message>, ) where - A::Theme: DefaultStyle, + P::Theme: DefaultStyle, { for message in messages.drain(..) { debug.log_message(&message); @@ -994,24 +1029,24 @@ fn update<A: Application, E: Executor>( runtime.track(subscription.map(Action::Output).into_recipes()); } -fn run_action<A, C>( - action: Action<A::Message>, - application: &A, +fn run_action<P, C>( + action: Action<P::Message>, + application: &P, compositor: &mut C, - messages: &mut Vec<A::Message>, + messages: &mut Vec<P::Message>, clipboard: &mut Clipboard, control_sender: &mut mpsc::UnboundedSender<Control>, debug: &mut Debug, interfaces: &mut FxHashMap< window::Id, - UserInterface<'_, A::Message, A::Theme, A::Renderer>, + UserInterface<'_, P::Message, P::Theme, P::Renderer>, >, - window_manager: &mut WindowManager<A, C>, + window_manager: &mut WindowManager<P, C>, ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>, ) where - A: Application, - C: Compositor<Renderer = A::Renderer> + 'static, - A::Theme: DefaultStyle, + P: Program, + C: Compositor<Renderer = P::Renderer> + 'static, + P::Theme: DefaultStyle, { use crate::runtime::clipboard; use crate::runtime::system; @@ -1047,12 +1082,6 @@ fn run_action<A, C>( window::Action::Close(id) => { let _ = window_manager.remove(id); let _ = ui_caches.remove(&id); - - if window_manager.is_empty() { - control_sender - .start_send(Control::Exit) - .expect("Send control action"); - } } window::Action::Drag(id) => { if let Some(window) = window_manager.get_mut(id) { @@ -1266,19 +1295,24 @@ fn run_action<A, C>( 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, A: Application, C>( - application: &'a A, +pub fn build_user_interfaces<'a, P: Program, C>( + application: &'a P, debug: &mut Debug, - window_manager: &mut WindowManager<A, C>, + window_manager: &mut WindowManager<P, C>, mut cached_user_interfaces: FxHashMap<window::Id, user_interface::Cache>, -) -> FxHashMap<window::Id, UserInterface<'a, A::Message, A::Theme, A::Renderer>> +) -> FxHashMap<window::Id, UserInterface<'a, P::Message, P::Theme, P::Renderer>> where - C: Compositor<Renderer = A::Renderer>, - A::Theme: DefaultStyle, + C: Compositor<Renderer = P::Renderer>, + P::Theme: DefaultStyle, { cached_user_interfaces .drain() @@ -1300,7 +1334,7 @@ where .collect() } -/// Returns true if the provided event should cause an [`Application`] to +/// Returns true if the provided event should cause a [`Program`] to /// exit. pub fn user_force_quit( event: &winit::event::WindowEvent, diff --git a/winit/src/multi_window/state.rs b/winit/src/program/state.rs index dfd8e696..a7fa2788 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/program/state.rs @@ -2,16 +2,16 @@ use crate::conversion; use crate::core::{mouse, window}; use crate::core::{Color, Size}; use crate::graphics::Viewport; -use crate::multi_window::{self, Application}; +use crate::program::{self, Program}; use std::fmt::{Debug, Formatter}; use winit::event::{Touch, WindowEvent}; use winit::window::Window; -/// The state of a multi-windowed [`Application`]. -pub struct State<A: Application> +/// The state of a multi-windowed [`Program`]. +pub struct State<P: Program> where - A::Theme: multi_window::DefaultStyle, + P::Theme: program::DefaultStyle, { title: String, scale_factor: f64, @@ -19,13 +19,13 @@ where viewport_version: u64, cursor_position: Option<winit::dpi::PhysicalPosition<f64>>, modifiers: winit::keyboard::ModifiersState, - theme: A::Theme, - appearance: multi_window::Appearance, + theme: P::Theme, + appearance: program::Appearance, } -impl<A: Application> Debug for State<A> +impl<P: Program> Debug for State<P> where - A::Theme: multi_window::DefaultStyle, + P::Theme: program::DefaultStyle, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("multi_window::State") @@ -39,13 +39,13 @@ where } } -impl<A: Application> State<A> +impl<P: Program> State<P> where - A::Theme: multi_window::DefaultStyle, + P::Theme: program::DefaultStyle, { - /// Creates a new [`State`] for the provided [`Application`]'s `window`. + /// Creates a new [`State`] for the provided [`Program`]'s `window`. pub fn new( - application: &A, + application: &P, window_id: window::Id, window: &Window, ) -> Self { @@ -121,7 +121,7 @@ where } /// Returns the current theme of the [`State`]. - pub fn theme(&self) -> &A::Theme { + pub fn theme(&self) -> &P::Theme { &self.theme } @@ -195,14 +195,14 @@ where } } - /// Synchronizes the [`State`] with its [`Application`] and its respective + /// Synchronizes the [`State`] with its [`Program`] and its respective /// window. /// - /// Normally, an [`Application`] should be synchronized with its [`State`] + /// Normally, a [`Program`] should be synchronized with its [`State`] /// and window after calling [`State::update`]. pub fn synchronize( &mut self, - application: &A, + application: &P, window_id: window::Id, window: &Window, ) { diff --git a/winit/src/multi_window/window_manager.rs b/winit/src/program/window_manager.rs index 57a7dc7e..fcbf79f6 100644 --- a/winit/src/multi_window/window_manager.rs +++ b/winit/src/program/window_manager.rs @@ -2,28 +2,28 @@ use crate::core::mouse; use crate::core::window::Id; use crate::core::{Point, Size}; use crate::graphics::Compositor; -use crate::multi_window::{Application, DefaultStyle, State}; +use crate::program::{DefaultStyle, Program, State}; use std::collections::BTreeMap; use std::sync::Arc; use winit::monitor::MonitorHandle; #[allow(missing_debug_implementations)] -pub struct WindowManager<A, C> +pub struct WindowManager<P, C> where - A: Application, - C: Compositor<Renderer = A::Renderer>, - A::Theme: DefaultStyle, + P: Program, + C: Compositor<Renderer = P::Renderer>, + P::Theme: DefaultStyle, { aliases: BTreeMap<winit::window::WindowId, Id>, - entries: BTreeMap<Id, Window<A, C>>, + entries: BTreeMap<Id, Window<P, C>>, } -impl<A, C> WindowManager<A, C> +impl<P, C> WindowManager<P, C> where - A: Application, - C: Compositor<Renderer = A::Renderer>, - A::Theme: DefaultStyle, + P: Program, + C: Compositor<Renderer = P::Renderer>, + P::Theme: DefaultStyle, { pub fn new() -> Self { Self { @@ -36,10 +36,10 @@ where &mut self, id: Id, window: Arc<winit::window::Window>, - application: &A, + application: &P, compositor: &mut C, exit_on_close_request: bool, - ) -> &mut Window<A, C> { + ) -> &mut Window<P, C> { let state = State::new(application, id, &window); let viewport_version = state.viewport_version(); let physical_size = state.physical_size(); @@ -76,18 +76,18 @@ where pub fn iter_mut( &mut self, - ) -> impl Iterator<Item = (Id, &mut Window<A, C>)> { + ) -> impl Iterator<Item = (Id, &mut Window<P, C>)> { self.entries.iter_mut().map(|(k, v)| (*k, v)) } - pub fn get_mut(&mut self, id: Id) -> Option<&mut Window<A, C>> { + pub fn get_mut(&mut self, id: Id) -> Option<&mut Window<P, C>> { self.entries.get_mut(&id) } pub fn get_mut_alias( &mut self, id: winit::window::WindowId, - ) -> Option<(Id, &mut Window<A, C>)> { + ) -> Option<(Id, &mut Window<P, C>)> { let id = self.aliases.get(&id).copied()?; Some((id, self.get_mut(id)?)) @@ -97,7 +97,7 @@ where self.entries.values().last()?.raw.current_monitor() } - pub fn remove(&mut self, id: Id) -> Option<Window<A, C>> { + pub fn remove(&mut self, id: Id) -> Option<Window<P, C>> { let window = self.entries.remove(&id)?; let _ = self.aliases.remove(&window.raw.id()); @@ -105,11 +105,11 @@ where } } -impl<A, C> Default for WindowManager<A, C> +impl<P, C> Default for WindowManager<P, C> where - A: Application, - C: Compositor<Renderer = A::Renderer>, - A::Theme: DefaultStyle, + P: Program, + C: Compositor<Renderer = P::Renderer>, + P::Theme: DefaultStyle, { fn default() -> Self { Self::new() @@ -117,26 +117,26 @@ where } #[allow(missing_debug_implementations)] -pub struct Window<A, C> +pub struct Window<P, C> where - A: Application, - C: Compositor<Renderer = A::Renderer>, - A::Theme: DefaultStyle, + P: Program, + C: Compositor<Renderer = P::Renderer>, + P::Theme: DefaultStyle, { pub raw: Arc<winit::window::Window>, - pub state: State<A>, + pub state: State<P>, pub viewport_version: u64, pub exit_on_close_request: bool, pub mouse_interaction: mouse::Interaction, pub surface: C::Surface, - pub renderer: A::Renderer, + pub renderer: P::Renderer, } -impl<A, C> Window<A, C> +impl<P, C> Window<P, C> where - A: Application, - C: Compositor<Renderer = A::Renderer>, - A::Theme: DefaultStyle, + P: Program, + C: Compositor<Renderer = P::Renderer>, + P::Theme: DefaultStyle, { pub fn position(&self) -> Option<Point> { self.raw diff --git a/winit/src/proxy.rs b/winit/src/proxy.rs index 0ab61375..d8ad8b3f 100644 --- a/winit/src/proxy.rs +++ b/winit/src/proxy.rs @@ -81,8 +81,19 @@ impl<T: 'static> Proxy<T> { where T: std::fmt::Debug, { + self.send_action(Action::Output(value)); + } + + /// Sends an action to the event loop. + /// + /// Note: This skips the backpressure mechanism with an unbounded + /// channel. Use sparingly! + pub fn send_action(&mut self, action: Action<T>) + where + T: std::fmt::Debug, + { self.raw - .send_event(Action::Output(value)) + .send_event(action) .expect("Send message to event loop"); } diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 2e541128..78368a04 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -1,25 +1,15 @@ //! Configure your application. -use crate::core::window; - use std::borrow::Cow; /// The settings of an application. #[derive(Debug, Clone, Default)] -pub struct Settings<Flags> { +pub struct Settings { /// The identifier of the application. /// /// If provided, this identifier may be used to identify the application or /// communicate with it through the windowing system. pub id: Option<String>, - /// The [`window::Settings`]. - pub window: window::Settings, - - /// The data needed to initialize an [`Application`]. - /// - /// [`Application`]: crate::Application - pub flags: Flags, - /// The fonts to load on boot. pub fonts: Vec<Cow<'static, [u8]>>, } |