From 287306e1ebec610c31e37782320fe00d20a6c9ac Mon Sep 17 00:00:00 2001 From: Richard Date: Fri, 1 Apr 2022 17:27:19 -0300 Subject: Introduce `multi_window` in `iced_winit` --- winit/src/lib.rs | 3 + winit/src/multi_window.rs | 742 ++++++++++++++++++++++++++++++++++++++++ winit/src/multi_window/state.rs | 212 ++++++++++++ 3 files changed, 957 insertions(+) create mode 100644 winit/src/multi_window.rs create mode 100644 winit/src/multi_window/state.rs (limited to 'winit/src') diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 06674109..9b3c0a02 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -35,6 +35,9 @@ pub use iced_native::*; pub use winit; +#[cfg(feature = "multi_window")] +pub mod multi_window; + #[cfg(feature = "application")] pub mod application; pub mod clipboard; diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs new file mode 100644 index 00000000..4ea7383a --- /dev/null +++ b/winit/src/multi_window.rs @@ -0,0 +1,742 @@ +//! Create interactive, native cross-platform applications. +mod state; + +pub use state::State; + +use crate::clipboard::{self, Clipboard}; +use crate::conversion; +use crate::mouse; +use crate::renderer; +use crate::widget::operation; +use crate::{ + Command, Debug, Error, Executor, Proxy, Runtime, Settings, Size, + Subscription, +}; + +use iced_futures::futures; +use iced_futures::futures::channel::mpsc; +use iced_graphics::compositor; +use iced_graphics::window; +use iced_native::program::Program; +use iced_native::user_interface::{self, UserInterface}; + +pub use iced_native::application::{Appearance, StyleSheet}; + +use std::mem::ManuallyDrop; + +/// 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 +/// [`Command`] in some of its methods. +/// +/// When using an [`Application`] with the `debug` feature enabled, a debug view +/// can be toggled by pressing `F12`. +pub trait Application: Program +where + ::Theme: StyleSheet, +{ + /// 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 [`Command`] if you need to perform some + /// async action in the background on startup. This is useful if you want to + /// load state from a file, perform an initial HTTP request, etc. + fn new(flags: Self::Flags) -> (Self, Command); + + /// Returns the current title of 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) -> ::Theme; + + /// Returns the [`Style`] variation of the [`Theme`]. + fn style( + &self, + ) -> <::Theme as StyleSheet>::Style { + Default::default() + } + + /// Returns the event `Subscription` for the current state of the + /// application. + /// + /// The messages produced by the `Subscription` will be handled by + /// [`update`](#tymethod.update). + /// + /// A `Subscription` will be kept alive as long as you keep returning it! + /// + /// By default, it returns an empty subscription. + fn subscription(&self) -> Subscription { + Subscription::none() + } + + /// Returns the scale factor of the [`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 + } + + /// Returns whether the [`Application`] should be terminated. + /// + /// By default, it returns `false`. + fn should_exit(&self) -> bool { + false + } +} + +/// Runs an [`Application`] with an executor, compositor, and the provided +/// settings. +pub fn run( + settings: Settings, + compositor_settings: C::Settings, +) -> Result<(), Error> +where + A: Application + 'static, + E: Executor + 'static, + C: window::Compositor + 'static, + ::Theme: StyleSheet, +{ + use futures::task; + use futures::Future; + use winit::event_loop::EventLoopBuilder; + + let mut debug = Debug::new(); + debug.startup_started(); + + let event_loop = EventLoopBuilder::with_user_event().build(); + let proxy = event_loop.create_proxy(); + + let runtime = { + let proxy = Proxy::new(event_loop.create_proxy()); + let executor = E::new().map_err(Error::ExecutorCreationFailed)?; + + Runtime::new(executor, proxy) + }; + + let (application, init_command) = { + let flags = settings.flags; + + runtime.enter(|| A::new(flags)) + }; + + let builder = settings.window.into_builder( + &application.title(), + event_loop.primary_monitor(), + settings.id, + ); + + log::info!("Window builder: {:#?}", builder); + + let window = builder + .build(&event_loop) + .map_err(Error::WindowCreationFailed)?; + + #[cfg(target_arch = "wasm32")] + { + use winit::platform::web::WindowExtWebSys; + + let canvas = window.canvas(); + + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let body = document.body().unwrap(); + + let _ = body + .append_child(&canvas) + .expect("Append canvas to HTML body"); + } + + let (compositor, renderer) = C::new(compositor_settings, Some(&window))?; + + let (mut sender, receiver) = mpsc::unbounded(); + + let mut instance = Box::pin(run_instance::( + application, + compositor, + renderer, + runtime, + proxy, + debug, + receiver, + init_command, + window, + settings.exit_on_close_request, + )); + + let mut context = task::Context::from_waker(task::noop_waker_ref()); + + platform::run(event_loop, move |event, _, control_flow| { + use winit::event_loop::ControlFlow; + + if let ControlFlow::ExitWithCode(_) = control_flow { + return; + } + + let event = match event { + winit::event::Event::WindowEvent { + event: + winit::event::WindowEvent::ScaleFactorChanged { + new_inner_size, + .. + }, + window_id, + } => Some(winit::event::Event::WindowEvent { + event: winit::event::WindowEvent::Resized(*new_inner_size), + window_id, + }), + _ => event.to_static(), + }; + + if let Some(event) = event { + sender.start_send(event).expect("Send event"); + + let poll = instance.as_mut().poll(&mut context); + + *control_flow = match poll { + task::Poll::Pending => ControlFlow::Wait, + task::Poll::Ready(_) => ControlFlow::Exit, + }; + } + }) +} + +async fn run_instance( + mut application: A, + mut compositor: C, + mut renderer: A::Renderer, + mut runtime: Runtime, A::Message>, + mut proxy: winit::event_loop::EventLoopProxy, + mut debug: Debug, + mut receiver: mpsc::UnboundedReceiver>, + init_command: Command, + window: winit::window::Window, + exit_on_close_request: bool, +) where + A: Application + 'static, + E: Executor + 'static, + C: window::Compositor + 'static, + ::Theme: StyleSheet, +{ + use iced_futures::futures::stream::StreamExt; + use winit::event; + + let mut clipboard = Clipboard::connect(&window); + let mut cache = user_interface::Cache::default(); + let mut surface = compositor.create_surface(&window); + + let mut state = State::new(&application, &window); + let mut viewport_version = state.viewport_version(); + + let physical_size = state.physical_size(); + + compositor.configure_surface( + &mut surface, + physical_size.width, + physical_size.height, + ); + + run_command( + &application, + &mut cache, + &state, + &mut renderer, + init_command, + &mut runtime, + &mut clipboard, + &mut proxy, + &mut debug, + &window, + || compositor.fetch_information(), + ); + runtime.track(application.subscription()); + + let mut user_interface = ManuallyDrop::new(build_user_interface( + &application, + user_interface::Cache::default(), + &mut renderer, + state.logical_size(), + &mut debug, + )); + + let mut mouse_interaction = mouse::Interaction::default(); + let mut events = Vec::new(); + let mut messages = Vec::new(); + + debug.startup_finished(); + + while let Some(event) = receiver.next().await { + match event { + event::Event::MainEventsCleared => { + if events.is_empty() && messages.is_empty() { + continue; + } + + debug.event_processing_started(); + + let (interface_state, statuses) = user_interface.update( + &events, + state.cursor_position(), + &mut renderer, + &mut clipboard, + &mut messages, + ); + + debug.event_processing_finished(); + + for event in events.drain(..).zip(statuses.into_iter()) { + runtime.broadcast(event); + } + + if !messages.is_empty() + || matches!( + interface_state, + user_interface::State::Outdated, + ) + { + let mut cache = + ManuallyDrop::into_inner(user_interface).into_cache(); + + // Update application + update( + &mut application, + &mut cache, + &state, + &mut renderer, + &mut runtime, + &mut clipboard, + &mut proxy, + &mut debug, + &mut messages, + &window, + || compositor.fetch_information(), + ); + + // Update window + state.synchronize(&application, &window); + + let should_exit = application.should_exit(); + + user_interface = ManuallyDrop::new(build_user_interface( + &application, + cache, + &mut renderer, + state.logical_size(), + &mut debug, + )); + + if should_exit { + break; + } + } + + debug.draw_started(); + let new_mouse_interaction = user_interface.draw( + &mut renderer, + state.theme(), + &renderer::Style { + text_color: state.text_color(), + }, + state.cursor_position(), + ); + debug.draw_finished(); + + if new_mouse_interaction != mouse_interaction { + window.set_cursor_icon(conversion::mouse_interaction( + new_mouse_interaction, + )); + + mouse_interaction = new_mouse_interaction; + } + + window.request_redraw(); + } + event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( + event::MacOS::ReceivedUrl(url), + )) => { + use iced_native::event; + + events.push(iced_native::Event::PlatformSpecific( + event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl( + url, + )), + )); + } + event::Event::UserEvent(message) => { + messages.push(message); + } + event::Event::RedrawRequested(_) => { + let physical_size = state.physical_size(); + + if physical_size.width == 0 || physical_size.height == 0 { + continue; + } + + debug.render_started(); + 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(); + + debug.draw_started(); + let new_mouse_interaction = user_interface.draw( + &mut renderer, + state.theme(), + &renderer::Style { + text_color: state.text_color(), + }, + state.cursor_position(), + ); + + if new_mouse_interaction != mouse_interaction { + window.set_cursor_icon(conversion::mouse_interaction( + new_mouse_interaction, + )); + + mouse_interaction = new_mouse_interaction; + } + debug.draw_finished(); + + compositor.configure_surface( + &mut surface, + physical_size.width, + physical_size.height, + ); + + viewport_version = current_viewport_version; + } + + 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); + } + } + _ => {} + } + } + + // 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::event::ModifiersState, +) -> bool { + use winit::event::WindowEvent; + + match event { + WindowEvent::CloseRequested => true, + #[cfg(target_os = "macos")] + WindowEvent::KeyboardInput { + input: + winit::event::KeyboardInput { + virtual_keycode: Some(winit::event::VirtualKeyCode::Q), + state: winit::event::ElementState::Pressed, + .. + }, + .. + } if _modifiers.logo() => 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::Renderer> +where + ::Theme: StyleSheet, +{ + 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 [`Command`], and tracking its [`Subscription`]. +pub fn update( + application: &mut A, + cache: &mut user_interface::Cache, + state: &State, + renderer: &mut A::Renderer, + runtime: &mut Runtime, A::Message>, + clipboard: &mut Clipboard, + proxy: &mut winit::event_loop::EventLoopProxy, + debug: &mut Debug, + messages: &mut Vec, + window: &winit::window::Window, + graphics_info: impl FnOnce() -> compositor::Information + Copy, +) where + ::Theme: StyleSheet, +{ + for message in messages.drain(..) { + debug.log_message(&message); + + debug.update_started(); + let command = runtime.enter(|| application.update(message)); + debug.update_finished(); + + run_command( + application, + cache, + state, + renderer, + command, + runtime, + clipboard, + proxy, + debug, + window, + graphics_info, + ); + } + + let subscription = application.subscription(); + runtime.track(subscription); +} + +/// Runs the actions of a [`Command`]. +pub fn run_command( + application: &A, + cache: &mut user_interface::Cache, + state: &State, + renderer: &mut A::Renderer, + command: Command, + runtime: &mut Runtime, A::Message>, + clipboard: &mut Clipboard, + proxy: &mut winit::event_loop::EventLoopProxy, + debug: &mut Debug, + window: &winit::window::Window, + _graphics_info: impl FnOnce() -> compositor::Information + Copy, +) where + A: Application, + E: Executor, + ::Theme: StyleSheet, +{ + use iced_native::command; + use iced_native::system; + use iced_native::window; + + for action in command.actions() { + match action { + command::Action::Future(future) => { + runtime.spawn(future); + } + command::Action::Clipboard(action) => match action { + clipboard::Action::Read(tag) => { + let message = tag(clipboard.read()); + + proxy + .send_event(message) + .expect("Send message to event loop"); + } + clipboard::Action::Write(contents) => { + clipboard.write(contents); + } + }, + command::Action::Window(action) => match action { + window::Action::Resize { width, height } => { + window.set_inner_size(winit::dpi::LogicalSize { + width, + height, + }); + } + window::Action::Move { x, y } => { + window.set_outer_position(winit::dpi::LogicalPosition { + x, + y, + }); + } + window::Action::SetMode(mode) => { + window.set_visible(conversion::visible(mode)); + window.set_fullscreen(conversion::fullscreen( + window.primary_monitor(), + mode, + )); + } + window::Action::FetchMode(tag) => { + let mode = if window.is_visible().unwrap_or(true) { + conversion::mode(window.fullscreen()) + } else { + window::Mode::Hidden + }; + + proxy + .send_event(tag(mode)) + .expect("Send message to event loop"); + } + }, + command::Action::System(action) => match action { + system::Action::QueryInformation(_tag) => { + #[cfg(feature = "system")] + { + let graphics_info = _graphics_info(); + let proxy = proxy.clone(); + + let _ = std::thread::spawn(move || { + let information = + crate::system::information(graphics_info); + + let message = _tag(information); + + proxy + .send_event(message) + .expect("Send message to event loop") + }); + } + } + }, + command::Action::Widget(action) => { + let mut current_cache = std::mem::take(cache); + let mut current_operation = Some(action.into_operation()); + + let mut user_interface = build_user_interface( + application, + current_cache, + renderer, + state.logical_size(), + debug, + ); + + while let Some(mut operation) = current_operation.take() { + user_interface.operate(renderer, operation.as_mut()); + + match operation.finish() { + operation::Outcome::None => {} + operation::Outcome::Some(message) => { + proxy + .send_event(message) + .expect("Send message to event loop"); + } + operation::Outcome::Chain(next) => { + current_operation = Some(next); + } + } + } + + current_cache = user_interface.into_cache(); + *cache = current_cache; + } + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +mod platform { + pub fn run( + mut event_loop: winit::event_loop::EventLoop, + event_handler: F, + ) -> Result<(), super::Error> + where + F: 'static + + FnMut( + winit::event::Event<'_, T>, + &winit::event_loop::EventLoopWindowTarget, + &mut winit::event_loop::ControlFlow, + ), + { + use winit::platform::run_return::EventLoopExtRunReturn; + + let _ = event_loop.run_return(event_handler); + + Ok(()) + } +} + +#[cfg(target_arch = "wasm32")] +mod platform { + pub fn run( + event_loop: winit::event_loop::EventLoop, + event_handler: F, + ) -> ! + where + F: 'static + + FnMut( + winit::event::Event<'_, T>, + &winit::event_loop::EventLoopWindowTarget, + &mut winit::event_loop::ControlFlow, + ), + { + event_loop.run(event_handler) + } +} diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs new file mode 100644 index 00000000..2d120ca1 --- /dev/null +++ b/winit/src/multi_window/state.rs @@ -0,0 +1,212 @@ +use crate::application::{self, StyleSheet as _}; +use crate::conversion; +use crate::multi_window::Application; +use crate::{Color, Debug, Point, Size, Viewport}; + +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 +where + ::Theme: application::StyleSheet, +{ + title: String, + scale_factor: f64, + viewport: Viewport, + viewport_version: usize, + cursor_position: winit::dpi::PhysicalPosition, + modifiers: winit::event::ModifiersState, + theme: ::Theme, + appearance: application::Appearance, + application: PhantomData, +} + +impl State +where + ::Theme: application::StyleSheet, +{ + /// 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 = theme.appearance(application.style()); + + 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, + // TODO: Encode cursor availability in the type-system + cursor_position: winit::dpi::PhysicalPosition::new(-1.0, -1.0), + modifiers: winit::event::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 { + self.viewport.physical_size() + } + + /// Returns the logical [`Size`] of the [`Viewport`] of the [`State`]. + pub fn logical_size(&self) -> Size { + 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_position(&self) -> Point { + conversion::cursor_position( + self.cursor_position, + self.viewport.scale_factor(), + ) + } + + /// Returns the current keyboard modifiers of the [`State`]. + pub fn modifiers(&self) -> winit::event::ModifiersState { + self.modifiers + } + + /// Returns the current theme of the [`State`]. + pub fn theme(&self) -> &::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, + new_inner_size, + } => { + let size = + Size::new(new_inner_size.width, new_inner_size.height); + + 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 = *position; + } + WindowEvent::CursorLeft { .. } => { + // TODO: Encode cursor availability in the type-system + self.cursor_position = + winit::dpi::PhysicalPosition::new(-1.0, -1.0); + } + WindowEvent::ModifiersChanged(new_modifiers) => { + self.modifiers = *new_modifiers; + } + #[cfg(feature = "debug")] + WindowEvent::KeyboardInput { + input: + winit::event::KeyboardInput { + virtual_keycode: Some(winit::event::VirtualKeyCode::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 [`Application::update`]. + /// + /// [`Application::update`]: crate::Program::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 + let new_scale_factor = application.scale_factor(); + + if self.scale_factor != new_scale_factor { + let size = window.inner_size(); + + self.viewport = Viewport::with_physical_size( + Size::new(size.width, size.height), + window.scale_factor() * new_scale_factor, + ); + + self.scale_factor = new_scale_factor; + } + + // Update theme and appearance + self.theme = application.theme(); + self.appearance = self.theme.appearance(application.style()); + } +} -- cgit From b896e41c6e03f1447419194ce41d15fb0db39d96 Mon Sep 17 00:00:00 2001 From: Richard Date: Fri, 1 Apr 2022 17:39:08 -0300 Subject: Unify `Application` and `Program` Instead of creating a separate `multi_window::Program`, the new `multi_window::Application` unifies both traits --- winit/src/multi_window.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 4ea7383a..b519e471 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -9,15 +9,14 @@ use crate::mouse; use crate::renderer; use crate::widget::operation; use crate::{ - Command, Debug, Error, Executor, Proxy, Runtime, Settings, Size, - Subscription, + Command, Debug, Element, Error, Executor, Proxy, Renderer, Runtime, + Settings, Size, Subscription, }; use iced_futures::futures; use iced_futures::futures::channel::mpsc; use iced_graphics::compositor; use iced_graphics::window; -use iced_native::program::Program; use iced_native::user_interface::{self, UserInterface}; pub use iced_native::application::{Appearance, StyleSheet}; @@ -35,13 +34,34 @@ use std::mem::ManuallyDrop; /// /// When using an [`Application`] with the `debug` feature enabled, a debug view /// can be toggled by pressing `F12`. -pub trait Application: Program +pub trait Application: Sized where ::Theme: StyleSheet, { /// The data needed to initialize your [`Application`]. type Flags; + /// The graphics backend to use to draw the [`Program`]. + type Renderer: Renderer; + + /// The type of __messages__ your [`Program`] will produce. + type Message: std::fmt::Debug + Send; + + /// Handles a __message__ and updates the state of the [`Program`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by either user interactions or commands, will be handled by + /// this method. + /// + /// Any [`Command`] returned will be executed immediately in the + /// background by shells. + fn update(&mut self, message: Self::Message) -> Command; + + /// Returns the widgets to display in the [`Program`]. + /// + /// These widgets can produce __messages__ based on user interaction. + fn view(&self) -> Element<'_, Self::Message, Self::Renderer>; + /// Initializes the [`Application`] with the flags provided to /// [`run`] as part of the [`Settings`]. /// -- cgit From 12538d3c5be08f2109f1dc61936ceb2fda0fc5c6 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 15 Jun 2022 16:46:37 -0300 Subject: Use map of windows internally --- winit/src/multi_window.rs | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index b519e471..8a967207 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -21,6 +21,7 @@ use iced_native::user_interface::{self, UserInterface}; pub use iced_native::application::{Appearance, StyleSheet}; +use std::collections::HashMap; use std::mem::ManuallyDrop; /// An interactive, native cross-platform application. @@ -169,6 +170,10 @@ where .build(&event_loop) .map_err(Error::WindowCreationFailed)?; + let windows: HashMap = + HashMap::from([(0usize, window)]); + let window = windows.values().next().expect("No window found"); + #[cfg(target_arch = "wasm32")] { use winit::platform::web::WindowExtWebSys; @@ -197,7 +202,7 @@ where debug, receiver, init_command, - window, + windows, settings.exit_on_close_request, )); @@ -247,7 +252,7 @@ async fn run_instance( mut debug: Debug, mut receiver: mpsc::UnboundedReceiver>, init_command: Command, - window: winit::window::Window, + windows: HashMap, exit_on_close_request: bool, ) where A: Application + 'static, @@ -258,11 +263,12 @@ async fn run_instance( use iced_futures::futures::stream::StreamExt; use winit::event; - let mut clipboard = Clipboard::connect(&window); + let window = windows.values().next().expect("No window found"); + let mut clipboard = Clipboard::connect(window); let mut cache = user_interface::Cache::default(); let mut surface = compositor.create_surface(&window); - let mut state = State::new(&application, &window); + let mut state = State::new(&application, window); let mut viewport_version = state.viewport_version(); let physical_size = state.physical_size(); @@ -283,7 +289,7 @@ async fn run_instance( &mut clipboard, &mut proxy, &mut debug, - &window, + &windows, || compositor.fetch_information(), ); runtime.track(application.subscription()); @@ -345,12 +351,12 @@ async fn run_instance( &mut proxy, &mut debug, &mut messages, - &window, + &windows, || compositor.fetch_information(), ); // Update window - state.synchronize(&application, &window); + state.synchronize(&application, window); let should_exit = application.should_exit(); @@ -487,7 +493,7 @@ async fn run_instance( break; } - state.update(&window, &window_event, &mut debug); + state.update(window, &window_event, &mut debug); if let Some(event) = conversion::window_event( &window_event, @@ -564,7 +570,7 @@ pub fn update( proxy: &mut winit::event_loop::EventLoopProxy, debug: &mut Debug, messages: &mut Vec, - window: &winit::window::Window, + windows: &HashMap, graphics_info: impl FnOnce() -> compositor::Information + Copy, ) where ::Theme: StyleSheet, @@ -586,7 +592,7 @@ pub fn update( clipboard, proxy, debug, - window, + windows, graphics_info, ); } @@ -606,7 +612,7 @@ pub fn run_command( clipboard: &mut Clipboard, proxy: &mut winit::event_loop::EventLoopProxy, debug: &mut Debug, - window: &winit::window::Window, + windows: &HashMap, _graphics_info: impl FnOnce() -> compositor::Information + Copy, ) where A: Application, @@ -617,6 +623,8 @@ pub fn run_command( use iced_native::system; use iced_native::window; + let window = windows.values().next().expect("No window found"); + for action in command.actions() { match action { command::Action::Future(future) => { -- cgit From 5919325d9b9ebf86f7358059201e6fc1af2412d8 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 15 Jun 2022 20:01:50 -0300 Subject: Internally wrap `Message` with a `Event` enum --- winit/src/multi_window.rs | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 8a967207..61984b93 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -13,8 +13,8 @@ use crate::{ Settings, Size, Subscription, }; -use iced_futures::futures; use iced_futures::futures::channel::mpsc; +use iced_futures::futures::{self, FutureExt}; use iced_graphics::compositor; use iced_graphics::window; use iced_native::user_interface::{self, UserInterface}; @@ -24,6 +24,16 @@ pub use iced_native::application::{Appearance, StyleSheet}; use std::collections::HashMap; use std::mem::ManuallyDrop; +/// TODO(derezzedex) +// This is the an wrapper around the `Application::Message` associate type +// to allows the `shell` to create internal messages, while still having +// the current user specified custom messages. +#[derive(Debug, Clone)] +pub enum Event { + /// An [`Application`] generated message + Application(Message), +} + /// An interactive, native cross-platform application. /// /// This trait is the main entrypoint of Iced. Once implemented, you can run @@ -247,10 +257,12 @@ async fn run_instance( mut application: A, mut compositor: C, mut renderer: A::Renderer, - mut runtime: Runtime, A::Message>, - mut proxy: winit::event_loop::EventLoopProxy, + mut runtime: Runtime>, Event>, + mut proxy: winit::event_loop::EventLoopProxy>, mut debug: Debug, - mut receiver: mpsc::UnboundedReceiver>, + mut receiver: mpsc::UnboundedReceiver< + winit::event::Event<'_, Event>, + >, init_command: Command, windows: HashMap, exit_on_close_request: bool, @@ -292,7 +304,7 @@ async fn run_instance( &windows, || compositor.fetch_information(), ); - runtime.track(application.subscription()); + runtime.track(application.subscription().map(Event::Application)); let mut user_interface = ManuallyDrop::new(build_user_interface( &application, @@ -406,6 +418,7 @@ async fn run_instance( )); } event::Event::UserEvent(message) => { + let Event::Application(message) = message; messages.push(message); } event::Event::RedrawRequested(_) => { @@ -565,9 +578,9 @@ pub fn update( cache: &mut user_interface::Cache, state: &State, renderer: &mut A::Renderer, - runtime: &mut Runtime, A::Message>, + runtime: &mut Runtime>, Event>, clipboard: &mut Clipboard, - proxy: &mut winit::event_loop::EventLoopProxy, + proxy: &mut winit::event_loop::EventLoopProxy>, debug: &mut Debug, messages: &mut Vec, windows: &HashMap, @@ -597,7 +610,7 @@ pub fn update( ); } - let subscription = application.subscription(); + let subscription = application.subscription().map(Event::Application); runtime.track(subscription); } @@ -608,9 +621,9 @@ pub fn run_command( state: &State, renderer: &mut A::Renderer, command: Command, - runtime: &mut Runtime, A::Message>, + runtime: &mut Runtime>, Event>, clipboard: &mut Clipboard, - proxy: &mut winit::event_loop::EventLoopProxy, + proxy: &mut winit::event_loop::EventLoopProxy>, debug: &mut Debug, windows: &HashMap, _graphics_info: impl FnOnce() -> compositor::Information + Copy, @@ -628,14 +641,14 @@ pub fn run_command( for action in command.actions() { match action { command::Action::Future(future) => { - runtime.spawn(future); + runtime.spawn(Box::pin(future.map(Event::Application))); } command::Action::Clipboard(action) => match action { clipboard::Action::Read(tag) => { let message = tag(clipboard.read()); proxy - .send_event(message) + .send_event(Event::Application(message)) .expect("Send message to event loop"); } clipboard::Action::Write(contents) => { @@ -670,7 +683,7 @@ pub fn run_command( }; proxy - .send_event(tag(mode)) + .send_event(Event::Application(tag(mode))) .expect("Send message to event loop"); } }, @@ -688,7 +701,7 @@ pub fn run_command( let message = _tag(information); proxy - .send_event(message) + .send_event(Event::Application(message)) .expect("Send message to event loop") }); } @@ -713,7 +726,7 @@ pub fn run_command( operation::Outcome::None => {} operation::Outcome::Some(message) => { proxy - .send_event(message) + .send_event(Event::Application(message)) .expect("Send message to event loop"); } operation::Outcome::Chain(next) => { -- cgit From 00d6baf861ba57984a341283823e9fea3c262130 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 15 Jun 2022 20:10:15 -0300 Subject: fix: temporalily remove the unsafe pointer `HWND` --- winit/src/settings.rs | 6 +++--- winit/src/settings/windows.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'winit/src') diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 9bbdef5c..94d243a7 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -154,9 +154,9 @@ impl Window { { use winit::platform::windows::WindowBuilderExtWindows; - if let Some(parent) = self.platform_specific.parent { - window_builder = window_builder.with_parent_window(parent); - } + // if let Some(parent) = self.platform_specific.parent { + // window_builder = window_builder.with_parent_window(parent); + // } window_builder = window_builder .with_drag_and_drop(self.platform_specific.drag_and_drop); diff --git a/winit/src/settings/windows.rs b/winit/src/settings/windows.rs index ff03a9c5..0891ec2c 100644 --- a/winit/src/settings/windows.rs +++ b/winit/src/settings/windows.rs @@ -4,7 +4,7 @@ #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PlatformSpecific { /// Parent window - pub parent: Option, + // pub parent: Option, /// Drag and drop support pub drag_and_drop: bool, @@ -13,7 +13,7 @@ pub struct PlatformSpecific { impl Default for PlatformSpecific { fn default() -> Self { Self { - parent: None, + // parent: None, drag_and_drop: true, } } -- cgit From 8fdd5ee8b60e551088d4a18fb1d58b6c3e62ba7d Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 15 Jun 2022 20:38:51 -0300 Subject: Synchronize window list with `windows` method --- winit/src/multi_window.rs | 72 +++++++++++++++++++++++++++++++++-------- winit/src/multi_window/state.rs | 22 +++++++++++-- 2 files changed, 79 insertions(+), 15 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 61984b93..900ee92a 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -7,6 +7,7 @@ use crate::clipboard::{self, Clipboard}; use crate::conversion; use crate::mouse; use crate::renderer; +use crate::settings; use crate::widget::operation; use crate::{ Command, Debug, Element, Error, Executor, Proxy, Renderer, Runtime, @@ -28,10 +29,17 @@ use std::mem::ManuallyDrop; // This is the an wrapper around the `Application::Message` associate type // to allows the `shell` to create internal messages, while still having // the current user specified custom messages. -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum Event { /// An [`Application`] generated message Application(Message), + + /// TODO(derezzedex) + // Create a wrapper variant of `window::Event` type instead + // (maybe we should also allow users to listen/react to those internal messages?) + NewWindow(usize, settings::Window), + /// TODO(derezzedex) + WindowCreated(usize, winit::window::Window), } /// An interactive, native cross-platform application. @@ -218,7 +226,7 @@ where let mut context = task::Context::from_waker(task::noop_waker_ref()); - platform::run(event_loop, move |event, _, control_flow| { + platform::run(event_loop, move |event, event_loop, control_flow| { use winit::event_loop::ControlFlow; if let ControlFlow::ExitWithCode(_) = control_flow { @@ -237,6 +245,21 @@ where event: winit::event::WindowEvent::Resized(*new_inner_size), window_id, }), + winit::event::Event::UserEvent(Event::NewWindow(id, settings)) => { + // TODO(derezzedex) + let window = settings + .into_builder( + "fix window title", + event_loop.primary_monitor(), + None, + ) + .build(event_loop) + .expect("Failed to build window"); + + Some(winit::event::Event::UserEvent(Event::WindowCreated( + id, window, + ))) + } _ => event.to_static(), }; @@ -264,7 +287,7 @@ async fn run_instance( winit::event::Event<'_, Event>, >, init_command: Command, - windows: HashMap, + mut windows: HashMap, exit_on_close_request: bool, ) where A: Application + 'static, @@ -275,12 +298,18 @@ async fn run_instance( use iced_futures::futures::stream::StreamExt; use winit::event; - let window = windows.values().next().expect("No window found"); - let mut clipboard = Clipboard::connect(window); + // TODO(derezzedex) + let mut clipboard = + Clipboard::connect(windows.values().next().expect("No window found")); let mut cache = user_interface::Cache::default(); - let mut surface = compositor.create_surface(&window); + let mut surface = compositor + .create_surface(&windows.values().next().expect("No window found")); - let mut state = State::new(&application, window); + // TODO(derezzedex) + let mut state = State::new( + &application, + windows.values().next().expect("No window found"), + ); let mut viewport_version = state.viewport_version(); let physical_size = state.physical_size(); @@ -368,7 +397,7 @@ async fn run_instance( ); // Update window - state.synchronize(&application, window); + state.synchronize(&application, &windows, &proxy); let should_exit = application.should_exit(); @@ -396,6 +425,8 @@ async fn run_instance( ); debug.draw_finished(); + // TODO(derezzedex) + let window = windows.values().next().expect("No window found"); if new_mouse_interaction != mouse_interaction { window.set_cursor_icon(conversion::mouse_interaction( new_mouse_interaction, @@ -417,10 +448,15 @@ async fn run_instance( )), )); } - event::Event::UserEvent(message) => { - let Event::Application(message) = message; - messages.push(message); - } + event::Event::UserEvent(event) => match event { + Event::Application(message) => { + messages.push(message); + } + Event::WindowCreated(id, window) => { + let _ = windows.insert(id, window); + } + Event::NewWindow(_, _) => unreachable!(), + }, event::Event::RedrawRequested(_) => { let physical_size = state.physical_size(); @@ -451,6 +487,9 @@ async fn run_instance( state.cursor_position(), ); + // TODO(derezzedex) + let window = + windows.values().next().expect("No window found"); if new_mouse_interaction != mouse_interaction { window.set_cursor_icon(conversion::mouse_interaction( new_mouse_interaction, @@ -491,7 +530,12 @@ async fn run_instance( debug.render_finished(); // Try rendering again next frame. - window.request_redraw(); + // TODO(derezzedex) + windows + .values() + .next() + .expect("No window found") + .request_redraw(); } }, } @@ -506,6 +550,8 @@ async fn run_instance( break; } + // TODO(derezzedex) + let window = windows.values().next().expect("No window found"); state.update(window, &window_event, &mut debug); if let Some(event) = conversion::window_event( diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs index 2d120ca1..009a3698 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/multi_window/state.rs @@ -1,10 +1,12 @@ use crate::application::{self, StyleSheet as _}; use crate::conversion; -use crate::multi_window::Application; +use crate::multi_window::{Application, Event}; use crate::{Color, Debug, Point, Size, Viewport}; +use std::collections::HashMap; use std::marker::PhantomData; use winit::event::{Touch, WindowEvent}; +use winit::event_loop::EventLoopProxy; use winit::window::Window; /// The state of a windowed [`Application`]. @@ -181,7 +183,23 @@ where /// and window after calling [`Application::update`]. /// /// [`Application::update`]: crate::Program::update - pub fn synchronize(&mut self, application: &A, window: &Window) { + pub fn synchronize( + &mut self, + application: &A, + windows: &HashMap, + proxy: &EventLoopProxy>, + ) { + let new_windows = application.windows(); + for (id, settings) in new_windows { + if !windows.contains_key(&id) { + proxy + .send_event(Event::NewWindow(id, settings)) + .expect("Failed to send message"); + } + } + + let window = windows.values().next().expect("No window found"); + // Update window title let new_title = application.title(); -- cgit From ec56c0686df1a200e37af951a3a8eca562c32a5c Mon Sep 17 00:00:00 2001 From: Richard Date: Tue, 21 Jun 2022 15:59:45 -0300 Subject: Introduce opaque `window::Id` type --- winit/src/multi_window.rs | 23 +++++++++++++---------- winit/src/multi_window/state.rs | 3 ++- winit/src/window.rs | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 900ee92a..14be4de3 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -9,6 +9,7 @@ use crate::mouse; use crate::renderer; use crate::settings; use crate::widget::operation; +use crate::window; use crate::{ Command, Debug, Element, Error, Executor, Proxy, Renderer, Runtime, Settings, Size, Subscription, @@ -17,7 +18,6 @@ use crate::{ use iced_futures::futures::channel::mpsc; use iced_futures::futures::{self, FutureExt}; use iced_graphics::compositor; -use iced_graphics::window; use iced_native::user_interface::{self, UserInterface}; pub use iced_native::application::{Appearance, StyleSheet}; @@ -37,9 +37,9 @@ pub enum Event { /// TODO(derezzedex) // Create a wrapper variant of `window::Event` type instead // (maybe we should also allow users to listen/react to those internal messages?) - NewWindow(usize, settings::Window), + NewWindow(window::Id, settings::Window), /// TODO(derezzedex) - WindowCreated(usize, winit::window::Window), + WindowCreated(window::Id, winit::window::Window), } /// An interactive, native cross-platform application. @@ -66,6 +66,9 @@ where /// The type of __messages__ your [`Program`] will produce. type Message: std::fmt::Debug + Send; + /// TODO(derezzedex) + fn windows(&self) -> Vec<(window::Id, settings::Window)>; + /// Handles a __message__ and updates the state of the [`Program`]. /// /// This is where you define your __update logic__. All the __messages__, @@ -150,7 +153,7 @@ pub fn run( where A: Application + 'static, E: Executor + 'static, - C: window::Compositor + 'static, + C: iced_graphics::window::Compositor + 'static, ::Theme: StyleSheet, { use futures::task; @@ -188,8 +191,8 @@ where .build(&event_loop) .map_err(Error::WindowCreationFailed)?; - let windows: HashMap = - HashMap::from([(0usize, window)]); + let windows: HashMap = + HashMap::from([(window::Id::new(0usize), window)]); let window = windows.values().next().expect("No window found"); #[cfg(target_arch = "wasm32")] @@ -287,12 +290,12 @@ async fn run_instance( winit::event::Event<'_, Event>, >, init_command: Command, - mut windows: HashMap, + mut windows: HashMap, exit_on_close_request: bool, ) where A: Application + 'static, E: Executor + 'static, - C: window::Compositor + 'static, + C: iced_graphics::window::Compositor + 'static, ::Theme: StyleSheet, { use iced_futures::futures::stream::StreamExt; @@ -629,7 +632,7 @@ pub fn update( proxy: &mut winit::event_loop::EventLoopProxy>, debug: &mut Debug, messages: &mut Vec, - windows: &HashMap, + windows: &HashMap, graphics_info: impl FnOnce() -> compositor::Information + Copy, ) where ::Theme: StyleSheet, @@ -671,7 +674,7 @@ pub fn run_command( clipboard: &mut Clipboard, proxy: &mut winit::event_loop::EventLoopProxy>, debug: &mut Debug, - windows: &HashMap, + windows: &HashMap, _graphics_info: impl FnOnce() -> compositor::Information + Copy, ) where A: Application, diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs index 009a3698..dd2d25ce 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/multi_window/state.rs @@ -1,6 +1,7 @@ use crate::application::{self, StyleSheet as _}; use crate::conversion; use crate::multi_window::{Application, Event}; +use crate::window; use crate::{Color, Debug, Point, Size, Viewport}; use std::collections::HashMap; @@ -186,7 +187,7 @@ where pub fn synchronize( &mut self, application: &A, - windows: &HashMap, + windows: &HashMap, proxy: &EventLoopProxy>, ) { let new_windows = application.windows(); diff --git a/winit/src/window.rs b/winit/src/window.rs index 89db3262..f2c7037a 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -2,7 +2,7 @@ use crate::command::{self, Command}; use iced_native::window; -pub use window::{Event, Mode, UserAttention}; +pub use window::{Id, Event, Mode, UserAttention}; /// Closes the current window and exits the application. pub fn close() -> Command { -- cgit From 97914daaab477ce47a8329f07958332b5caa4ed0 Mon Sep 17 00:00:00 2001 From: Richard Date: Tue, 12 Jul 2022 10:26:16 -0300 Subject: what is this --- winit/src/multi_window.rs | 421 ++++++++++++++++++++++++++-------------- winit/src/multi_window/state.rs | 16 +- 2 files changed, 284 insertions(+), 153 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 14be4de3..82ee30ed 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -301,50 +301,63 @@ async fn run_instance( use iced_futures::futures::stream::StreamExt; use winit::event; - // TODO(derezzedex) let mut clipboard = Clipboard::connect(windows.values().next().expect("No window found")); let mut cache = user_interface::Cache::default(); - let mut surface = compositor - .create_surface(&windows.values().next().expect("No window found")); + let mut window_ids: HashMap<_, _> = windows + .iter() + .map(|(&id, window)| (window.id(), id)) + .collect(); - // TODO(derezzedex) - let mut state = State::new( - &application, - windows.values().next().expect("No window found"), - ); - let mut viewport_version = state.viewport_version(); + let mut states = HashMap::new(); + let mut interfaces = ManuallyDrop::new(HashMap::new()); - let physical_size = state.physical_size(); + for (&id, window) in windows.keys().zip(windows.values()) { + let mut surface = compositor.create_surface(window); - compositor.configure_surface( - &mut surface, - physical_size.width, - physical_size.height, - ); + let state = State::new(&application, window); - run_command( - &application, - &mut cache, - &state, - &mut renderer, - init_command, - &mut runtime, - &mut clipboard, - &mut proxy, - &mut debug, - &windows, - || compositor.fetch_information(), - ); - runtime.track(application.subscription().map(Event::Application)); + let physical_size = state.physical_size(); - let mut user_interface = ManuallyDrop::new(build_user_interface( - &application, - user_interface::Cache::default(), - &mut renderer, - state.logical_size(), - &mut debug, - )); + compositor.configure_surface( + &mut surface, + physical_size.width, + physical_size.height, + ); + + let user_interface = build_user_interface( + &application, + user_interface::Cache::default(), + &mut renderer, + state.logical_size(), + &mut debug, + ); + + let window_state: WindowState = WindowState { surface, state }; + + let _ = states.insert(id, window_state); + let _ = interfaces.insert(id, user_interface); + } + + { + // TODO(derezzedex) + let window_state = states.values().next().expect("No state found"); + + run_command( + &application, + &mut cache, + &window_state.state, + &mut renderer, + init_command, + &mut runtime, + &mut clipboard, + &mut proxy, + &mut debug, + &windows, + || compositor.fetch_information(), + ); + } + runtime.track(application.subscription().map(Event::Application)); let mut mouse_interaction = mouse::Interaction::default(); let mut events = Vec::new(); @@ -352,93 +365,118 @@ async fn run_instance( debug.startup_finished(); - while let Some(event) = receiver.next().await { + 'main: while let Some(event) = receiver.next().await { match event { event::Event::MainEventsCleared => { - if events.is_empty() && messages.is_empty() { - continue; - } - - debug.event_processing_started(); + dbg!(states.keys().collect::>()); + for id in states.keys().copied().collect::>() { + let cursor_position = + states.get(&id).unwrap().state.cursor_position(); + let window = windows.get(&id).unwrap(); + + if events.is_empty() && messages.is_empty() { + continue; + } - let (interface_state, statuses) = user_interface.update( - &events, - state.cursor_position(), - &mut renderer, - &mut clipboard, - &mut messages, - ); + debug.event_processing_started(); + + let (interface_state, statuses) = { + let user_interface = interfaces.get_mut(&id).unwrap(); + user_interface.update( + &events, + cursor_position, + &mut renderer, + &mut clipboard, + &mut messages, + ) + }; - debug.event_processing_finished(); + debug.event_processing_finished(); - for event in events.drain(..).zip(statuses.into_iter()) { - runtime.broadcast(event); - } + // TODO(derezzedex): only drain events for this window + for event in events.drain(..).zip(statuses.into_iter()) { + runtime.broadcast(event); + } - if !messages.is_empty() - || matches!( - interface_state, - user_interface::State::Outdated, - ) - { - let mut cache = - ManuallyDrop::into_inner(user_interface).into_cache(); - - // Update application - update( - &mut application, - &mut cache, - &state, - &mut renderer, - &mut runtime, - &mut clipboard, - &mut proxy, - &mut debug, - &mut messages, - &windows, - || compositor.fetch_information(), - ); + if !messages.is_empty() + || matches!( + interface_state, + user_interface::State::Outdated, + ) + { + let state = &mut states.get_mut(&id).unwrap().state; + let pure_states: HashMap<_, _> = + ManuallyDrop::into_inner(interfaces) + .drain() + .map( + |(id, interface): ( + window::Id, + UserInterface<'_, _, _>, + )| { + (id, interface.into_cache()) + }, + ) + .collect(); + + // Update application + update( + &mut application, + &mut cache, + state, + &mut renderer, + &mut runtime, + &mut clipboard, + &mut proxy, + &mut debug, + &mut messages, + &windows, + || compositor.fetch_information(), + ); + + // Update window + state.synchronize(&application, &windows, &proxy); + + let should_exit = application.should_exit(); + + interfaces = ManuallyDrop::new(build_user_interfaces( + &application, + &mut renderer, + &mut debug, + &states, + pure_states, + )); - // Update window - state.synchronize(&application, &windows, &proxy); + if should_exit { + break 'main; + } + } - let should_exit = application.should_exit(); + debug.draw_started(); + let new_mouse_interaction = { + let user_interface = interfaces.get_mut(&id).unwrap(); + let state = &states.get(&id).unwrap().state; + + user_interface.draw( + &mut renderer, + state.theme(), + &renderer::Style { + text_color: state.text_color(), + }, + state.cursor_position(), + ) + }; + debug.draw_finished(); - user_interface = ManuallyDrop::new(build_user_interface( - &application, - cache, - &mut renderer, - state.logical_size(), - &mut debug, - )); + if new_mouse_interaction != mouse_interaction { + window.set_cursor_icon(conversion::mouse_interaction( + new_mouse_interaction, + )); - if should_exit { - break; + mouse_interaction = new_mouse_interaction; } - } - - debug.draw_started(); - let new_mouse_interaction = user_interface.draw( - &mut renderer, - state.theme(), - &renderer::Style { - text_color: state.text_color(), - }, - state.cursor_position(), - ); - debug.draw_finished(); - - // TODO(derezzedex) - let window = windows.values().next().expect("No window found"); - if new_mouse_interaction != mouse_interaction { - window.set_cursor_icon(conversion::mouse_interaction( - new_mouse_interaction, - )); - mouse_interaction = new_mouse_interaction; + window.request_redraw(); } - - window.request_redraw(); } event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( event::MacOS::ReceivedUrl(url), @@ -456,43 +494,81 @@ async fn run_instance( messages.push(message); } Event::WindowCreated(id, window) => { + let mut surface = compositor.create_surface(&window); + + let state = State::new(&application, &window); + + let physical_size = state.physical_size(); + + compositor.configure_surface( + &mut surface, + physical_size.width, + physical_size.height, + ); + + let user_interface = build_user_interface( + &application, + user_interface::Cache::default(), + &mut renderer, + state.logical_size(), + &mut debug, + ); + + let window_state: WindowState = + WindowState { surface, state }; + + let _ = states.insert(id, window_state); + let _ = interfaces.insert(id, user_interface); + let _ = window_ids.insert(window.id(), id); let _ = windows.insert(id, window); } Event::NewWindow(_, _) => unreachable!(), }, - event::Event::RedrawRequested(_) => { - let physical_size = state.physical_size(); + event::Event::RedrawRequested(id) => { + let window_state = window_ids + .get(&id) + .and_then(|id| states.get_mut(id)) + .unwrap(); + + let mut user_interface = window_ids + .get(&id) + .and_then(|id| interfaces.remove(id)) + .unwrap(); + + let physical_size = window_state.state.physical_size(); if physical_size.width == 0 || physical_size.height == 0 { continue; } debug.render_started(); - let current_viewport_version = state.viewport_version(); - if viewport_version != current_viewport_version { - let logical_size = state.logical_size(); + if window_state.state.viewport_changed() { + let logical_size = window_state.state.logical_size(); debug.layout_started(); - user_interface = ManuallyDrop::new( - ManuallyDrop::into_inner(user_interface) - .relayout(logical_size, &mut renderer), - ); + user_interface = + user_interface.relayout(logical_size, &mut renderer); debug.layout_finished(); debug.draw_started(); - let new_mouse_interaction = user_interface.draw( - &mut renderer, - state.theme(), - &renderer::Style { - text_color: state.text_color(), - }, - state.cursor_position(), - ); + let new_mouse_interaction = { + let state = &window_state.state; + + user_interface.draw( + &mut renderer, + state.theme(), + &renderer::Style { + text_color: state.text_color(), + }, + state.cursor_position(), + ) + }; - // TODO(derezzedex) - let window = - windows.values().next().expect("No window found"); + let window = window_ids + .get(&id) + .and_then(|id| windows.get(id)) + .unwrap(); if new_mouse_interaction != mouse_interaction { window.set_cursor_icon(conversion::mouse_interaction( new_mouse_interaction, @@ -502,20 +578,21 @@ async fn run_instance( } debug.draw_finished(); + let _ = interfaces + .insert(*window_ids.get(&id).unwrap(), user_interface); + compositor.configure_surface( - &mut surface, + &mut window_state.surface, physical_size.width, physical_size.height, ); - - viewport_version = current_viewport_version; } match compositor.present( &mut renderer, - &mut surface, - state.viewport(), - state.background_color(), + &mut window_state.surface, + window_state.state.viewport(), + window_state.state.background_color(), &debug.overlay(), ) { Ok(()) => { @@ -545,22 +622,30 @@ async fn run_instance( } event::Event::WindowEvent { event: window_event, - .. + window_id, } => { - if requests_exit(&window_event, state.modifiers()) + // dbg!(window_id); + let window = window_ids + .get(&window_id) + .and_then(|id| windows.get(id)) + .unwrap(); + let window_state = window_ids + .get(&window_id) + .and_then(|id| states.get_mut(id)) + .unwrap(); + + if requests_exit(&window_event, window_state.state.modifiers()) && exit_on_close_request { break; } - // TODO(derezzedex) - let window = windows.values().next().expect("No window found"); - state.update(window, &window_event, &mut debug); + window_state.state.update(window, &window_event, &mut debug); if let Some(event) = conversion::window_event( &window_event, - state.scale_factor(), - state.modifiers(), + window_state.state.scale_factor(), + window_state.state.modifiers(), ) { events.push(event); } @@ -570,7 +655,7 @@ async fn run_instance( } // Manually drop the user interface - drop(ManuallyDrop::into_inner(user_interface)); + // drop(ManuallyDrop::into_inner(user_interface)); } /// Returns true if the provided event should cause an [`Application`] to @@ -791,6 +876,54 @@ pub fn run_command( } } +struct WindowState +where + A: Application, + C: iced_graphics::window::Compositor, + ::Theme: StyleSheet, +{ + surface: ::Surface, + state: State, +} + +fn build_user_interfaces<'a, A, C>( + application: &'a A, + renderer: &mut A::Renderer, + debug: &mut Debug, + states: &HashMap>, + mut pure_states: HashMap, +) -> HashMap< + window::Id, + UserInterface< + 'a, + ::Message, + ::Renderer, + >, +> +where + A: Application + 'static, + C: iced_graphics::window::Compositor + 'static, + ::Theme: StyleSheet, +{ + let mut interfaces = HashMap::new(); + + for (id, pure_state) in pure_states.drain() { + let state = &states.get(&id).unwrap().state; + + let user_interface = build_user_interface( + application, + pure_state, + renderer, + state.logical_size(), + debug, + ); + + let _ = interfaces.insert(id, user_interface); + } + + interfaces +} + #[cfg(not(target_arch = "wasm32"))] mod platform { pub fn run( diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs index dd2d25ce..d22de961 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/multi_window/state.rs @@ -19,7 +19,7 @@ where title: String, scale_factor: f64, viewport: Viewport, - viewport_version: usize, + viewport_changed: bool, cursor_position: winit::dpi::PhysicalPosition, modifiers: winit::event::ModifiersState, theme: ::Theme, @@ -51,7 +51,7 @@ where title, scale_factor, viewport, - viewport_version: 0, + viewport_changed: false, // TODO: Encode cursor availability in the type-system cursor_position: winit::dpi::PhysicalPosition::new(-1.0, -1.0), modifiers: winit::event::ModifiersState::default(), @@ -66,11 +66,9 @@ where &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 + /// TODO(derezzedex) + pub fn viewport_changed(&self) -> bool { + self.viewport_changed } /// Returns the physical [`Size`] of the [`Viewport`] of the [`State`]. @@ -133,7 +131,7 @@ where window.scale_factor() * self.scale_factor, ); - self.viewport_version = self.viewport_version.wrapping_add(1); + self.viewport_changed = true; } WindowEvent::ScaleFactorChanged { scale_factor: new_scale_factor, @@ -147,7 +145,7 @@ where new_scale_factor * self.scale_factor, ); - self.viewport_version = self.viewport_version.wrapping_add(1); + self.viewport_changed = true; } WindowEvent::CursorMoved { position, .. } | WindowEvent::Touch(Touch { -- cgit From 8f53df560e1bde33e874977e5115cd0f9301640d Mon Sep 17 00:00:00 2001 From: Richard Date: Thu, 14 Jul 2022 10:13:37 -0300 Subject: fix: temporarily add `window::Id` to events internaly --- winit/src/multi_window.rs | 53 +++++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 18 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 82ee30ed..6c2dc888 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -368,13 +368,27 @@ async fn run_instance( 'main: while let Some(event) = receiver.next().await { match event { event::Event::MainEventsCleared => { - dbg!(states.keys().collect::>()); for id in states.keys().copied().collect::>() { + let (filtered, remaining): (Vec<_>, Vec<_>) = events + .iter() + .cloned() + .partition( + |(window_id, _event): &( + Option, + iced_native::event::Event, + )| { + *window_id == Some(id) || *window_id == None + }, + ); + + events.retain(|el| remaining.contains(el)); + let filtered: Vec<_> = filtered.into_iter().map(|(_id, event)| event.clone()).collect(); + let cursor_position = states.get(&id).unwrap().state.cursor_position(); let window = windows.get(&id).unwrap(); - if events.is_empty() && messages.is_empty() { + if filtered.is_empty() && messages.is_empty() { continue; } @@ -383,7 +397,7 @@ async fn run_instance( let (interface_state, statuses) = { let user_interface = interfaces.get_mut(&id).unwrap(); user_interface.update( - &events, + &filtered, cursor_position, &mut renderer, &mut clipboard, @@ -393,11 +407,11 @@ async fn run_instance( debug.event_processing_finished(); - // TODO(derezzedex): only drain events for this window - for event in events.drain(..).zip(statuses.into_iter()) { + for event in filtered.into_iter().zip(statuses.into_iter()) { runtime.broadcast(event); } + // TODO(derezzedex): Should we redraw every window? We can't know what changed. if !messages.is_empty() || matches!( interface_state, @@ -475,18 +489,22 @@ async fn run_instance( mouse_interaction = new_mouse_interaction; } - window.request_redraw(); + for window in windows.values(){ + window.request_redraw(); + } } } event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( event::MacOS::ReceivedUrl(url), )) => { use iced_native::event; - - events.push(iced_native::Event::PlatformSpecific( - event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl( - url, - )), + events.push(( + None, + iced_native::Event::PlatformSpecific( + event::PlatformSpecific::MacOS( + event::MacOS::ReceivedUrl(url), + ), + ), )); } event::Event::UserEvent(event) => match event { @@ -529,12 +547,6 @@ async fn run_instance( .get(&id) .and_then(|id| states.get_mut(id)) .unwrap(); - - let mut user_interface = window_ids - .get(&id) - .and_then(|id| interfaces.remove(id)) - .unwrap(); - let physical_size = window_state.state.physical_size(); if physical_size.width == 0 || physical_size.height == 0 { @@ -544,6 +556,11 @@ async fn run_instance( debug.render_started(); if window_state.state.viewport_changed() { + let mut user_interface = window_ids + .get(&id) + .and_then(|id| interfaces.remove(id)) + .unwrap(); + let logical_size = window_state.state.logical_size(); debug.layout_started(); @@ -647,7 +664,7 @@ async fn run_instance( window_state.state.scale_factor(), window_state.state.modifiers(), ) { - events.push(event); + events.push((window_ids.get(&window_id).cloned(), event)); } } _ => {} -- cgit From 2fe58e12619186eb3755491db2bdaf02de297afb Mon Sep 17 00:00:00 2001 From: Richard Date: Thu, 21 Jul 2022 09:52:32 -0300 Subject: add `window::Id` to `view` --- winit/src/multi_window.rs | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 6c2dc888..dc00d737 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -82,7 +82,10 @@ where /// Returns the widgets to display in the [`Program`]. /// /// These widgets can produce __messages__ based on user interaction. - fn view(&self) -> Element<'_, Self::Message, Self::Renderer>; + fn view( + &self, + window: window::Id, + ) -> Element<'_, Self::Message, Self::Renderer>; /// Initializes the [`Application`] with the flags provided to /// [`run`] as part of the [`Settings`]. @@ -331,6 +334,7 @@ async fn run_instance( &mut renderer, state.logical_size(), &mut debug, + id, ); let window_state: WindowState = WindowState { surface, state }; @@ -354,6 +358,7 @@ async fn run_instance( &mut proxy, &mut debug, &windows, + &window_ids, || compositor.fetch_information(), ); } @@ -369,10 +374,8 @@ async fn run_instance( match event { event::Event::MainEventsCleared => { for id in states.keys().copied().collect::>() { - let (filtered, remaining): (Vec<_>, Vec<_>) = events - .iter() - .cloned() - .partition( + let (filtered, remaining): (Vec<_>, Vec<_>) = + events.iter().cloned().partition( |(window_id, _event): &( Option, iced_native::event::Event, @@ -382,7 +385,10 @@ async fn run_instance( ); events.retain(|el| remaining.contains(el)); - let filtered: Vec<_> = filtered.into_iter().map(|(_id, event)| event.clone()).collect(); + let filtered: Vec<_> = filtered + .into_iter() + .map(|(_id, event)| event) + .collect(); let cursor_position = states.get(&id).unwrap().state.cursor_position(); @@ -407,7 +413,8 @@ async fn run_instance( debug.event_processing_finished(); - for event in filtered.into_iter().zip(statuses.into_iter()) { + for event in filtered.into_iter().zip(statuses.into_iter()) + { runtime.broadcast(event); } @@ -444,6 +451,7 @@ async fn run_instance( &mut debug, &mut messages, &windows, + &window_ids, || compositor.fetch_information(), ); @@ -489,7 +497,7 @@ async fn run_instance( mouse_interaction = new_mouse_interaction; } - for window in windows.values(){ + for window in windows.values() { window.request_redraw(); } } @@ -530,6 +538,7 @@ async fn run_instance( &mut renderer, state.logical_size(), &mut debug, + id, ); let window_state: WindowState = @@ -707,12 +716,13 @@ pub fn build_user_interface<'a, A: Application>( renderer: &mut A::Renderer, size: Size, debug: &mut Debug, + id: window::Id, ) -> UserInterface<'a, A::Message, A::Renderer> where ::Theme: StyleSheet, { debug.view_started(); - let view = application.view(); + let view = application.view(id); debug.view_finished(); debug.layout_started(); @@ -735,6 +745,7 @@ pub fn update( debug: &mut Debug, messages: &mut Vec, windows: &HashMap, + window_ids: &HashMap, graphics_info: impl FnOnce() -> compositor::Information + Copy, ) where ::Theme: StyleSheet, @@ -757,6 +768,7 @@ pub fn update( proxy, debug, windows, + window_ids, graphics_info, ); } @@ -777,6 +789,7 @@ pub fn run_command( proxy: &mut winit::event_loop::EventLoopProxy>, debug: &mut Debug, windows: &HashMap, + window_ids: &HashMap, _graphics_info: impl FnOnce() -> compositor::Information + Copy, ) where A: Application, @@ -787,7 +800,9 @@ pub fn run_command( use iced_native::system; use iced_native::window; + // TODO(derezzedex) let window = windows.values().next().expect("No window found"); + let id = *window_ids.get(&window.id()).unwrap(); for action in command.actions() { match action { @@ -868,6 +883,7 @@ pub fn run_command( renderer, state.logical_size(), debug, + id, ); while let Some(mut operation) = current_operation.take() { @@ -933,6 +949,7 @@ where renderer, state.logical_size(), debug, + id, ); let _ = interfaces.insert(id, user_interface); -- cgit From 35331d0a41a53b8ff5c642b8274c7377ae6c6182 Mon Sep 17 00:00:00 2001 From: Richard Date: Tue, 26 Jul 2022 16:46:12 -0300 Subject: Allow closing the window from user code --- winit/src/multi_window.rs | 77 ++++++++++++++++++++++++++++++----------- winit/src/multi_window/state.rs | 11 ++++++ 2 files changed, 68 insertions(+), 20 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index dc00d737..3c720a69 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -39,6 +39,8 @@ pub enum Event { // (maybe we should also allow users to listen/react to those internal messages?) NewWindow(window::Id, settings::Window), /// TODO(derezzedex) + CloseWindow(window::Id), + /// TODO(derezzedex) WindowCreated(window::Id, winit::window::Window), } @@ -549,6 +551,27 @@ async fn run_instance( let _ = window_ids.insert(window.id(), id); let _ = windows.insert(id, window); } + Event::CloseWindow(id) => { + // TODO(derezzedex): log errors + if let Some(window) = windows.get(&id) { + if window_ids.remove(&window.id()).is_none() { + println!("Failed to remove from `window_ids`!"); + } + } + if states.remove(&id).is_none() { + println!("Failed to remove from `states`!") + } + if interfaces.remove(&id).is_none() { + println!("Failed to remove from `interfaces`!"); + } + if windows.remove(&id).is_none() { + println!("Failed to remove from `windows`!") + } + + if windows.is_empty() { + break 'main; + } + } Event::NewWindow(_, _) => unreachable!(), }, event::Event::RedrawRequested(id) => { @@ -651,29 +674,43 @@ async fn run_instance( window_id, } => { // dbg!(window_id); - let window = window_ids - .get(&window_id) - .and_then(|id| windows.get(id)) - .unwrap(); - let window_state = window_ids - .get(&window_id) - .and_then(|id| states.get_mut(id)) - .unwrap(); - - if requests_exit(&window_event, window_state.state.modifiers()) - && exit_on_close_request + if let Some(window) = + window_ids.get(&window_id).and_then(|id| windows.get(id)) { - break; - } + if let Some(window_state) = window_ids + .get(&window_id) + .and_then(|id| states.get_mut(id)) + { + if requests_exit( + &window_event, + window_state.state.modifiers(), + ) && exit_on_close_request + { + break; + } - window_state.state.update(window, &window_event, &mut debug); + window_state.state.update( + window, + &window_event, + &mut debug, + ); - if let Some(event) = conversion::window_event( - &window_event, - window_state.state.scale_factor(), - window_state.state.modifiers(), - ) { - events.push((window_ids.get(&window_id).cloned(), event)); + if let Some(event) = conversion::window_event( + &window_event, + window_state.state.scale_factor(), + window_state.state.modifiers(), + ) { + events.push(( + window_ids.get(&window_id).cloned(), + event, + )); + } + } else { + // TODO(derezzedex): log error + } + } else { + // TODO(derezzedex): log error + // println!("{:?}: {:?}", window_id, window_event); } } _ => {} diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs index d22de961..ae353e3b 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/multi_window/state.rs @@ -189,6 +189,17 @@ where proxy: &EventLoopProxy>, ) { let new_windows = application.windows(); + + // Check for windows to close + for window_id in windows.keys() { + if !new_windows.iter().any(|(id, _)| id == window_id) { + proxy + .send_event(Event::CloseWindow(*window_id)) + .expect("Failed to send message"); + } + } + + // Check for windows to spawn for (id, settings) in new_windows { if !windows.contains_key(&id) { proxy -- cgit From dc86bd03733969033df7389c3d21e78ecc6291bb Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 27 Jul 2022 15:37:48 -0300 Subject: Introduce `close_requested` for `multi-window` --- winit/src/multi_window.rs | 15 +++++++++++---- winit/src/settings.rs | 4 ++++ 2 files changed, 15 insertions(+), 4 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 3c720a69..6fbedc55 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -147,6 +147,9 @@ where fn should_exit(&self) -> bool { false } + + /// TODO(derezzedex) + fn close_requested(&self, window: window::Id) -> Self::Message; } /// Runs an [`Application`] with an executor, compositor, and the provided @@ -296,7 +299,7 @@ async fn run_instance( >, init_command: Command, mut windows: HashMap, - exit_on_close_request: bool, + _exit_on_close_request: bool, ) where A: Application + 'static, E: Executor + 'static, @@ -684,9 +687,13 @@ async fn run_instance( if requests_exit( &window_event, window_state.state.modifiers(), - ) && exit_on_close_request - { - break; + ) { + if let Some(id) = + window_ids.get(&window_id).cloned() + { + let message = application.close_requested(id); + messages.push(message); + } } window_state.state.update( diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 94d243a7..ea0ba361 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -46,6 +46,10 @@ pub struct Settings { /// Whether the [`Application`] should exit when the user requests the /// window to close (e.g. the user presses the close button). /// + /// NOTE: This is not used for `multi-window`, instead check [`Application::close_requested`]. + /// + /// [`close_requested`]: crate::multi_window::Application::close_requested + /// /// [`Application`]: crate::Application pub exit_on_close_request: bool, -- cgit From 7f35256573db789fa6552c2cfd8aa16dde2a1a4d Mon Sep 17 00:00:00 2001 From: Richard Date: Thu, 15 Sep 2022 05:02:18 -0300 Subject: Split `Surface` and `Window` --- winit/src/multi_window.rs | 82 ++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 47 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 6fbedc55..3e7fecd0 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -318,6 +318,7 @@ async fn run_instance( .collect(); let mut states = HashMap::new(); + let mut surfaces = HashMap::new(); let mut interfaces = ManuallyDrop::new(HashMap::new()); for (&id, window) in windows.keys().zip(windows.values()) { @@ -342,20 +343,19 @@ async fn run_instance( id, ); - let window_state: WindowState = WindowState { surface, state }; - - let _ = states.insert(id, window_state); + let _ = states.insert(id, state); + let _ = surfaces.insert(id, surface); let _ = interfaces.insert(id, user_interface); } { // TODO(derezzedex) - let window_state = states.values().next().expect("No state found"); + let state = states.values().next().expect("No state found"); run_command( &application, &mut cache, - &window_state.state, + state, &mut renderer, init_command, &mut runtime, @@ -396,7 +396,7 @@ async fn run_instance( .collect(); let cursor_position = - states.get(&id).unwrap().state.cursor_position(); + states.get(&id).unwrap().cursor_position(); let window = windows.get(&id).unwrap(); if filtered.is_empty() && messages.is_empty() { @@ -430,7 +430,7 @@ async fn run_instance( user_interface::State::Outdated, ) { - let state = &mut states.get_mut(&id).unwrap().state; + let state = &mut states.get_mut(&id).unwrap(); let pure_states: HashMap<_, _> = ManuallyDrop::into_inner(interfaces) .drain() @@ -481,7 +481,7 @@ async fn run_instance( debug.draw_started(); let new_mouse_interaction = { let user_interface = interfaces.get_mut(&id).unwrap(); - let state = &states.get(&id).unwrap().state; + let state = states.get(&id).unwrap(); user_interface.draw( &mut renderer, @@ -546,10 +546,8 @@ async fn run_instance( id, ); - let window_state: WindowState = - WindowState { surface, state }; - - let _ = states.insert(id, window_state); + let _ = states.insert(id, state); + let _ = surfaces.insert(id, surface); let _ = interfaces.insert(id, user_interface); let _ = window_ids.insert(window.id(), id); let _ = windows.insert(id, window); @@ -570,6 +568,9 @@ async fn run_instance( if windows.remove(&id).is_none() { println!("Failed to remove from `windows`!") } + if surfaces.remove(&id).is_none() { + println!("Failed to remove from `surfaces`!") + } if windows.is_empty() { break 'main; @@ -578,11 +579,15 @@ async fn run_instance( Event::NewWindow(_, _) => unreachable!(), }, event::Event::RedrawRequested(id) => { - let window_state = window_ids + let state = window_ids .get(&id) .and_then(|id| states.get_mut(id)) .unwrap(); - let physical_size = window_state.state.physical_size(); + let surface = window_ids + .get(&id) + .and_then(|id| surfaces.get_mut(id)) + .unwrap(); + let physical_size = state.physical_size(); if physical_size.width == 0 || physical_size.height == 0 { continue; @@ -590,13 +595,13 @@ async fn run_instance( debug.render_started(); - if window_state.state.viewport_changed() { + if state.viewport_changed() { let mut user_interface = window_ids .get(&id) .and_then(|id| interfaces.remove(id)) .unwrap(); - let logical_size = window_state.state.logical_size(); + let logical_size = state.logical_size(); debug.layout_started(); user_interface = @@ -605,7 +610,7 @@ async fn run_instance( debug.draw_started(); let new_mouse_interaction = { - let state = &window_state.state; + let state = &state; user_interface.draw( &mut renderer, @@ -634,7 +639,7 @@ async fn run_instance( .insert(*window_ids.get(&id).unwrap(), user_interface); compositor.configure_surface( - &mut window_state.surface, + surface, physical_size.width, physical_size.height, ); @@ -642,9 +647,9 @@ async fn run_instance( match compositor.present( &mut renderer, - &mut window_state.surface, - window_state.state.viewport(), - window_state.state.background_color(), + surface, + state.viewport(), + state.background_color(), &debug.overlay(), ) { Ok(()) => { @@ -680,14 +685,11 @@ async fn run_instance( if let Some(window) = window_ids.get(&window_id).and_then(|id| windows.get(id)) { - if let Some(window_state) = window_ids + if let Some(state) = window_ids .get(&window_id) .and_then(|id| states.get_mut(id)) { - if requests_exit( - &window_event, - window_state.state.modifiers(), - ) { + if requests_exit(&window_event, state.modifiers()) { if let Some(id) = window_ids.get(&window_id).cloned() { @@ -696,16 +698,12 @@ async fn run_instance( } } - window_state.state.update( - window, - &window_event, - &mut debug, - ); + state.update(window, &window_event, &mut debug); if let Some(event) = conversion::window_event( &window_event, - window_state.state.scale_factor(), - window_state.state.modifiers(), + state.scale_factor(), + state.modifiers(), ) { events.push(( window_ids.get(&window_id).cloned(), @@ -953,21 +951,12 @@ pub fn run_command( } } -struct WindowState -where - A: Application, - C: iced_graphics::window::Compositor, - ::Theme: StyleSheet, -{ - surface: ::Surface, - state: State, -} - -fn build_user_interfaces<'a, A, C>( +/// TODO(derezzedex) +pub fn build_user_interfaces<'a, A>( application: &'a A, renderer: &mut A::Renderer, debug: &mut Debug, - states: &HashMap>, + states: &HashMap>, mut pure_states: HashMap, ) -> HashMap< window::Id, @@ -979,13 +968,12 @@ fn build_user_interfaces<'a, A, C>( > where A: Application + 'static, - C: iced_graphics::window::Compositor + 'static, ::Theme: StyleSheet, { let mut interfaces = HashMap::new(); for (id, pure_state) in pure_states.drain() { - let state = &states.get(&id).unwrap().state; + let state = &states.get(&id).unwrap(); let user_interface = build_user_interface( application, -- cgit From 0ad53a3d5c7b5fb5785a64102ee1ad7df9a5fb2b Mon Sep 17 00:00:00 2001 From: Richard Date: Mon, 19 Sep 2022 20:59:37 -0300 Subject: add `window::Id` to `Event` and `Action` --- winit/src/application.rs | 3 +- winit/src/conversion.rs | 46 +++++++++++++++++----------- winit/src/multi_window.rs | 77 ++++++++++++++++++++++------------------------- winit/src/window.rs | 30 ++++++++++-------- 4 files changed, 84 insertions(+), 72 deletions(-) (limited to 'winit/src') diff --git a/winit/src/application.rs b/winit/src/application.rs index 74c73815..4486f5d9 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -502,6 +502,7 @@ async fn run_instance( state.update(&window, &window_event, &mut debug); if let Some(event) = conversion::window_event( + crate::window::Id::MAIN, &window_event, state.scale_factor(), state.modifiers(), @@ -667,7 +668,7 @@ pub fn run_command( clipboard.write(contents); } }, - command::Action::Window(action) => match action { + command::Action::Window(_id, action) => match action { window::Action::Close => { *should_exit = true; } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 1418e346..6c809d19 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -10,6 +10,7 @@ use crate::{Event, Point, Position}; /// Converts a winit window event into an iced event. pub fn window_event( + id: window::Id, event: &winit::event::WindowEvent<'_>, scale_factor: f64, modifiers: winit::event::ModifiersState, @@ -20,21 +21,27 @@ pub fn window_event( WindowEvent::Resized(new_size) => { let logical_size = new_size.to_logical(scale_factor); - Some(Event::Window(window::Event::Resized { - width: logical_size.width, - height: logical_size.height, - })) + Some(Event::Window( + id, + window::Event::Resized { + width: logical_size.width, + height: logical_size.height, + }, + )) } WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { let logical_size = new_inner_size.to_logical(scale_factor); - Some(Event::Window(window::Event::Resized { - width: logical_size.width, - height: logical_size.height, - })) + Some(Event::Window( + id, + window::Event::Resized { + width: logical_size.width, + height: logical_size.height, + }, + )) } WindowEvent::CloseRequested => { - Some(Event::Window(window::Event::CloseRequested)) + Some(Event::Window(id, window::Event::CloseRequested)) } WindowEvent::CursorMoved { position, .. } => { let position = position.to_logical::(scale_factor); @@ -112,19 +119,22 @@ pub fn window_event( WindowEvent::ModifiersChanged(new_modifiers) => Some(Event::Keyboard( keyboard::Event::ModifiersChanged(self::modifiers(*new_modifiers)), )), - WindowEvent::Focused(focused) => Some(Event::Window(if *focused { - window::Event::Focused - } else { - window::Event::Unfocused - })), + WindowEvent::Focused(focused) => Some(Event::Window( + id, + if *focused { + window::Event::Focused + } else { + window::Event::Unfocused + }, + )), WindowEvent::HoveredFile(path) => { - Some(Event::Window(window::Event::FileHovered(path.clone()))) + Some(Event::Window(id, window::Event::FileHovered(path.clone()))) } WindowEvent::DroppedFile(path) => { - Some(Event::Window(window::Event::FileDropped(path.clone()))) + Some(Event::Window(id, window::Event::FileDropped(path.clone()))) } WindowEvent::HoveredFileCancelled => { - Some(Event::Window(window::Event::FilesHoveredLeft)) + Some(Event::Window(id, window::Event::FilesHoveredLeft)) } WindowEvent::Touch(touch) => { Some(Event::Touch(touch_event(*touch, scale_factor))) @@ -133,7 +143,7 @@ pub fn window_event( let winit::dpi::LogicalPosition { x, y } = position.to_logical(scale_factor); - Some(Event::Window(window::Event::Moved { x, y })) + Some(Event::Window(id, window::Event::Moved { x, y })) } _ => None, } diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 3e7fecd0..9f46b88d 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -363,7 +363,6 @@ async fn run_instance( &mut proxy, &mut debug, &windows, - &window_ids, || compositor.fetch_information(), ); } @@ -456,7 +455,6 @@ async fn run_instance( &mut debug, &mut messages, &windows, - &window_ids, || compositor.fetch_information(), ); @@ -701,6 +699,7 @@ async fn run_instance( state.update(window, &window_event, &mut debug); if let Some(event) = conversion::window_event( + *window_ids.get(&window_id).unwrap(), &window_event, state.scale_factor(), state.modifiers(), @@ -787,7 +786,6 @@ pub fn update( debug: &mut Debug, messages: &mut Vec, windows: &HashMap, - window_ids: &HashMap, graphics_info: impl FnOnce() -> compositor::Information + Copy, ) where ::Theme: StyleSheet, @@ -810,7 +808,6 @@ pub fn update( proxy, debug, windows, - window_ids, graphics_info, ); } @@ -831,7 +828,6 @@ pub fn run_command( proxy: &mut winit::event_loop::EventLoopProxy>, debug: &mut Debug, windows: &HashMap, - window_ids: &HashMap, _graphics_info: impl FnOnce() -> compositor::Information + Copy, ) where A: Application, @@ -842,10 +838,6 @@ pub fn run_command( use iced_native::system; use iced_native::window; - // TODO(derezzedex) - let window = windows.values().next().expect("No window found"); - let id = *window_ids.get(&window.id()).unwrap(); - for action in command.actions() { match action { command::Action::Future(future) => { @@ -863,38 +855,41 @@ pub fn run_command( clipboard.write(contents); } }, - command::Action::Window(action) => match action { - window::Action::Resize { width, height } => { - window.set_inner_size(winit::dpi::LogicalSize { - width, - height, - }); - } - window::Action::Move { x, y } => { - window.set_outer_position(winit::dpi::LogicalPosition { - x, - y, - }); - } - window::Action::SetMode(mode) => { - window.set_visible(conversion::visible(mode)); - window.set_fullscreen(conversion::fullscreen( - window.primary_monitor(), - mode, - )); - } - window::Action::FetchMode(tag) => { - let mode = if window.is_visible().unwrap_or(true) { - conversion::mode(window.fullscreen()) - } else { - window::Mode::Hidden - }; - - proxy - .send_event(Event::Application(tag(mode))) - .expect("Send message to event loop"); + command::Action::Window(id, action) => { + let window = windows.get(&id).expect("No window found"); + + match action { + window::Action::Resize { width, height } => { + window.set_inner_size(winit::dpi::LogicalSize { + width, + height, + }); + } + window::Action::Move { x, y } => { + window.set_outer_position( + winit::dpi::LogicalPosition { x, y }, + ); + } + window::Action::SetMode(mode) => { + window.set_visible(conversion::visible(mode)); + window.set_fullscreen(conversion::fullscreen( + window.primary_monitor(), + mode, + )); + } + window::Action::FetchMode(tag) => { + let mode = if window.is_visible().unwrap_or(true) { + conversion::mode(window.fullscreen()) + } else { + window::Mode::Hidden + }; + + proxy + .send_event(Event::Application(tag(mode))) + .expect("Send message to event loop"); + } } - }, + } command::Action::System(action) => match action { system::Action::QueryInformation(_tag) => { #[cfg(feature = "system")] @@ -925,7 +920,7 @@ pub fn run_command( renderer, state.logical_size(), debug, - id, + window::Id::MAIN, // TODO(derezzedex): run the operation on every widget tree ); while let Some(mut operation) = current_operation.take() { diff --git a/winit/src/window.rs b/winit/src/window.rs index f2c7037a..d9bc0d83 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -15,11 +15,15 @@ pub fn drag() -> Command { } /// Resizes the window to the given logical dimensions. -pub fn resize(width: u32, height: u32) -> Command { - Command::single(command::Action::Window(window::Action::Resize { - width, - height, - })) +pub fn resize( + id: window::Id, + width: u32, + height: u32, +) -> Command { + Command::single(command::Action::Window( + id, + window::Action::Resize { width, height }, + )) } /// Sets the window to maximized or back. @@ -33,13 +37,13 @@ pub fn minimize(value: bool) -> Command { } /// Moves a window to the given logical coordinates. -pub fn move_to(x: i32, y: i32) -> Command { - Command::single(command::Action::Window(window::Action::Move { x, y })) +pub fn move_to(id: window::Id, x: i32, y: i32) -> Command { + Command::single(command::Action::Window(id, window::Action::Move { x, y })) } /// Sets the [`Mode`] of the window. -pub fn set_mode(mode: Mode) -> Command { - Command::single(command::Action::Window(window::Action::SetMode(mode))) +pub fn set_mode(id: window::Id, mode: Mode) -> Command { + Command::single(command::Action::Window(id, window::Action::SetMode(mode))) } /// Sets the window to maximized or back. @@ -49,9 +53,11 @@ pub fn toggle_maximize() -> Command { /// Fetches the current [`Mode`] of the window. pub fn fetch_mode( + id: window::Id, f: impl FnOnce(Mode) -> Message + 'static, ) -> Command { - Command::single(command::Action::Window(window::Action::FetchMode( - Box::new(f), - ))) + Command::single(command::Action::Window( + id, + window::Action::FetchMode(Box::new(f)), + )) } -- cgit From 1bc0c480f9747826b244c30e92d8c4a29b576e4a Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 19 Oct 2022 22:56:00 -0300 Subject: move window settings to `iced_native` --- winit/src/icon.rs | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++ winit/src/lib.rs | 5 +- winit/src/position.rs | 22 ------- winit/src/settings.rs | 21 ++++++ 4 files changed, 203 insertions(+), 24 deletions(-) create mode 100644 winit/src/icon.rs delete mode 100644 winit/src/position.rs (limited to 'winit/src') diff --git a/winit/src/icon.rs b/winit/src/icon.rs new file mode 100644 index 00000000..84b88b39 --- /dev/null +++ b/winit/src/icon.rs @@ -0,0 +1,179 @@ +//! Attach an icon to the window of your application. +use std::fmt; +use std::io; + +#[cfg(feature = "image_rs")] +use std::path::Path; + +/// The icon of a window. +#[derive(Debug, Clone)] +pub struct Icon(winit::window::Icon); + +impl Icon { + /// Creates an icon from 32bpp RGBA data. + pub fn from_rgba( + rgba: Vec, + width: u32, + height: u32, + ) -> Result { + let raw = winit::window::Icon::from_rgba(rgba, width, height)?; + + Ok(Icon(raw)) + } + + /// Creates an icon from an image file. + /// + /// This will return an error in case the file is missing at run-time. You may prefer [`Self::from_file_data`] instead. + #[cfg(feature = "image_rs")] + pub fn from_file>(icon_path: P) -> Result { + let icon = image_rs::io::Reader::open(icon_path)?.decode()?.to_rgba8(); + + Self::from_rgba(icon.to_vec(), icon.width(), icon.height()) + } + + /// Creates an icon from the content of an image file. + /// + /// This content can be included in your application at compile-time, e.g. using the `include_bytes!` macro. \ + /// You can pass an explicit file format. Otherwise, the file format will be guessed at runtime. + #[cfg(feature = "image_rs")] + pub fn from_file_data( + data: &[u8], + explicit_format: Option, + ) -> Result { + let mut icon = image_rs::io::Reader::new(std::io::Cursor::new(data)); + let icon_with_format = match explicit_format { + Some(format) => { + icon.set_format(format); + icon + } + None => icon.with_guessed_format()?, + }; + + let pixels = icon_with_format.decode()?.to_rgba8(); + + Self::from_rgba(pixels.to_vec(), pixels.width(), pixels.height()) + } +} + +/// An error produced when using `Icon::from_rgba` with invalid arguments. +#[derive(Debug)] +pub enum Error { + /// The provided RGBA data isn't divisble by 4. + /// + /// Therefore, it cannot be safely interpreted as 32bpp RGBA pixels. + InvalidData { + /// The length of the provided RGBA data. + byte_count: usize, + }, + + /// The number of RGBA pixels does not match the provided dimensions. + DimensionsMismatch { + /// The provided width. + width: u32, + /// The provided height. + height: u32, + /// The amount of pixels of the provided RGBA data. + pixel_count: usize, + }, + + /// The underlying OS failed to create the icon. + OsError(io::Error), + + /// The `image` crate reported an error + #[cfg(feature = "image_rs")] + ImageError(image_rs::error::ImageError), +} + +impl From for Error { + fn from(os_error: std::io::Error) -> Self { + Error::OsError(os_error) + } +} + +impl From for Error { + fn from(error: winit::window::BadIcon) -> Self { + use winit::window::BadIcon; + + match error { + BadIcon::ByteCountNotDivisibleBy4 { byte_count } => { + Error::InvalidData { byte_count } + } + BadIcon::DimensionsVsPixelCount { + width, + height, + pixel_count, + .. + } => Error::DimensionsMismatch { + width, + height, + pixel_count, + }, + BadIcon::OsError(os_error) => Error::OsError(os_error), + } + } +} + +impl From for winit::window::Icon { + fn from(icon: Icon) -> Self { + icon.0 + } +} + +#[cfg(feature = "image_rs")] +impl From for Error { + fn from(image_error: image_rs::error::ImageError) -> Self { + Self::ImageError(image_error) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::InvalidData { byte_count } => { + write!( + f, + "The provided RGBA data (with length {:?}) isn't divisble by \ + 4. Therefore, it cannot be safely interpreted as 32bpp RGBA \ + pixels.", + byte_count, + ) + } + Error::DimensionsMismatch { + width, + height, + pixel_count, + } => { + write!( + f, + "The number of RGBA pixels ({:?}) does not match the provided \ + dimensions ({:?}x{:?}).", + pixel_count, width, height, + ) + } + Error::OsError(e) => write!( + f, + "The underlying OS failed to create the window \ + icon: {:?}", + e + ), + #[cfg(feature = "image_rs")] + Error::ImageError(e) => { + write!(f, "Unable to create icon from a file: {:?}", e) + } + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(self) + } +} + +impl TryFrom for Icon { + type Error = Error; + + fn try_from(icon: iced_native::window::Icon) -> Result { + Icon::from_rgba(icon.rgba, icon.width, icon.height) + } +} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 9b3c0a02..eb58482b 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -49,7 +49,7 @@ pub mod window; pub mod system; mod error; -mod position; +mod icon; mod proxy; #[cfg(feature = "application")] @@ -58,8 +58,9 @@ pub use application::Application; pub use application::Profiler; pub use clipboard::Clipboard; pub use error::Error; -pub use position::Position; +pub use icon::Icon; pub use proxy::Proxy; pub use settings::Settings; pub use iced_graphics::Viewport; +pub use iced_native::window::Position; diff --git a/winit/src/position.rs b/winit/src/position.rs deleted file mode 100644 index c260c29e..00000000 --- a/winit/src/position.rs +++ /dev/null @@ -1,22 +0,0 @@ -/// The position of a window in a given screen. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Position { - /// The platform-specific default position for a new window. - Default, - /// The window is completely centered on the screen. - Centered, - /// The window is positioned with specific coordinates: `(X, Y)`. - /// - /// When the decorations of the window are enabled, Windows 10 will add some - /// invisible padding to the window. This padding gets included in the - /// position. So if you have decorations enabled and want the window to be - /// at (0, 0) you would have to set the position to - /// `(PADDING_X, PADDING_Y)`. - Specific(i32, i32), -} - -impl Default for Position { - fn default() -> Self { - Self::Default - } -} diff --git a/winit/src/settings.rs b/winit/src/settings.rs index ea0ba361..78c8c156 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -22,6 +22,7 @@ mod platform; pub use platform::PlatformSpecific; use crate::conversion; +use crate::Icon; use crate::Position; use winit::monitor::MonitorHandle; use winit::window::WindowBuilder; @@ -201,3 +202,23 @@ impl Default for Window { } } } + +impl From for Window { + fn from(settings: iced_native::window::Settings) -> Self { + Self { + size: settings.size, + position: Position::from(settings.position), + min_size: settings.min_size, + max_size: settings.max_size, + visible: settings.visible, + resizable: settings.resizable, + decorations: settings.decorations, + transparent: settings.transparent, + always_on_top: settings.always_on_top, + icon: settings.icon.and_then(|icon| { + Icon::try_from(icon).map(winit::window::Icon::from).ok() + }), + platform_specific: Default::default(), + } + } +} -- cgit From f93fa0254329ebddca21ea1a79bd8ee6d8b4bdaf Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 19 Oct 2022 23:33:20 -0300 Subject: introduce `window::spawn` and `window::close` --- winit/src/application.rs | 5 +++ winit/src/multi_window.rs | 79 +++++++++++++++++++++++++++-------------------- winit/src/window.rs | 16 ++++++++++ 3 files changed, 66 insertions(+), 34 deletions(-) (limited to 'winit/src') diff --git a/winit/src/application.rs b/winit/src/application.rs index 4486f5d9..910f3d94 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -675,6 +675,11 @@ pub fn run_command( window::Action::Drag => { let _res = window.drag_window(); } + window::Action::Spawn { .. } | window::Action::Close => { + log::info!( + "This is only available on `multi_window::Application`" + ) + } window::Action::Resize { width, height } => { window.set_inner_size(winit::dpi::LogicalSize { width, diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 9f46b88d..1d71d801 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -855,41 +855,52 @@ pub fn run_command( clipboard.write(contents); } }, - command::Action::Window(id, action) => { - let window = windows.get(&id).expect("No window found"); - - match action { - window::Action::Resize { width, height } => { - window.set_inner_size(winit::dpi::LogicalSize { - width, - height, - }); - } - window::Action::Move { x, y } => { - window.set_outer_position( - winit::dpi::LogicalPosition { x, y }, - ); - } - window::Action::SetMode(mode) => { - window.set_visible(conversion::visible(mode)); - window.set_fullscreen(conversion::fullscreen( - window.primary_monitor(), - mode, - )); - } - window::Action::FetchMode(tag) => { - let mode = if window.is_visible().unwrap_or(true) { - conversion::mode(window.fullscreen()) - } else { - window::Mode::Hidden - }; - - proxy - .send_event(Event::Application(tag(mode))) - .expect("Send message to event loop"); - } + command::Action::Window(id, action) => match action { + window::Action::Spawn { settings } => { + proxy + .send_event(Event::NewWindow(id, settings.into())) + .expect("Send message to event loop"); } - } + window::Action::Close => { + proxy + .send_event(Event::CloseWindow(id)) + .expect("Send message to event loop"); + } + window::Action::Resize { width, height } => { + let window = windows.get(&id).expect("No window found"); + window.set_inner_size(winit::dpi::LogicalSize { + width, + height, + }); + } + window::Action::Move { x, y } => { + let window = windows.get(&id).expect("No window found"); + window.set_outer_position(winit::dpi::LogicalPosition { + x, + y, + }); + } + window::Action::SetMode(mode) => { + let window = windows.get(&id).expect("No window found"); + window.set_visible(conversion::visible(mode)); + window.set_fullscreen(conversion::fullscreen( + window.primary_monitor(), + mode, + )); + } + window::Action::FetchMode(tag) => { + let window = windows.get(&id).expect("No window found"); + let mode = if window.is_visible().unwrap_or(true) { + conversion::mode(window.fullscreen()) + } else { + window::Mode::Hidden + }; + + proxy + .send_event(Event::Application(tag(mode))) + .expect("Send message to event loop"); + } + }, command::Action::System(action) => match action { system::Action::QueryInformation(_tag) => { #[cfg(feature = "system")] diff --git a/winit/src/window.rs b/winit/src/window.rs index d9bc0d83..fba863ef 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -14,6 +14,22 @@ pub fn drag() -> Command { Command::single(command::Action::Window(window::Action::Drag)) } +/// TODO(derezzedex) +pub fn spawn( + id: window::Id, + settings: window::Settings, +) -> Command { + Command::single(command::Action::Window( + id, + window::Action::Spawn { settings }, + )) +} + +/// TODO(derezzedex) +pub fn close(id: window::Id) -> Command { + Command::single(command::Action::Window(id, window::Action::Close)) +} + /// Resizes the window to the given logical dimensions. pub fn resize( id: window::Id, -- cgit From 5e4e410b18eb744cf70ae1f18b9ef08611f59150 Mon Sep 17 00:00:00 2001 From: Richard Date: Thu, 3 Nov 2022 14:53:05 -0300 Subject: remove `windows` method (use commands instead) --- winit/src/multi_window.rs | 5 +---- winit/src/multi_window/state.rs | 24 +----------------------- 2 files changed, 2 insertions(+), 27 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 1d71d801..c0c233c5 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -68,9 +68,6 @@ where /// The type of __messages__ your [`Program`] will produce. type Message: std::fmt::Debug + Send; - /// TODO(derezzedex) - fn windows(&self) -> Vec<(window::Id, settings::Window)>; - /// Handles a __message__ and updates the state of the [`Program`]. /// /// This is where you define your __update logic__. All the __messages__, @@ -459,7 +456,7 @@ async fn run_instance( ); // Update window - state.synchronize(&application, &windows, &proxy); + state.synchronize(&application, &windows); let should_exit = application.should_exit(); diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs index ae353e3b..a7d51df4 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/multi_window/state.rs @@ -1,13 +1,12 @@ use crate::application::{self, StyleSheet as _}; use crate::conversion; -use crate::multi_window::{Application, Event}; +use crate::multi_window::Application; use crate::window; use crate::{Color, Debug, Point, Size, Viewport}; use std::collections::HashMap; use std::marker::PhantomData; use winit::event::{Touch, WindowEvent}; -use winit::event_loop::EventLoopProxy; use winit::window::Window; /// The state of a windowed [`Application`]. @@ -186,28 +185,7 @@ where &mut self, application: &A, windows: &HashMap, - proxy: &EventLoopProxy>, ) { - let new_windows = application.windows(); - - // Check for windows to close - for window_id in windows.keys() { - if !new_windows.iter().any(|(id, _)| id == window_id) { - proxy - .send_event(Event::CloseWindow(*window_id)) - .expect("Failed to send message"); - } - } - - // Check for windows to spawn - for (id, settings) in new_windows { - if !windows.contains_key(&id) { - proxy - .send_event(Event::NewWindow(id, settings)) - .expect("Failed to send message"); - } - } - let window = windows.values().next().expect("No window found"); // Update window title -- cgit From 942f1c91afb8257e289af8d0c229f74819f68361 Mon Sep 17 00:00:00 2001 From: bungoboingo Date: Mon, 2 Jan 2023 10:58:07 -0800 Subject: merged in iced master --- winit/src/multi_window.rs | 20 ++++++++++++++++++++ winit/src/multi_window/state.rs | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index c0c233c5..0a2f71ad 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -863,6 +863,10 @@ pub fn run_command( .send_event(Event::CloseWindow(id)) .expect("Send message to event loop"); } + window::Action::Drag => { + let window = windows.get(&id).expect("No window found"); + let _res = window.drag_window(); + } window::Action::Resize { width, height } => { let window = windows.get(&id).expect("No window found"); window.set_inner_size(winit::dpi::LogicalSize { @@ -897,6 +901,22 @@ pub fn run_command( .send_event(Event::Application(tag(mode))) .expect("Send message to event loop"); } + window::Action::Maximize(value) => { + let window = windows.get(&id).expect("No window found!"); + window.set_maximized(value); + } + window::Action::Minimize(value) => { + let window = windows.get(&id).expect("No window found!"); + window.set_minimized(value); + } + window::Action::ToggleMaximize => { + let window = windows.get(&id).expect("No window found!"); + window.set_maximized(!window.is_maximized()); + } + window::Action::ToggleDecorations => { + let window = windows.get(&id).expect("No window found!"); + window.set_decorations(!window.is_decorated()); + } }, command::Action::System(action) => match action { system::Action::QueryInformation(_tag) => { diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs index a7d51df4..eebdcdf1 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/multi_window/state.rs @@ -35,7 +35,7 @@ where let title = application.title(); let scale_factor = application.scale_factor(); let theme = application.theme(); - let appearance = theme.appearance(application.style()); + let appearance = theme.appearance(&application.style()); let viewport = { let physical_size = window.inner_size(); @@ -213,6 +213,6 @@ where // Update theme and appearance self.theme = application.theme(); - self.appearance = self.theme.appearance(application.style()); + self.appearance = self.theme.appearance(&application.style()); } } -- cgit From f43419d4752fe18065c0e1b7c2a26e65b9d6e253 Mon Sep 17 00:00:00 2001 From: bungoboingo Date: Mon, 2 Jan 2023 18:14:31 -0800 Subject: Fixed issue with window ID on winit --- winit/src/multi_window.rs | 110 +++++++++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 51 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 0a2f71ad..7d8bbc39 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -33,7 +33,6 @@ use std::mem::ManuallyDrop; pub enum Event { /// An [`Application`] generated message Application(Message), - /// TODO(derezzedex) // Create a wrapper variant of `window::Event` type instead // (maybe we should also allow users to listen/react to those internal messages?) @@ -197,7 +196,8 @@ where .map_err(Error::WindowCreationFailed)?; let windows: HashMap = - HashMap::from([(window::Id::new(0usize), window)]); + HashMap::from([(window::Id::MAIN, window)]); + let window = windows.values().next().expect("No window found"); #[cfg(target_arch = "wasm32")] @@ -515,64 +515,72 @@ async fn run_instance( ), )); } - event::Event::UserEvent(event) => match event { - Event::Application(message) => { - messages.push(message); - } - Event::WindowCreated(id, window) => { - let mut surface = compositor.create_surface(&window); + event::Event::UserEvent(event) => { + match event { + Event::Application(message) => { + messages.push(message); + } + Event::WindowCreated(id, window) => { + let mut surface = compositor.create_surface(&window); - let state = State::new(&application, &window); + let state = State::new(&application, &window); - let physical_size = state.physical_size(); + let physical_size = state.physical_size(); - compositor.configure_surface( - &mut surface, - physical_size.width, - physical_size.height, - ); + compositor.configure_surface( + &mut surface, + physical_size.width, + physical_size.height, + ); - let user_interface = build_user_interface( - &application, - user_interface::Cache::default(), - &mut renderer, - state.logical_size(), - &mut debug, - id, - ); + let user_interface = build_user_interface( + &application, + user_interface::Cache::default(), + &mut renderer, + state.logical_size(), + &mut debug, + id, + ); - let _ = states.insert(id, state); - let _ = surfaces.insert(id, surface); - let _ = interfaces.insert(id, user_interface); - let _ = window_ids.insert(window.id(), id); - let _ = windows.insert(id, window); - } - Event::CloseWindow(id) => { - // TODO(derezzedex): log errors - if let Some(window) = windows.get(&id) { - if window_ids.remove(&window.id()).is_none() { - println!("Failed to remove from `window_ids`!"); - } - } - if states.remove(&id).is_none() { - println!("Failed to remove from `states`!") - } - if interfaces.remove(&id).is_none() { - println!("Failed to remove from `interfaces`!"); - } - if windows.remove(&id).is_none() { - println!("Failed to remove from `windows`!") - } - if surfaces.remove(&id).is_none() { - println!("Failed to remove from `surfaces`!") + let _ = states.insert(id, state); + let _ = surfaces.insert(id, surface); + let _ = interfaces.insert(id, user_interface); + let _ = window_ids.insert(window.id(), id); + let _ = windows.insert(id, window); } + Event::CloseWindow(id) => { + println!("Closing window {:?}. Total: {}", id, windows.len()); - if windows.is_empty() { - break 'main; + if let Some(window) = windows.get(&id) { + if window_ids.remove(&window.id()).is_none() { + log::error!("Failed to remove window with id {:?} from window_ids.", window.id()); + } + } else { + log::error!("Could not find window with id {:?} in windows.", id); + } + if states.remove(&id).is_none() { + log::error!("Failed to remove window {:?} from states.", id); + } + if interfaces.remove(&id).is_none() { + log::error!("Failed to remove window {:?} from interfaces.", id); + } + if windows.remove(&id).is_none() { + log::error!("Failed to remove window {:?} from windows.", id); + } + if surfaces.remove(&id).is_none() { + log::error!("Failed to remove window {:?} from surfaces.", id); + } + + if windows.is_empty() { + log::info!("All windows are closed. Terminating program."); + break 'main; + } else { + log::info!("Remaining windows: {:?}", windows.len()); + } } + Event::NewWindow(_, _) => unreachable!(), } - Event::NewWindow(_, _) => unreachable!(), - }, + } event::Event::RedrawRequested(id) => { let state = window_ids .get(&id) -- cgit From 1944e98f82b7efd5b268e04ba5ced065e55a218e Mon Sep 17 00:00:00 2001 From: Bingus Date: Mon, 2 Jan 2023 21:06:59 -0800 Subject: Fix multi-window example for Glutin on MacOS --- winit/src/multi_window.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 7d8bbc39..43455148 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -320,6 +320,7 @@ async fn run_instance( for (&id, window) in windows.keys().zip(windows.values()) { let mut surface = compositor.create_surface(window); + println!("Creating surface for window: {:?}", window); let state = State::new(&application, window); -- cgit From ec41918ec40bddaba81235372f1566da59fd09f2 Mon Sep 17 00:00:00 2001 From: bungoboingo Date: Thu, 5 Jan 2023 15:26:28 -0800 Subject: Implemented window title update functionality for multiwindow. --- winit/src/application.rs | 2 +- winit/src/multi_window.rs | 190 +++++++++++++++++++++++----------------- winit/src/multi_window/state.rs | 14 ++- winit/src/window.rs | 37 ++++---- 4 files changed, 138 insertions(+), 105 deletions(-) (limited to 'winit/src') diff --git a/winit/src/application.rs b/winit/src/application.rs index 910f3d94..eef6833c 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -675,7 +675,7 @@ pub fn run_command( window::Action::Drag => { let _res = window.drag_window(); } - window::Action::Spawn { .. } | window::Action::Close => { + window::Action::Spawn { .. } => { log::info!( "This is only available on `multi_window::Application`" ) diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 43455148..6a2bdca9 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -22,6 +22,7 @@ use iced_native::user_interface::{self, UserInterface}; pub use iced_native::application::{Appearance, StyleSheet}; +use iced_native::window::Action; use std::collections::HashMap; use std::mem::ManuallyDrop; @@ -36,7 +37,14 @@ pub enum Event { /// TODO(derezzedex) // Create a wrapper variant of `window::Event` type instead // (maybe we should also allow users to listen/react to those internal messages?) - NewWindow(window::Id, settings::Window), + NewWindow { + /// The [window::Id] of the newly spawned [`Window`]. + id: window::Id, + /// The [settings::Window] of the newly spawned [`Window`]. + settings: settings::Window, + /// The title of the newly spawned [`Window`]. + title: String, + }, /// TODO(derezzedex) CloseWindow(window::Id), /// TODO(derezzedex) @@ -95,11 +103,11 @@ where /// load state from a file, perform an initial HTTP request, etc. fn new(flags: Self::Flags) -> (Self, Command); - /// Returns the current title of the [`Application`]. + /// Returns the current title of the current [`Application`] window. /// /// This title can be dynamic! The runtime will automatically update the /// title of your application when necessary. - fn title(&self) -> String; + fn title(&self, window_id: window::Id) -> String; /// Returns the current [`Theme`] of the [`Application`]. fn theme(&self) -> ::Theme; @@ -144,7 +152,7 @@ where false } - /// TODO(derezzedex) + /// Requests that the [`window`] be closed. fn close_requested(&self, window: window::Id) -> Self::Message; } @@ -184,7 +192,7 @@ where }; let builder = settings.window.into_builder( - &application.title(), + &application.title(window::Id::MAIN), event_loop.primary_monitor(), settings.id, ); @@ -253,14 +261,13 @@ where event: winit::event::WindowEvent::Resized(*new_inner_size), window_id, }), - winit::event::Event::UserEvent(Event::NewWindow(id, settings)) => { - // TODO(derezzedex) + winit::event::Event::UserEvent(Event::NewWindow { + id, + settings, + title, + }) => { let window = settings - .into_builder( - "fix window title", - event_loop.primary_monitor(), - None, - ) + .into_builder(&title, event_loop.primary_monitor(), None) .build(event_loop) .expect("Failed to build window"); @@ -320,10 +327,7 @@ async fn run_instance( for (&id, window) in windows.keys().zip(windows.values()) { let mut surface = compositor.create_surface(window); - println!("Creating surface for window: {:?}", window); - - let state = State::new(&application, window); - + let state = State::new(&application, id, window); let physical_size = state.physical_size(); compositor.configure_surface( @@ -457,7 +461,11 @@ async fn run_instance( ); // Update window - state.synchronize(&application, &windows); + state.synchronize( + &application, + id, + windows.get(&id).expect("No window found with ID."), + ); let should_exit = application.should_exit(); @@ -516,72 +524,85 @@ async fn run_instance( ), )); } - event::Event::UserEvent(event) => { - match event { - Event::Application(message) => { - messages.push(message); - } - Event::WindowCreated(id, window) => { - let mut surface = compositor.create_surface(&window); - - let state = State::new(&application, &window); + event::Event::UserEvent(event) => match event { + Event::Application(message) => { + messages.push(message); + } + Event::WindowCreated(id, window) => { + let mut surface = compositor.create_surface(&window); - let physical_size = state.physical_size(); + let state = State::new(&application, id, &window); - compositor.configure_surface( - &mut surface, - physical_size.width, - physical_size.height, - ); + let physical_size = state.physical_size(); - let user_interface = build_user_interface( - &application, - user_interface::Cache::default(), - &mut renderer, - state.logical_size(), - &mut debug, - id, - ); + compositor.configure_surface( + &mut surface, + physical_size.width, + physical_size.height, + ); - let _ = states.insert(id, state); - let _ = surfaces.insert(id, surface); - let _ = interfaces.insert(id, user_interface); - let _ = window_ids.insert(window.id(), id); - let _ = windows.insert(id, window); - } - Event::CloseWindow(id) => { - println!("Closing window {:?}. Total: {}", id, windows.len()); + let user_interface = build_user_interface( + &application, + user_interface::Cache::default(), + &mut renderer, + state.logical_size(), + &mut debug, + id, + ); - if let Some(window) = windows.get(&id) { - if window_ids.remove(&window.id()).is_none() { - log::error!("Failed to remove window with id {:?} from window_ids.", window.id()); - } - } else { - log::error!("Could not find window with id {:?} in windows.", id); - } - if states.remove(&id).is_none() { - log::error!("Failed to remove window {:?} from states.", id); - } - if interfaces.remove(&id).is_none() { - log::error!("Failed to remove window {:?} from interfaces.", id); - } - if windows.remove(&id).is_none() { - log::error!("Failed to remove window {:?} from windows.", id); - } - if surfaces.remove(&id).is_none() { - log::error!("Failed to remove window {:?} from surfaces.", id); + let _ = states.insert(id, state); + let _ = surfaces.insert(id, surface); + let _ = interfaces.insert(id, user_interface); + let _ = window_ids.insert(window.id(), id); + let _ = windows.insert(id, window); + } + Event::CloseWindow(id) => { + if let Some(window) = windows.get(&id) { + if window_ids.remove(&window.id()).is_none() { + log::error!("Failed to remove window with id {:?} from window_ids.", window.id()); } + } else { + log::error!( + "Could not find window with id {:?} in windows.", + id + ); + } + if states.remove(&id).is_none() { + log::error!( + "Failed to remove window {:?} from states.", + id + ); + } + if interfaces.remove(&id).is_none() { + log::error!( + "Failed to remove window {:?} from interfaces.", + id + ); + } + if windows.remove(&id).is_none() { + log::error!( + "Failed to remove window {:?} from windows.", + id + ); + } + if surfaces.remove(&id).is_none() { + log::error!( + "Failed to remove window {:?} from surfaces.", + id + ); + } - if windows.is_empty() { - log::info!("All windows are closed. Terminating program."); - break 'main; - } else { - log::info!("Remaining windows: {:?}", windows.len()); - } + if windows.is_empty() { + log::info!( + "All windows are closed. Terminating program." + ); + break 'main; + } else { + log::info!("Remaining windows: {:?}", windows.len()); } - Event::NewWindow(_, _) => unreachable!(), } - } + Event::NewWindow { .. } => unreachable!(), + }, event::Event::RedrawRequested(id) => { let state = window_ids .get(&id) @@ -716,11 +737,10 @@ async fn run_instance( )); } } else { - // TODO(derezzedex): log error + log::error!("No window state found for id: {:?}", window_id); } } else { - // TODO(derezzedex): log error - // println!("{:?}: {:?}", window_id, window_event); + log::error!("No window found with id: {:?}", window_id); } } _ => {} @@ -864,7 +884,11 @@ pub fn run_command( command::Action::Window(id, action) => match action { window::Action::Spawn { settings } => { proxy - .send_event(Event::NewWindow(id, settings.into())) + .send_event(Event::NewWindow { + id, + settings: settings.into(), + title: application.title(id), + }) .expect("Send message to event loop"); } window::Action::Close => { @@ -926,6 +950,16 @@ pub fn run_command( let window = windows.get(&id).expect("No window found!"); window.set_decorations(!window.is_decorated()); } + window::Action::RequestUserAttention(attention_type) => { + let window = windows.get(&id).expect("No window found!"); + window.request_user_attention( + attention_type.map(conversion::user_attention), + ); + } + Action::GainFocus => { + let window = windows.get(&id).expect("No window found!"); + window.focus_window(); + } }, command::Action::System(action) => match action { system::Action::QueryInformation(_tag) => { diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs index eebdcdf1..7a598b98 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/multi_window/state.rs @@ -4,7 +4,6 @@ use crate::multi_window::Application; use crate::window; use crate::{Color, Debug, Point, Size, Viewport}; -use std::collections::HashMap; use std::marker::PhantomData; use winit::event::{Touch, WindowEvent}; use winit::window::Window; @@ -31,8 +30,8 @@ where ::Theme: application::StyleSheet, { /// Creates a new [`State`] for the provided [`Application`] and window. - pub fn new(application: &A, window: &Window) -> Self { - let title = application.title(); + pub fn new(application: &A, window_id: window::Id, window: &Window) -> Self { + let title = application.title(window_id); let scale_factor = application.scale_factor(); let theme = application.theme(); let appearance = theme.appearance(&application.style()); @@ -65,7 +64,7 @@ where &self.viewport } - /// TODO(derezzedex) + /// Returns whether or not the viewport changed. pub fn viewport_changed(&self) -> bool { self.viewport_changed } @@ -184,12 +183,11 @@ where pub fn synchronize( &mut self, application: &A, - windows: &HashMap, + window_id: window::Id, + window: &Window, ) { - let window = windows.values().next().expect("No window found"); - // Update window title - let new_title = application.title(); + let new_title = application.title(window_id); if self.title != new_title { window.set_title(&new_title); diff --git a/winit/src/window.rs b/winit/src/window.rs index fba863ef..5a8ff6df 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -2,19 +2,19 @@ use crate::command::{self, Command}; use iced_native::window; -pub use window::{Id, Event, Mode, UserAttention}; +pub use window::{Event, Id, Mode, UserAttention}; -/// Closes the current window and exits the application. -pub fn close() -> Command { - Command::single(command::Action::Window(window::Action::Close)) +/// Closes the window. +pub fn close(id: window::Id) -> Command { + Command::single(command::Action::Window(id, window::Action::Close)) } /// Begins dragging the window while the left mouse button is held. -pub fn drag() -> Command { - Command::single(command::Action::Window(window::Action::Drag)) +pub fn drag(id: window::Id) -> Command { + Command::single(command::Action::Window(id, window::Action::Drag)) } -/// TODO(derezzedex) +/// Spawns a new window. pub fn spawn( id: window::Id, settings: window::Settings, @@ -25,11 +25,6 @@ pub fn spawn( )) } -/// TODO(derezzedex) -pub fn close(id: window::Id) -> Command { - Command::single(command::Action::Window(id, window::Action::Close)) -} - /// Resizes the window to the given logical dimensions. pub fn resize( id: window::Id, @@ -43,13 +38,19 @@ pub fn resize( } /// Sets the window to maximized or back. -pub fn maximize(value: bool) -> Command { - Command::single(command::Action::Window(window::Action::Maximize(value))) +pub fn maximize(id: window::Id, value: bool) -> Command { + Command::single(command::Action::Window( + id, + window::Action::Maximize(value), + )) } /// Set the window to minimized or back. -pub fn minimize(value: bool) -> Command { - Command::single(command::Action::Window(window::Action::Minimize(value))) +pub fn minimize(id: window::Id, value: bool) -> Command { + Command::single(command::Action::Window( + id, + window::Action::Minimize(value), + )) } /// Moves a window to the given logical coordinates. @@ -63,8 +64,8 @@ pub fn set_mode(id: window::Id, mode: Mode) -> Command { } /// Sets the window to maximized or back. -pub fn toggle_maximize() -> Command { - Command::single(command::Action::Window(window::Action::ToggleMaximize)) +pub fn toggle_maximize(id: window::Id) -> Command { + Command::single(command::Action::Window(id, window::Action::ToggleMaximize)) } /// Fetches the current [`Mode`] of the window. -- cgit From 3e5d34f25fa07fa99f57b686bbde87d73b8ed548 Mon Sep 17 00:00:00 2001 From: bungoboingo Date: Mon, 9 Jan 2023 10:19:12 -0800 Subject: Formatting --- winit/src/multi_window/state.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs index 7a598b98..2c2a4693 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/multi_window/state.rs @@ -29,8 +29,12 @@ impl State where ::Theme: application::StyleSheet, { - /// Creates a new [`State`] for the provided [`Application`] and window. - pub fn new(application: &A, window_id: window::Id, window: &Window) -> Self { + /// Creates a new [`State`] for the provided [`Application`]'s window. + pub fn new( + application: &A, + window_id: window::Id, + window: &Window, + ) -> Self { let title = application.title(window_id); let scale_factor = application.scale_factor(); let theme = application.theme(); @@ -191,7 +195,6 @@ where if self.title != new_title { window.set_title(&new_title); - self.title = new_title; } -- cgit From 790fa3e7a01a790aa3f07083fe9abf6b68fa7ba1 Mon Sep 17 00:00:00 2001 From: Bingus Date: Fri, 13 Jan 2023 11:56:28 -0800 Subject: Added tracing to multi_window applications --- winit/src/application.rs | 4 +- winit/src/application/profiler.rs | 101 ------------------------------------- winit/src/lib.rs | 4 +- winit/src/multi_window.rs | 61 ++++++++++++++++++----- winit/src/profiler.rs | 102 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 117 deletions(-) delete mode 100644 winit/src/application/profiler.rs create mode 100644 winit/src/profiler.rs (limited to 'winit/src') diff --git a/winit/src/application.rs b/winit/src/application.rs index eef6833c..76553988 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,6 +1,4 @@ //! Create interactive, native cross-platform applications. -#[cfg(feature = "trace")] -mod profiler; mod state; pub use state::State; @@ -27,7 +25,7 @@ pub use iced_native::application::{Appearance, StyleSheet}; use std::mem::ManuallyDrop; #[cfg(feature = "trace")] -pub use profiler::Profiler; +pub use crate::Profiler; #[cfg(feature = "trace")] use tracing::{info_span, instrument::Instrument}; diff --git a/winit/src/application/profiler.rs b/winit/src/application/profiler.rs deleted file mode 100644 index 23eaa390..00000000 --- a/winit/src/application/profiler.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! A simple profiler for Iced. -use std::ffi::OsStr; -use std::path::Path; -use std::time::Duration; -use tracing_subscriber::prelude::*; -use tracing_subscriber::Registry; -#[cfg(feature = "chrome-trace")] -use { - tracing_chrome::FlushGuard, - tracing_subscriber::fmt::{format::DefaultFields, FormattedFields}, -}; - -/// Profiler state. This will likely need to be updated or reworked when adding new tracing backends. -#[allow(missing_debug_implementations)] -pub struct Profiler { - #[cfg(feature = "chrome-trace")] - /// [`FlushGuard`] must not be dropped until the application scope is dropped for accurate tracing. - _guard: FlushGuard, -} - -impl Profiler { - /// Initializes the [`Profiler`]. - pub fn init() -> Self { - // Registry stores the spans & generates unique span IDs - let subscriber = Registry::default(); - - let default_path = Path::new(env!("CARGO_MANIFEST_DIR")); - let curr_exe = std::env::current_exe() - .unwrap_or_else(|_| default_path.to_path_buf()); - let out_dir = curr_exe.parent().unwrap_or(default_path).join("traces"); - - #[cfg(feature = "chrome-trace")] - let (chrome_layer, guard) = { - let mut layer = tracing_chrome::ChromeLayerBuilder::new(); - - // Optional configurable env var: CHROME_TRACE_FILE=/path/to/trace_file/file.json, - // for uploading to chrome://tracing (old) or ui.perfetto.dev (new). - if let Ok(path) = std::env::var("CHROME_TRACE_FILE") { - layer = layer.file(path); - } else if std::fs::create_dir_all(&out_dir).is_ok() { - let time = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or(Duration::from_millis(0)) - .as_millis(); - - let curr_exe_name = curr_exe - .file_name() - .unwrap_or_else(|| OsStr::new("trace")) - .to_str() - .unwrap_or("trace"); - - let path = out_dir - .join(format!("{}_trace_{}.json", curr_exe_name, time)); - - layer = layer.file(path); - } else { - layer = layer.file(env!("CARGO_MANIFEST_DIR")) - } - - let (chrome_layer, guard) = layer - .name_fn(Box::new(|event_or_span| match event_or_span { - tracing_chrome::EventOrSpan::Event(event) => { - event.metadata().name().into() - } - tracing_chrome::EventOrSpan::Span(span) => { - if let Some(fields) = span - .extensions() - .get::>() - { - format!( - "{}: {}", - span.metadata().name(), - fields.fields.as_str() - ) - } else { - span.metadata().name().into() - } - } - })) - .build(); - - (chrome_layer, guard) - }; - - let fmt_layer = tracing_subscriber::fmt::Layer::default(); - let subscriber = subscriber.with(fmt_layer); - - #[cfg(feature = "chrome-trace")] - let subscriber = subscriber.with(chrome_layer); - - // create dispatcher which will forward span events to the subscriber - // this can only be set once or will panic - tracing::subscriber::set_global_default(subscriber) - .expect("Tracer could not set the global default subscriber."); - - Profiler { - #[cfg(feature = "chrome-trace")] - _guard: guard, - } - } -} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index eb58482b..99a46850 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -51,11 +51,13 @@ pub mod system; mod error; mod icon; mod proxy; +#[cfg(feature = "trace")] +mod profiler; #[cfg(feature = "application")] pub use application::Application; #[cfg(feature = "trace")] -pub use application::Profiler; +pub use profiler::Profiler; pub use clipboard::Clipboard; pub use error::Error; pub use icon::Icon; diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 6a2bdca9..d7378a1d 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -26,6 +26,11 @@ use iced_native::window::Action; use std::collections::HashMap; use std::mem::ManuallyDrop; +#[cfg(feature = "trace")] +pub use crate::Profiler; +#[cfg(feature = "trace")] +use tracing::{info_span, instrument::Instrument}; + /// TODO(derezzedex) // This is the an wrapper around the `Application::Message` associate type // to allows the `shell` to create internal messages, while still having @@ -172,9 +177,15 @@ where use futures::Future; use winit::event_loop::EventLoopBuilder; + #[cfg(feature = "trace")] + let _guard = Profiler::init(); + let mut debug = Debug::new(); debug.startup_started(); + #[cfg(feature = "trace")] + let _ = info_span!("Application", "RUN").entered(); + let event_loop = EventLoopBuilder::with_user_event().build(); let proxy = event_loop.create_proxy(); @@ -227,18 +238,26 @@ where let (mut sender, receiver) = mpsc::unbounded(); - let mut instance = Box::pin(run_instance::( - application, - compositor, - renderer, - runtime, - proxy, - debug, - receiver, - init_command, - windows, - settings.exit_on_close_request, - )); + let mut instance = Box::pin({ + let run_instance = run_instance::( + application, + compositor, + renderer, + runtime, + proxy, + debug, + receiver, + init_command, + windows, + settings.exit_on_close_request, + ); + + #[cfg(feature = "trace")] + let run_instance = + run_instance.instrument(info_span!("Application", "LOOP")); + + run_instance + }); let mut context = task::Context::from_waker(task::noop_waker_ref()); @@ -604,6 +623,9 @@ async fn run_instance( Event::NewWindow { .. } => unreachable!(), }, event::Event::RedrawRequested(id) => { + #[cfg(feature = "trace")] + let _ = info_span!("Application", "FRAME").entered(); + let state = window_ids .get(&id) .and_then(|id| states.get_mut(id)) @@ -788,12 +810,22 @@ pub fn build_user_interface<'a, A: Application>( where ::Theme: StyleSheet, { + #[cfg(feature = "trace")] + let view_span = info_span!("Application", "VIEW").entered(); + debug.view_started(); let view = application.view(id); + + #[cfg(feature = "trace")] + let _ = view_span.exit(); debug.view_finished(); + #[cfg(feature = "trace")] + let layout_span = info_span!("Application", "LAYOUT").entered(); debug.layout_started(); let user_interface = UserInterface::build(view, size, cache, renderer); + #[cfg(feature = "trace")] + let _ = layout_span.exit(); debug.layout_finished(); user_interface @@ -817,10 +849,15 @@ pub fn update( ::Theme: StyleSheet, { for message in messages.drain(..) { + #[cfg(feature = "trace")] + let update_span = info_span!("Application", "UPDATE").entered(); + debug.log_message(&message); debug.update_started(); let command = runtime.enter(|| application.update(message)); + #[cfg(feature = "trace")] + let _ = update_span.exit(); debug.update_finished(); run_command( diff --git a/winit/src/profiler.rs b/winit/src/profiler.rs new file mode 100644 index 00000000..1f638de8 --- /dev/null +++ b/winit/src/profiler.rs @@ -0,0 +1,102 @@ +//! A simple profiler for Iced. +use std::ffi::OsStr; +use std::path::Path; +use std::time::Duration; +use tracing_subscriber::prelude::*; +use tracing_subscriber::Registry; +#[cfg(feature = "chrome-trace")] +use { + tracing_chrome::FlushGuard, + tracing_subscriber::fmt::{format::DefaultFields, FormattedFields}, +}; + +/// Profiler state. This will likely need to be updated or reworked when adding new tracing backends. +#[allow(missing_debug_implementations)] +pub struct Profiler { + #[cfg(feature = "chrome-trace")] + /// [`FlushGuard`] must not be dropped until the application scope is dropped for accurate tracing. + _guard: FlushGuard, +} + +impl Profiler { + /// Initializes the [`Profiler`]. + pub fn init() -> Self { + log::info!("Capturing trace.."); + // Registry stores the spans & generates unique span IDs + let subscriber = Registry::default(); + + let default_path = Path::new(env!("CARGO_MANIFEST_DIR")); + let curr_exe = std::env::current_exe() + .unwrap_or_else(|_| default_path.to_path_buf()); + let out_dir = curr_exe.parent().unwrap_or(default_path).join("traces"); + + #[cfg(feature = "chrome-trace")] + let (chrome_layer, guard) = { + let mut layer = tracing_chrome::ChromeLayerBuilder::new(); + + // Optional configurable env var: CHROME_TRACE_FILE=/path/to/trace_file/file.json, + // for uploading to chrome://tracing (old) or ui.perfetto.dev (new). + if let Ok(path) = std::env::var("CHROME_TRACE_FILE") { + layer = layer.file(path); + } else if std::fs::create_dir_all(&out_dir).is_ok() { + let time = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or(Duration::from_millis(0)) + .as_millis(); + + let curr_exe_name = curr_exe + .file_name() + .unwrap_or_else(|| OsStr::new("trace")) + .to_str() + .unwrap_or("trace"); + + let path = out_dir + .join(format!("{}_trace_{}.json", curr_exe_name, time)); + + layer = layer.file(path); + } else { + layer = layer.file(env!("CARGO_MANIFEST_DIR")) + } + + let (chrome_layer, guard) = layer + .name_fn(Box::new(|event_or_span| match event_or_span { + tracing_chrome::EventOrSpan::Event(event) => { + event.metadata().name().into() + } + tracing_chrome::EventOrSpan::Span(span) => { + if let Some(fields) = span + .extensions() + .get::>() + { + format!( + "{}: {}", + span.metadata().name(), + fields.fields.as_str() + ) + } else { + span.metadata().name().into() + } + } + })) + .build(); + + (chrome_layer, guard) + }; + + let fmt_layer = tracing_subscriber::fmt::Layer::default(); + let subscriber = subscriber.with(fmt_layer); + + #[cfg(feature = "chrome-trace")] + let subscriber = subscriber.with(chrome_layer); + + // create dispatcher which will forward span events to the subscriber + // this can only be set once or will panic + tracing::subscriber::set_global_default(subscriber) + .expect("Tracer could not set the global default subscriber."); + + Profiler { + #[cfg(feature = "chrome-trace")] + _guard: guard, + } + } +} -- cgit From 7e9a12a4aa64deda193dfc0f18c34f93e3adc852 Mon Sep 17 00:00:00 2001 From: Bingus Date: Wed, 18 Jan 2023 15:17:20 -0800 Subject: New iced changes --- winit/src/application.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'winit/src') diff --git a/winit/src/application.rs b/winit/src/application.rs index c66e08b2..d586fd21 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -402,6 +402,7 @@ async fn run_instance( // 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( + crate::window::Id::MAIN, crate::window::Event::RedrawRequested(Instant::now()), ); -- cgit From 0a643287deece9234b64cc843a9f6ae3e6e4806e Mon Sep 17 00:00:00 2001 From: Bingus Date: Wed, 18 Jan 2023 17:04:11 -0800 Subject: Added window::Id to multi_window application's scale_factor --- winit/src/multi_window.rs | 2 +- winit/src/multi_window/state.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index d7378a1d..ad65e6a5 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -146,7 +146,7 @@ where /// 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 { + fn scale_factor(&self, window: window::Id) -> f64 { 1.0 } diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs index 2c2a4693..35c69924 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/multi_window/state.rs @@ -36,7 +36,7 @@ where window: &Window, ) -> Self { let title = application.title(window_id); - let scale_factor = application.scale_factor(); + let scale_factor = application.scale_factor(window_id); let theme = application.theme(); let appearance = theme.appearance(&application.style()); @@ -199,7 +199,7 @@ where } // Update scale factor - let new_scale_factor = application.scale_factor(); + let new_scale_factor = application.scale_factor(window_id); if self.scale_factor != new_scale_factor { let size = window.inner_size(); -- cgit From 367fea5dc8e94584334e880970126b40a046bfa6 Mon Sep 17 00:00:00 2001 From: Bingus Date: Wed, 15 Feb 2023 11:28:36 -0800 Subject: Redraw request events for multiwindow. --- winit/src/multi_window.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index ad65e6a5..430e6706 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -25,6 +25,7 @@ pub use iced_native::application::{Appearance, StyleSheet}; use iced_native::window::Action; use std::collections::HashMap; use std::mem::ManuallyDrop; +use std::time::Instant; #[cfg(feature = "trace")] pub use crate::Profiler; @@ -402,7 +403,7 @@ async fn run_instance( let (filtered, remaining): (Vec<_>, Vec<_>) = events.iter().cloned().partition( |(window_id, _event): &( - Option, + Option, iced_native::event::Event, )| { *window_id == Some(id) || *window_id == None @@ -410,10 +411,14 @@ async fn run_instance( ); events.retain(|el| remaining.contains(el)); - let filtered: Vec<_> = filtered + let mut filtered: Vec<_> = filtered .into_iter() .map(|(_id, event)| event) .collect(); + filtered.push(iced_native::Event::Window( + id, + window::Event::RedrawRequested(Instant::now()), + )); let cursor_position = states.get(&id).unwrap().cursor_position(); @@ -450,7 +455,7 @@ async fn run_instance( user_interface::State::Outdated, ) { - let state = &mut states.get_mut(&id).unwrap(); + let state = states.get_mut(&id).unwrap(); let pure_states: HashMap<_, _> = ManuallyDrop::into_inner(interfaces) .drain() -- cgit From 64e0e817c27d720dc954ee94de58ded35b3f9f9a Mon Sep 17 00:00:00 2001 From: Bingus Date: Wed, 15 Feb 2023 14:31:16 -0800 Subject: Widget operations for multi-window. --- winit/src/multi_window.rs | 117 ++++++++++++++++++++++++---------------------- 1 file changed, 62 insertions(+), 55 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 430e6706..bd7e9d44 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -4,12 +4,12 @@ mod state; pub use state::State; use crate::clipboard::{self, Clipboard}; -use crate::conversion; use crate::mouse; use crate::renderer; use crate::settings; use crate::widget::operation; use crate::window; +use crate::{conversion, multi_window}; use crate::{ Command, Debug, Element, Error, Executor, Proxy, Renderer, Runtime, Settings, Size, Subscription, @@ -254,7 +254,7 @@ where ); #[cfg(feature = "trace")] - let run_instance = + let run_instance = run_instance.instrument(info_span!("Application", "LOOP")); run_instance @@ -335,7 +335,7 @@ async fn run_instance( let mut clipboard = Clipboard::connect(windows.values().next().expect("No window found")); - let mut cache = user_interface::Cache::default(); + let mut caches = HashMap::new(); let mut window_ids: HashMap<_, _> = windows .iter() .map(|(&id, window)| (window.id(), id)) @@ -368,26 +368,23 @@ async fn run_instance( let _ = states.insert(id, state); let _ = surfaces.insert(id, surface); let _ = interfaces.insert(id, user_interface); + let _ = caches.insert(id, user_interface::Cache::default()); } - { - // TODO(derezzedex) - let state = states.values().next().expect("No state found"); + run_command( + &application, + &mut caches, + &states, + &mut renderer, + init_command, + &mut runtime, + &mut clipboard, + &mut proxy, + &mut debug, + &windows, + || compositor.fetch_information(), + ); - run_command( - &application, - &mut cache, - state, - &mut renderer, - init_command, - &mut runtime, - &mut clipboard, - &mut proxy, - &mut debug, - &windows, - || compositor.fetch_information(), - ); - } runtime.track(application.subscription().map(Event::Application)); let mut mouse_interaction = mouse::Interaction::default(); @@ -455,8 +452,7 @@ async fn run_instance( user_interface::State::Outdated, ) { - let state = states.get_mut(&id).unwrap(); - let pure_states: HashMap<_, _> = + let user_interfaces: HashMap<_, _> = ManuallyDrop::into_inner(interfaces) .drain() .map( @@ -472,8 +468,8 @@ async fn run_instance( // Update application update( &mut application, - &mut cache, - state, + &mut caches, + &states, &mut renderer, &mut runtime, &mut clipboard, @@ -485,7 +481,7 @@ async fn run_instance( ); // Update window - state.synchronize( + states.get_mut(&id).unwrap().synchronize( &application, id, windows.get(&id).expect("No window found with ID."), @@ -498,7 +494,7 @@ async fn run_instance( &mut renderer, &mut debug, &states, - pure_states, + user_interfaces, )); if should_exit { @@ -579,6 +575,7 @@ async fn run_instance( let _ = interfaces.insert(id, user_interface); let _ = window_ids.insert(window.id(), id); let _ = windows.insert(id, window); + let _ = caches.insert(id, user_interface::Cache::default()); } Event::CloseWindow(id) => { if let Some(window) = windows.get(&id) { @@ -764,7 +761,10 @@ async fn run_instance( )); } } else { - log::error!("No window state found for id: {:?}", window_id); + log::error!( + "No window state found for id: {:?}", + window_id + ); } } else { log::error!("No window found with id: {:?}", window_id); @@ -840,8 +840,8 @@ where /// resulting [`Command`], and tracking its [`Subscription`]. pub fn update( application: &mut A, - cache: &mut user_interface::Cache, - state: &State, + caches: &mut HashMap, + states: &HashMap>, renderer: &mut A::Renderer, runtime: &mut Runtime>, Event>, clipboard: &mut Clipboard, @@ -851,6 +851,7 @@ pub fn update( windows: &HashMap, graphics_info: impl FnOnce() -> compositor::Information + Copy, ) where + A: Application + 'static, ::Theme: StyleSheet, { for message in messages.drain(..) { @@ -867,8 +868,8 @@ pub fn update( run_command( application, - cache, - state, + caches, + states, renderer, command, runtime, @@ -887,8 +888,8 @@ pub fn update( /// Runs the actions of a [`Command`]. pub fn run_command( application: &A, - cache: &mut user_interface::Cache, - state: &State, + caches: &mut HashMap, + states: &HashMap>, renderer: &mut A::Renderer, command: Command, runtime: &mut Runtime>, Event>, @@ -898,7 +899,7 @@ pub fn run_command( windows: &HashMap, _graphics_info: impl FnOnce() -> compositor::Information + Copy, ) where - A: Application, + A: Application + 'static, E: Executor, ::Theme: StyleSheet, { @@ -1024,36 +1025,42 @@ pub fn run_command( } }, command::Action::Widget(action) => { - let mut current_cache = std::mem::take(cache); + let mut current_caches = std::mem::take(caches); let mut current_operation = Some(action.into_operation()); - let mut user_interface = build_user_interface( + let mut user_interfaces = build_user_interfaces( application, - current_cache, renderer, - state.logical_size(), debug, - window::Id::MAIN, // TODO(derezzedex): run the operation on every widget tree + states, + current_caches, ); while let Some(mut operation) = current_operation.take() { - user_interface.operate(renderer, operation.as_mut()); - - match operation.finish() { - operation::Outcome::None => {} - operation::Outcome::Some(message) => { - proxy - .send_event(Event::Application(message)) - .expect("Send message to event loop"); - } - operation::Outcome::Chain(next) => { - current_operation = Some(next); + for user_interface in user_interfaces.values_mut() { + user_interface.operate(renderer, operation.as_mut()); + + match operation.finish() { + operation::Outcome::None => {} + operation::Outcome::Some(message) => { + proxy + .send_event(Event::Application(message)) + .expect("Send message to event loop"); + } + operation::Outcome::Chain(next) => { + current_operation = Some(next); + } } } } - current_cache = user_interface.into_cache(); - *cache = current_cache; + let user_interfaces: HashMap<_, _> = user_interfaces + .drain() + .map(|(id, interface)| (id, interface.into_cache())) + .collect(); + + current_caches = user_interfaces; + *caches = current_caches; } } } @@ -1065,7 +1072,7 @@ pub fn build_user_interfaces<'a, A>( renderer: &mut A::Renderer, debug: &mut Debug, states: &HashMap>, - mut pure_states: HashMap, + mut cached_user_interfaces: HashMap, ) -> HashMap< window::Id, UserInterface< @@ -1080,12 +1087,12 @@ where { let mut interfaces = HashMap::new(); - for (id, pure_state) in pure_states.drain() { + for (id, cache) in cached_user_interfaces.drain() { let state = &states.get(&id).unwrap(); let user_interface = build_user_interface( application, - pure_state, + cache, renderer, state.logical_size(), debug, -- cgit From 3c095aa3f09f28c6fd9d2a7ba220ced407693e0b Mon Sep 17 00:00:00 2001 From: Bingus Date: Wed, 15 Feb 2023 14:56:15 -0800 Subject: Merged in iced master --- winit/src/multi_window.rs | 2 +- winit/src/window.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index bd7e9d44..f846e124 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -957,7 +957,7 @@ pub fn run_command( y, }); } - window::Action::SetMode(mode) => { + window::Action::ChangeMode(mode) => { let window = windows.get(&id).expect("No window found"); window.set_visible(conversion::visible(mode)); window.set_fullscreen(conversion::fullscreen( diff --git a/winit/src/window.rs b/winit/src/window.rs index fa31dca1..97d80c38 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -60,7 +60,7 @@ pub fn move_to(id: window::Id, x: i32, y: i32) -> Command { /// Changes the [`Mode`] of the window. pub fn change_mode(id: window::Id, mode: Mode) -> Command { - Command::single(command::Action::Window(id, window::Action::SetMode(mode))) + Command::single(command::Action::Window(id, window::Action::ChangeMode(mode))) } /// Fetches the current [`Mode`] of the window. -- cgit From 8da098330b58542cc929f4f24d02e26bd654bae4 Mon Sep 17 00:00:00 2001 From: Bingus Date: Fri, 17 Feb 2023 11:42:49 -0800 Subject: Fixed widget animations implementation --- winit/src/multi_window.rs | 110 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 84 insertions(+), 26 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index f846e124..17eaa6fe 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -9,7 +9,7 @@ use crate::renderer; use crate::settings; use crate::widget::operation; use crate::window; -use crate::{conversion, multi_window}; +use crate::conversion; use crate::{ Command, Debug, Element, Error, Executor, Proxy, Renderer, Runtime, Settings, Size, Subscription, @@ -237,7 +237,8 @@ where let (compositor, renderer) = C::new(compositor_settings, Some(&window))?; - let (mut sender, receiver) = mpsc::unbounded(); + let (mut event_sender, event_receiver) = mpsc::unbounded(); + let (control_sender, mut control_receiver) = mpsc::unbounded(); let mut instance = Box::pin({ let run_instance = run_instance::( @@ -247,7 +248,8 @@ where runtime, proxy, debug, - receiver, + event_receiver, + control_sender, init_command, windows, settings.exit_on_close_request, @@ -299,13 +301,19 @@ where }; if let Some(event) = event { - sender.start_send(event).expect("Send event"); + event_sender.start_send(event).expect("Send event"); let poll = instance.as_mut().poll(&mut context); - *control_flow = match poll { - task::Poll::Pending => ControlFlow::Wait, - task::Poll::Ready(_) => ControlFlow::Exit, + match poll { + task::Poll::Pending => { + if let Ok(Some(flow)) = control_receiver.try_next() { + *control_flow = flow; + } + } + task::Poll::Ready(_) => { + *control_flow = ControlFlow::Exit; + } }; } }) @@ -318,9 +326,10 @@ async fn run_instance( mut runtime: Runtime>, Event>, mut proxy: winit::event_loop::EventLoopProxy>, mut debug: Debug, - mut receiver: mpsc::UnboundedReceiver< + mut event_receiver: mpsc::UnboundedReceiver< winit::event::Event<'_, Event>, >, + mut control_sender: mpsc::UnboundedSender, init_command: Command, mut windows: HashMap, _exit_on_close_request: bool, @@ -332,6 +341,7 @@ async fn run_instance( { use iced_futures::futures::stream::StreamExt; use winit::event; + use winit::event_loop::ControlFlow; let mut clipboard = Clipboard::connect(windows.values().next().expect("No window found")); @@ -390,11 +400,20 @@ async fn run_instance( let mut mouse_interaction = mouse::Interaction::default(); let mut events = Vec::new(); let mut messages = Vec::new(); + let mut redraw_pending = false; debug.startup_finished(); - 'main: while let Some(event) = receiver.next().await { + 'main: while let Some(event) = event_receiver.next().await { match event { + event::Event::NewEvents(start_cause) => { + redraw_pending = matches!( + start_cause, + event::StartCause::Init + | event::StartCause::Poll + | event::StartCause::ResumeTimeReached { .. } + ); + } event::Event::MainEventsCleared => { for id in states.keys().copied().collect::>() { let (filtered, remaining): (Vec<_>, Vec<_>) = @@ -408,29 +427,27 @@ async fn run_instance( ); events.retain(|el| remaining.contains(el)); - let mut filtered: Vec<_> = filtered + let window_events: Vec<_> = filtered .into_iter() .map(|(_id, event)| event) .collect(); - filtered.push(iced_native::Event::Window( - id, - window::Event::RedrawRequested(Instant::now()), - )); - let cursor_position = - states.get(&id).unwrap().cursor_position(); - let window = windows.get(&id).unwrap(); - - if filtered.is_empty() && messages.is_empty() { + if !redraw_pending + && window_events.is_empty() + && messages.is_empty() + { continue; } debug.event_processing_started(); + let cursor_position = + states.get(&id).unwrap().cursor_position(); + let (interface_state, statuses) = { let user_interface = interfaces.get_mut(&id).unwrap(); user_interface.update( - &filtered, + &window_events, cursor_position, &mut renderer, &mut clipboard, @@ -440,7 +457,8 @@ async fn run_instance( debug.event_processing_finished(); - for event in filtered.into_iter().zip(statuses.into_iter()) + for event in + window_events.into_iter().zip(statuses.into_iter()) { runtime.broadcast(event); } @@ -487,8 +505,6 @@ async fn run_instance( windows.get(&id).expect("No window found with ID."), ); - let should_exit = application.should_exit(); - interfaces = ManuallyDrop::new(build_user_interfaces( &application, &mut renderer, @@ -497,17 +513,35 @@ async fn run_instance( user_interfaces, )); - if should_exit { + if application.should_exit() { break 'main; } } + // TODO: Avoid redrawing all the time by forcing widgets to + // request redraws on state changes + // + // Then, we can use the `interface_state` here to decide if a redraw + // is needed right away, or simply wait until a specific time. + let redraw_event = iced_native::Event::Window( + id, + window::Event::RedrawRequested(Instant::now()), + ); + + let (interface_state, _) = + interfaces.get_mut(&id).unwrap().update( + &[redraw_event.clone()], + cursor_position, + &mut renderer, + &mut clipboard, + &mut messages, + ); + debug.draw_started(); let new_mouse_interaction = { - let user_interface = interfaces.get_mut(&id).unwrap(); let state = states.get(&id).unwrap(); - user_interface.draw( + interfaces.get_mut(&id).unwrap().draw( &mut renderer, state.theme(), &renderer::Style { @@ -518,6 +552,8 @@ async fn run_instance( }; debug.draw_finished(); + let window = windows.get(&id).unwrap(); + if new_mouse_interaction != mouse_interaction { window.set_cursor_icon(conversion::mouse_interaction( new_mouse_interaction, @@ -528,6 +564,28 @@ async fn run_instance( for window in windows.values() { window.request_redraw(); + + runtime.broadcast(( + redraw_event.clone(), + crate::event::Status::Ignored, + )); + + let _ = + control_sender.start_send(match interface_state { + user_interface::State::Updated { + redraw_request: Some(redraw_request), + } => match redraw_request { + window::RedrawRequest::NextFrame => { + ControlFlow::Poll + } + window::RedrawRequest::At(at) => { + ControlFlow::WaitUntil(at) + } + }, + _ => ControlFlow::Wait, + }); + + redraw_pending = false; } } } -- cgit From 2d427455ce8f9627da7c09eb80f38a615f0ddcb7 Mon Sep 17 00:00:00 2001 From: Bingus Date: Fri, 17 Feb 2023 11:50:52 -0800 Subject: Iced master merge (again) --- winit/src/multi_window.rs | 21 ++++++++++++++++----- winit/src/window.rs | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 17eaa6fe..96dd1c8b 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -4,12 +4,12 @@ mod state; pub use state::State; use crate::clipboard::{self, Clipboard}; +use crate::conversion; use crate::mouse; use crate::renderer; use crate::settings; use crate::widget::operation; use crate::window; -use crate::conversion; use crate::{ Command, Debug, Element, Error, Executor, Proxy, Renderer, Runtime, Settings, Size, Subscription, @@ -22,7 +22,6 @@ use iced_native::user_interface::{self, UserInterface}; pub use iced_native::application::{Appearance, StyleSheet}; -use iced_native::window::Action; use std::collections::HashMap; use std::mem::ManuallyDrop; use std::time::Instant; @@ -147,6 +146,7 @@ where /// 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 } @@ -470,7 +470,7 @@ async fn run_instance( user_interface::State::Outdated, ) { - let user_interfaces: HashMap<_, _> = + let cached_interfaces: HashMap<_, _> = ManuallyDrop::into_inner(interfaces) .drain() .map( @@ -510,7 +510,7 @@ async fn run_instance( &mut renderer, &mut debug, &states, - user_interfaces, + cached_interfaces, )); if application.should_exit() { @@ -1057,10 +1057,21 @@ pub fn run_command( attention_type.map(conversion::user_attention), ); } - Action::GainFocus => { + window::Action::GainFocus => { let window = windows.get(&id).expect("No window found!"); window.focus_window(); } + window::Action::ChangeAlwaysOnTop(on_top) => { + let window = windows.get(&id).expect("No window found!"); + window.set_always_on_top(on_top); + } + window::Action::FetchId(tag) => { + let window = windows.get(&id).expect("No window found!"); + + proxy + .send_event(Event::Application(tag(window.id().into()))) + .expect("Send message to event loop.") + } }, command::Action::System(action) => match action { system::Action::QueryInformation(_tag) => { diff --git a/winit/src/window.rs b/winit/src/window.rs index 88cd3f14..8fd415ef 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -122,7 +122,7 @@ pub fn fetch_id( id: window::Id, f: impl FnOnce(u64) -> Message + 'static, ) -> Command { - Command::single(command::Action::Window(id: window::Id, window::Action::FetchId(Box::new( + Command::single(command::Action::Window(id, window::Action::FetchId(Box::new( f, )))) } -- cgit From 3aaf5d8873b16302badb14dc5508329c943862fb Mon Sep 17 00:00:00 2001 From: Bingus Date: Fri, 17 Feb 2023 13:26:31 -0800 Subject: Fixed widget operations --- winit/src/multi_window.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 96dd1c8b..fc91f41b 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -470,7 +470,7 @@ async fn run_instance( user_interface::State::Outdated, ) { - let cached_interfaces: HashMap<_, _> = + let mut cached_interfaces: HashMap<_, _> = ManuallyDrop::into_inner(interfaces) .drain() .map( @@ -486,7 +486,7 @@ async fn run_instance( // Update application update( &mut application, - &mut caches, + &mut cached_interfaces, &states, &mut renderer, &mut runtime, -- cgit From bd58d5fe25182908e99fdb0ced07b86666e45081 Mon Sep 17 00:00:00 2001 From: Bingus Date: Mon, 20 Feb 2023 12:34:04 -0800 Subject: Cargo fix --- winit/src/multi_window.rs | 56 +++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 26 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index fc91f41b..788f39f7 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -416,6 +416,7 @@ async fn run_instance( } event::Event::MainEventsCleared => { for id in states.keys().copied().collect::>() { + // Partition events into only events for this window let (filtered, remaining): (Vec<_>, Vec<_>) = events.iter().cloned().partition( |(window_id, _event): &( @@ -426,7 +427,9 @@ async fn run_instance( }, ); + // Only retain events which have not been processed for next iteration events.retain(|el| remaining.contains(el)); + let window_events: Vec<_> = filtered .into_iter() .map(|(_id, event)| event) @@ -439,8 +442,8 @@ async fn run_instance( continue; } + // Process winit events for window debug.event_processing_started(); - let cursor_position = states.get(&id).unwrap().cursor_position(); @@ -455,15 +458,16 @@ async fn run_instance( ) }; - debug.event_processing_finished(); - for event in window_events.into_iter().zip(statuses.into_iter()) { runtime.broadcast(event); } + debug.event_processing_finished(); - // TODO(derezzedex): Should we redraw every window? We can't know what changed. + // Update application with app message(s) + // Note: without tying an app message to a window ID, we must redraw all windows + // as we cannot know what changed without some kind of damage tracking. if !messages.is_empty() || matches!( interface_state, @@ -498,7 +502,7 @@ async fn run_instance( || compositor.fetch_information(), ); - // Update window + // synchronize window state with application state. states.get_mut(&id).unwrap().synchronize( &application, id, @@ -564,29 +568,29 @@ async fn run_instance( for window in windows.values() { window.request_redraw(); + } - runtime.broadcast(( - redraw_event.clone(), - crate::event::Status::Ignored, - )); + runtime.broadcast(( + redraw_event.clone(), + crate::event::Status::Ignored, + )); - let _ = - control_sender.start_send(match interface_state { - user_interface::State::Updated { - redraw_request: Some(redraw_request), - } => match redraw_request { - window::RedrawRequest::NextFrame => { - ControlFlow::Poll - } - window::RedrawRequest::At(at) => { - ControlFlow::WaitUntil(at) - } - }, - _ => ControlFlow::Wait, - }); - - redraw_pending = false; - } + let _ = + control_sender.start_send(match interface_state { + user_interface::State::Updated { + redraw_request: Some(redraw_request), + } => match redraw_request { + window::RedrawRequest::NextFrame => { + ControlFlow::Poll + } + window::RedrawRequest::At(at) => { + ControlFlow::WaitUntil(at) + } + }, + _ => ControlFlow::Wait, + }); + + redraw_pending = false; } } event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( -- cgit From 50b9c778b1e49fc83f827a340dce36a09bb291d6 Mon Sep 17 00:00:00 2001 From: Bingus Date: Tue, 21 Feb 2023 16:46:32 -0800 Subject: Fixed state syncing issue with MW. --- winit/src/multi_window.rs | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 788f39f7..1a9d4a1c 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -502,12 +502,16 @@ async fn run_instance( || compositor.fetch_information(), ); - // synchronize window state with application state. - states.get_mut(&id).unwrap().synchronize( - &application, - id, - windows.get(&id).expect("No window found with ID."), - ); + // synchronize window states with application states. + for (id, state) in states.iter_mut() { + state.synchronize( + &application, + *id, + windows + .get(id) + .expect("No window found with ID."), + ); + } interfaces = ManuallyDrop::new(build_user_interfaces( &application, @@ -575,20 +579,19 @@ async fn run_instance( crate::event::Status::Ignored, )); - let _ = - control_sender.start_send(match interface_state { - user_interface::State::Updated { - redraw_request: Some(redraw_request), - } => match redraw_request { - window::RedrawRequest::NextFrame => { - ControlFlow::Poll - } - window::RedrawRequest::At(at) => { - ControlFlow::WaitUntil(at) - } - }, - _ => ControlFlow::Wait, - }); + let _ = control_sender.start_send(match interface_state { + user_interface::State::Updated { + redraw_request: Some(redraw_request), + } => match redraw_request { + window::RedrawRequest::NextFrame => { + ControlFlow::Poll + } + window::RedrawRequest::At(at) => { + ControlFlow::WaitUntil(at) + } + }, + _ => ControlFlow::Wait, + }); redraw_pending = false; } -- cgit From e36daa6f937abd7cb2071fd8852a3c12263944ea Mon Sep 17 00:00:00 2001 From: bungoboingo Date: Tue, 28 Feb 2023 13:44:36 -0800 Subject: Removed glutin MW support and reverted glutin changes back to Iced master since it's being axed as we speak. --- winit/src/application.rs | 2 +- winit/src/multi_window.rs | 25 +++++++++++-------------- winit/src/multi_window/state.rs | 7 +++---- winit/src/profiler.rs | 1 - winit/src/settings.rs | 8 ++++---- winit/src/settings/windows.rs | 4 ++-- 6 files changed, 21 insertions(+), 26 deletions(-) (limited to 'winit/src') diff --git a/winit/src/application.rs b/winit/src/application.rs index 58556da4..1310ba1c 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -734,7 +734,7 @@ pub fn run_command( clipboard.write(contents); } }, - command::Action::Window(_id, action) => match action { + command::Action::Window(_, action) => match action { window::Action::Close => { *should_exit = true; } diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 1a9d4a1c..eac8b260 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -1,4 +1,4 @@ -//! Create interactive, native cross-platform applications. +//! Create interactive, native cross-platform applications for WGPU. mod state; pub use state::State; @@ -31,17 +31,14 @@ pub use crate::Profiler; #[cfg(feature = "trace")] use tracing::{info_span, instrument::Instrument}; -/// TODO(derezzedex) -// This is the an wrapper around the `Application::Message` associate type -// to allows the `shell` to create internal messages, while still having -// the current user specified custom messages. +/// This is a wrapper around the `Application::Message` associate type +/// to allows the `shell` to create internal messages, while still having +/// the current user specified custom messages. #[derive(Debug)] pub enum Event { /// An [`Application`] generated message Application(Message), - /// TODO(derezzedex) - // Create a wrapper variant of `window::Event` type instead - // (maybe we should also allow users to listen/react to those internal messages?) + /// A message which spawns a new window. NewWindow { /// The [window::Id] of the newly spawned [`Window`]. id: window::Id, @@ -50,9 +47,9 @@ pub enum Event { /// The title of the newly spawned [`Window`]. title: String, }, - /// TODO(derezzedex) + /// Close a window. CloseWindow(window::Id), - /// TODO(derezzedex) + /// A message for when the window has finished being created. WindowCreated(window::Id, winit::window::Window), } @@ -90,7 +87,7 @@ where /// background by shells. fn update(&mut self, message: Self::Message) -> Command; - /// Returns the widgets to display in the [`Program`]. + /// Returns the widgets to display for the `window` in the [`Program`]. /// /// These widgets can produce __messages__ based on user interaction. fn view( @@ -108,7 +105,7 @@ where /// load state from a file, perform an initial HTTP request, etc. fn new(flags: Self::Flags) -> (Self, Command); - /// Returns the current title of the current [`Application`] window. + /// Returns the current title of each [`Application`] window. /// /// This title can be dynamic! The runtime will automatically update the /// title of your application when necessary. @@ -137,7 +134,7 @@ where Subscription::none() } - /// Returns the scale factor of the [`Application`]. + /// Returns the scale factor of the window of the [`Application`]. /// /// It can be used to dynamically control the size of the UI at runtime /// (i.e. zooming). @@ -1142,7 +1139,7 @@ pub fn run_command( } } -/// TODO(derezzedex) +/// Build the user interfaces for every window. pub fn build_user_interfaces<'a, A>( application: &'a A, renderer: &mut A::Renderer, diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs index 35c69924..a7e65de7 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/multi_window/state.rs @@ -8,7 +8,7 @@ use std::marker::PhantomData; use winit::event::{Touch, WindowEvent}; use winit::window::Window; -/// The state of a windowed [`Application`]. +/// The state of a multi-windowed [`Application`]. #[allow(missing_debug_implementations)] pub struct State where @@ -29,7 +29,7 @@ impl State where ::Theme: application::StyleSheet, { - /// Creates a new [`State`] for the provided [`Application`]'s window. + /// Creates a new [`State`] for the provided [`Application`]'s `window`. pub fn new( application: &A, window_id: window::Id, @@ -116,8 +116,7 @@ where self.appearance.text_color } - /// Processes the provided window event and updates the [`State`] - /// accordingly. + /// Processes the provided window event and updates the [`State`] accordingly. pub fn update( &mut self, window: &Window, diff --git a/winit/src/profiler.rs b/winit/src/profiler.rs index ff9bbdc0..7031507a 100644 --- a/winit/src/profiler.rs +++ b/winit/src/profiler.rs @@ -21,7 +21,6 @@ pub struct Profiler { impl Profiler { /// Initializes the [`Profiler`]. pub fn init() -> Self { - log::info!("Capturing trace.."); // Registry stores the spans & generates unique span IDs let subscriber = Registry::default(); diff --git a/winit/src/settings.rs b/winit/src/settings.rs index b26de542..88d7c1de 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -179,9 +179,9 @@ impl Window { { use winit::platform::windows::WindowBuilderExtWindows; - // if let Some(parent) = self.platform_specific.parent { - // window_builder = window_builder.with_parent_window(parent); - // } + if let Some(parent) = self.platform_specific.parent { + window_builder = window_builder.with_parent_window(parent); + } window_builder = window_builder .with_drag_and_drop(self.platform_specific.drag_and_drop); @@ -227,7 +227,7 @@ impl From for Window { fn from(settings: iced_native::window::Settings) -> Self { Self { size: settings.size, - position: Position::from(settings.position), + position: settings.position, min_size: settings.min_size, max_size: settings.max_size, visible: settings.visible, diff --git a/winit/src/settings/windows.rs b/winit/src/settings/windows.rs index 0891ec2c..ff03a9c5 100644 --- a/winit/src/settings/windows.rs +++ b/winit/src/settings/windows.rs @@ -4,7 +4,7 @@ #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PlatformSpecific { /// Parent window - // pub parent: Option, + pub parent: Option, /// Drag and drop support pub drag_and_drop: bool, @@ -13,7 +13,7 @@ pub struct PlatformSpecific { impl Default for PlatformSpecific { fn default() -> Self { Self { - // parent: None, + parent: None, drag_and_drop: true, } } -- cgit From 8ba18430800142965549077373e2a45d0a3429a1 Mon Sep 17 00:00:00 2001 From: Bingus Date: Mon, 13 Mar 2023 14:16:45 -0700 Subject: Code cleanup, clearer comments + removed some unnecessary dupe; Removed `Frames` struct return for `window::frames()` since we are just redrawing every window anyways; Interface dropping; --- winit/src/application.rs | 2 +- winit/src/lib.rs | 2 +- winit/src/multi_window.rs | 126 +++++++++++++++------------------------- winit/src/multi_window/state.rs | 2 +- 4 files changed, 49 insertions(+), 83 deletions(-) (limited to 'winit/src') diff --git a/winit/src/application.rs b/winit/src/application.rs index 906da3c6..fe97486f 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -743,7 +743,7 @@ pub fn run_command( } window::Action::Spawn { .. } => { log::info!( - "This is only available on `multi_window::Application`" + "Spawning a window is only available with `multi_window::Application`s." ) } window::Action::Resize { width, height } => { diff --git a/winit/src/lib.rs b/winit/src/lib.rs index fe5768df..54b9c31f 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -35,7 +35,7 @@ pub use iced_native::*; pub use winit; -#[cfg(feature = "multi_window")] +#[cfg(feature = "multi-window")] pub mod multi_window; #[cfg(feature = "application")] diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index eac8b260..d5da406c 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -33,7 +33,7 @@ use tracing::{info_span, instrument::Instrument}; /// This is a wrapper around the `Application::Message` associate type /// to allows the `shell` to create internal messages, while still having -/// the current user specified custom messages. +/// the current user-specified custom messages. #[derive(Debug)] pub enum Event { /// An [`Application`] generated message @@ -53,9 +53,9 @@ pub enum Event { WindowCreated(window::Id, winit::window::Window), } -/// An interactive, native cross-platform application. +/// An interactive, native, cross-platform, multi-windowed application. /// -/// This trait is the main entrypoint of Iced. Once implemented, you can run +/// 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. /// @@ -105,7 +105,7 @@ where /// load state from a file, perform an initial HTTP request, etc. fn new(flags: Self::Flags) -> (Self, Command); - /// Returns the current title of each [`Application`] window. + /// Returns the current title of each window of the [`Application`]. /// /// This title can be dynamic! The runtime will automatically update the /// title of your application when necessary. @@ -155,7 +155,8 @@ where false } - /// Requests that the [`window`] be closed. + /// Returns the `Self::Message` that should be processed when a `window` is requested to + /// be closed. fn close_requested(&self, window: window::Id) -> Self::Message; } @@ -462,9 +463,9 @@ async fn run_instance( } debug.event_processing_finished(); - // Update application with app message(s) - // Note: without tying an app message to a window ID, we must redraw all windows - // as we cannot know what changed without some kind of damage tracking. + // Update application with app messages + // Unless we implement some kind of diffing, we must redraw all windows as we + // cannot know what changed. if !messages.is_empty() || matches!( interface_state, @@ -612,9 +613,7 @@ async fn run_instance( } Event::WindowCreated(id, window) => { let mut surface = compositor.create_surface(&window); - let state = State::new(&application, id, &window); - let physical_size = state.physical_size(); compositor.configure_surface( @@ -776,14 +775,12 @@ async fn run_instance( } _ => { debug.render_finished(); + log::error!("Error {error:?} when presenting surface."); - // Try rendering again next frame. - // TODO(derezzedex) - windows - .values() - .next() - .expect("No window found") - .request_redraw(); + // Try rendering windows again next frame. + for window in windows.values() { + window.request_redraw(); + } } }, } @@ -792,80 +789,45 @@ async fn run_instance( event: window_event, window_id, } => { - // dbg!(window_id); - if let Some(window) = - window_ids.get(&window_id).and_then(|id| windows.get(id)) - { - if let Some(state) = window_ids + if let (Some(window), Some(state)) = ( + window_ids.get(&window_id).and_then(|id| windows.get(id)), + window_ids .get(&window_id) - .and_then(|id| states.get_mut(id)) - { - if requests_exit(&window_event, state.modifiers()) { - if let Some(id) = - window_ids.get(&window_id).cloned() - { - let message = application.close_requested(id); - messages.push(message); - } + .and_then(|id| states.get_mut(id)), + ) { + if crate::application::requests_exit(&window_event, state.modifiers()) { + if let Some(id) = window_ids.get(&window_id).cloned() { + let message = application.close_requested(id); + messages.push(message); } + } - state.update(window, &window_event, &mut debug); - - if let Some(event) = conversion::window_event( - *window_ids.get(&window_id).unwrap(), - &window_event, - state.scale_factor(), - state.modifiers(), - ) { - events.push(( - window_ids.get(&window_id).cloned(), - event, - )); - } - } else { - log::error!( - "No window state found for id: {:?}", - window_id - ); + state.update(window, &window_event, &mut debug); + + if let Some(event) = conversion::window_event( + *window_ids.get(&window_id).unwrap(), + &window_event, + state.scale_factor(), + state.modifiers(), + ) { + events + .push((window_ids.get(&window_id).cloned(), event)); } } else { - log::error!("No window found with id: {:?}", window_id); + log::error!( + "Could not find window or state for id: {window_id:?}" + ); } } _ => {} } } - // 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::event::ModifiersState, -) -> bool { - use winit::event::WindowEvent; - - match event { - WindowEvent::CloseRequested => true, - #[cfg(target_os = "macos")] - WindowEvent::KeyboardInput { - input: - winit::event::KeyboardInput { - virtual_keycode: Some(winit::event::VirtualKeyCode::Q), - state: winit::event::ElementState::Pressed, - .. - }, - .. - } if _modifiers.logo() => true, - _ => false, - } + // Manually drop the user interfaces + drop(ManuallyDrop::into_inner(interfaces)); } -/// Builds a [`UserInterface`] for the provided [`Application`], logging -/// [`struct@Debug`] information accordingly. +/// Builds a window's [`UserInterface`] for the [`Application`]. pub fn build_user_interface<'a, A: Application>( application: &'a A, cache: user_interface::Cache, @@ -890,7 +852,9 @@ where #[cfg(feature = "trace")] let layout_span = info_span!("Application", "LAYOUT").entered(); debug.layout_started(); + let user_interface = UserInterface::build(view, size, cache, renderer); + #[cfg(feature = "trace")] let _ = layout_span.exit(); debug.layout_finished(); @@ -898,7 +862,7 @@ where user_interface } -/// Updates an [`Application`] by feeding it the provided messages, spawning any +/// Updates an [`Application`] by feeding it messages, spawning any /// resulting [`Command`], and tracking its [`Subscription`]. pub fn update( application: &mut A, @@ -923,7 +887,9 @@ pub fn update( debug.log_message(&message); debug.update_started(); + let command = runtime.enter(|| application.update(message)); + #[cfg(feature = "trace")] let _ = update_span.exit(); debug.update_finished(); @@ -1023,7 +989,7 @@ pub fn run_command( let window = windows.get(&id).expect("No window found"); window.set_visible(conversion::visible(mode)); window.set_fullscreen(conversion::fullscreen( - window.primary_monitor(), + window.current_monitor(), mode, )); } diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs index a7e65de7..d0e442d0 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/multi_window/state.rs @@ -179,7 +179,7 @@ where /// Synchronizes the [`State`] with its [`Application`] and its respective /// window. /// - /// Normally an [`Application`] should be synchronized with its [`State`] + /// Normally, an [`Application`] should be synchronized with its [`State`] /// and window after calling [`Application::update`]. /// /// [`Application::update`]: crate::Program::update -- cgit From ce4b2c93d9802dfb8cd3fc9033d76651d4bbc75b Mon Sep 17 00:00:00 2001 From: Bingus Date: Mon, 13 Mar 2023 18:19:16 -0700 Subject: Added simpler MW example --- winit/src/multi_window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index d5da406c..6e28f1fa 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -109,7 +109,7 @@ where /// /// This title can be dynamic! The runtime will automatically update the /// title of your application when necessary. - fn title(&self, window_id: window::Id) -> String; + fn title(&self, window: window::Id) -> String; /// Returns the current [`Theme`] of the [`Application`]. fn theme(&self) -> ::Theme; -- cgit From 41836dd80d0534608e7aedfbf2319c540a23de1a Mon Sep 17 00:00:00 2001 From: Bingus Date: Wed, 15 Mar 2023 18:20:38 -0700 Subject: Added per-window theme support. --- winit/src/multi_window.rs | 2 +- winit/src/multi_window/state.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 6e28f1fa..9b395c1d 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -112,7 +112,7 @@ where fn title(&self, window: window::Id) -> String; /// Returns the current [`Theme`] of the [`Application`]. - fn theme(&self) -> ::Theme; + fn theme(&self, window: window::Id) -> ::Theme; /// Returns the [`Style`] variation of the [`Theme`]. fn style( diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs index d0e442d0..54a114ad 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/multi_window/state.rs @@ -37,7 +37,7 @@ where ) -> Self { let title = application.title(window_id); let scale_factor = application.scale_factor(window_id); - let theme = application.theme(); + let theme = application.theme(window_id); let appearance = theme.appearance(&application.style()); let viewport = { @@ -212,7 +212,7 @@ where } // Update theme and appearance - self.theme = application.theme(); + self.theme = application.theme(window_id); self.appearance = self.theme.appearance(&application.style()); } } -- cgit From d53ccc857da4d4cda769904342aeb5a82a64f146 Mon Sep 17 00:00:00 2001 From: Bingus Date: Wed, 12 Jul 2023 19:21:05 -0700 Subject: refactored window storage; new helper window events (Destroyed, Created); clippy + fmt; --- winit/src/application.rs | 86 +-- winit/src/conversion.rs | 9 +- winit/src/icon.rs | 63 --- winit/src/lib.rs | 8 +- winit/src/multi_window.rs | 1078 ++++++++++++++++++------------------- winit/src/multi_window/state.rs | 96 ++-- winit/src/multi_window/windows.rs | 170 ++++++ winit/src/profiler.rs | 101 ---- winit/src/settings.rs | 246 +++------ winit/src/settings/macos.rs | 12 - winit/src/settings/other.rs | 3 - winit/src/settings/wasm.rs | 11 - winit/src/settings/windows.rs | 21 - winit/src/window.rs | 0 14 files changed, 856 insertions(+), 1048 deletions(-) delete mode 100644 winit/src/icon.rs create mode 100644 winit/src/multi_window/windows.rs delete mode 100644 winit/src/profiler.rs delete mode 100644 winit/src/settings/macos.rs delete mode 100644 winit/src/settings/other.rs delete mode 100644 winit/src/settings/wasm.rs delete mode 100644 winit/src/settings/windows.rs delete mode 100644 winit/src/window.rs (limited to 'winit/src') diff --git a/winit/src/application.rs b/winit/src/application.rs index ab7b2495..5c45bbda 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -18,6 +18,7 @@ use crate::runtime::clipboard; use crate::runtime::program::Program; use crate::runtime::user_interface::{self, UserInterface}; use crate::runtime::{Command, Debug}; +use crate::settings; use crate::style::application::{Appearance, StyleSheet}; use crate::{Clipboard, Error, Proxy, Settings}; @@ -25,11 +26,6 @@ use futures::channel::mpsc; use std::mem::ManuallyDrop; -#[cfg(feature = "trace")] -pub use crate::Profiler; -#[cfg(feature = "trace")] -use tracing::{info_span, instrument::Instrument}; - /// An interactive, native cross-platform application. /// /// This trait is the main entrypoint of Iced. Once implemented, you can run @@ -117,15 +113,9 @@ where use futures::Future; use winit::event_loop::EventLoopBuilder; - #[cfg(feature = "trace")] - let _guard = Profiler::init(); - let mut debug = Debug::new(); debug.startup_started(); - #[cfg(feature = "trace")] - let _ = info_span!("Application", "RUN").entered(); - let event_loop = EventLoopBuilder::with_user_event().build(); let proxy = event_loop.create_proxy(); @@ -146,14 +136,13 @@ where let target = settings.window.platform_specific.target.clone(); let should_be_visible = settings.window.visible; - let builder = settings - .window - .into_builder( - &application.title(), - event_loop.primary_monitor(), - settings.id, - ) - .with_visible(false); + let builder = settings::window_builder( + settings.window, + &application.title(), + event_loop.primary_monitor(), + settings.id, + ) + .with_visible(false); log::debug!("Window builder: {:#?}", builder); @@ -196,28 +185,20 @@ where let (mut event_sender, event_receiver) = mpsc::unbounded(); let (control_sender, mut control_receiver) = mpsc::unbounded(); - let mut instance = Box::pin({ - let run_instance = run_instance::( - application, - compositor, - renderer, - runtime, - proxy, - debug, - event_receiver, - control_sender, - init_command, - window, - should_be_visible, - settings.exit_on_close_request, - ); - - #[cfg(feature = "trace")] - let run_instance = - run_instance.instrument(info_span!("Application", "LOOP")); - - run_instance - }); + let mut instance = Box::pin(run_instance::( + application, + compositor, + renderer, + runtime, + proxy, + debug, + event_receiver, + control_sender, + init_command, + window, + should_be_visible, + settings.exit_on_close_request, + )); let mut context = task::Context::from_waker(task::noop_waker_ref()); @@ -480,9 +461,6 @@ async fn run_instance( messages.push(message); } event::Event::RedrawRequested(_) => { - #[cfg(feature = "trace")] - let _ = info_span!("Application", "FRAME").entered(); - let physical_size = state.physical_size(); if physical_size.width == 0 || physical_size.height == 0 { @@ -622,24 +600,12 @@ pub fn build_user_interface<'a, A: Application>( where ::Theme: StyleSheet, { - #[cfg(feature = "trace")] - let view_span = info_span!("Application", "VIEW").entered(); - debug.view_started(); let view = application.view(); - - #[cfg(feature = "trace")] - let _ = view_span.exit(); debug.view_finished(); - #[cfg(feature = "trace")] - let layout_span = info_span!("Application", "LAYOUT").entered(); - debug.layout_started(); let user_interface = UserInterface::build(view, size, cache, renderer); - - #[cfg(feature = "trace")] - let _ = layout_span.exit(); debug.layout_finished(); user_interface @@ -666,16 +632,10 @@ pub fn update( ::Theme: StyleSheet, { for message in messages.drain(..) { - #[cfg(feature = "trace")] - let update_span = info_span!("Application", "UPDATE").entered(); - debug.log_message(&message); debug.update_started(); let command = runtime.enter(|| application.update(message)); - - #[cfg(feature = "trace")] - let _ = update_span.exit(); debug.update_finished(); run_command( @@ -750,7 +710,7 @@ pub fn run_command( } window::Action::Spawn { .. } => { log::info!( - "Spawning a window is only available with `multi_window::Application`s." + "Spawning a window is only available with multi-window applications." ) } window::Action::Resize(size) => { diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index fe0fce19..0625e74b 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -7,7 +7,6 @@ use crate::core::mouse; use crate::core::touch; use crate::core::window; use crate::core::{Event, Point}; -use crate::Position; /// Converts a winit window event into an iced event. pub fn window_event( @@ -169,17 +168,17 @@ pub fn window_level(level: window::Level) -> winit::window::WindowLevel { pub fn position( monitor: Option<&winit::monitor::MonitorHandle>, (width, height): (u32, u32), - position: Position, + position: window::Position, ) -> Option { match position { - Position::Default => None, - Position::Specific(x, y) => { + window::Position::Default => None, + window::Position::Specific(x, y) => { Some(winit::dpi::Position::Logical(winit::dpi::LogicalPosition { x: f64::from(x), y: f64::from(y), })) } - Position::Centered => { + window::Position::Centered => { if let Some(monitor) = monitor { let start = monitor.position(); diff --git a/winit/src/icon.rs b/winit/src/icon.rs deleted file mode 100644 index 0fe010ca..00000000 --- a/winit/src/icon.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Attach an icon to the window of your application. -pub use crate::core::window::icon::*; - -use crate::core::window::icon; - -use std::io; - -#[cfg(feature = "image")] -use std::path::Path; - -/// Creates an icon from an image file. -/// -/// This will return an error in case the file is missing at run-time. You may prefer [`Self::from_file_data`] instead. -#[cfg(feature = "image")] -pub fn from_file>(icon_path: P) -> Result { - let icon = image_rs::io::Reader::open(icon_path)?.decode()?.to_rgba8(); - - Ok(icon::from_rgba(icon.to_vec(), icon.width(), icon.height())?) -} - -/// Creates an icon from the content of an image file. -/// -/// This content can be included in your application at compile-time, e.g. using the `include_bytes!` macro. -/// You can pass an explicit file format. Otherwise, the file format will be guessed at runtime. -#[cfg(feature = "image")] -pub fn from_file_data( - data: &[u8], - explicit_format: Option, -) -> Result { - let mut icon = image_rs::io::Reader::new(std::io::Cursor::new(data)); - let icon_with_format = match explicit_format { - Some(format) => { - icon.set_format(format); - icon - } - None => icon.with_guessed_format()?, - }; - - let pixels = icon_with_format.decode()?.to_rgba8(); - - Ok(icon::from_rgba( - pixels.to_vec(), - pixels.width(), - pixels.height(), - )?) -} - -/// An error produced when creating an [`Icon`]. -#[derive(Debug, thiserror::Error)] -pub enum Error { - /// The [`Icon`] is not valid. - #[error("The icon is invalid: {0}")] - InvalidError(#[from] icon::Error), - - /// The underlying OS failed to create the icon. - #[error("The underlying OS failted to create the window icon: {0}")] - OsError(#[from] io::Error), - - /// The `image` crate reported an error. - #[cfg(feature = "image")] - #[error("Unable to create icon from a file: {0}")] - ImageError(#[from] image_rs::error::ImageError), -} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index dc163430..31002f51 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -51,20 +51,14 @@ pub mod settings; pub mod system; mod error; -mod icon; mod proxy; -#[cfg(feature = "trace")] -mod profiler; #[cfg(feature = "application")] pub use application::Application; -#[cfg(feature = "trace")] -pub use profiler::Profiler; pub use clipboard::Clipboard; pub use error::Error; -pub use icon::Icon; pub use proxy::Proxy; pub use settings::Settings; +pub use crate::core::window::*; pub use iced_graphics::Viewport; -pub use iced_native::window::Position; diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 9b395c1d..e6f440bc 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -1,58 +1,57 @@ //! Create interactive, native cross-platform applications for WGPU. mod state; +mod windows; pub use state::State; -use crate::clipboard::{self, Clipboard}; -use crate::conversion; -use crate::mouse; -use crate::renderer; -use crate::settings; -use crate::widget::operation; -use crate::window; -use crate::{ - Command, Debug, Element, Error, Executor, Proxy, Renderer, Runtime, - Settings, Size, Subscription, -}; - -use iced_futures::futures::channel::mpsc; -use iced_futures::futures::{self, FutureExt}; -use iced_graphics::compositor; -use iced_native::user_interface::{self, UserInterface}; - -pub use iced_native::application::{Appearance, StyleSheet}; - -use std::collections::HashMap; +use crate::core::widget::operation; +use crate::core::{self, mouse, renderer, window, Size}; +use crate::futures::futures::channel::mpsc; +use crate::futures::futures::{task, Future, FutureExt, StreamExt}; +use crate::futures::{Executor, Runtime, Subscription}; +use crate::graphics::{compositor, Compositor}; +use crate::multi_window::windows::Windows; +use crate::runtime::command::{self, Command}; +use crate::runtime::multi_window::Program; +use crate::runtime::user_interface::{self, UserInterface}; +use crate::runtime::Debug; +use crate::settings::window_builder; +use crate::style::application::StyleSheet; +use crate::{conversion, settings, Clipboard, Error, Proxy, Settings}; + +use iced_runtime::user_interface::Cache; use std::mem::ManuallyDrop; use std::time::Instant; - -#[cfg(feature = "trace")] -pub use crate::Profiler; -#[cfg(feature = "trace")] -use tracing::{info_span, instrument::Instrument}; +use winit::monitor::MonitorHandle; /// This is a wrapper around the `Application::Message` associate type /// to allows the `shell` to create internal messages, while still having /// the current user-specified custom messages. #[derive(Debug)] pub enum Event { - /// An [`Application`] generated message + /// An internal event which contains an [`Application`] generated message. Application(Message), - /// A message which spawns a new window. + /// An internal event which spawns a new window. NewWindow { /// The [window::Id] of the newly spawned [`Window`]. id: window::Id, /// The [settings::Window] of the newly spawned [`Window`]. - settings: settings::Window, + settings: window::Settings, /// The title of the newly spawned [`Window`]. title: String, + /// The monitor on which to spawn the window. If `None`, will use monitor of the last window + /// spawned. + monitor: Option, }, - /// Close a window. + /// An internal event for closing a window. CloseWindow(window::Id), - /// A message for when the window has finished being created. + /// An internal event for when the window has finished being created. WindowCreated(window::Id, winit::window::Window), } +#[allow(unsafe_code)] +unsafe impl std::marker::Send for Event {} + /// An interactive, native, cross-platform, multi-windowed application. /// /// This trait is the main entrypoint of multi-window Iced. Once implemented, you can run @@ -64,37 +63,13 @@ pub enum Event { /// /// When using an [`Application`] with the `debug` feature enabled, a debug view /// can be toggled by pressing `F12`. -pub trait Application: Sized +pub trait Application: Program where - ::Theme: StyleSheet, + ::Theme: StyleSheet, { /// The data needed to initialize your [`Application`]. type Flags; - /// The graphics backend to use to draw the [`Program`]. - type Renderer: Renderer; - - /// The type of __messages__ your [`Program`] will produce. - type Message: std::fmt::Debug + Send; - - /// Handles a __message__ and updates the state of the [`Program`]. - /// - /// This is where you define your __update logic__. All the __messages__, - /// produced by either user interactions or commands, will be handled by - /// this method. - /// - /// Any [`Command`] returned will be executed immediately in the - /// background by shells. - fn update(&mut self, message: Self::Message) -> Command; - - /// Returns the widgets to display for the `window` in the [`Program`]. - /// - /// These widgets can produce __messages__ based on user interaction. - fn view( - &self, - window: window::Id, - ) -> Element<'_, Self::Message, Self::Renderer>; - /// Initializes the [`Application`] with the flags provided to /// [`run`] as part of the [`Settings`]. /// @@ -105,19 +80,22 @@ where /// load state from a file, perform an initial HTTP request, etc. fn new(flags: Self::Flags) -> (Self, Command); - /// Returns the current title of each window of the [`Application`]. + /// 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, window: window::Id) -> String; - /// Returns the current [`Theme`] of the [`Application`]. - fn theme(&self, window: window::Id) -> ::Theme; + /// Returns the current `Theme` of the [`Application`]. + fn theme( + &self, + window: window::Id, + ) -> ::Theme; - /// Returns the [`Style`] variation of the [`Theme`]. + /// Returns the `Style` variation of the `Theme`. fn style( &self, - ) -> <::Theme as StyleSheet>::Style { + ) -> <::Theme as StyleSheet>::Style { Default::default() } @@ -147,17 +125,6 @@ where fn scale_factor(&self, window: window::Id) -> f64 { 1.0 } - - /// Returns whether the [`Application`] should be terminated. - /// - /// By default, it returns `false`. - fn should_exit(&self) -> bool { - false - } - - /// Returns the `Self::Message` that should be processed when a `window` is requested to - /// be closed. - fn close_requested(&self, window: window::Id) -> Self::Message; } /// Runs an [`Application`] with an executor, compositor, and the provided @@ -169,22 +136,14 @@ pub fn run( where A: Application + 'static, E: Executor + 'static, - C: iced_graphics::window::Compositor + 'static, - ::Theme: StyleSheet, + C: Compositor + 'static, + ::Theme: StyleSheet, { - use futures::task; - use futures::Future; use winit::event_loop::EventLoopBuilder; - #[cfg(feature = "trace")] - let _guard = Profiler::init(); - let mut debug = Debug::new(); debug.startup_started(); - #[cfg(feature = "trace")] - let _ = info_span!("Application", "RUN").entered(); - let event_loop = EventLoopBuilder::with_user_event().build(); let proxy = event_loop.create_proxy(); @@ -201,68 +160,77 @@ where runtime.enter(|| A::new(flags)) }; - let builder = settings.window.into_builder( + let should_main_be_visible = settings.window.visible; + let builder = window_builder( + settings.window, &application.title(window::Id::MAIN), event_loop.primary_monitor(), settings.id, - ); + ) + .with_visible(false); log::info!("Window builder: {:#?}", builder); - let window = builder + let main_window = builder .build(&event_loop) .map_err(Error::WindowCreationFailed)?; - let windows: HashMap = - HashMap::from([(window::Id::MAIN, window)]); - - let window = windows.values().next().expect("No window found"); - #[cfg(target_arch = "wasm32")] { use winit::platform::web::WindowExtWebSys; - let canvas = window.canvas(); + let canvas = main_window.canvas(); let window = web_sys::window().unwrap(); let document = window.document().unwrap(); let body = document.body().unwrap(); - let _ = body - .append_child(&canvas) - .expect("Append canvas to HTML body"); + let 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 (compositor, renderer) = C::new(compositor_settings, Some(&window))?; + let (mut compositor, renderer) = + C::new(compositor_settings, Some(&main_window))?; + + let windows = + Windows::new(&application, &mut compositor, renderer, main_window); let (mut event_sender, event_receiver) = mpsc::unbounded(); let (control_sender, mut control_receiver) = mpsc::unbounded(); - let mut instance = Box::pin({ - let run_instance = run_instance::( - application, - compositor, - renderer, - runtime, - proxy, - debug, - event_receiver, - control_sender, - init_command, - windows, - settings.exit_on_close_request, - ); - - #[cfg(feature = "trace")] - let run_instance = - run_instance.instrument(info_span!("Application", "LOOP")); - - run_instance - }); + let mut instance = Box::pin(run_instance::( + application, + compositor, + runtime, + proxy, + debug, + event_receiver, + control_sender, + init_command, + windows, + should_main_be_visible, + settings.exit_on_close_request, + )); let mut context = task::Context::from_waker(task::noop_waker_ref()); - platform::run(event_loop, move |event, event_loop, control_flow| { + platform::run(event_loop, move |event, window_target, control_flow| { use winit::event_loop::ControlFlow; if let ControlFlow::ExitWithCode(_) = control_flow { @@ -285,11 +253,12 @@ where id, settings, title, + monitor, }) => { - let window = settings - .into_builder(&title, event_loop.primary_monitor(), None) - .build(event_loop) - .expect("Failed to build window"); + let window = + settings::window_builder(settings, &title, monitor, None) + .build(window_target) + .expect("Failed to build window"); Some(winit::event::Event::UserEvent(Event::WindowCreated( id, window, @@ -320,7 +289,6 @@ where async fn run_instance( mut application: A, mut compositor: C, - mut renderer: A::Renderer, mut runtime: Runtime>, Event>, mut proxy: winit::event_loop::EventLoopProxy>, mut debug: Debug, @@ -329,74 +297,65 @@ async fn run_instance( >, mut control_sender: mpsc::UnboundedSender, init_command: Command, - mut windows: HashMap, - _exit_on_close_request: bool, + mut windows: Windows, + should_main_window_be_visible: bool, + exit_on_main_closed: bool, ) where A: Application + 'static, E: Executor + 'static, - C: iced_graphics::window::Compositor + 'static, - ::Theme: StyleSheet, + C: Compositor + 'static, + ::Theme: StyleSheet, { - use iced_futures::futures::stream::StreamExt; use winit::event; use winit::event_loop::ControlFlow; - let mut clipboard = - Clipboard::connect(windows.values().next().expect("No window found")); - let mut caches = HashMap::new(); - let mut window_ids: HashMap<_, _> = windows - .iter() - .map(|(&id, window)| (window.id(), id)) - .collect(); - - let mut states = HashMap::new(); - let mut surfaces = HashMap::new(); - let mut interfaces = ManuallyDrop::new(HashMap::new()); - - for (&id, window) in windows.keys().zip(windows.values()) { - let mut surface = compositor.create_surface(window); - let state = State::new(&application, id, window); - let physical_size = state.physical_size(); - - compositor.configure_surface( - &mut surface, - physical_size.width, - physical_size.height, - ); + let mut clipboard = Clipboard::connect(windows.main()); - let user_interface = build_user_interface( - &application, - user_interface::Cache::default(), - &mut renderer, - state.logical_size(), - &mut debug, - id, - ); + let mut ui_caches = vec![user_interface::Cache::default()]; + let mut user_interfaces = ManuallyDrop::new(build_user_interfaces( + &application, + &mut debug, + &mut windows, + vec![user_interface::Cache::default()], + )); - let _ = states.insert(id, state); - let _ = surfaces.insert(id, surface); - let _ = interfaces.insert(id, user_interface); - let _ = caches.insert(id, user_interface::Cache::default()); + if should_main_window_be_visible { + windows.main().set_visible(true); } run_command( &application, - &mut caches, - &states, - &mut renderer, + &mut compositor, init_command, &mut runtime, &mut clipboard, &mut proxy, &mut debug, - &windows, - || compositor.fetch_information(), + &mut windows, + &mut ui_caches, ); - runtime.track(application.subscription().map(Event::Application)); + runtime.track( + application + .subscription() + .map(Event::Application) + .into_recipes(), + ); let mut mouse_interaction = mouse::Interaction::default(); - let mut events = Vec::new(); + + let mut events = + if let Some((position, size)) = logical_bounds_of(windows.main()) { + vec![( + Some(window::Id::MAIN), + core::Event::Window( + window::Id::MAIN, + window::Event::Created { position, size }, + ), + )] + } else { + Vec::new() + }; let mut messages = Vec::new(); let mut redraw_pending = false; @@ -413,25 +372,20 @@ async fn run_instance( ); } event::Event::MainEventsCleared => { - for id in states.keys().copied().collect::>() { - // Partition events into only events for this window - let (filtered, remaining): (Vec<_>, Vec<_>) = - events.iter().cloned().partition( - |(window_id, _event): &( - Option, - iced_native::event::Event, - )| { - *window_id == Some(id) || *window_id == None - }, - ); - - // Only retain events which have not been processed for next iteration - events.retain(|el| remaining.contains(el)); - - let window_events: Vec<_> = filtered - .into_iter() - .map(|(_id, event)| event) - .collect(); + debug.event_processing_started(); + let mut uis_stale = false; + + for (i, id) in windows.ids.iter().enumerate() { + let mut window_events = vec![]; + + events.retain(|(window_id, event)| { + if *window_id == Some(*id) || window_id.is_none() { + window_events.push(event.clone()); + false + } else { + true + } + }); if !redraw_pending && window_events.is_empty() @@ -440,144 +394,124 @@ async fn run_instance( continue; } - // Process winit events for window - debug.event_processing_started(); - let cursor_position = - states.get(&id).unwrap().cursor_position(); - - let (interface_state, statuses) = { - let user_interface = interfaces.get_mut(&id).unwrap(); - user_interface.update( - &window_events, - cursor_position, - &mut renderer, - &mut clipboard, - &mut messages, - ) - }; + let (ui_state, statuses) = user_interfaces[i].update( + &window_events, + windows.states[i].cursor(), + &mut windows.renderers[i], + &mut clipboard, + &mut messages, + ); + + if !uis_stale { + uis_stale = + matches!(ui_state, user_interface::State::Outdated); + } - for event in + for (event, status) in window_events.into_iter().zip(statuses.into_iter()) { - runtime.broadcast(event); + runtime.broadcast(event, status); } - debug.event_processing_finished(); - - // Update application with app messages - // Unless we implement some kind of diffing, we must redraw all windows as we - // cannot know what changed. - if !messages.is_empty() - || matches!( - interface_state, - user_interface::State::Outdated, - ) - { - let mut cached_interfaces: HashMap<_, _> = - ManuallyDrop::into_inner(interfaces) - .drain() - .map( - |(id, interface): ( - window::Id, - UserInterface<'_, _, _>, - )| { - (id, interface.into_cache()) - }, - ) - .collect(); - - // Update application - update( - &mut application, - &mut cached_interfaces, - &states, - &mut renderer, - &mut runtime, - &mut clipboard, - &mut proxy, - &mut debug, - &mut messages, - &windows, - || compositor.fetch_information(), - ); - - // synchronize window states with application states. - for (id, state) in states.iter_mut() { - state.synchronize( - &application, - *id, - windows - .get(id) - .expect("No window found with ID."), - ); - } + } - interfaces = ManuallyDrop::new(build_user_interfaces( - &application, - &mut renderer, - &mut debug, - &states, - cached_interfaces, - )); + debug.event_processing_finished(); + + // TODO mw application update returns which window IDs to update + if !messages.is_empty() || uis_stale { + let mut cached_interfaces: Vec = + ManuallyDrop::into_inner(user_interfaces) + .drain(..) + .map(UserInterface::into_cache) + .collect(); + + // Update application + update( + &mut application, + &mut compositor, + &mut runtime, + &mut clipboard, + &mut proxy, + &mut debug, + &mut messages, + &mut windows, + &mut cached_interfaces, + ); - if application.should_exit() { - break 'main; - } + // we must synchronize all window states with application state after an + // application update since we don't know what changed + for (state, (id, window)) in windows + .states + .iter_mut() + .zip(windows.ids.iter().zip(windows.raw.iter())) + { + state.synchronize(&application, *id, window); } + // rebuild UIs with the synchronized states + user_interfaces = ManuallyDrop::new(build_user_interfaces( + &application, + &mut debug, + &mut windows, + cached_interfaces, + )); + } + + debug.draw_started(); + + for (i, id) in windows.ids.iter().enumerate() { // TODO: Avoid redrawing all the time by forcing widgets to - // request redraws on state changes + // request redraws on state changes // // Then, we can use the `interface_state` here to decide if a redraw // is needed right away, or simply wait until a specific time. - let redraw_event = iced_native::Event::Window( - id, + let redraw_event = core::Event::Window( + *id, window::Event::RedrawRequested(Instant::now()), ); - let (interface_state, _) = - interfaces.get_mut(&id).unwrap().update( - &[redraw_event.clone()], - cursor_position, - &mut renderer, - &mut clipboard, - &mut messages, - ); + let cursor = windows.states[i].cursor(); + + let (ui_state, _) = user_interfaces[i].update( + &[redraw_event.clone()], + cursor, + &mut windows.renderers[i], + &mut clipboard, + &mut messages, + ); - debug.draw_started(); let new_mouse_interaction = { - let state = states.get(&id).unwrap(); + let state = &windows.states[i]; - interfaces.get_mut(&id).unwrap().draw( - &mut renderer, + user_interfaces[i].draw( + &mut windows.renderers[i], state.theme(), &renderer::Style { text_color: state.text_color(), }, - state.cursor_position(), + cursor, ) }; - debug.draw_finished(); - - let window = windows.get(&id).unwrap(); if new_mouse_interaction != mouse_interaction { - window.set_cursor_icon(conversion::mouse_interaction( - new_mouse_interaction, - )); + windows.raw[i].set_cursor_icon( + conversion::mouse_interaction( + new_mouse_interaction, + ), + ); mouse_interaction = new_mouse_interaction; } - for window in windows.values() { - window.request_redraw(); - } + // TODO once widgets can request to be redrawn, we can avoid always requesting a + // redraw + windows.raw[i].request_redraw(); - runtime.broadcast(( + runtime.broadcast( redraw_event.clone(), - crate::event::Status::Ignored, - )); + core::event::Status::Ignored, + ); - let _ = control_sender.start_send(match interface_state { + let _ = control_sender.start_send(match ui_state { user_interface::State::Updated { redraw_request: Some(redraw_request), } => match redraw_request { @@ -590,17 +524,20 @@ async fn run_instance( }, _ => ControlFlow::Wait, }); - - redraw_pending = false; } + + redraw_pending = false; + + debug.draw_finished(); } event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( event::MacOS::ReceivedUrl(url), )) => { - use iced_native::event; + use crate::core::event; + events.push(( None, - iced_native::Event::PlatformSpecific( + event::Event::PlatformSpecific( event::PlatformSpecific::MacOS( event::MacOS::ReceivedUrl(url), ), @@ -612,91 +549,48 @@ async fn run_instance( messages.push(message); } Event::WindowCreated(id, window) => { - let mut surface = compositor.create_surface(&window); - let state = State::new(&application, id, &window); - let physical_size = state.physical_size(); + let bounds = logical_bounds_of(&window); - compositor.configure_surface( - &mut surface, - physical_size.width, - physical_size.height, - ); + let (inner_size, i) = + windows.add(&application, &mut compositor, id, window); - let user_interface = build_user_interface( + user_interfaces.push(build_user_interface( &application, user_interface::Cache::default(), - &mut renderer, - state.logical_size(), + &mut windows.renderers[i], + inner_size, &mut debug, id, - ); - - let _ = states.insert(id, state); - let _ = surfaces.insert(id, surface); - let _ = interfaces.insert(id, user_interface); - let _ = window_ids.insert(window.id(), id); - let _ = windows.insert(id, window); - let _ = caches.insert(id, user_interface::Cache::default()); + )); + ui_caches.push(user_interface::Cache::default()); + + if let Some(bounds) = bounds { + events.push(( + Some(id), + core::Event::Window( + id, + window::Event::Created { + position: bounds.0, + size: bounds.1, + }, + ), + )); + } } Event::CloseWindow(id) => { - if let Some(window) = windows.get(&id) { - if window_ids.remove(&window.id()).is_none() { - log::error!("Failed to remove window with id {:?} from window_ids.", window.id()); - } - } else { - log::error!( - "Could not find window with id {:?} in windows.", - id - ); - } - if states.remove(&id).is_none() { - log::error!( - "Failed to remove window {:?} from states.", - id - ); - } - if interfaces.remove(&id).is_none() { - log::error!( - "Failed to remove window {:?} from interfaces.", - id - ); - } - if windows.remove(&id).is_none() { - log::error!( - "Failed to remove window {:?} from windows.", - id - ); - } - if surfaces.remove(&id).is_none() { - log::error!( - "Failed to remove window {:?} from surfaces.", - id - ); - } + let i = windows.delete(id); + let _ = user_interfaces.remove(i); + let _ = ui_caches.remove(i); if windows.is_empty() { - log::info!( - "All windows are closed. Terminating program." - ); break 'main; - } else { - log::info!("Remaining windows: {:?}", windows.len()); } } Event::NewWindow { .. } => unreachable!(), }, event::Event::RedrawRequested(id) => { - #[cfg(feature = "trace")] - let _ = info_span!("Application", "FRAME").entered(); - - let state = window_ids - .get(&id) - .and_then(|id| states.get_mut(id)) - .unwrap(); - let surface = window_ids - .get(&id) - .and_then(|id| surfaces.get_mut(id)) - .unwrap(); + let i = windows.index_from_raw(id); + let state = &windows.states[i]; let physical_size = state.physical_size(); if physical_size.width == 0 || physical_size.height == 0 { @@ -704,60 +598,55 @@ async fn run_instance( } debug.render_started(); + let current_viewport_version = state.viewport_version(); + let window_viewport_version = windows.viewport_versions[i]; - if state.viewport_changed() { - let mut user_interface = window_ids - .get(&id) - .and_then(|id| interfaces.remove(id)) - .unwrap(); - + if window_viewport_version != current_viewport_version { let logical_size = state.logical_size(); debug.layout_started(); - user_interface = - user_interface.relayout(logical_size, &mut renderer); + + let renderer = &mut windows.renderers[i]; + let ui = user_interfaces.remove(i); + + user_interfaces + .insert(i, ui.relayout(logical_size, renderer)); + debug.layout_finished(); debug.draw_started(); - let new_mouse_interaction = { - let state = &state; - - user_interface.draw( - &mut renderer, - state.theme(), - &renderer::Style { - text_color: state.text_color(), - }, - state.cursor_position(), - ) - }; + let new_mouse_interaction = user_interfaces[i].draw( + renderer, + state.theme(), + &renderer::Style { + text_color: state.text_color(), + }, + state.cursor(), + ); - let window = window_ids - .get(&id) - .and_then(|id| windows.get(id)) - .unwrap(); if new_mouse_interaction != mouse_interaction { - window.set_cursor_icon(conversion::mouse_interaction( - new_mouse_interaction, - )); + windows.raw[i].set_cursor_icon( + conversion::mouse_interaction( + new_mouse_interaction, + ), + ); mouse_interaction = new_mouse_interaction; } debug.draw_finished(); - let _ = interfaces - .insert(*window_ids.get(&id).unwrap(), user_interface); - compositor.configure_surface( - surface, + &mut windows.surfaces[i], physical_size.width, physical_size.height, ); + + windows.viewport_versions[i] = current_viewport_version; } match compositor.present( - &mut renderer, - surface, + &mut windows.renderers[i], + &mut windows.surfaces[i], state.viewport(), state.background_color(), &debug.overlay(), @@ -775,10 +664,12 @@ async fn run_instance( } _ => { debug.render_finished(); - log::error!("Error {error:?} when presenting surface."); + log::error!( + "Error {error:?} when presenting surface." + ); - // Try rendering windows again next frame. - for window in windows.values() { + // Try rendering all windows again next frame. + for window in &windows.raw { window.request_redraw(); } } @@ -789,42 +680,58 @@ async fn run_instance( event: window_event, window_id, } => { - if let (Some(window), Some(state)) = ( - window_ids.get(&window_id).and_then(|id| windows.get(id)), - window_ids - .get(&window_id) - .and_then(|id| states.get_mut(id)), - ) { - if crate::application::requests_exit(&window_event, state.modifiers()) { - if let Some(id) = window_ids.get(&window_id).cloned() { - let message = application.close_requested(id); - messages.push(message); - } + let window_deleted = windows + .pending_destroy + .iter() + .any(|(_, w_id)| window_id == *w_id); + + if matches!(window_event, winit::event::WindowEvent::Destroyed) + { + // This is the only special case, since in order trigger the Destroyed event the + // window reference from winit must be dropped, but we still want to inform the + // user that the window was destroyed so they can clean up any specific window + // code for this window + let id = windows.get_pending_destroy(window_id); + + events.push(( + None, + core::Event::Window(id, window::Event::Destroyed), + )); + } else if !window_deleted { + let i = windows.index_from_raw(window_id); + let id = windows.ids[i]; + let raw = &windows.raw[i]; + let state = &mut windows.states[i]; + + // first check if we need to just break the entire application + // e.g. a user does a force quit on MacOS, or if a user has set "exit on main closed" + // as an option in window settings and wants to close the main window + if requests_exit( + i, + exit_on_main_closed, + &window_event, + state.modifiers(), + ) { + break 'main; } - state.update(window, &window_event, &mut debug); + state.update(raw, &window_event, &mut debug); if let Some(event) = conversion::window_event( - *window_ids.get(&window_id).unwrap(), + id, &window_event, state.scale_factor(), state.modifiers(), ) { - events - .push((window_ids.get(&window_id).cloned(), event)); + events.push((Some(id), event)); } - } else { - log::error!( - "Could not find window or state for id: {window_id:?}" - ); } } _ => {} } } - // Manually drop the user interfaces - drop(ManuallyDrop::into_inner(interfaces)); + let _ = ManuallyDrop::into_inner(user_interfaces); } /// Builds a window's [`UserInterface`] for the [`Application`]. @@ -837,103 +744,79 @@ pub fn build_user_interface<'a, A: Application>( id: window::Id, ) -> UserInterface<'a, A::Message, A::Renderer> where - ::Theme: StyleSheet, + ::Theme: StyleSheet, { - #[cfg(feature = "trace")] - let view_span = info_span!("Application", "VIEW").entered(); - debug.view_started(); let view = application.view(id); - - #[cfg(feature = "trace")] - let _ = view_span.exit(); debug.view_finished(); - #[cfg(feature = "trace")] - let layout_span = info_span!("Application", "LAYOUT").entered(); debug.layout_started(); - let user_interface = UserInterface::build(view, size, cache, renderer); - - #[cfg(feature = "trace")] - let _ = layout_span.exit(); debug.layout_finished(); user_interface } -/// Updates an [`Application`] by feeding it messages, spawning any +/// Updates a multi-window [`Application`] by feeding it messages, spawning any /// resulting [`Command`], and tracking its [`Subscription`]. -pub fn update( +pub fn update( application: &mut A, - caches: &mut HashMap, - states: &HashMap>, - renderer: &mut A::Renderer, + compositor: &mut C, runtime: &mut Runtime>, Event>, clipboard: &mut Clipboard, proxy: &mut winit::event_loop::EventLoopProxy>, debug: &mut Debug, messages: &mut Vec, - windows: &HashMap, - graphics_info: impl FnOnce() -> compositor::Information + Copy, + windows: &mut Windows, + ui_caches: &mut Vec, ) where - A: Application + 'static, - ::Theme: StyleSheet, + C: Compositor + 'static, + ::Theme: StyleSheet, { for message in messages.drain(..) { - #[cfg(feature = "trace")] - let update_span = info_span!("Application", "UPDATE").entered(); - debug.log_message(&message); - debug.update_started(); let command = runtime.enter(|| application.update(message)); - - #[cfg(feature = "trace")] - let _ = update_span.exit(); debug.update_finished(); run_command( application, - caches, - states, - renderer, + compositor, command, runtime, clipboard, proxy, debug, windows, - graphics_info, + ui_caches, ); } let subscription = application.subscription().map(Event::Application); - runtime.track(subscription); + runtime.track(subscription.into_recipes()); } /// Runs the actions of a [`Command`]. -pub fn run_command( +pub fn run_command( application: &A, - caches: &mut HashMap, - states: &HashMap>, - renderer: &mut A::Renderer, + compositor: &mut C, command: Command, runtime: &mut Runtime>, Event>, clipboard: &mut Clipboard, proxy: &mut winit::event_loop::EventLoopProxy>, debug: &mut Debug, - windows: &HashMap, - _graphics_info: impl FnOnce() -> compositor::Information + Copy, + windows: &mut Windows, + ui_caches: &mut Vec, ) where - A: Application + 'static, + A: Application, E: Executor, - ::Theme: StyleSheet, + C: Compositor + 'static, + ::Theme: StyleSheet, { - use iced_native::command; - use iced_native::system; - use iced_native::window; + use crate::runtime::clipboard; + use crate::runtime::system; + use crate::runtime::window; for action in command.actions() { match action { @@ -954,11 +837,14 @@ pub fn run_command( }, command::Action::Window(id, action) => match action { window::Action::Spawn { settings } => { + let monitor = windows.last_monitor(); + proxy .send_event(Event::NewWindow { id, - settings: settings.into(), + settings, title: application.title(id), + monitor, }) .expect("Send message to event loop"); } @@ -968,86 +854,117 @@ pub fn run_command( .expect("Send message to event loop"); } window::Action::Drag => { - let window = windows.get(&id).expect("No window found"); - let _res = window.drag_window(); + let _ = windows.with_raw(id).drag_window(); } - window::Action::Resize { width, height } => { - let window = windows.get(&id).expect("No window found"); - window.set_inner_size(winit::dpi::LogicalSize { - width, - height, - }); + window::Action::Resize(size) => { + windows.with_raw(id).set_inner_size( + winit::dpi::LogicalSize { + width: size.width, + height: size.height, + }, + ); + } + window::Action::FetchSize(callback) => { + let window = windows.with_raw(id); + let size = window.inner_size(); + + proxy + .send_event(Event::Application(callback(Size::new( + size.width, + size.height, + )))) + .expect("Send message to event loop") + } + window::Action::Maximize(maximized) => { + windows.with_raw(id).set_maximized(maximized); + } + window::Action::Minimize(minimized) => { + windows.with_raw(id).set_minimized(minimized); } window::Action::Move { x, y } => { - let window = windows.get(&id).expect("No window found"); - window.set_outer_position(winit::dpi::LogicalPosition { - x, - y, - }); + windows.with_raw(id).set_outer_position( + winit::dpi::LogicalPosition { x, y }, + ); } window::Action::ChangeMode(mode) => { - let window = windows.get(&id).expect("No window found"); + let window = windows.with_raw(id); window.set_visible(conversion::visible(mode)); window.set_fullscreen(conversion::fullscreen( window.current_monitor(), mode, )); } + window::Action::ChangeIcon(icon) => { + windows.with_raw(id).set_window_icon(conversion::icon(icon)) + } window::Action::FetchMode(tag) => { - let window = windows.get(&id).expect("No window found"); + let window = windows.with_raw(id); let mode = if window.is_visible().unwrap_or(true) { conversion::mode(window.fullscreen()) } else { - window::Mode::Hidden + core::window::Mode::Hidden }; proxy .send_event(Event::Application(tag(mode))) - .expect("Send message to event loop"); - } - window::Action::Maximize(value) => { - let window = windows.get(&id).expect("No window found!"); - window.set_maximized(value); - } - window::Action::Minimize(value) => { - let window = windows.get(&id).expect("No window found!"); - window.set_minimized(value); + .expect("Event loop doesn't exist."); } window::Action::ToggleMaximize => { - let window = windows.get(&id).expect("No window found!"); + let window = windows.with_raw(id); window.set_maximized(!window.is_maximized()); } window::Action::ToggleDecorations => { - let window = windows.get(&id).expect("No window found!"); + let window = windows.with_raw(id); window.set_decorations(!window.is_decorated()); } window::Action::RequestUserAttention(attention_type) => { - let window = windows.get(&id).expect("No window found!"); - window.request_user_attention( + windows.with_raw(id).request_user_attention( attention_type.map(conversion::user_attention), ); } window::Action::GainFocus => { - let window = windows.get(&id).expect("No window found!"); - window.focus_window(); + windows.with_raw(id).focus_window(); } - window::Action::ChangeAlwaysOnTop(on_top) => { - let window = windows.get(&id).expect("No window found!"); - window.set_always_on_top(on_top); + window::Action::ChangeLevel(level) => { + windows + .with_raw(id) + .set_window_level(conversion::window_level(level)); } - window::Action::FetchId(tag) => { - let window = windows.get(&id).expect("No window found!"); + window::Action::FetchId(tag) => proxy + .send_event(Event::Application(tag(windows + .with_raw(id) + .id() + .into()))) + .expect("Event loop doesn't exist."), + window::Action::Screenshot(tag) => { + let i = windows.index_from_id(id); + let state = &windows.states[i]; + let surface = &mut windows.surfaces[i]; + let renderer = &mut windows.renderers[i]; + + let bytes = compositor.screenshot( + renderer, + surface, + state.viewport(), + state.background_color(), + &debug.overlay(), + ); proxy - .send_event(Event::Application(tag(window.id().into()))) - .expect("Send message to event loop.") + .send_event(Event::Application(tag( + window::Screenshot::new( + bytes, + state.physical_size(), + ), + ))) + .expect("Event loop doesn't exist.") } }, command::Action::System(action) => match action { system::Action::QueryInformation(_tag) => { #[cfg(feature = "system")] { - let graphics_info = _graphics_info(); + let graphics_info = compositor.fetch_information(); let proxy = proxy.clone(); let _ = std::thread::spawn(move || { @@ -1058,33 +975,36 @@ pub fn run_command( proxy .send_event(Event::Application(message)) - .expect("Send message to event loop") + .expect("Event loop doesn't exist.") }); } } }, command::Action::Widget(action) => { - let mut current_caches = std::mem::take(caches); - let mut current_operation = Some(action.into_operation()); + let mut current_operation = Some(action); - let mut user_interfaces = build_user_interfaces( + let mut uis = build_user_interfaces( application, - renderer, debug, - states, - current_caches, + windows, + std::mem::take(ui_caches), ); - while let Some(mut operation) = current_operation.take() { - for user_interface in user_interfaces.values_mut() { - user_interface.operate(renderer, operation.as_mut()); + 'operate: while let Some(mut operation) = + current_operation.take() + { + for (i, ui) in uis.iter_mut().enumerate() { + ui.operate(&windows.renderers[i], operation.as_mut()); match operation.finish() { operation::Outcome::None => {} operation::Outcome::Some(message) => { proxy .send_event(Event::Application(message)) - .expect("Send message to event loop"); + .expect("Event loop doesn't exist."); + + // operation completed, don't need to try to operate on rest of UIs + break 'operate; } operation::Outcome::Chain(next) => { current_operation = Some(next); @@ -1093,55 +1013,105 @@ pub fn run_command( } } - let user_interfaces: HashMap<_, _> = user_interfaces - .drain() - .map(|(id, interface)| (id, interface.into_cache())) - .collect(); + *ui_caches = + uis.drain(..).map(UserInterface::into_cache).collect(); + } + command::Action::LoadFont { bytes, tagger } => { + use crate::core::text::Renderer; + + // TODO change this once we change each renderer to having a single backend reference.. :pain: + // TODO: Error handling (?) + for renderer in &mut windows.renderers { + renderer.load_font(bytes.clone()); + } - current_caches = user_interfaces; - *caches = current_caches; + proxy + .send_event(Event::Application(tagger(Ok(())))) + .expect("Send message to event loop"); } } } } -/// Build the user interfaces for every window. -pub fn build_user_interfaces<'a, A>( +/// Build the user interface for every window. +pub fn build_user_interfaces<'a, A: Application, C: Compositor>( application: &'a A, - renderer: &mut A::Renderer, debug: &mut Debug, - states: &HashMap>, - mut cached_user_interfaces: HashMap, -) -> HashMap< - window::Id, - UserInterface< - 'a, - ::Message, - ::Renderer, - >, -> + windows: &mut Windows, + mut cached_user_interfaces: Vec, +) -> Vec> where - A: Application + 'static, - ::Theme: StyleSheet, + ::Theme: StyleSheet, + C: Compositor, { - let mut interfaces = HashMap::new(); - - for (id, cache) in cached_user_interfaces.drain() { - let state = &states.get(&id).unwrap(); - - let user_interface = build_user_interface( - application, - cache, - renderer, - state.logical_size(), - debug, - id, - ); + cached_user_interfaces + .drain(..) + .zip( + windows + .ids + .iter() + .zip(windows.states.iter().zip(windows.renderers.iter_mut())), + ) + .fold(vec![], |mut uis, (cache, (id, (state, renderer)))| { + uis.push(build_user_interface( + application, + cache, + renderer, + state.logical_size(), + debug, + *id, + )); + + uis + }) +} - let _ = interfaces.insert(id, user_interface); +/// Returns true if the provided event should cause an [`Application`] to +/// exit. +pub fn requests_exit( + window: usize, + exit_on_main_closed: bool, + event: &winit::event::WindowEvent<'_>, + _modifiers: winit::event::ModifiersState, +) -> bool { + use winit::event::WindowEvent; + + //TODO alt f4..? + match event { + WindowEvent::CloseRequested => exit_on_main_closed && window == 0, + #[cfg(target_os = "macos")] + WindowEvent::KeyboardInput { + input: + winit::event::KeyboardInput { + virtual_keycode: Some(winit::event::VirtualKeyCode::Q), + state: winit::event::ElementState::Pressed, + .. + }, + .. + } if _modifiers.logo() => true, + _ => false, } +} + +fn logical_bounds_of( + window: &winit::window::Window, +) -> Option<((i32, i32), Size)> { + let scale = window.scale_factor(); + let pos = window + .inner_position() + .map(|pos| { + ((pos.x as f64 / scale) as i32, (pos.y as f64 / scale) as i32) + }) + .ok()?; + let size = { + let size = window.inner_size(); + Size::new( + (size.width as f64 / scale) as u32, + (size.height as f64 / scale) as u32, + ) + }; - interfaces + Some((pos, size)) } #[cfg(not(target_arch = "wasm32"))] diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs index 54a114ad..f2741c3c 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/multi_window/state.rs @@ -1,33 +1,50 @@ -use crate::application::{self, StyleSheet as _}; use crate::conversion; +use crate::core; +use crate::core::{mouse, window}; +use crate::core::{Color, Size}; +use crate::graphics::Viewport; use crate::multi_window::Application; -use crate::window; -use crate::{Color, Debug, Point, Size, Viewport}; +use crate::style::application; +use std::fmt::{Debug, Formatter}; -use std::marker::PhantomData; +use iced_style::application::StyleSheet; use winit::event::{Touch, WindowEvent}; use winit::window::Window; /// The state of a multi-windowed [`Application`]. -#[allow(missing_debug_implementations)] pub struct State where - ::Theme: application::StyleSheet, + ::Theme: application::StyleSheet, { title: String, scale_factor: f64, viewport: Viewport, - viewport_changed: bool, - cursor_position: winit::dpi::PhysicalPosition, + viewport_version: usize, + cursor_position: Option>, modifiers: winit::event::ModifiersState, - theme: ::Theme, + theme: ::Theme, appearance: application::Appearance, - application: PhantomData, +} + +impl Debug for State +where + ::Theme: application::StyleSheet, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("multi_window::State") + .field("title", &self.title) + .field("scale_factor", &self.scale_factor) + .field("viewport", &self.viewport) + .field("viewport_version", &self.viewport_version) + .field("cursor_position", &self.cursor_position) + .field("appearance", &self.appearance) + .finish() + } } impl State where - ::Theme: application::StyleSheet, + ::Theme: application::StyleSheet, { /// Creates a new [`State`] for the provided [`Application`]'s `window`. pub fn new( @@ -53,13 +70,11 @@ where title, scale_factor, viewport, - viewport_changed: false, - // TODO: Encode cursor availability in the type-system - cursor_position: winit::dpi::PhysicalPosition::new(-1.0, -1.0), + viewport_version: 0, + cursor_position: None, modifiers: winit::event::ModifiersState::default(), theme, appearance, - application: PhantomData, } } @@ -68,9 +83,11 @@ where &self.viewport } - /// Returns whether or not the viewport changed. - pub fn viewport_changed(&self) -> bool { - self.viewport_changed + /// 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`]. @@ -89,11 +106,16 @@ where } /// Returns the current cursor position of the [`State`]. - pub fn cursor_position(&self) -> Point { - conversion::cursor_position( - self.cursor_position, - self.viewport.scale_factor(), - ) + 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`]. @@ -102,7 +124,7 @@ where } /// Returns the current theme of the [`State`]. - pub fn theme(&self) -> &::Theme { + pub fn theme(&self) -> &::Theme { &self.theme } @@ -121,7 +143,7 @@ where &mut self, window: &Window, event: &WindowEvent<'_>, - _debug: &mut Debug, + _debug: &mut crate::runtime::Debug, ) { match event { WindowEvent::Resized(new_size) => { @@ -132,7 +154,7 @@ where window.scale_factor() * self.scale_factor, ); - self.viewport_changed = true; + self.viewport_version = self.viewport_version.wrapping_add(1); } WindowEvent::ScaleFactorChanged { scale_factor: new_scale_factor, @@ -146,18 +168,16 @@ where new_scale_factor * self.scale_factor, ); - self.viewport_changed = true; + self.viewport_version = self.viewport_version.wrapping_add(1); } WindowEvent::CursorMoved { position, .. } | WindowEvent::Touch(Touch { location: position, .. }) => { - self.cursor_position = *position; + self.cursor_position = Some(*position); } WindowEvent::CursorLeft { .. } => { - // TODO: Encode cursor availability in the type-system - self.cursor_position = - winit::dpi::PhysicalPosition::new(-1.0, -1.0); + self.cursor_position = None; } WindowEvent::ModifiersChanged(new_modifiers) => { self.modifiers = *new_modifiers; @@ -197,16 +217,20 @@ where self.title = new_title; } - // Update scale factor + // Update scale factor and size let new_scale_factor = application.scale_factor(window_id); + let new_size = window.inner_size(); + let current_size = self.viewport.physical_size(); - if self.scale_factor != new_scale_factor { - let size = window.inner_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(size.width, size.height), + 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; } diff --git a/winit/src/multi_window/windows.rs b/winit/src/multi_window/windows.rs new file mode 100644 index 00000000..7b63defa --- /dev/null +++ b/winit/src/multi_window/windows.rs @@ -0,0 +1,170 @@ +use crate::core::{window, Size}; +use crate::multi_window::{Application, State}; +use iced_graphics::Compositor; +use iced_style::application::StyleSheet; +use std::fmt::{Debug, Formatter}; +use winit::monitor::MonitorHandle; + +pub struct Windows +where + ::Theme: StyleSheet, + C: Compositor, +{ + pub ids: Vec, + pub raw: Vec, + pub states: Vec>, + pub viewport_versions: Vec, + pub surfaces: Vec, + pub renderers: Vec, + pub pending_destroy: Vec<(window::Id, winit::window::WindowId)>, +} + +impl Debug for Windows +where + ::Theme: StyleSheet, + C: Compositor, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Windows") + .field("ids", &self.ids) + .field( + "raw", + &self + .raw + .iter() + .map(|raw| raw.id()) + .collect::>(), + ) + .field("states", &self.states) + .field("viewport_versions", &self.viewport_versions) + .finish() + } +} + +impl Windows +where + ::Theme: StyleSheet, + C: Compositor, +{ + /// Creates a new [`Windows`] with a single `window::Id::MAIN` window. + pub fn new( + application: &A, + compositor: &mut C, + renderer: A::Renderer, + main: winit::window::Window, + ) -> Self { + let state = State::new(application, window::Id::MAIN, &main); + let viewport_version = state.viewport_version(); + let physical_size = state.physical_size(); + let surface = compositor.create_surface( + &main, + physical_size.width, + physical_size.height, + ); + + Self { + ids: vec![window::Id::MAIN], + raw: vec![main], + states: vec![state], + viewport_versions: vec![viewport_version], + surfaces: vec![surface], + renderers: vec![renderer], + pending_destroy: vec![], + } + } + + /// Adds a new window to [`Windows`]. Returns the size of the newly created window in logical + /// pixels & the index of the window within [`Windows`]. + pub fn add( + &mut self, + application: &A, + compositor: &mut C, + id: window::Id, + window: winit::window::Window, + ) -> (Size, usize) { + let state = State::new(application, id, &window); + let window_size = state.logical_size(); + let viewport_version = state.viewport_version(); + let physical_size = state.physical_size(); + let surface = compositor.create_surface( + &window, + physical_size.width, + physical_size.height, + ); + let renderer = compositor.renderer(); + + self.ids.push(id); + self.raw.push(window); + self.states.push(state); + self.viewport_versions.push(viewport_version); + self.surfaces.push(surface); + self.renderers.push(renderer); + + (window_size, self.ids.len() - 1) + } + + pub fn is_empty(&self) -> bool { + self.ids.is_empty() + } + + pub fn main(&self) -> &winit::window::Window { + &self.raw[0] + } + + pub fn index_from_raw(&self, id: winit::window::WindowId) -> usize { + self.raw + .iter() + .position(|window| window.id() == id) + .expect("No raw window in multi_window::Windows") + } + + pub fn index_from_id(&self, id: window::Id) -> usize { + self.ids + .iter() + .position(|window_id| *window_id == id) + .expect("No window in multi_window::Windows") + } + + pub fn last_monitor(&self) -> Option { + self.raw.last().and_then(|w| w.current_monitor()) + } + + pub fn last(&self) -> usize { + self.ids.len() - 1 + } + + pub fn with_raw(&self, id: window::Id) -> &winit::window::Window { + let i = self.index_from_id(id); + &self.raw[i] + } + + /// Deletes the window with `id` from [`Windows`]. Returns the index that the window had. + pub fn delete(&mut self, id: window::Id) -> usize { + let i = self.index_from_id(id); + + let id = self.ids.remove(i); + let window = self.raw.remove(i); + let _ = self.states.remove(i); + let _ = self.viewport_versions.remove(i); + let _ = self.surfaces.remove(i); + + self.pending_destroy.push((id, window.id())); + + i + } + + /// Gets the winit `window` that is pending to be destroyed if it exists. + pub fn get_pending_destroy( + &mut self, + window: winit::window::WindowId, + ) -> window::Id { + let i = self + .pending_destroy + .iter() + .position(|(_, window_id)| window == *window_id) + .unwrap(); + + let (id, _) = self.pending_destroy.remove(i); + id + } +} diff --git a/winit/src/profiler.rs b/winit/src/profiler.rs deleted file mode 100644 index 7031507a..00000000 --- a/winit/src/profiler.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! A simple profiler for Iced. -use std::ffi::OsStr; -use std::path::Path; -use std::time::Duration; -use tracing_subscriber::prelude::*; -use tracing_subscriber::Registry; -#[cfg(feature = "chrome-trace")] -use { - tracing_chrome::FlushGuard, - tracing_subscriber::fmt::{format::DefaultFields, FormattedFields}, -}; - -/// Profiler state. This will likely need to be updated or reworked when adding new tracing backends. -#[allow(missing_debug_implementations)] -pub struct Profiler { - #[cfg(feature = "chrome-trace")] - /// [`FlushGuard`] must not be dropped until the application scope is dropped for accurate tracing. - _guard: FlushGuard, -} - -impl Profiler { - /// Initializes the [`Profiler`]. - pub fn init() -> Self { - // Registry stores the spans & generates unique span IDs - let subscriber = Registry::default(); - - let default_path = Path::new(env!("CARGO_MANIFEST_DIR")); - let curr_exe = std::env::current_exe() - .unwrap_or_else(|_| default_path.to_path_buf()); - let out_dir = curr_exe.parent().unwrap_or(default_path).join("traces"); - - #[cfg(feature = "chrome-trace")] - let (chrome_layer, guard) = { - let mut layer = tracing_chrome::ChromeLayerBuilder::new(); - - // Optional configurable env var: CHROME_TRACE_FILE=/path/to/trace_file/file.json, - // for uploading to chrome://tracing (old) or ui.perfetto.dev (new). - if let Ok(path) = std::env::var("CHROME_TRACE_FILE") { - layer = layer.file(path); - } else if std::fs::create_dir_all(&out_dir).is_ok() { - let time = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or(Duration::from_millis(0)) - .as_millis(); - - let curr_exe_name = curr_exe - .file_name() - .unwrap_or_else(|| OsStr::new("trace")) - .to_str() - .unwrap_or("trace"); - - let path = - out_dir.join(format!("{curr_exe_name}_trace_{time}.json")); - - layer = layer.file(path); - } else { - layer = layer.file(env!("CARGO_MANIFEST_DIR")) - } - - let (chrome_layer, guard) = layer - .name_fn(Box::new(|event_or_span| match event_or_span { - tracing_chrome::EventOrSpan::Event(event) => { - event.metadata().name().into() - } - tracing_chrome::EventOrSpan::Span(span) => { - if let Some(fields) = span - .extensions() - .get::>() - { - format!( - "{}: {}", - span.metadata().name(), - fields.fields.as_str() - ) - } else { - span.metadata().name().into() - } - } - })) - .build(); - - (chrome_layer, guard) - }; - - let fmt_layer = tracing_subscriber::fmt::Layer::default(); - let subscriber = subscriber.with(fmt_layer); - - #[cfg(feature = "chrome-trace")] - let subscriber = subscriber.with(chrome_layer); - - // create dispatcher which will forward span events to the subscriber - // this can only be set once or will panic - tracing::subscriber::set_global_default(subscriber) - .expect("Tracer could not set the global default subscriber."); - - Profiler { - #[cfg(feature = "chrome-trace")] - _guard: guard, - } - } -} diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 40b3d487..2b846fbd 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -1,35 +1,10 @@ //! Configure your application. -#[cfg(target_os = "windows")] -#[path = "settings/windows.rs"] -mod platform; - -#[cfg(target_os = "macos")] -#[path = "settings/macos.rs"] -mod platform; - -#[cfg(target_arch = "wasm32")] -#[path = "settings/wasm.rs"] -mod platform; - -#[cfg(not(any( - target_os = "windows", - target_os = "macos", - target_arch = "wasm32" -)))] -#[path = "settings/other.rs"] -mod platform; - -pub use platform::PlatformSpecific; - use crate::conversion; -use crate::core::window::{Icon, Level}; -use crate::Position; +use crate::core::window; use winit::monitor::MonitorHandle; use winit::window::WindowBuilder; -use std::fmt; - /// The settings of an application. #[derive(Debug, Clone, Default)] pub struct Settings { @@ -40,7 +15,7 @@ pub struct Settings { pub id: Option, /// The [`Window`] settings. - pub window: Window, + pub window: window::Settings, /// The data needed to initialize an [`Application`]. /// @@ -50,166 +25,93 @@ pub struct Settings { /// Whether the [`Application`] should exit when the user requests the /// window to close (e.g. the user presses the close button). /// + /// With a [`multi_window::Application`] this will instead be used to determine whether the + /// application should exit when the "main"" window is closed, i.e. the first window created on + /// app launch. + /// /// [`Application`]: crate::Application pub exit_on_close_request: bool, } -/// The window settings of an application. -#[derive(Clone)] -pub struct Window { - /// The size of the window. - pub size: (u32, u32), - - /// The position of the window. - pub position: Position, - - /// The minimum size of the window. - pub min_size: Option<(u32, u32)>, - - /// The maximum size of the window. - pub max_size: Option<(u32, u32)>, - - /// Whether the window should be visible or not. - pub visible: bool, - - /// Whether the window should be resizable or not. - pub resizable: bool, - - /// Whether the window should have a border, a title bar, etc. - pub decorations: bool, - - /// Whether the window should be transparent. - pub transparent: bool, - - /// The window [`Level`]. - pub level: Level, - - /// The window icon, which is also usually used in the taskbar - pub icon: Option, - - /// Platform specific settings. - pub platform_specific: platform::PlatformSpecific, -} - -impl fmt::Debug for Window { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Window") - .field("size", &self.size) - .field("position", &self.position) - .field("min_size", &self.min_size) - .field("max_size", &self.max_size) - .field("visible", &self.visible) - .field("resizable", &self.resizable) - .field("decorations", &self.decorations) - .field("transparent", &self.transparent) - .field("level", &self.level) - .field("icon", &self.icon.is_some()) - .field("platform_specific", &self.platform_specific) - .finish() +/// Converts the window settings into a `WindowBuilder` from `winit`. +pub fn window_builder( + settings: window::Settings, + title: &str, + monitor: Option, + _id: Option, +) -> WindowBuilder { + let mut window_builder = WindowBuilder::new(); + + let (width, height) = settings.size; + + window_builder = window_builder + .with_title(title) + .with_inner_size(winit::dpi::LogicalSize { width, height }) + .with_resizable(settings.resizable) + .with_decorations(settings.decorations) + .with_transparent(settings.transparent) + .with_window_icon(settings.icon.and_then(conversion::icon)) + .with_window_level(conversion::window_level(settings.level)) + .with_visible(settings.visible); + + if let Some(position) = + conversion::position(monitor.as_ref(), settings.size, settings.position) + { + window_builder = window_builder.with_position(position); } -} - -impl Window { - /// Converts the window settings into a `WindowBuilder` from `winit`. - pub fn into_builder( - self, - title: &str, - primary_monitor: Option, - _id: Option, - ) -> WindowBuilder { - let mut window_builder = WindowBuilder::new(); - - let (width, height) = self.size; + if let Some((width, height)) = settings.min_size { window_builder = window_builder - .with_title(title) - .with_inner_size(winit::dpi::LogicalSize { width, height }) - .with_resizable(self.resizable) - .with_decorations(self.decorations) - .with_transparent(self.transparent) - .with_window_icon(self.icon.and_then(conversion::icon)) - .with_window_level(conversion::window_level(self.level)) - .with_visible(self.visible); - - if let Some(position) = conversion::position( - primary_monitor.as_ref(), - self.size, - self.position, - ) { - window_builder = window_builder.with_position(position); - } - - if let Some((width, height)) = self.min_size { - window_builder = window_builder - .with_min_inner_size(winit::dpi::LogicalSize { width, height }); - } + .with_min_inner_size(winit::dpi::LogicalSize { width, height }); + } - if let Some((width, height)) = self.max_size { - window_builder = window_builder - .with_max_inner_size(winit::dpi::LogicalSize { width, height }); - } + if let Some((width, height)) = settings.max_size { + window_builder = window_builder + .with_max_inner_size(winit::dpi::LogicalSize { width, height }); + } - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - { - // `with_name` is available on both `WindowBuilderExtWayland` and `WindowBuilderExtX11` and they do - // exactly the same thing. We arbitrarily choose `WindowBuilderExtWayland` here. - use ::winit::platform::wayland::WindowBuilderExtWayland; - - if let Some(id) = _id { - window_builder = window_builder.with_name(id.clone(), id); - } + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + { + // `with_name` is available on both `WindowBuilderExtWayland` and `WindowBuilderExtX11` and they do + // exactly the same thing. We arbitrarily choose `WindowBuilderExtWayland` here. + use ::winit::platform::wayland::WindowBuilderExtWayland; + + if let Some(id) = _id { + window_builder = window_builder.with_name(id.clone(), id); } + } - #[cfg(target_os = "windows")] - { - use winit::platform::windows::WindowBuilderExtWindows; - #[allow(unsafe_code)] - unsafe { - window_builder = window_builder - .with_parent_window(self.platform_specific.parent); - } + #[cfg(target_os = "windows")] + { + use winit::platform::windows::WindowBuilderExtWindows; + #[allow(unsafe_code)] + unsafe { window_builder = window_builder - .with_drag_and_drop(self.platform_specific.drag_and_drop); + .with_parent_window(settings.platform_specific.parent); } + window_builder = window_builder + .with_drag_and_drop(settings.platform_specific.drag_and_drop); + } - #[cfg(target_os = "macos")] - { - use winit::platform::macos::WindowBuilderExtMacOS; - - window_builder = window_builder - .with_title_hidden(self.platform_specific.title_hidden) - .with_titlebar_transparent( - self.platform_specific.titlebar_transparent, - ) - .with_fullsize_content_view( - self.platform_specific.fullsize_content_view, - ); - } + #[cfg(target_os = "macos")] + { + use winit::platform::macos::WindowBuilderExtMacOS; - window_builder + window_builder = window_builder + .with_title_hidden(settings.platform_specific.title_hidden) + .with_titlebar_transparent( + settings.platform_specific.titlebar_transparent, + ) + .with_fullsize_content_view( + settings.platform_specific.fullsize_content_view, + ); } -} -impl Default for Window { - fn default() -> Window { - Window { - size: (1024, 768), - position: Position::default(), - min_size: None, - max_size: None, - visible: true, - resizable: true, - decorations: true, - transparent: false, - level: Level::default(), - icon: None, - platform_specific: Default::default(), - } - } + window_builder } diff --git a/winit/src/settings/macos.rs b/winit/src/settings/macos.rs deleted file mode 100644 index f86e63ad..00000000 --- a/winit/src/settings/macos.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Platform specific settings for macOS. - -/// The platform specific window settings of an application. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct PlatformSpecific { - /// Hides the window title. - pub title_hidden: bool, - /// Makes the titlebar transparent and allows the content to appear behind it. - pub titlebar_transparent: bool, - /// Makes the window content appear behind the titlebar. - pub fullsize_content_view: bool, -} diff --git a/winit/src/settings/other.rs b/winit/src/settings/other.rs deleted file mode 100644 index b1103f62..00000000 --- a/winit/src/settings/other.rs +++ /dev/null @@ -1,3 +0,0 @@ -/// The platform specific window settings of an application. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct PlatformSpecific; diff --git a/winit/src/settings/wasm.rs b/winit/src/settings/wasm.rs deleted file mode 100644 index 8e0f1bbc..00000000 --- a/winit/src/settings/wasm.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Platform specific settings for WebAssembly. - -/// The platform specific window settings of an application. -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct PlatformSpecific { - /// The identifier of a DOM element that will be replaced with the - /// application. - /// - /// If set to `None`, the application will be appended to the HTML body. - pub target: Option, -} diff --git a/winit/src/settings/windows.rs b/winit/src/settings/windows.rs deleted file mode 100644 index 45d753bd..00000000 --- a/winit/src/settings/windows.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! Platform specific settings for Windows. -use raw_window_handle::RawWindowHandle; - -/// The platform specific window settings of an application. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct PlatformSpecific { - /// Parent window - pub parent: Option, - - /// Drag and drop support - pub drag_and_drop: bool, -} - -impl Default for PlatformSpecific { - fn default() -> Self { - Self { - parent: None, - drag_and_drop: true, - } - } -} diff --git a/winit/src/window.rs b/winit/src/window.rs deleted file mode 100644 index e69de29b..00000000 -- cgit From 83c7870c569a2976923ee6243a19813094d44673 Mon Sep 17 00:00:00 2001 From: Bingus Date: Mon, 24 Jul 2023 14:32:59 -0700 Subject: Moved `exit_on_close_request` to window settings. This now controls whether each INDIVIDUAL window should close on CloseRequested events. --- winit/src/application.rs | 4 +- winit/src/multi_window.rs | 148 +++++++++++++++++++++++--------------- winit/src/multi_window/windows.rs | 26 +++++++ winit/src/settings.rs | 12 +--- 4 files changed, 120 insertions(+), 70 deletions(-) (limited to 'winit/src') diff --git a/winit/src/application.rs b/winit/src/application.rs index 5c45bbda..cffcb884 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -136,6 +136,8 @@ where let target = settings.window.platform_specific.target.clone(); let should_be_visible = settings.window.visible; + let exit_on_close_request = settings.window.exit_on_close_request; + let builder = settings::window_builder( settings.window, &application.title(), @@ -197,7 +199,7 @@ where init_command, window, should_be_visible, - settings.exit_on_close_request, + exit_on_close_request, )); let mut context = task::Context::from_waker(task::noop_waker_ref()); diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index e6f440bc..b67c0a48 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -46,7 +46,14 @@ pub enum Event { /// An internal event for closing a window. CloseWindow(window::Id), /// An internal event for when the window has finished being created. - WindowCreated(window::Id, winit::window::Window), + WindowCreated { + /// The internal ID of the window. + id: window::Id, + /// The raw window. + window: winit::window::Window, + /// Whether or not the window should close when a user requests it does. + exit_on_close_request: bool, + }, } #[allow(unsafe_code)] @@ -161,6 +168,8 @@ where }; let should_main_be_visible = settings.window.visible; + let exit_on_close_request = settings.window.exit_on_close_request; + let builder = window_builder( settings.window, &application.title(window::Id::MAIN), @@ -208,8 +217,13 @@ where let (mut compositor, renderer) = C::new(compositor_settings, Some(&main_window))?; - let windows = - Windows::new(&application, &mut compositor, renderer, main_window); + let windows = Windows::new( + &application, + &mut compositor, + renderer, + main_window, + exit_on_close_request, + ); let (mut event_sender, event_receiver) = mpsc::unbounded(); let (control_sender, mut control_receiver) = mpsc::unbounded(); @@ -225,7 +239,6 @@ where init_command, windows, should_main_be_visible, - settings.exit_on_close_request, )); let mut context = task::Context::from_waker(task::noop_waker_ref()); @@ -255,14 +268,18 @@ where title, monitor, }) => { + let exit_on_close_request = settings.exit_on_close_request; + let window = settings::window_builder(settings, &title, monitor, None) .build(window_target) .expect("Failed to build window"); - Some(winit::event::Event::UserEvent(Event::WindowCreated( - id, window, - ))) + Some(winit::event::Event::UserEvent(Event::WindowCreated { + id, + window, + exit_on_close_request, + })) } _ => event.to_static(), }; @@ -299,7 +316,6 @@ async fn run_instance( init_command: Command, mut windows: Windows, should_main_window_be_visible: bool, - exit_on_main_closed: bool, ) where A: Application + 'static, E: Executor + 'static, @@ -548,11 +564,20 @@ async fn run_instance( Event::Application(message) => { messages.push(message); } - Event::WindowCreated(id, window) => { + Event::WindowCreated { + id, + window, + exit_on_close_request, + } => { let bounds = logical_bounds_of(&window); - let (inner_size, i) = - windows.add(&application, &mut compositor, id, window); + let (inner_size, i) = windows.add( + &application, + &mut compositor, + id, + window, + exit_on_close_request, + ); user_interfaces.push(build_user_interface( &application, @@ -680,50 +705,61 @@ async fn run_instance( event: window_event, window_id, } => { - let window_deleted = windows - .pending_destroy - .iter() - .any(|(_, w_id)| window_id == *w_id); + let window_index = + windows.raw.iter().position(|w| w.id() == window_id); + + match window_index { + Some(i) => { + let id = windows.ids[i]; + let raw = &windows.raw[i]; + let exit_on_close_request = + windows.exit_on_close_requested[i]; + + if matches!( + window_event, + winit::event::WindowEvent::CloseRequested + ) && exit_on_close_request + { + let i = windows.delete(id); + let _ = user_interfaces.remove(i); + let _ = ui_caches.remove(i); + + if windows.is_empty() { + break 'main; + } + } else { + let state = &mut windows.states[i]; + state.update(raw, &window_event, &mut debug); - if matches!(window_event, winit::event::WindowEvent::Destroyed) - { - // This is the only special case, since in order trigger the Destroyed event the - // window reference from winit must be dropped, but we still want to inform the - // user that the window was destroyed so they can clean up any specific window - // code for this window - let id = windows.get_pending_destroy(window_id); - - events.push(( - None, - core::Event::Window(id, window::Event::Destroyed), - )); - } else if !window_deleted { - let i = windows.index_from_raw(window_id); - let id = windows.ids[i]; - let raw = &windows.raw[i]; - let state = &mut windows.states[i]; - - // first check if we need to just break the entire application - // e.g. a user does a force quit on MacOS, or if a user has set "exit on main closed" - // as an option in window settings and wants to close the main window - if requests_exit( - i, - exit_on_main_closed, - &window_event, - state.modifiers(), - ) { - break 'main; + if let Some(event) = conversion::window_event( + id, + &window_event, + state.scale_factor(), + state.modifiers(), + ) { + events.push((Some(id), event)); + } + } } - - state.update(raw, &window_event, &mut debug); - - if let Some(event) = conversion::window_event( - id, - &window_event, - state.scale_factor(), - state.modifiers(), - ) { - events.push((Some(id), event)); + None => { + // This is the only special case, since in order to trigger the Destroyed event the + // window reference from winit must be dropped, but we still want to inform the + // user that the window was destroyed so they can clean up any specific window + // code for this window + if matches!( + window_event, + winit::event::WindowEvent::Destroyed + ) { + let id = windows.get_pending_destroy(window_id); + + events.push(( + None, + core::Event::Window( + id, + window::Event::Destroyed, + ), + )); + } } } } @@ -1068,17 +1104,13 @@ where /// Returns true if the provided event should cause an [`Application`] to /// exit. -pub fn requests_exit( - window: usize, - exit_on_main_closed: bool, +pub fn user_force_quit( event: &winit::event::WindowEvent<'_>, _modifiers: winit::event::ModifiersState, ) -> bool { use winit::event::WindowEvent; - //TODO alt f4..? match event { - WindowEvent::CloseRequested => exit_on_main_closed && window == 0, #[cfg(target_os = "macos")] WindowEvent::KeyboardInput { input: diff --git a/winit/src/multi_window/windows.rs b/winit/src/multi_window/windows.rs index 7b63defa..1f606b31 100644 --- a/winit/src/multi_window/windows.rs +++ b/winit/src/multi_window/windows.rs @@ -14,6 +14,7 @@ where pub raw: Vec, pub states: Vec>, pub viewport_versions: Vec, + pub exit_on_close_requested: Vec, pub surfaces: Vec, pub renderers: Vec, pub pending_destroy: Vec<(window::Id, winit::window::WindowId)>, @@ -52,6 +53,7 @@ where compositor: &mut C, renderer: A::Renderer, main: winit::window::Window, + exit_on_close_requested: bool, ) -> Self { let state = State::new(application, window::Id::MAIN, &main); let viewport_version = state.viewport_version(); @@ -67,6 +69,7 @@ where raw: vec![main], states: vec![state], viewport_versions: vec![viewport_version], + exit_on_close_requested: vec![exit_on_close_requested], surfaces: vec![surface], renderers: vec![renderer], pending_destroy: vec![], @@ -81,6 +84,7 @@ where compositor: &mut C, id: window::Id, window: winit::window::Window, + exit_on_close_requested: bool, ) -> (Size, usize) { let state = State::new(application, id, &window); let window_size = state.logical_size(); @@ -96,6 +100,7 @@ where self.ids.push(id); self.raw.push(window); self.states.push(state); + self.exit_on_close_requested.push(exit_on_close_requested); self.viewport_versions.push(viewport_version); self.surfaces.push(surface); self.renderers.push(renderer); @@ -145,6 +150,7 @@ where let id = self.ids.remove(i); let window = self.raw.remove(i); let _ = self.states.remove(i); + let _ = self.exit_on_close_requested.remove(i); let _ = self.viewport_versions.remove(i); let _ = self.surfaces.remove(i); @@ -167,4 +173,24 @@ where let (id, _) = self.pending_destroy.remove(i); id } + + /// Returns the windows that need to be requested to closed, and also the windows that can be + /// closed immediately. + pub fn partition_close_requests(&self) -> (Vec, Vec) { + self.exit_on_close_requested.iter().enumerate().fold( + (vec![], vec![]), + |(mut close_immediately, mut needs_request_closed), + (i, close)| { + let id = self.ids[i]; + + if *close { + close_immediately.push(id); + } else { + needs_request_closed.push(id); + } + + (close_immediately, needs_request_closed) + }, + ) + } } diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 2b846fbd..c0b3b047 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -1,6 +1,6 @@ //! Configure your application. -use crate::conversion; use crate::core::window; +use crate::conversion; use winit::monitor::MonitorHandle; use winit::window::WindowBuilder; @@ -21,16 +21,6 @@ pub struct Settings { /// /// [`Application`]: crate::Application pub flags: Flags, - - /// Whether the [`Application`] should exit when the user requests the - /// window to close (e.g. the user presses the close button). - /// - /// With a [`multi_window::Application`] this will instead be used to determine whether the - /// application should exit when the "main"" window is closed, i.e. the first window created on - /// app launch. - /// - /// [`Application`]: crate::Application - pub exit_on_close_request: bool, } /// Converts the window settings into a `WindowBuilder` from `winit`. -- cgit From 9b34b2ac19a8fdd424581d160bc702e096a2b46a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 29 Nov 2023 22:33:41 +0100 Subject: Run `cargo fmt` --- winit/src/multi_window/windows.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window/windows.rs b/winit/src/multi_window/windows.rs index 1f606b31..6846abb3 100644 --- a/winit/src/multi_window/windows.rs +++ b/winit/src/multi_window/windows.rs @@ -176,11 +176,12 @@ where /// Returns the windows that need to be requested to closed, and also the windows that can be /// closed immediately. - pub fn partition_close_requests(&self) -> (Vec, Vec) { + pub fn partition_close_requests( + &self, + ) -> (Vec, Vec) { self.exit_on_close_requested.iter().enumerate().fold( (vec![], vec![]), - |(mut close_immediately, mut needs_request_closed), - (i, close)| { + |(mut close_immediately, mut needs_request_closed), (i, close)| { let id = self.ids[i]; if *close { -- cgit From 7def3ee38a3f0f24a331d722b09f325fc9584625 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 29 Nov 2023 22:37:54 +0100 Subject: Fix `clippy` lints --- winit/src/application.rs | 7 ++++--- winit/src/multi_window.rs | 24 ++++++++++++++---------- winit/src/multi_window/windows.rs | 14 +++++++++----- 3 files changed, 27 insertions(+), 18 deletions(-) (limited to 'winit/src') diff --git a/winit/src/application.rs b/winit/src/application.rs index 8457fd92..b197c4ed 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -720,9 +720,10 @@ pub fn run_command( let _res = window.drag_window(); } window::Action::Spawn { .. } => { - log::info!( - "Spawning a window is only available with multi-window applications." - ) + log::warn!( + "Spawning a window is only available with \ + multi-window applications." + ); } window::Action::Resize(size) => { window.set_inner_size(winit::dpi::LogicalSize { diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index f2452eb3..b233564a 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -912,7 +912,7 @@ pub fn run_command( size.width, size.height, )))) - .expect("Send message to event loop") + .expect("Send message to event loop"); } window::Action::Maximize(maximized) => { windows.with_raw(id).set_maximized(maximized); @@ -934,7 +934,9 @@ pub fn run_command( )); } window::Action::ChangeIcon(icon) => { - windows.with_raw(id).set_window_icon(conversion::icon(icon)) + windows + .with_raw(id) + .set_window_icon(conversion::icon(icon)); } window::Action::FetchMode(tag) => { let window = windows.with_raw(id); @@ -969,12 +971,14 @@ pub fn run_command( .with_raw(id) .set_window_level(conversion::window_level(level)); } - window::Action::FetchId(tag) => proxy - .send_event(Event::Application(tag(windows - .with_raw(id) - .id() - .into()))) - .expect("Event loop doesn't exist."), + window::Action::FetchId(tag) => { + proxy + .send_event(Event::Application(tag(windows + .with_raw(id) + .id() + .into()))) + .expect("Event loop doesn't exist."); + } window::Action::Screenshot(tag) => { let i = windows.index_from_id(id); let state = &windows.states[i]; @@ -996,7 +1000,7 @@ pub fn run_command( state.physical_size(), ), ))) - .expect("Event loop doesn't exist.") + .expect("Event loop doesn't exist."); } }, command::Action::System(action) => match action { @@ -1014,7 +1018,7 @@ pub fn run_command( proxy .send_event(Event::Application(message)) - .expect("Event loop doesn't exist.") + .expect("Event loop doesn't exist."); }); } } diff --git a/winit/src/multi_window/windows.rs b/winit/src/multi_window/windows.rs index 6846abb3..a4841a45 100644 --- a/winit/src/multi_window/windows.rs +++ b/winit/src/multi_window/windows.rs @@ -1,10 +1,12 @@ use crate::core::{window, Size}; +use crate::graphics::Compositor; use crate::multi_window::{Application, State}; -use iced_graphics::Compositor; -use iced_style::application::StyleSheet; -use std::fmt::{Debug, Formatter}; +use crate::style::application::StyleSheet; + use winit::monitor::MonitorHandle; +use std::fmt::{Debug, Formatter}; + pub struct Windows where ::Theme: StyleSheet, @@ -33,7 +35,7 @@ where &self .raw .iter() - .map(|raw| raw.id()) + .map(winit::window::Window::id) .collect::>(), ) .field("states", &self.states) @@ -131,7 +133,9 @@ where } pub fn last_monitor(&self) -> Option { - self.raw.last().and_then(|w| w.current_monitor()) + self.raw + .last() + .and_then(winit::window::Window::current_monitor) } pub fn last(&self) -> usize { -- cgit From 6740c2c5d6b24399dab1343abdfec5daf4b03c98 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 29 Nov 2023 22:46:47 +0100 Subject: Fix broken intra-doc links --- winit/src/conversion.rs | 2 +- winit/src/multi_window.rs | 23 ++++------------------- winit/src/multi_window/state.rs | 4 +--- winit/src/settings.rs | 2 +- 4 files changed, 7 insertions(+), 24 deletions(-) (limited to 'winit/src') diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 22e6b9be..68c2b905 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -272,7 +272,7 @@ pub fn window_level(level: window::Level) -> winit::window::WindowLevel { } } -/// Converts a [`Position`] to a [`winit`] logical position for a given monitor. +/// Converts a [`window::Position`] to a [`winit`] logical position for a given monitor. /// /// [`winit`]: https://github.com/rust-windowing/winit pub fn position( diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index b233564a..0e08a081 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -23,34 +23,19 @@ use std::mem::ManuallyDrop; use std::time::Instant; use winit::monitor::MonitorHandle; -/// This is a wrapper around the `Application::Message` associate type -/// to allows the `shell` to create internal messages, while still having -/// the current user-specified custom messages. #[derive(Debug)] -pub enum Event { - /// An internal event which contains an [`Application`] generated message. +enum Event { Application(Message), - /// An internal event which spawns a new window. NewWindow { - /// The [window::Id] of the newly spawned [`Window`]. id: window::Id, - /// The [settings::Window] of the newly spawned [`Window`]. settings: window::Settings, - /// The title of the newly spawned [`Window`]. title: String, - /// The monitor on which to spawn the window. If `None`, will use monitor of the last window - /// spawned. monitor: Option, }, - /// An internal event for closing a window. CloseWindow(window::Id), - /// An internal event for when the window has finished being created. WindowCreated { - /// The internal ID of the window. id: window::Id, - /// The raw window. window: winit::window::Window, - /// Whether or not the window should close when a user requests it does. exit_on_close_request: bool, }, } @@ -771,7 +756,7 @@ async fn run_instance( } /// Builds a window's [`UserInterface`] for the [`Application`]. -pub fn build_user_interface<'a, A: Application>( +fn build_user_interface<'a, A: Application>( application: &'a A, cache: user_interface::Cache, renderer: &mut A::Renderer, @@ -795,7 +780,7 @@ where /// Updates a multi-window [`Application`] by feeding it messages, spawning any /// resulting [`Command`], and tracking its [`Subscription`]. -pub fn update( +fn update( application: &mut A, compositor: &mut C, runtime: &mut Runtime>, Event>, @@ -834,7 +819,7 @@ pub fn update( } /// Runs the actions of a [`Command`]. -pub fn run_command( +fn run_command( application: &A, compositor: &mut C, command: Command, diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs index f2741c3c..e9a9f91a 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/multi_window/state.rs @@ -200,9 +200,7 @@ where /// window. /// /// Normally, an [`Application`] should be synchronized with its [`State`] - /// and window after calling [`Application::update`]. - /// - /// [`Application::update`]: crate::Program::update + /// and window after calling [`State::update`]. pub fn synchronize( &mut self, application: &A, diff --git a/winit/src/settings.rs b/winit/src/settings.rs index dc0f65a5..2e541128 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -12,7 +12,7 @@ pub struct Settings { /// communicate with it through the windowing system. pub id: Option, - /// The [`Window`] settings. + /// The [`window::Settings`]. pub window: window::Settings, /// The data needed to initialize an [`Application`]. -- cgit From ac12d2d099d9ae996d0ccfdc7e5b82d9cef990ee Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 29 Nov 2023 22:50:35 +0100 Subject: Remove unnecessary unsafe `Send` marker in `iced_winit` --- winit/src/multi_window.rs | 3 --- 1 file changed, 3 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 0e08a081..ef142c77 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -40,9 +40,6 @@ enum Event { }, } -#[allow(unsafe_code)] -unsafe impl std::marker::Send for Event {} - /// An interactive, native, cross-platform, multi-windowed application. /// /// This trait is the main entrypoint of multi-window Iced. Once implemented, you can run -- cgit From d34bc4e4a251bb28854770575d379d4a53f2db12 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 29 Nov 2023 23:55:17 +0100 Subject: Refactor event loop <-> instance communication in `multi_window` --- winit/src/multi_window.rs | 871 ++++++++++++++++++++++++---------------------- 1 file changed, 452 insertions(+), 419 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index ef142c77..f4ebbe09 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -8,7 +8,7 @@ use crate::conversion; use crate::core::widget::operation; use crate::core::{self, mouse, renderer, window, Size}; use crate::futures::futures::channel::mpsc; -use crate::futures::futures::{task, Future, FutureExt, StreamExt}; +use crate::futures::futures::{task, Future, StreamExt}; use crate::futures::{Executor, Runtime, Subscription}; use crate::graphics::{compositor, Compositor}; use crate::multi_window::windows::Windows; @@ -21,23 +21,24 @@ use crate::{Clipboard, Error, Proxy, Settings}; use std::mem::ManuallyDrop; use std::time::Instant; -use winit::monitor::MonitorHandle; -#[derive(Debug)] -enum Event { - Application(Message), - NewWindow { - id: window::Id, - settings: window::Settings, - title: String, - monitor: Option, - }, - CloseWindow(window::Id), +enum Event { WindowCreated { id: window::Id, window: winit::window::Window, exit_on_close_request: bool, }, + EventLoopAwakened(winit::event::Event<'static, Message>), +} + +enum Control { + ChangeFlow(winit::event_loop::ControlFlow), + CreateWindow { + id: window::Id, + settings: window::Settings, + title: String, + monitor: Option, + }, } /// An interactive, native, cross-platform, multi-windowed application. @@ -243,44 +244,57 @@ where event: winit::event::WindowEvent::Resized(*new_inner_size), window_id, }), - winit::event::Event::UserEvent(Event::NewWindow { - id, - settings, - title, - monitor, - }) => { - let exit_on_close_request = settings.exit_on_close_request; - - let window = conversion::window_settings( - settings, &title, monitor, None, - ) - .build(window_target) - .expect("Failed to build window"); - - Some(winit::event::Event::UserEvent(Event::WindowCreated { - id, - window, - exit_on_close_request, - })) - } _ => event.to_static(), }; if let Some(event) = event { - event_sender.start_send(event).expect("Send event"); - - let poll = instance.as_mut().poll(&mut context); - - match poll { - task::Poll::Pending => { - if let Ok(Some(flow)) = control_receiver.try_next() { - *control_flow = flow; + event_sender + .start_send(Event::EventLoopAwakened(event)) + .expect("Send event"); + + loop { + let poll = instance.as_mut().poll(&mut context); + + match poll { + task::Poll::Pending => match control_receiver.try_next() { + Ok(Some(control)) => match control { + Control::ChangeFlow(flow) => { + *control_flow = flow; + } + Control::CreateWindow { + id, + settings, + title, + monitor, + } => { + let exit_on_close_request = + settings.exit_on_close_request; + + let window = conversion::window_settings( + settings, &title, monitor, None, + ) + .build(window_target) + .expect("Failed to build window"); + + event_sender + .start_send(Event::WindowCreated { + id, + window, + exit_on_close_request, + }) + .expect("Send event"); + } + }, + _ => { + break; + } + }, + task::Poll::Ready(_) => { + *control_flow = ControlFlow::Exit; + break; } - } - task::Poll::Ready(_) => { - *control_flow = ControlFlow::Exit; - } - }; + }; + } } }) } @@ -288,13 +302,11 @@ where async fn run_instance( mut application: A, mut compositor: C, - mut runtime: Runtime>, Event>, - mut proxy: winit::event_loop::EventLoopProxy>, + mut runtime: Runtime, A::Message>, + mut proxy: winit::event_loop::EventLoopProxy, mut debug: Debug, - mut event_receiver: mpsc::UnboundedReceiver< - winit::event::Event<'_, Event>, - >, - mut control_sender: mpsc::UnboundedSender, + mut event_receiver: mpsc::UnboundedReceiver>, + mut control_sender: mpsc::UnboundedSender, init_command: Command, mut windows: Windows, should_main_window_be_visible: bool, @@ -327,18 +339,14 @@ async fn run_instance( init_command, &mut runtime, &mut clipboard, + &mut control_sender, &mut proxy, &mut debug, &mut windows, &mut ui_caches, ); - runtime.track( - application - .subscription() - .map(Event::Application) - .into_recipes(), - ); + runtime.track(application.subscription().into_recipes()); let mut mouse_interaction = mouse::Interaction::default(); @@ -361,391 +369,409 @@ async fn run_instance( 'main: while let Some(event) = event_receiver.next().await { match event { - event::Event::NewEvents(start_cause) => { - redraw_pending = matches!( - start_cause, - event::StartCause::Init - | event::StartCause::Poll - | event::StartCause::ResumeTimeReached { .. } - ); - } - event::Event::MainEventsCleared => { - debug.event_processing_started(); - let mut uis_stale = false; - - for (i, id) in windows.ids.iter().enumerate() { - let mut window_events = vec![]; - - events.retain(|(window_id, event)| { - if *window_id == Some(*id) || window_id.is_none() { - window_events.push(event.clone()); - false - } else { - true - } - }); - - if !redraw_pending - && window_events.is_empty() - && messages.is_empty() - { - continue; - } + Event::WindowCreated { + id, + window, + exit_on_close_request, + } => { + let bounds = logical_bounds_of(&window); - let (ui_state, statuses) = user_interfaces[i].update( - &window_events, - windows.states[i].cursor(), - &mut windows.renderers[i], - &mut clipboard, - &mut messages, - ); + let (inner_size, i) = windows.add( + &application, + &mut compositor, + id, + window, + exit_on_close_request, + ); - if !uis_stale { - uis_stale = - matches!(ui_state, user_interface::State::Outdated); - } + user_interfaces.push(build_user_interface( + &application, + user_interface::Cache::default(), + &mut windows.renderers[i], + inner_size, + &mut debug, + id, + )); + ui_caches.push(user_interface::Cache::default()); - for (event, status) in - window_events.into_iter().zip(statuses.into_iter()) - { - runtime.broadcast(event, status); - } + if let Some(bounds) = bounds { + events.push(( + Some(id), + core::Event::Window( + id, + window::Event::Created { + position: bounds.0, + size: bounds.1, + }, + ), + )); } - - debug.event_processing_finished(); - - // TODO mw application update returns which window IDs to update - if !messages.is_empty() || uis_stale { - let mut cached_interfaces: Vec = - ManuallyDrop::into_inner(user_interfaces) - .drain(..) - .map(UserInterface::into_cache) - .collect(); - - // Update application - update( - &mut application, - &mut compositor, - &mut runtime, - &mut clipboard, - &mut proxy, - &mut debug, - &mut messages, - &mut windows, - &mut cached_interfaces, - ); - - // we must synchronize all window states with application state after an - // application update since we don't know what changed - for (state, (id, window)) in windows - .states - .iter_mut() - .zip(windows.ids.iter().zip(windows.raw.iter())) - { - state.synchronize(&application, *id, window); + } + Event::EventLoopAwakened(event) => { + match event { + event::Event::NewEvents(start_cause) => { + redraw_pending = matches!( + start_cause, + event::StartCause::Init + | event::StartCause::Poll + | event::StartCause::ResumeTimeReached { .. } + ); } + event::Event::MainEventsCleared => { + debug.event_processing_started(); + let mut uis_stale = false; + + for (i, id) in windows.ids.iter().enumerate() { + let mut window_events = vec![]; + + events.retain(|(window_id, event)| { + if *window_id == Some(*id) + || window_id.is_none() + { + window_events.push(event.clone()); + false + } else { + true + } + }); + + if !redraw_pending + && window_events.is_empty() + && messages.is_empty() + { + continue; + } - // rebuild UIs with the synchronized states - user_interfaces = ManuallyDrop::new(build_user_interfaces( - &application, - &mut debug, - &mut windows, - cached_interfaces, - )); - } + let (ui_state, statuses) = user_interfaces[i] + .update( + &window_events, + windows.states[i].cursor(), + &mut windows.renderers[i], + &mut clipboard, + &mut messages, + ); + + if !uis_stale { + uis_stale = matches!( + ui_state, + user_interface::State::Outdated + ); + } - debug.draw_started(); - - for (i, id) in windows.ids.iter().enumerate() { - // 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 = core::Event::Window( - *id, - window::Event::RedrawRequested(Instant::now()), - ); + for (event, status) in window_events + .into_iter() + .zip(statuses.into_iter()) + { + runtime.broadcast(event, status); + } + } - let cursor = windows.states[i].cursor(); + debug.event_processing_finished(); + + // TODO mw application update returns which window IDs to update + if !messages.is_empty() || uis_stale { + let mut cached_interfaces: Vec< + user_interface::Cache, + > = ManuallyDrop::into_inner(user_interfaces) + .drain(..) + .map(UserInterface::into_cache) + .collect(); + + // Update application + update( + &mut application, + &mut compositor, + &mut runtime, + &mut clipboard, + &mut control_sender, + &mut proxy, + &mut debug, + &mut messages, + &mut windows, + &mut cached_interfaces, + ); - let (ui_state, _) = user_interfaces[i].update( - &[redraw_event.clone()], - cursor, - &mut windows.renderers[i], - &mut clipboard, - &mut messages, - ); + // we must synchronize all window states with application state after an + // application update since we don't know what changed + for (state, (id, window)) in windows + .states + .iter_mut() + .zip(windows.ids.iter().zip(windows.raw.iter())) + { + state.synchronize(&application, *id, window); + } - let new_mouse_interaction = { - let state = &windows.states[i]; + // rebuild UIs with the synchronized states + user_interfaces = + ManuallyDrop::new(build_user_interfaces( + &application, + &mut debug, + &mut windows, + cached_interfaces, + )); + } - user_interfaces[i].draw( - &mut windows.renderers[i], - state.theme(), - &renderer::Style { - text_color: state.text_color(), - }, - cursor, - ) - }; + debug.draw_started(); + + for (i, id) in windows.ids.iter().enumerate() { + // 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 = core::Event::Window( + *id, + window::Event::RedrawRequested(Instant::now()), + ); - if new_mouse_interaction != mouse_interaction { - windows.raw[i].set_cursor_icon( - conversion::mouse_interaction( - new_mouse_interaction, - ), - ); + let cursor = windows.states[i].cursor(); - mouse_interaction = new_mouse_interaction; - } + let (ui_state, _) = user_interfaces[i].update( + &[redraw_event.clone()], + cursor, + &mut windows.renderers[i], + &mut clipboard, + &mut messages, + ); - // TODO once widgets can request to be redrawn, we can avoid always requesting a - // redraw - windows.raw[i].request_redraw(); + let new_mouse_interaction = { + let state = &windows.states[i]; + + user_interfaces[i].draw( + &mut windows.renderers[i], + state.theme(), + &renderer::Style { + text_color: state.text_color(), + }, + cursor, + ) + }; + + if new_mouse_interaction != mouse_interaction { + windows.raw[i].set_cursor_icon( + conversion::mouse_interaction( + new_mouse_interaction, + ), + ); + + mouse_interaction = new_mouse_interaction; + } - runtime.broadcast( - redraw_event.clone(), - core::event::Status::Ignored, - ); + // TODO once widgets can request to be redrawn, we can avoid always requesting a + // redraw + windows.raw[i].request_redraw(); - let _ = control_sender.start_send(match ui_state { - user_interface::State::Updated { - redraw_request: Some(redraw_request), - } => match redraw_request { - window::RedrawRequest::NextFrame => { - ControlFlow::Poll - } - window::RedrawRequest::At(at) => { - ControlFlow::WaitUntil(at) - } - }, - _ => ControlFlow::Wait, - }); - } + runtime.broadcast( + redraw_event.clone(), + core::event::Status::Ignored, + ); - redraw_pending = false; + let _ = control_sender.start_send( + Control::ChangeFlow(match ui_state { + user_interface::State::Updated { + redraw_request: Some(redraw_request), + } => match redraw_request { + window::RedrawRequest::NextFrame => { + ControlFlow::Poll + } + window::RedrawRequest::At(at) => { + ControlFlow::WaitUntil(at) + } + }, + _ => ControlFlow::Wait, + }), + ); + } - debug.draw_finished(); - } - event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( - event::MacOS::ReceivedUrl(url), - )) => { - use crate::core::event; + redraw_pending = false; - events.push(( - None, + debug.draw_finished(); + } event::Event::PlatformSpecific( event::PlatformSpecific::MacOS( event::MacOS::ReceivedUrl(url), ), - ), - )); - } - event::Event::UserEvent(event) => match event { - Event::Application(message) => { - messages.push(message); - } - Event::WindowCreated { - id, - window, - exit_on_close_request, - } => { - let bounds = logical_bounds_of(&window); - - let (inner_size, i) = windows.add( - &application, - &mut compositor, - id, - window, - exit_on_close_request, - ); + ) => { + use crate::core::event; - user_interfaces.push(build_user_interface( - &application, - user_interface::Cache::default(), - &mut windows.renderers[i], - inner_size, - &mut debug, - id, - )); - ui_caches.push(user_interface::Cache::default()); - - if let Some(bounds) = bounds { events.push(( - Some(id), - core::Event::Window( - id, - window::Event::Created { - position: bounds.0, - size: bounds.1, - }, + None, + event::Event::PlatformSpecific( + event::PlatformSpecific::MacOS( + event::MacOS::ReceivedUrl(url), + ), ), )); } - } - Event::CloseWindow(id) => { - let i = windows.delete(id); - let _ = user_interfaces.remove(i); - let _ = ui_caches.remove(i); - - if windows.is_empty() { - break 'main; + event::Event::UserEvent(message) => { + messages.push(message); } - } - Event::NewWindow { .. } => unreachable!(), - }, - event::Event::RedrawRequested(id) => { - let i = windows.index_from_raw(id); - let state = &windows.states[i]; - let physical_size = state.physical_size(); - - if physical_size.width == 0 || physical_size.height == 0 { - continue; - } - - debug.render_started(); - let current_viewport_version = state.viewport_version(); - let window_viewport_version = windows.viewport_versions[i]; + event::Event::RedrawRequested(id) => { + let i = windows.index_from_raw(id); + let state = &windows.states[i]; + let physical_size = state.physical_size(); - if window_viewport_version != current_viewport_version { - let logical_size = state.logical_size(); + if physical_size.width == 0 || physical_size.height == 0 + { + continue; + } - debug.layout_started(); + debug.render_started(); + let current_viewport_version = state.viewport_version(); + let window_viewport_version = + windows.viewport_versions[i]; - let renderer = &mut windows.renderers[i]; - let ui = user_interfaces.remove(i); + if window_viewport_version != current_viewport_version { + let logical_size = state.logical_size(); - user_interfaces - .insert(i, ui.relayout(logical_size, renderer)); + debug.layout_started(); - debug.layout_finished(); + let renderer = &mut windows.renderers[i]; + let ui = user_interfaces.remove(i); - debug.draw_started(); - let new_mouse_interaction = user_interfaces[i].draw( - renderer, - state.theme(), - &renderer::Style { - text_color: state.text_color(), - }, - state.cursor(), - ); + user_interfaces + .insert(i, ui.relayout(logical_size, renderer)); - if new_mouse_interaction != mouse_interaction { - windows.raw[i].set_cursor_icon( - conversion::mouse_interaction( - new_mouse_interaction, - ), - ); + debug.layout_finished(); - mouse_interaction = new_mouse_interaction; - } - debug.draw_finished(); + debug.draw_started(); + let new_mouse_interaction = user_interfaces[i] + .draw( + renderer, + state.theme(), + &renderer::Style { + text_color: state.text_color(), + }, + state.cursor(), + ); - compositor.configure_surface( - &mut windows.surfaces[i], - physical_size.width, - physical_size.height, - ); + if new_mouse_interaction != mouse_interaction { + windows.raw[i].set_cursor_icon( + conversion::mouse_interaction( + new_mouse_interaction, + ), + ); - windows.viewport_versions[i] = current_viewport_version; - } + mouse_interaction = new_mouse_interaction; + } + debug.draw_finished(); - match compositor.present( - &mut windows.renderers[i], - &mut windows.surfaces[i], - state.viewport(), - state.background_color(), - &debug.overlay(), - ) { - Ok(()) => { - debug.render_finished(); - - // TODO: Handle animations! - // Maybe we can use `ControlFlow::WaitUntil` for this. - } - Err(error) => match error { - // This is an unrecoverable error. - compositor::SurfaceError::OutOfMemory => { - panic!("{:?}", error); - } - _ => { - debug.render_finished(); - log::error!( - "Error {error:?} when presenting surface." + compositor.configure_surface( + &mut windows.surfaces[i], + physical_size.width, + physical_size.height, ); - // Try rendering all windows again next frame. - for window in &windows.raw { - window.request_redraw(); - } + windows.viewport_versions[i] = + current_viewport_version; } - }, - } - } - event::Event::WindowEvent { - event: window_event, - window_id, - } => { - let window_index = - windows.raw.iter().position(|w| w.id() == window_id); - - match window_index { - Some(i) => { - let id = windows.ids[i]; - let raw = &windows.raw[i]; - let exit_on_close_request = - windows.exit_on_close_requested[i]; - - if matches!( - window_event, - winit::event::WindowEvent::CloseRequested - ) && exit_on_close_request - { - let i = windows.delete(id); - let _ = user_interfaces.remove(i); - let _ = ui_caches.remove(i); - if windows.is_empty() { - break 'main; - } - } else { - let state = &mut windows.states[i]; - state.update(raw, &window_event, &mut debug); + match compositor.present( + &mut windows.renderers[i], + &mut windows.surfaces[i], + state.viewport(), + state.background_color(), + &debug.overlay(), + ) { + Ok(()) => { + debug.render_finished(); - if let Some(event) = conversion::window_event( - id, - &window_event, - state.scale_factor(), - state.modifiers(), - ) { - events.push((Some(id), event)); + // TODO: Handle animations! + // Maybe we can use `ControlFlow::WaitUntil` for this. } + Err(error) => match error { + // This is an unrecoverable error. + compositor::SurfaceError::OutOfMemory => { + panic!("{:?}", error); + } + _ => { + debug.render_finished(); + log::error!( + "Error {error:?} when presenting surface." + ); + + // Try rendering all windows again next frame. + for window in &windows.raw { + window.request_redraw(); + } + } + }, } } - None => { - // This is the only special case, since in order to trigger the Destroyed event the - // window reference from winit must be dropped, but we still want to inform the - // user that the window was destroyed so they can clean up any specific window - // code for this window - if matches!( - window_event, - winit::event::WindowEvent::Destroyed - ) { - let id = windows.get_pending_destroy(window_id); - - events.push(( - None, - core::Event::Window( - id, - window::Event::Destroyed, - ), - )); + event::Event::WindowEvent { + event: window_event, + window_id, + } => { + let window_index = windows + .raw + .iter() + .position(|w| w.id() == window_id); + + match window_index { + Some(i) => { + let id = windows.ids[i]; + let raw = &windows.raw[i]; + let exit_on_close_request = + windows.exit_on_close_requested[i]; + + if matches!( + window_event, + winit::event::WindowEvent::CloseRequested + ) && exit_on_close_request + { + let i = windows.delete(id); + let _ = user_interfaces.remove(i); + let _ = ui_caches.remove(i); + + if windows.is_empty() { + break 'main; + } + } else { + let state = &mut windows.states[i]; + state.update( + raw, + &window_event, + &mut debug, + ); + + if let Some(event) = + conversion::window_event( + id, + &window_event, + state.scale_factor(), + state.modifiers(), + ) + { + events.push((Some(id), event)); + } + } + } + None => { + // This is the only special case, since in order to trigger the Destroyed event the + // window reference from winit must be dropped, but we still want to inform the + // user that the window was destroyed so they can clean up any specific window + // code for this window + if matches!( + window_event, + winit::event::WindowEvent::Destroyed + ) { + let id = + windows.get_pending_destroy(window_id); + + events.push(( + None, + core::Event::Window( + id, + window::Event::Destroyed, + ), + )); + } + } } } + _ => {} } } - _ => {} } } @@ -780,9 +806,10 @@ where fn update( application: &mut A, compositor: &mut C, - runtime: &mut Runtime>, Event>, + runtime: &mut Runtime, A::Message>, clipboard: &mut Clipboard, - proxy: &mut winit::event_loop::EventLoopProxy>, + control_sender: &mut mpsc::UnboundedSender, + proxy: &mut winit::event_loop::EventLoopProxy, debug: &mut Debug, messages: &mut Vec, windows: &mut Windows, @@ -804,6 +831,7 @@ fn update( command, runtime, clipboard, + control_sender, proxy, debug, windows, @@ -811,7 +839,7 @@ fn update( ); } - let subscription = application.subscription().map(Event::Application); + let subscription = application.subscription(); runtime.track(subscription.into_recipes()); } @@ -820,9 +848,10 @@ fn run_command( application: &A, compositor: &mut C, command: Command, - runtime: &mut Runtime>, Event>, + runtime: &mut Runtime, A::Message>, clipboard: &mut Clipboard, - proxy: &mut winit::event_loop::EventLoopProxy>, + control_sender: &mut mpsc::UnboundedSender, + proxy: &mut winit::event_loop::EventLoopProxy, debug: &mut Debug, windows: &mut Windows, ui_caches: &mut Vec, @@ -839,17 +868,17 @@ fn run_command( for action in command.actions() { match action { command::Action::Future(future) => { - runtime.spawn(Box::pin(future.map(Event::Application))); + runtime.spawn(Box::pin(future)); } command::Action::Stream(stream) => { - runtime.run(Box::pin(stream.map(Event::Application))); + runtime.run(Box::pin(stream)); } command::Action::Clipboard(action) => match action { clipboard::Action::Read(tag) => { let message = tag(clipboard.read()); proxy - .send_event(Event::Application(message)) + .send_event(message) .expect("Send message to event loop"); } clipboard::Action::Write(contents) => { @@ -860,19 +889,28 @@ fn run_command( window::Action::Spawn { settings } => { let monitor = windows.last_monitor(); - proxy - .send_event(Event::NewWindow { + control_sender + .start_send(Control::CreateWindow { id, settings, title: application.title(id), monitor, }) - .expect("Send message to event loop"); + .expect("Send control action"); } window::Action::Close => { - proxy - .send_event(Event::CloseWindow(id)) - .expect("Send message to event loop"); + use winit::event_loop::ControlFlow; + + let i = windows.delete(id); + let _ = ui_caches.remove(i); + + if windows.is_empty() { + control_sender + .start_send(Control::ChangeFlow( + ControlFlow::ExitWithCode(0), + )) + .expect("Send control action"); + } } window::Action::Drag => { let _ = windows.with_raw(id).drag_window(); @@ -890,10 +928,10 @@ fn run_command( let size = window.inner_size(); proxy - .send_event(Event::Application(callback(Size::new( + .send_event(callback(Size::new( size.width, size.height, - )))) + ))) .expect("Send message to event loop"); } window::Action::Maximize(maximized) => { @@ -929,7 +967,7 @@ fn run_command( }; proxy - .send_event(Event::Application(tag(mode))) + .send_event(tag(mode)) .expect("Event loop doesn't exist."); } window::Action::ToggleMaximize => { @@ -955,10 +993,7 @@ fn run_command( } window::Action::FetchId(tag) => { proxy - .send_event(Event::Application(tag(windows - .with_raw(id) - .id() - .into()))) + .send_event(tag(windows.with_raw(id).id().into())) .expect("Event loop doesn't exist."); } window::Action::Screenshot(tag) => { @@ -976,11 +1011,9 @@ fn run_command( ); proxy - .send_event(Event::Application(tag( - window::Screenshot::new( - bytes, - state.physical_size(), - ), + .send_event(tag(window::Screenshot::new( + bytes, + state.physical_size(), ))) .expect("Event loop doesn't exist."); } @@ -999,7 +1032,7 @@ fn run_command( let message = _tag(information); proxy - .send_event(Event::Application(message)) + .send_event(message) .expect("Event loop doesn't exist."); }); } @@ -1025,7 +1058,7 @@ fn run_command( operation::Outcome::None => {} operation::Outcome::Some(message) => { proxy - .send_event(Event::Application(message)) + .send_event(message) .expect("Event loop doesn't exist."); // operation completed, don't need to try to operate on rest of UIs @@ -1051,7 +1084,7 @@ fn run_command( } proxy - .send_event(Event::Application(tagger(Ok(())))) + .send_event(tagger(Ok(()))) .expect("Send message to event loop"); } } -- cgit From 9f29aec128ccf51c620a8b69a9fbd64186ab8c65 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 30 Nov 2023 00:01:32 +0100 Subject: Move `Event` and `Control` types after `multi_window::run` --- winit/src/multi_window.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index f4ebbe09..f8cedcb8 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -22,25 +22,6 @@ use crate::{Clipboard, Error, Proxy, Settings}; use std::mem::ManuallyDrop; use std::time::Instant; -enum Event { - WindowCreated { - id: window::Id, - window: winit::window::Window, - exit_on_close_request: bool, - }, - EventLoopAwakened(winit::event::Event<'static, Message>), -} - -enum Control { - ChangeFlow(winit::event_loop::ControlFlow), - CreateWindow { - id: window::Id, - settings: window::Settings, - title: String, - monitor: Option, - }, -} - /// An interactive, native, cross-platform, multi-windowed application. /// /// This trait is the main entrypoint of multi-window Iced. Once implemented, you can run @@ -299,6 +280,25 @@ where }) } +enum Event { + WindowCreated { + id: window::Id, + window: winit::window::Window, + exit_on_close_request: bool, + }, + EventLoopAwakened(winit::event::Event<'static, Message>), +} + +enum Control { + ChangeFlow(winit::event_loop::ControlFlow), + CreateWindow { + id: window::Id, + settings: window::Settings, + title: String, + monitor: Option, + }, +} + async fn run_instance( mut application: A, mut compositor: C, -- cgit From 67408311f45d341509538f8cc185978da66b6ace Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 30 Nov 2023 23:40:33 +0100 Subject: Use actual floats for logical coordinates --- winit/src/application.rs | 9 ++--- winit/src/conversion.rs | 39 ++++++++++++--------- winit/src/multi_window.rs | 89 ++++++++++++++++++++++++----------------------- 3 files changed, 73 insertions(+), 64 deletions(-) (limited to 'winit/src') diff --git a/winit/src/application.rs b/winit/src/application.rs index b197c4ed..4e6a879f 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -732,7 +732,8 @@ pub fn run_command( }); } window::Action::FetchSize(callback) => { - let size = window.inner_size(); + let size = + window.inner_size().to_logical(window.scale_factor()); proxy .send_event(callback(Size::new( @@ -747,10 +748,10 @@ pub fn run_command( window::Action::Minimize(minimized) => { window.set_minimized(minimized); } - window::Action::Move { x, y } => { + window::Action::Move(position) => { window.set_outer_position(winit::dpi::LogicalPosition { - x, - y, + x: position.x, + y: position.y, }); } window::Action::ChangeMode(mode) => { diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 68c2b905..7e51a2d4 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -6,7 +6,7 @@ use crate::core::keyboard; use crate::core::mouse; use crate::core::touch; use crate::core::window; -use crate::core::{Event, Point}; +use crate::core::{Event, Point, Size}; /// Converts some [`window::Settings`] into a `WindowBuilder` from `winit`. pub fn window_settings( @@ -17,11 +17,12 @@ pub fn window_settings( ) -> winit::window::WindowBuilder { let mut window_builder = winit::window::WindowBuilder::new(); - let (width, height) = settings.size; - window_builder = window_builder .with_title(title) - .with_inner_size(winit::dpi::LogicalSize { width, height }) + .with_inner_size(winit::dpi::LogicalSize { + width: settings.size.width, + height: settings.size.height, + }) .with_resizable(settings.resizable) .with_enabled_buttons(if settings.resizable { winit::window::WindowButtons::all() @@ -41,14 +42,20 @@ pub fn window_settings( window_builder = window_builder.with_position(position); } - if let Some((width, height)) = settings.min_size { - window_builder = window_builder - .with_min_inner_size(winit::dpi::LogicalSize { width, height }); + if let Some(min_size) = settings.min_size { + window_builder = + window_builder.with_min_inner_size(winit::dpi::LogicalSize { + width: min_size.width, + height: min_size.height, + }); } - if let Some((width, height)) = settings.max_size { - window_builder = window_builder - .with_max_inner_size(winit::dpi::LogicalSize { width, height }); + if let Some(max_size) = settings.max_size { + window_builder = + window_builder.with_max_inner_size(winit::dpi::LogicalSize { + width: max_size.width, + height: max_size.height, + }); } #[cfg(any( @@ -277,15 +284,15 @@ pub fn window_level(level: window::Level) -> winit::window::WindowLevel { /// [`winit`]: https://github.com/rust-windowing/winit pub fn position( monitor: Option<&winit::monitor::MonitorHandle>, - (width, height): (u32, u32), + size: Size, position: window::Position, ) -> Option { match position { window::Position::Default => None, - window::Position::Specific(x, y) => { + window::Position::Specific(position) => { Some(winit::dpi::Position::Logical(winit::dpi::LogicalPosition { - x: f64::from(x), - y: f64::from(y), + x: f64::from(position.x), + y: f64::from(position.y), })) } window::Position::Centered => { @@ -297,8 +304,8 @@ pub fn position( let centered: winit::dpi::PhysicalPosition = winit::dpi::LogicalPosition { - x: (resolution.width - f64::from(width)) / 2.0, - y: (resolution.height - f64::from(height)) / 2.0, + x: (resolution.width - f64::from(size.width)) / 2.0, + y: (resolution.height - f64::from(size.height)) / 2.0, } .to_physical(monitor.scale_factor()); diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index f8cedcb8..73476452 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -5,8 +5,12 @@ mod windows; pub use state::State; use crate::conversion; +use crate::core; +use crate::core::mouse; +use crate::core::renderer; use crate::core::widget::operation; -use crate::core::{self, mouse, renderer, window, Size}; +use crate::core::window; +use crate::core::{Point, Size}; use crate::futures::futures::channel::mpsc; use crate::futures::futures::{task, Future, StreamExt}; use crate::futures::{Executor, Runtime, Subscription}; @@ -350,18 +354,18 @@ async fn run_instance( let mut mouse_interaction = mouse::Interaction::default(); - let mut events = - if let Some((position, size)) = logical_bounds_of(windows.main()) { - vec![( - Some(window::Id::MAIN), - core::Event::Window( - window::Id::MAIN, - window::Event::Created { position, size }, - ), - )] - } else { - Vec::new() - }; + let mut events = { + let (position, size) = logical_bounds_of(windows.main()); + + vec![( + Some(window::Id::MAIN), + core::Event::Window( + window::Id::MAIN, + window::Event::Created { position, size }, + ), + )] + }; + let mut messages = Vec::new(); let mut redraw_pending = false; @@ -374,7 +378,7 @@ async fn run_instance( window, exit_on_close_request, } => { - let bounds = logical_bounds_of(&window); + let (position, size) = logical_bounds_of(&window); let (inner_size, i) = windows.add( &application, @@ -394,18 +398,13 @@ async fn run_instance( )); ui_caches.push(user_interface::Cache::default()); - if let Some(bounds) = bounds { - events.push(( - Some(id), - core::Event::Window( - id, - window::Event::Created { - position: bounds.0, - size: bounds.1, - }, - ), - )); - } + events.push(( + Some(id), + core::Event::Window( + id, + window::Event::Created { position, size }, + ), + )); } Event::EventLoopAwakened(event) => { match event { @@ -925,7 +924,8 @@ fn run_command( } window::Action::FetchSize(callback) => { let window = windows.with_raw(id); - let size = window.inner_size(); + let size = + window.inner_size().to_logical(window.scale_factor()); proxy .send_event(callback(Size::new( @@ -940,9 +940,12 @@ fn run_command( window::Action::Minimize(minimized) => { windows.with_raw(id).set_minimized(minimized); } - window::Action::Move { x, y } => { + window::Action::Move(position) => { windows.with_raw(id).set_outer_position( - winit::dpi::LogicalPosition { x, y }, + winit::dpi::LogicalPosition { + x: position.x, + y: position.y, + }, ); } window::Action::ChangeMode(mode) => { @@ -1145,25 +1148,23 @@ pub fn user_force_quit( } } -fn logical_bounds_of( - window: &winit::window::Window, -) -> Option<((i32, i32), Size)> { - let scale = window.scale_factor(); - let pos = window +fn logical_bounds_of(window: &winit::window::Window) -> (Option, Size) { + let position = window .inner_position() - .map(|pos| { - ((pos.x as f64 / scale) as i32, (pos.y as f64 / scale) as i32) - }) - .ok()?; + .ok() + .map(|position| position.to_logical(window.scale_factor())) + .map(|position| Point { + x: position.x, + y: position.y, + }); + let size = { - let size = window.inner_size(); - Size::new( - (size.width as f64 / scale) as u32, - (size.height as f64 / scale) as u32, - ) + let size = window.inner_size().to_logical(window.scale_factor()); + + Size::new(size.width, size.height) }; - Some((pos, size)) + (position, size) } #[cfg(not(target_arch = "wasm32"))] -- cgit From ea42af766f345715ff7a7168182d3896ee79cfbc Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 2 Dec 2023 20:41:58 +0100 Subject: Use `AtomicU64` for `window::Id` --- winit/src/application.rs | 36 ++++++++++++++++++------------------ winit/src/multi_window.rs | 44 ++++++++++++++++++++++---------------------- 2 files changed, 40 insertions(+), 40 deletions(-) (limited to 'winit/src') diff --git a/winit/src/application.rs b/winit/src/application.rs index 4e6a879f..cc1db8cb 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -712,11 +712,11 @@ pub fn run_command( clipboard.write(contents); } }, - command::Action::Window(_, action) => match action { - window::Action::Close => { + command::Action::Window(action) => match action { + window::Action::Close(_id) => { *should_exit = true; } - window::Action::Drag => { + window::Action::Drag(_id) => { let _res = window.drag_window(); } window::Action::Spawn { .. } => { @@ -725,13 +725,13 @@ pub fn run_command( multi-window applications." ); } - window::Action::Resize(size) => { + window::Action::Resize(_id, size) => { window.set_inner_size(winit::dpi::LogicalSize { width: size.width, height: size.height, }); } - window::Action::FetchSize(callback) => { + window::Action::FetchSize(_id, callback) => { let size = window.inner_size().to_logical(window.scale_factor()); @@ -742,29 +742,29 @@ pub fn run_command( ))) .expect("Send message to event loop"); } - window::Action::Maximize(maximized) => { + window::Action::Maximize(_id, maximized) => { window.set_maximized(maximized); } - window::Action::Minimize(minimized) => { + window::Action::Minimize(_id, minimized) => { window.set_minimized(minimized); } - window::Action::Move(position) => { + window::Action::Move(_id, position) => { window.set_outer_position(winit::dpi::LogicalPosition { x: position.x, y: position.y, }); } - window::Action::ChangeMode(mode) => { + window::Action::ChangeMode(_id, mode) => { window.set_visible(conversion::visible(mode)); window.set_fullscreen(conversion::fullscreen( window.current_monitor(), mode, )); } - window::Action::ChangeIcon(icon) => { + window::Action::ChangeIcon(_id, icon) => { window.set_window_icon(conversion::icon(icon)); } - window::Action::FetchMode(tag) => { + window::Action::FetchMode(_id, tag) => { let mode = if window.is_visible().unwrap_or(true) { conversion::mode(window.fullscreen()) } else { @@ -775,29 +775,29 @@ pub fn run_command( .send_event(tag(mode)) .expect("Send message to event loop"); } - window::Action::ToggleMaximize => { + window::Action::ToggleMaximize(_id) => { window.set_maximized(!window.is_maximized()); } - window::Action::ToggleDecorations => { + window::Action::ToggleDecorations(_id) => { window.set_decorations(!window.is_decorated()); } - window::Action::RequestUserAttention(user_attention) => { + window::Action::RequestUserAttention(_id, user_attention) => { window.request_user_attention( user_attention.map(conversion::user_attention), ); } - window::Action::GainFocus => { + window::Action::GainFocus(_id) => { window.focus_window(); } - window::Action::ChangeLevel(level) => { + window::Action::ChangeLevel(_id, level) => { window.set_window_level(conversion::window_level(level)); } - window::Action::FetchId(tag) => { + window::Action::FetchId(_id, tag) => { proxy .send_event(tag(window.id().into())) .expect("Send message to event loop"); } - window::Action::Screenshot(tag) => { + window::Action::Screenshot(_id, tag) => { let bytes = compositor.screenshot( renderer, surface, diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 73476452..aeb2c5e1 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -361,7 +361,7 @@ async fn run_instance( Some(window::Id::MAIN), core::Event::Window( window::Id::MAIN, - window::Event::Created { position, size }, + window::Event::Opened { position, size }, ), )] }; @@ -402,7 +402,7 @@ async fn run_instance( Some(id), core::Event::Window( id, - window::Event::Created { position, size }, + window::Event::Opened { position, size }, ), )); } @@ -761,7 +761,7 @@ async fn run_instance( None, core::Event::Window( id, - window::Event::Destroyed, + window::Event::Closed, ), )); } @@ -884,8 +884,8 @@ fn run_command( clipboard.write(contents); } }, - command::Action::Window(id, action) => match action { - window::Action::Spawn { settings } => { + command::Action::Window(action) => match action { + window::Action::Spawn(id, settings) => { let monitor = windows.last_monitor(); control_sender @@ -897,7 +897,7 @@ fn run_command( }) .expect("Send control action"); } - window::Action::Close => { + window::Action::Close(id) => { use winit::event_loop::ControlFlow; let i = windows.delete(id); @@ -911,10 +911,10 @@ fn run_command( .expect("Send control action"); } } - window::Action::Drag => { + window::Action::Drag(id) => { let _ = windows.with_raw(id).drag_window(); } - window::Action::Resize(size) => { + window::Action::Resize(id, size) => { windows.with_raw(id).set_inner_size( winit::dpi::LogicalSize { width: size.width, @@ -922,7 +922,7 @@ fn run_command( }, ); } - window::Action::FetchSize(callback) => { + window::Action::FetchSize(id, callback) => { let window = windows.with_raw(id); let size = window.inner_size().to_logical(window.scale_factor()); @@ -934,13 +934,13 @@ fn run_command( ))) .expect("Send message to event loop"); } - window::Action::Maximize(maximized) => { + window::Action::Maximize(id, maximized) => { windows.with_raw(id).set_maximized(maximized); } - window::Action::Minimize(minimized) => { + window::Action::Minimize(id, minimized) => { windows.with_raw(id).set_minimized(minimized); } - window::Action::Move(position) => { + window::Action::Move(id, position) => { windows.with_raw(id).set_outer_position( winit::dpi::LogicalPosition { x: position.x, @@ -948,7 +948,7 @@ fn run_command( }, ); } - window::Action::ChangeMode(mode) => { + window::Action::ChangeMode(id, mode) => { let window = windows.with_raw(id); window.set_visible(conversion::visible(mode)); window.set_fullscreen(conversion::fullscreen( @@ -956,12 +956,12 @@ fn run_command( mode, )); } - window::Action::ChangeIcon(icon) => { + window::Action::ChangeIcon(id, icon) => { windows .with_raw(id) .set_window_icon(conversion::icon(icon)); } - window::Action::FetchMode(tag) => { + window::Action::FetchMode(id, tag) => { let window = windows.with_raw(id); let mode = if window.is_visible().unwrap_or(true) { conversion::mode(window.fullscreen()) @@ -973,33 +973,33 @@ fn run_command( .send_event(tag(mode)) .expect("Event loop doesn't exist."); } - window::Action::ToggleMaximize => { + window::Action::ToggleMaximize(id) => { let window = windows.with_raw(id); window.set_maximized(!window.is_maximized()); } - window::Action::ToggleDecorations => { + window::Action::ToggleDecorations(id) => { let window = windows.with_raw(id); window.set_decorations(!window.is_decorated()); } - window::Action::RequestUserAttention(attention_type) => { + window::Action::RequestUserAttention(id, attention_type) => { windows.with_raw(id).request_user_attention( attention_type.map(conversion::user_attention), ); } - window::Action::GainFocus => { + window::Action::GainFocus(id) => { windows.with_raw(id).focus_window(); } - window::Action::ChangeLevel(level) => { + window::Action::ChangeLevel(id, level) => { windows .with_raw(id) .set_window_level(conversion::window_level(level)); } - window::Action::FetchId(tag) => { + window::Action::FetchId(id, tag) => { proxy .send_event(tag(windows.with_raw(id).id().into())) .expect("Event loop doesn't exist."); } - window::Action::Screenshot(tag) => { + window::Action::Screenshot(id, tag) => { let i = windows.index_from_id(id); let state = &windows.states[i]; let surface = &mut windows.surfaces[i]; -- cgit From b152ecda63238136f77b6eda3c582fa1eff99737 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 2 Dec 2023 20:49:47 +0100 Subject: Separate `Compositor::new` from `Compositor::create_renderer` --- winit/src/application.rs | 4 ++-- winit/src/multi_window.rs | 4 ++-- winit/src/multi_window/windows.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'winit/src') diff --git a/winit/src/application.rs b/winit/src/application.rs index cc1db8cb..d9700075 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -181,8 +181,8 @@ where }; } - let (compositor, mut renderer) = - C::new(compositor_settings, Some(&window))?; + let compositor = C::new(compositor_settings, Some(&window))?; + let mut renderer = compositor.create_renderer(); for font in settings.fonts { use crate::core::text::Renderer; diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index aeb2c5e1..31c27a6d 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -181,8 +181,8 @@ where }; } - let (mut compositor, renderer) = - C::new(compositor_settings, Some(&main_window))?; + let mut compositor = C::new(compositor_settings, Some(&main_window))?; + let renderer = compositor.create_renderer(); let windows = Windows::new( &application, diff --git a/winit/src/multi_window/windows.rs b/winit/src/multi_window/windows.rs index a4841a45..5a33b7b4 100644 --- a/winit/src/multi_window/windows.rs +++ b/winit/src/multi_window/windows.rs @@ -97,7 +97,7 @@ where physical_size.width, physical_size.height, ); - let renderer = compositor.renderer(); + let renderer = compositor.create_renderer(); self.ids.push(id); self.raw.push(window); -- cgit From 31cccd8f7b9b92d486cdcbf39ede112652c9dafa Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 2 Dec 2023 20:56:55 +0100 Subject: Remove unnecessary re-exports in `iced_winit` --- winit/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) (limited to 'winit/src') diff --git a/winit/src/lib.rs b/winit/src/lib.rs index cc886354..948576a2 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -54,6 +54,3 @@ pub use clipboard::Clipboard; pub use error::Error; pub use proxy::Proxy; pub use settings::Settings; - -pub use crate::core::window::*; -pub use iced_graphics::Viewport; -- cgit From 5c5e7653bed248ba63faa6563e4d673e4441415e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 2 Dec 2023 22:26:01 +0100 Subject: Refactor `Windows` abstraction into `WindowManager` --- winit/src/multi_window.rs | 620 ++++++++++++++++--------------- winit/src/multi_window/state.rs | 4 +- winit/src/multi_window/window_manager.rs | 156 ++++++++ winit/src/multi_window/windows.rs | 201 ---------- 4 files changed, 470 insertions(+), 511 deletions(-) create mode 100644 winit/src/multi_window/window_manager.rs delete mode 100644 winit/src/multi_window/windows.rs (limited to 'winit/src') diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 31c27a6d..84651d40 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -1,21 +1,20 @@ //! Create interactive, native cross-platform applications for WGPU. mod state; -mod windows; +mod window_manager; pub use state::State; use crate::conversion; use crate::core; -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::Size; use crate::futures::futures::channel::mpsc; use crate::futures::futures::{task, Future, StreamExt}; use crate::futures::{Executor, Runtime, Subscription}; use crate::graphics::{compositor, Compositor}; -use crate::multi_window::windows::Windows; +use crate::multi_window::window_manager::WindowManager; use crate::runtime::command::{self, Command}; use crate::runtime::multi_window::Program; use crate::runtime::user_interface::{self, UserInterface}; @@ -23,6 +22,7 @@ use crate::runtime::Debug; use crate::style::application::StyleSheet; use crate::{Clipboard, Error, Proxy, Settings}; +use std::collections::HashMap; use std::mem::ManuallyDrop; use std::time::Instant; @@ -182,13 +182,13 @@ where } let mut compositor = C::new(compositor_settings, Some(&main_window))?; - let renderer = compositor.create_renderer(); - let windows = Windows::new( + let mut window_manager = WindowManager::new(); + let _ = window_manager.insert( + window::Id::MAIN, + main_window, &application, &mut compositor, - renderer, - main_window, exit_on_close_request, ); @@ -204,7 +204,7 @@ where event_receiver, control_sender, init_command, - windows, + window_manager, should_main_be_visible, )); @@ -312,7 +312,7 @@ async fn run_instance( mut event_receiver: mpsc::UnboundedReceiver>, mut control_sender: mpsc::UnboundedSender, init_command: Command, - mut windows: Windows, + mut window_manager: WindowManager, should_main_window_be_visible: bool, ) where A: Application + 'static, @@ -323,20 +323,39 @@ async fn run_instance( use winit::event; use winit::event_loop::ControlFlow; - let mut clipboard = Clipboard::connect(windows.main()); + let main_window = window_manager + .get_mut(window::Id::MAIN) + .expect("Get main window"); + + if should_main_window_be_visible { + main_window.raw.set_visible(true); + } + + let mut clipboard = Clipboard::connect(&main_window.raw); + let mut events = { + vec![( + Some(window::Id::MAIN), + core::Event::Window( + window::Id::MAIN, + window::Event::Opened { + position: main_window.position(), + size: main_window.size(), + }, + ), + )] + }; - let mut ui_caches = vec![user_interface::Cache::default()]; + let mut ui_caches = HashMap::new(); let mut user_interfaces = ManuallyDrop::new(build_user_interfaces( &application, &mut debug, - &mut windows, - vec![user_interface::Cache::default()], + &mut window_manager, + HashMap::from_iter([( + window::Id::MAIN, + user_interface::Cache::default(), + )]), )); - if should_main_window_be_visible { - windows.main().set_visible(true); - } - run_command( &application, &mut compositor, @@ -346,26 +365,12 @@ async fn run_instance( &mut control_sender, &mut proxy, &mut debug, - &mut windows, + &mut window_manager, &mut ui_caches, ); runtime.track(application.subscription().into_recipes()); - let mut mouse_interaction = mouse::Interaction::default(); - - let mut events = { - let (position, size) = logical_bounds_of(windows.main()); - - vec![( - Some(window::Id::MAIN), - core::Event::Window( - window::Id::MAIN, - window::Event::Opened { position, size }, - ), - )] - }; - let mut messages = Vec::new(); let mut redraw_pending = false; @@ -378,31 +383,37 @@ async fn run_instance( window, exit_on_close_request, } => { - let (position, size) = logical_bounds_of(&window); - - let (inner_size, i) = windows.add( - &application, - &mut compositor, + let window = window_manager.insert( id, window, + &application, + &mut compositor, exit_on_close_request, ); - user_interfaces.push(build_user_interface( - &application, - user_interface::Cache::default(), - &mut windows.renderers[i], - inner_size, - &mut debug, + let logical_size = window.state.logical_size(); + + let _ = user_interfaces.insert( id, - )); - ui_caches.push(user_interface::Cache::default()); + build_user_interface( + &application, + user_interface::Cache::default(), + &mut window.renderer, + logical_size, + &mut debug, + id, + ), + ); + let _ = ui_caches.insert(id, user_interface::Cache::default()); events.push(( Some(id), core::Event::Window( id, - window::Event::Opened { position, size }, + window::Event::Opened { + position: window.position(), + size: window.size(), + }, ), )); } @@ -420,12 +431,11 @@ async fn run_instance( debug.event_processing_started(); let mut uis_stale = false; - for (i, id) in windows.ids.iter().enumerate() { + for (id, window) in window_manager.iter_mut() { let mut window_events = vec![]; events.retain(|(window_id, event)| { - if *window_id == Some(*id) - || window_id.is_none() + if *window_id == Some(id) || window_id.is_none() { window_events.push(event.clone()); false @@ -441,11 +451,13 @@ async fn run_instance( continue; } - let (ui_state, statuses) = user_interfaces[i] + let (ui_state, statuses) = user_interfaces + .get_mut(&id) + .expect("Get user interface") .update( &window_events, - windows.states[i].cursor(), - &mut windows.renderers[i], + window.state.cursor(), + &mut window.renderer, &mut clipboard, &mut messages, ); @@ -469,11 +481,12 @@ async fn run_instance( // TODO mw application update returns which window IDs to update if !messages.is_empty() || uis_stale { - let mut cached_interfaces: Vec< + let mut cached_interfaces: HashMap< + window::Id, user_interface::Cache, > = ManuallyDrop::into_inner(user_interfaces) - .drain(..) - .map(UserInterface::into_cache) + .drain() + .map(|(id, ui)| (id, ui.into_cache())) .collect(); // Update application @@ -486,18 +499,18 @@ async fn run_instance( &mut proxy, &mut debug, &mut messages, - &mut windows, + &mut window_manager, &mut cached_interfaces, ); // we must synchronize all window states with application state after an // application update since we don't know what changed - for (state, (id, window)) in windows - .states - .iter_mut() - .zip(windows.ids.iter().zip(windows.raw.iter())) - { - state.synchronize(&application, *id, window); + for (id, window) in window_manager.iter_mut() { + window.state.synchronize( + &application, + id, + &window.raw, + ); } // rebuild UIs with the synchronized states @@ -505,39 +518,43 @@ async fn run_instance( ManuallyDrop::new(build_user_interfaces( &application, &mut debug, - &mut windows, + &mut window_manager, cached_interfaces, )); } debug.draw_started(); - for (i, id) in windows.ids.iter().enumerate() { + for (id, window) in window_manager.iter_mut() { // 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 = core::Event::Window( - *id, + id, window::Event::RedrawRequested(Instant::now()), ); - let cursor = windows.states[i].cursor(); + let cursor = window.state.cursor(); + + let ui = user_interfaces + .get_mut(&id) + .expect("Get user interface"); - let (ui_state, _) = user_interfaces[i].update( + let (ui_state, _) = ui.update( &[redraw_event.clone()], cursor, - &mut windows.renderers[i], + &mut window.renderer, &mut clipboard, &mut messages, ); let new_mouse_interaction = { - let state = &windows.states[i]; + let state = &window.state; - user_interfaces[i].draw( - &mut windows.renderers[i], + ui.draw( + &mut window.renderer, state.theme(), &renderer::Style { text_color: state.text_color(), @@ -546,19 +563,21 @@ async fn run_instance( ) }; - if new_mouse_interaction != mouse_interaction { - windows.raw[i].set_cursor_icon( + if new_mouse_interaction != window.mouse_interaction + { + window.raw.set_cursor_icon( conversion::mouse_interaction( new_mouse_interaction, ), ); - mouse_interaction = new_mouse_interaction; + window.mouse_interaction = + new_mouse_interaction; } // TODO once widgets can request to be redrawn, we can avoid always requesting a // redraw - windows.raw[i].request_redraw(); + window.raw.request_redraw(); runtime.broadcast( redraw_event.clone(), @@ -606,9 +625,13 @@ async fn run_instance( messages.push(message); } event::Event::RedrawRequested(id) => { - let i = windows.index_from_raw(id); - let state = &windows.states[i]; - let physical_size = state.physical_size(); + 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 { @@ -616,60 +639,65 @@ async fn run_instance( } debug.render_started(); - let current_viewport_version = state.viewport_version(); - let window_viewport_version = - windows.viewport_versions[i]; - - if window_viewport_version != current_viewport_version { - let logical_size = state.logical_size(); + if window.viewport_version + != window.state.viewport_version() + { + let logical_size = window.state.logical_size(); debug.layout_started(); - let renderer = &mut windows.renderers[i]; - let ui = user_interfaces.remove(i); + let ui = user_interfaces + .remove(&id) + .expect("Remove user interface"); - user_interfaces - .insert(i, ui.relayout(logical_size, renderer)); + let _ = user_interfaces.insert( + id, + ui.relayout(logical_size, &mut window.renderer), + ); debug.layout_finished(); debug.draw_started(); - let new_mouse_interaction = user_interfaces[i] + let new_mouse_interaction = user_interfaces + .get_mut(&id) + .expect("Get user interface") .draw( - renderer, - state.theme(), + &mut window.renderer, + window.state.theme(), &renderer::Style { - text_color: state.text_color(), + text_color: window.state.text_color(), }, - state.cursor(), + window.state.cursor(), ); - if new_mouse_interaction != mouse_interaction { - windows.raw[i].set_cursor_icon( + if new_mouse_interaction != window.mouse_interaction + { + window.raw.set_cursor_icon( conversion::mouse_interaction( new_mouse_interaction, ), ); - mouse_interaction = new_mouse_interaction; + window.mouse_interaction = + new_mouse_interaction; } debug.draw_finished(); compositor.configure_surface( - &mut windows.surfaces[i], + &mut window.surface, physical_size.width, physical_size.height, ); - windows.viewport_versions[i] = - current_viewport_version; + window.viewport_version = + window.state.viewport_version(); } match compositor.present( - &mut windows.renderers[i], - &mut windows.surfaces[i], - state.viewport(), - state.background_color(), + &mut window.renderer, + &mut window.surface, + window.state.viewport(), + window.state.background_color(), &debug.overlay(), ) { Ok(()) => { @@ -690,8 +718,10 @@ async fn run_instance( ); // Try rendering all windows again next frame. - for window in &windows.raw { - window.request_redraw(); + for (_id, window) in + window_manager.iter_mut() + { + window.raw.request_redraw(); } } }, @@ -701,70 +731,43 @@ async fn run_instance( event: window_event, window_id, } => { - let window_index = windows - .raw - .iter() - .position(|w| w.id() == window_id); + let Some((id, window)) = + window_manager.get_mut_alias(window_id) + else { + continue; + }; - match window_index { - Some(i) => { - let id = windows.ids[i]; - let raw = &windows.raw[i]; - let exit_on_close_request = - windows.exit_on_close_requested[i]; + if matches!( + window_event, + winit::event::WindowEvent::CloseRequested + ) && window.exit_on_close_request + { + let _ = window_manager.remove(id); + let _ = user_interfaces.remove(&id); + let _ = ui_caches.remove(&id); - if matches!( - window_event, - winit::event::WindowEvent::CloseRequested - ) && exit_on_close_request - { - let i = windows.delete(id); - let _ = user_interfaces.remove(i); - let _ = ui_caches.remove(i); + events.push(( + None, + core::Event::Window(id, window::Event::Closed), + )); - if windows.is_empty() { - break 'main; - } - } else { - let state = &mut windows.states[i]; - state.update( - raw, - &window_event, - &mut debug, - ); - - if let Some(event) = - conversion::window_event( - id, - &window_event, - state.scale_factor(), - state.modifiers(), - ) - { - events.push((Some(id), event)); - } - } + if window_manager.is_empty() { + break 'main; } - None => { - // This is the only special case, since in order to trigger the Destroyed event the - // window reference from winit must be dropped, but we still want to inform the - // user that the window was destroyed so they can clean up any specific window - // code for this window - if matches!( - window_event, - winit::event::WindowEvent::Destroyed - ) { - let id = - windows.get_pending_destroy(window_id); - - events.push(( - None, - core::Event::Window( - id, - window::Event::Closed, - ), - )); - } + } else { + window.state.update( + &window.raw, + &window_event, + &mut debug, + ); + + if let Some(event) = conversion::window_event( + id, + &window_event, + window.state.scale_factor(), + window.state.modifiers(), + ) { + events.push((Some(id), event)); } } } @@ -811,8 +814,8 @@ fn update( proxy: &mut winit::event_loop::EventLoopProxy, debug: &mut Debug, messages: &mut Vec, - windows: &mut Windows, - ui_caches: &mut Vec, + window_manager: &mut WindowManager, + ui_caches: &mut HashMap, ) where C: Compositor + 'static, ::Theme: StyleSheet, @@ -833,7 +836,7 @@ fn update( control_sender, proxy, debug, - windows, + window_manager, ui_caches, ); } @@ -852,8 +855,8 @@ fn run_command( control_sender: &mut mpsc::UnboundedSender, proxy: &mut winit::event_loop::EventLoopProxy, debug: &mut Debug, - windows: &mut Windows, - ui_caches: &mut Vec, + window_manager: &mut WindowManager, + ui_caches: &mut HashMap, ) where A: Application, E: Executor, @@ -886,7 +889,7 @@ fn run_command( }, command::Action::Window(action) => match action { window::Action::Spawn(id, settings) => { - let monitor = windows.last_monitor(); + let monitor = window_manager.last_monitor(); control_sender .start_send(Control::CreateWindow { @@ -900,10 +903,10 @@ fn run_command( window::Action::Close(id) => { use winit::event_loop::ControlFlow; - let i = windows.delete(id); - let _ = ui_caches.remove(i); + let _ = window_manager.remove(id); + let _ = ui_caches.remove(&id); - if windows.is_empty() { + if window_manager.is_empty() { control_sender .start_send(Control::ChangeFlow( ControlFlow::ExitWithCode(0), @@ -912,113 +915,133 @@ fn run_command( } } window::Action::Drag(id) => { - let _ = windows.with_raw(id).drag_window(); + if let Some(window) = window_manager.get_mut(id) { + let _ = window.raw.drag_window(); + } } window::Action::Resize(id, size) => { - windows.with_raw(id).set_inner_size( - winit::dpi::LogicalSize { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_inner_size(winit::dpi::LogicalSize { width: size.width, height: size.height, - }, - ); + }); + } } window::Action::FetchSize(id, callback) => { - let window = windows.with_raw(id); - let size = - window.inner_size().to_logical(window.scale_factor()); - - proxy - .send_event(callback(Size::new( - size.width, - size.height, - ))) - .expect("Send message to event loop"); + if let Some(window) = window_manager.get_mut(id) { + let size = window + .raw + .inner_size() + .to_logical(window.raw.scale_factor()); + + proxy + .send_event(callback(Size::new( + size.width, + size.height, + ))) + .expect("Send message to event loop"); + } } window::Action::Maximize(id, maximized) => { - windows.with_raw(id).set_maximized(maximized); + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_maximized(maximized); + } } window::Action::Minimize(id, minimized) => { - windows.with_raw(id).set_minimized(minimized); + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_minimized(minimized); + } } window::Action::Move(id, position) => { - windows.with_raw(id).set_outer_position( - winit::dpi::LogicalPosition { - x: position.x, - y: position.y, - }, - ); + 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::ChangeMode(id, mode) => { - let window = windows.with_raw(id); - window.set_visible(conversion::visible(mode)); - window.set_fullscreen(conversion::fullscreen( - window.current_monitor(), - 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::ChangeIcon(id, icon) => { - windows - .with_raw(id) - .set_window_icon(conversion::icon(icon)); + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_window_icon(conversion::icon(icon)); + } } window::Action::FetchMode(id, tag) => { - let window = windows.with_raw(id); - let mode = if window.is_visible().unwrap_or(true) { - conversion::mode(window.fullscreen()) - } else { - core::window::Mode::Hidden - }; - - proxy - .send_event(tag(mode)) - .expect("Event loop doesn't exist."); + 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 + }; + + proxy + .send_event(tag(mode)) + .expect("Event loop doesn't exist."); + } } window::Action::ToggleMaximize(id) => { - let window = windows.with_raw(id); - window.set_maximized(!window.is_maximized()); + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_maximized(!window.raw.is_maximized()); + } } window::Action::ToggleDecorations(id) => { - let window = windows.with_raw(id); - window.set_decorations(!window.is_decorated()); + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_decorations(!window.raw.is_decorated()); + } } window::Action::RequestUserAttention(id, attention_type) => { - windows.with_raw(id).request_user_attention( - attention_type.map(conversion::user_attention), - ); + if let Some(window) = window_manager.get_mut(id) { + window.raw.request_user_attention( + attention_type.map(conversion::user_attention), + ); + } } window::Action::GainFocus(id) => { - windows.with_raw(id).focus_window(); + if let Some(window) = window_manager.get_mut(id) { + window.raw.focus_window(); + } } window::Action::ChangeLevel(id, level) => { - windows - .with_raw(id) - .set_window_level(conversion::window_level(level)); + if let Some(window) = window_manager.get_mut(id) { + window + .raw + .set_window_level(conversion::window_level(level)); + } } window::Action::FetchId(id, tag) => { - proxy - .send_event(tag(windows.with_raw(id).id().into())) - .expect("Event loop doesn't exist."); + if let Some(window) = window_manager.get_mut(id) { + proxy + .send_event(tag(window.raw.id().into())) + .expect("Event loop doesn't exist."); + } } window::Action::Screenshot(id, tag) => { - let i = windows.index_from_id(id); - let state = &windows.states[i]; - let surface = &mut windows.surfaces[i]; - let renderer = &mut windows.renderers[i]; - - let bytes = compositor.screenshot( - renderer, - surface, - state.viewport(), - state.background_color(), - &debug.overlay(), - ); + if let Some(window) = window_manager.get_mut(id) { + let bytes = compositor.screenshot( + &mut window.renderer, + &mut window.surface, + window.state.viewport(), + window.state.background_color(), + &debug.overlay(), + ); - proxy - .send_event(tag(window::Screenshot::new( - bytes, - state.physical_size(), - ))) - .expect("Event loop doesn't exist."); + proxy + .send_event(tag(window::Screenshot::new( + bytes, + window.state.physical_size(), + ))) + .expect("Event loop doesn't exist."); + } } }, command::Action::System(action) => match action { @@ -1047,43 +1070,45 @@ fn run_command( let mut uis = build_user_interfaces( application, debug, - windows, + window_manager, std::mem::take(ui_caches), ); 'operate: while let Some(mut operation) = current_operation.take() { - for (i, ui) in uis.iter_mut().enumerate() { - ui.operate(&windows.renderers[i], operation.as_mut()); - - match operation.finish() { - operation::Outcome::None => {} - operation::Outcome::Some(message) => { - proxy - .send_event(message) - .expect("Event loop doesn't exist."); - - // operation completed, don't need to try to operate on rest of UIs - break 'operate; - } - operation::Outcome::Chain(next) => { - current_operation = Some(next); + for (id, ui) in uis.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(message) => { + proxy + .send_event(message) + .expect("Event loop doesn't exist."); + + // operation completed, don't need to try to operate on rest of UIs + break 'operate; + } + operation::Outcome::Chain(next) => { + current_operation = Some(next); + } } } } } *ui_caches = - uis.drain(..).map(UserInterface::into_cache).collect(); + uis.drain().map(|(id, ui)| (id, ui.into_cache())).collect(); } command::Action::LoadFont { bytes, tagger } => { use crate::core::text::Renderer; // TODO change this once we change each renderer to having a single backend reference.. :pain: // TODO: Error handling (?) - for renderer in &mut windows.renderers { - renderer.load_font(bytes.clone()); + for (_, window) in window_manager.iter_mut() { + window.renderer.load_font(bytes.clone()); } proxy @@ -1098,33 +1123,31 @@ fn run_command( pub fn build_user_interfaces<'a, A: Application, C: Compositor>( application: &'a A, debug: &mut Debug, - windows: &mut Windows, - mut cached_user_interfaces: Vec, -) -> Vec> + window_manager: &mut WindowManager, + mut cached_user_interfaces: HashMap, +) -> HashMap> where ::Theme: StyleSheet, C: Compositor, { cached_user_interfaces - .drain(..) - .zip( - windows - .ids - .iter() - .zip(windows.states.iter().zip(windows.renderers.iter_mut())), - ) - .fold(vec![], |mut uis, (cache, (id, (state, renderer)))| { - uis.push(build_user_interface( - application, - cache, - renderer, - state.logical_size(), - debug, - *id, - )); - - uis + .drain() + .filter_map(|(id, cache)| { + let window = window_manager.get_mut(id)?; + + Some(( + id, + build_user_interface( + application, + cache, + &mut window.renderer, + window.state.logical_size(), + debug, + id, + ), + )) }) + .collect() } /// Returns true if the provided event should cause an [`Application`] to @@ -1148,25 +1171,6 @@ pub fn user_force_quit( } } -fn logical_bounds_of(window: &winit::window::Window) -> (Option, Size) { - let position = window - .inner_position() - .ok() - .map(|position| position.to_logical(window.scale_factor())) - .map(|position| Point { - x: position.x, - y: position.y, - }); - - let size = { - let size = window.inner_size().to_logical(window.scale_factor()); - - Size::new(size.width, size.height) - }; - - (position, size) -} - #[cfg(not(target_arch = "wasm32"))] mod platform { pub fn run( diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs index e9a9f91a..03da5ad7 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/multi_window/state.rs @@ -19,7 +19,7 @@ where title: String, scale_factor: f64, viewport: Viewport, - viewport_version: usize, + viewport_version: u64, cursor_position: Option>, modifiers: winit::event::ModifiersState, theme: ::Theme, @@ -86,7 +86,7 @@ where /// Returns the version of the [`Viewport`] of the [`State`]. /// /// The version is incremented every time the [`Viewport`] changes. - pub fn viewport_version(&self) -> usize { + pub fn viewport_version(&self) -> u64 { self.viewport_version } diff --git a/winit/src/multi_window/window_manager.rs b/winit/src/multi_window/window_manager.rs new file mode 100644 index 00000000..d54156e7 --- /dev/null +++ b/winit/src/multi_window/window_manager.rs @@ -0,0 +1,156 @@ +use crate::core::mouse; +use crate::core::window::Id; +use crate::core::{Point, Size}; +use crate::graphics::Compositor; +use crate::multi_window::{Application, State}; +use crate::style::application::StyleSheet; + +use std::collections::BTreeMap; +use winit::monitor::MonitorHandle; + +#[allow(missing_debug_implementations)] +pub struct WindowManager +where + ::Theme: StyleSheet, + C: Compositor, +{ + aliases: BTreeMap, + entries: BTreeMap>, +} + +impl WindowManager +where + A: Application, + C: Compositor, + ::Theme: StyleSheet, +{ + pub fn new() -> Self { + Self { + aliases: BTreeMap::new(), + entries: BTreeMap::new(), + } + } + + pub fn insert( + &mut self, + id: Id, + window: winit::window::Window, + application: &A, + compositor: &mut C, + exit_on_close_request: bool, + ) -> &mut Window { + let state = State::new(application, id, &window); + let viewport_version = state.viewport_version(); + let physical_size = state.physical_size(); + let surface = compositor.create_surface( + &window, + physical_size.width, + physical_size.height, + ); + let renderer = compositor.create_renderer(); + + let _ = self.aliases.insert(window.id(), id); + + let _ = self.entries.insert( + id, + Window { + raw: window, + state, + viewport_version, + exit_on_close_request, + surface, + renderer, + mouse_interaction: mouse::Interaction::Idle, + }, + ); + + self.entries + .get_mut(&id) + .expect("Get window that was just inserted") + } + + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + pub fn iter_mut( + &mut self, + ) -> impl Iterator)> { + self.entries.iter_mut().map(|(k, v)| (*k, v)) + } + + pub fn get_mut(&mut self, id: Id) -> Option<&mut Window> { + self.entries.get_mut(&id) + } + + pub fn get_mut_alias( + &mut self, + id: winit::window::WindowId, + ) -> Option<(Id, &mut Window)> { + let id = self.aliases.get(&id).copied()?; + + Some((id, self.get_mut(id)?)) + } + + pub fn last_monitor(&self) -> Option { + self.entries.values().last()?.raw.current_monitor() + } + + pub fn remove(&mut self, id: Id) -> Option> { + let window = self.entries.remove(&id)?; + let _ = self.aliases.remove(&window.raw.id()); + + Some(window) + } +} + +impl Default for WindowManager +where + A: Application, + C: Compositor, + ::Theme: StyleSheet, +{ + fn default() -> Self { + Self::new() + } +} + +#[allow(missing_debug_implementations)] +pub struct Window +where + A: Application, + C: Compositor, + ::Theme: StyleSheet, +{ + pub raw: winit::window::Window, + pub state: State, + pub viewport_version: u64, + pub exit_on_close_request: bool, + pub mouse_interaction: mouse::Interaction, + pub surface: C::Surface, + pub renderer: A::Renderer, +} + +impl Window +where + A: Application, + C: Compositor, + ::Theme: StyleSheet, +{ + pub fn position(&self) -> Option { + self.raw + .inner_position() + .ok() + .map(|position| position.to_logical(self.raw.scale_factor())) + .map(|position| Point { + x: position.x, + y: position.y, + }) + } + + pub fn size(&self) -> Size { + let size = self.raw.inner_size().to_logical(self.raw.scale_factor()); + + Size::new(size.width, size.height) + } +} diff --git a/winit/src/multi_window/windows.rs b/winit/src/multi_window/windows.rs deleted file mode 100644 index 5a33b7b4..00000000 --- a/winit/src/multi_window/windows.rs +++ /dev/null @@ -1,201 +0,0 @@ -use crate::core::{window, Size}; -use crate::graphics::Compositor; -use crate::multi_window::{Application, State}; -use crate::style::application::StyleSheet; - -use winit::monitor::MonitorHandle; - -use std::fmt::{Debug, Formatter}; - -pub struct Windows -where - ::Theme: StyleSheet, - C: Compositor, -{ - pub ids: Vec, - pub raw: Vec, - pub states: Vec>, - pub viewport_versions: Vec, - pub exit_on_close_requested: Vec, - pub surfaces: Vec, - pub renderers: Vec, - pub pending_destroy: Vec<(window::Id, winit::window::WindowId)>, -} - -impl Debug for Windows -where - ::Theme: StyleSheet, - C: Compositor, -{ - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Windows") - .field("ids", &self.ids) - .field( - "raw", - &self - .raw - .iter() - .map(winit::window::Window::id) - .collect::>(), - ) - .field("states", &self.states) - .field("viewport_versions", &self.viewport_versions) - .finish() - } -} - -impl Windows -where - ::Theme: StyleSheet, - C: Compositor, -{ - /// Creates a new [`Windows`] with a single `window::Id::MAIN` window. - pub fn new( - application: &A, - compositor: &mut C, - renderer: A::Renderer, - main: winit::window::Window, - exit_on_close_requested: bool, - ) -> Self { - let state = State::new(application, window::Id::MAIN, &main); - let viewport_version = state.viewport_version(); - let physical_size = state.physical_size(); - let surface = compositor.create_surface( - &main, - physical_size.width, - physical_size.height, - ); - - Self { - ids: vec![window::Id::MAIN], - raw: vec![main], - states: vec![state], - viewport_versions: vec![viewport_version], - exit_on_close_requested: vec![exit_on_close_requested], - surfaces: vec![surface], - renderers: vec![renderer], - pending_destroy: vec![], - } - } - - /// Adds a new window to [`Windows`]. Returns the size of the newly created window in logical - /// pixels & the index of the window within [`Windows`]. - pub fn add( - &mut self, - application: &A, - compositor: &mut C, - id: window::Id, - window: winit::window::Window, - exit_on_close_requested: bool, - ) -> (Size, usize) { - let state = State::new(application, id, &window); - let window_size = state.logical_size(); - let viewport_version = state.viewport_version(); - let physical_size = state.physical_size(); - let surface = compositor.create_surface( - &window, - physical_size.width, - physical_size.height, - ); - let renderer = compositor.create_renderer(); - - self.ids.push(id); - self.raw.push(window); - self.states.push(state); - self.exit_on_close_requested.push(exit_on_close_requested); - self.viewport_versions.push(viewport_version); - self.surfaces.push(surface); - self.renderers.push(renderer); - - (window_size, self.ids.len() - 1) - } - - pub fn is_empty(&self) -> bool { - self.ids.is_empty() - } - - pub fn main(&self) -> &winit::window::Window { - &self.raw[0] - } - - pub fn index_from_raw(&self, id: winit::window::WindowId) -> usize { - self.raw - .iter() - .position(|window| window.id() == id) - .expect("No raw window in multi_window::Windows") - } - - pub fn index_from_id(&self, id: window::Id) -> usize { - self.ids - .iter() - .position(|window_id| *window_id == id) - .expect("No window in multi_window::Windows") - } - - pub fn last_monitor(&self) -> Option { - self.raw - .last() - .and_then(winit::window::Window::current_monitor) - } - - pub fn last(&self) -> usize { - self.ids.len() - 1 - } - - pub fn with_raw(&self, id: window::Id) -> &winit::window::Window { - let i = self.index_from_id(id); - &self.raw[i] - } - - /// Deletes the window with `id` from [`Windows`]. Returns the index that the window had. - pub fn delete(&mut self, id: window::Id) -> usize { - let i = self.index_from_id(id); - - let id = self.ids.remove(i); - let window = self.raw.remove(i); - let _ = self.states.remove(i); - let _ = self.exit_on_close_requested.remove(i); - let _ = self.viewport_versions.remove(i); - let _ = self.surfaces.remove(i); - - self.pending_destroy.push((id, window.id())); - - i - } - - /// Gets the winit `window` that is pending to be destroyed if it exists. - pub fn get_pending_destroy( - &mut self, - window: winit::window::WindowId, - ) -> window::Id { - let i = self - .pending_destroy - .iter() - .position(|(_, window_id)| window == *window_id) - .unwrap(); - - let (id, _) = self.pending_destroy.remove(i); - id - } - - /// Returns the windows that need to be requested to closed, and also the windows that can be - /// closed immediately. - pub fn partition_close_requests( - &self, - ) -> (Vec, Vec) { - self.exit_on_close_requested.iter().enumerate().fold( - (vec![], vec![]), - |(mut close_immediately, mut needs_request_closed), (i, close)| { - let id = self.ids[i]; - - if *close { - close_immediately.push(id); - } else { - needs_request_closed.push(id); - } - - (close_immediately, needs_request_closed) - }, - ) - } -} -- cgit