diff options
Diffstat (limited to 'winit/src/program.rs')
-rw-r--r-- | winit/src/program.rs | 579 |
1 files changed, 303 insertions, 276 deletions
diff --git a/winit/src/program.rs b/winit/src/program.rs index 8d1eec3a..5387e5e5 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -8,10 +8,11 @@ use crate::conversion; use crate::core; use crate::core::mouse; use crate::core::renderer; +use crate::core::theme; use crate::core::time::Instant; use crate::core::widget::operation; use crate::core::window; -use crate::core::{Color, Element, Point, Size, Theme}; +use crate::core::{Element, Point, Size}; use crate::futures::futures::channel::mpsc; use crate::futures::futures::channel::oneshot; use crate::futures::futures::task; @@ -46,7 +47,7 @@ use std::sync::Arc; pub trait Program where Self: Sized, - Self::Theme: DefaultStyle, + Self::Theme: theme::Base, { /// The type of __messages__ your [`Program`] will produce. type Message: std::fmt::Debug + Send; @@ -106,8 +107,8 @@ where fn theme(&self, window: window::Id) -> Self::Theme; /// Returns the `Style` variation of the `Theme`. - fn style(&self, theme: &Self::Theme) -> Appearance { - theme.default_style() + fn style(&self, theme: &Self::Theme) -> theme::Style { + theme::Base::base(theme) } /// Returns the event `Subscription` for the current state of the @@ -138,37 +139,6 @@ where } } -/// The appearance of a program. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Appearance { - /// The background [`Color`] of the application. - pub background_color: Color, - - /// The default text [`Color`] of the application. - pub text_color: Color, -} - -/// The default style of a [`Program`]. -pub trait DefaultStyle { - /// Returns the default style of a [`Program`]. - fn default_style(&self) -> Appearance; -} - -impl DefaultStyle for Theme { - fn default_style(&self) -> Appearance { - default(self) - } -} - -/// The default [`Appearance`] of a [`Program`] with the built-in [`Theme`]. -pub fn default(theme: &Theme) -> Appearance { - let palette = theme.extended_palette(); - - Appearance { - background_color: palette.background.base.color, - text_color: palette.background.base.text, - } -} /// Runs a [`Program`] with an executor, compositor, and the provided /// settings. pub fn run<P, C>( @@ -180,7 +150,7 @@ pub fn run<P, C>( where P: Program + 'static, C: Compositor<Renderer = P::Renderer> + 'static, - P::Theme: DefaultStyle, + P::Theme: theme::Base, { use winit::event_loop::EventLoop; @@ -222,7 +192,6 @@ where runtime.enter(|| program.subscription().map(Action::Output)), )); - let (boot_sender, boot_receiver) = oneshot::channel(); let (event_sender, event_receiver) = mpsc::unbounded(); let (control_sender, control_receiver) = mpsc::unbounded(); @@ -231,133 +200,49 @@ where runtime, proxy.clone(), debug, - boot_receiver, event_receiver, control_sender, is_daemon, + graphics_settings, + settings.fonts, )); let context = task::Context::from_waker(task::noop_waker_ref()); - struct Runner<Message: 'static, F, C> { + struct Runner<Message: 'static, F> { instance: std::pin::Pin<Box<F>>, context: task::Context<'static>, id: Option<String>, - boot: Option<BootConfig<C>>, sender: mpsc::UnboundedSender<Event<Action<Message>>>, receiver: mpsc::UnboundedReceiver<Control>, error: Option<Error>, #[cfg(target_arch = "wasm32")] - is_booted: std::rc::Rc<std::cell::RefCell<bool>>, - #[cfg(target_arch = "wasm32")] canvas: Option<web_sys::HtmlCanvasElement>, } - struct BootConfig<C> { - sender: oneshot::Sender<Boot<C>>, - fonts: Vec<Cow<'static, [u8]>>, - graphics_settings: graphics::Settings, - } - let runner = Runner { instance, context, id: settings.id, - boot: Some(BootConfig { - sender: boot_sender, - fonts: settings.fonts, - graphics_settings, - }), sender: event_sender, receiver: control_receiver, error: None, #[cfg(target_arch = "wasm32")] - is_booted: std::rc::Rc::new(std::cell::RefCell::new(false)), - #[cfg(target_arch = "wasm32")] canvas: None, }; - impl<Message, F, C> winit::application::ApplicationHandler<Action<Message>> - for Runner<Message, F, C> + impl<Message, F> winit::application::ApplicationHandler<Action<Message>> + for Runner<Message, F> where Message: std::fmt::Debug, F: Future<Output = ()>, - C: Compositor + 'static, { - fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { - let Some(BootConfig { - sender, - fonts, - graphics_settings, - }) = self.boot.take() - else { - return; - }; - - let window = { - let attributes = winit::window::WindowAttributes::default(); - - #[cfg(target_os = "windows")] - let attributes = { - use winit::platform::windows::WindowAttributesExtWindows; - attributes.with_drag_and_drop(false) - }; - - match event_loop.create_window(attributes.with_visible(false)) { - Ok(window) => Arc::new(window), - Err(error) => { - self.error = Some(Error::WindowCreationFailed(error)); - event_loop.exit(); - return; - } - } - }; - - #[cfg(target_arch = "wasm32")] - { - use winit::platform::web::WindowExtWebSys; - self.canvas = window.canvas(); - } - - let finish_boot = async move { - let mut compositor = - C::new(graphics_settings, window.clone()).await?; - - for font in fonts { - compositor.load_font(font); - } - - sender - .send(Boot { compositor }) - .ok() - .expect("Send boot event"); - - Ok::<_, graphics::Error>(()) - }; - - #[cfg(not(target_arch = "wasm32"))] - if let Err(error) = - crate::futures::futures::executor::block_on(finish_boot) - { - self.error = Some(Error::GraphicsCreationFailed(error)); - event_loop.exit(); - } - - #[cfg(target_arch = "wasm32")] - { - let is_booted = self.is_booted.clone(); - - wasm_bindgen_futures::spawn_local(async move { - finish_boot.await.expect("Finish boot!"); - - *is_booted.borrow_mut() = true; - }); - - event_loop - .set_control_flow(winit::event_loop::ControlFlow::Poll); - } + fn resumed( + &mut self, + _event_loop: &winit::event_loop::ActiveEventLoop, + ) { } fn new_events( @@ -365,15 +250,6 @@ where event_loop: &winit::event_loop::ActiveEventLoop, cause: winit::event::StartCause, ) { - if self.boot.is_some() { - return; - } - - #[cfg(target_arch = "wasm32")] - if !*self.is_booted.borrow() { - return; - } - self.process_event( event_loop, Event::EventLoopAwakened(winit::event::Event::NewEvents(cause)), @@ -452,11 +328,6 @@ where &mut self, event_loop: &winit::event_loop::ActiveEventLoop, ) { - #[cfg(target_arch = "wasm32")] - if !*self.is_booted.borrow() { - return; - } - self.process_event( event_loop, Event::EventLoopAwakened(winit::event::Event::AboutToWait), @@ -464,10 +335,9 @@ where } } - impl<Message, F, C> Runner<Message, F, C> + impl<Message, F> Runner<Message, F> where F: Future<Output = ()>, - C: Compositor, { fn process_event( &mut self, @@ -538,10 +408,25 @@ where log::info!("Window attributes for id `{id:#?}`: {window_attributes:#?}"); + // On macOS, the `position` in `WindowAttributes` represents the "inner" + // position of the window; while on other platforms it's the "outer" position. + // We fix the inconsistency on macOS by positioning the window after creation. + #[cfg(target_os = "macos")] + let mut window_attributes = window_attributes; + + #[cfg(target_os = "macos")] + let position = + window_attributes.position.take(); + let window = event_loop .create_window(window_attributes) .expect("Create window"); + #[cfg(target_os = "macos")] + if let Some(position) = position { + window.set_outer_position(position); + } + #[cfg(target_arch = "wasm32")] { use winit::platform::web::WindowExtWebSys; @@ -592,7 +477,7 @@ where event_loop, Event::WindowCreated { id, - window, + window: Arc::new(window), exit_on_close_request, make_visible: visible, on_open, @@ -602,6 +487,10 @@ where Control::Exit => { event_loop.exit(); } + Control::Crash(error) => { + self.error = Some(error); + event_loop.exit(); + } }, _ => { break; @@ -633,15 +522,11 @@ where } } -struct Boot<C> { - compositor: C, -} - #[derive(Debug)] enum Event<Message: 'static> { WindowCreated { id: window::Id, - window: winit::window::Window, + window: Arc<winit::window::Window>, exit_on_close_request: bool, make_visible: bool, on_open: oneshot::Sender<window::Id>, @@ -653,6 +538,7 @@ enum Event<Message: 'static> { enum Control { ChangeFlow(winit::event_loop::ControlFlow), Exit, + Crash(Error), CreateWindow { id: window::Id, settings: window::Settings, @@ -667,23 +553,23 @@ async fn run_instance<P, C>( mut runtime: Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>, mut proxy: Proxy<P::Message>, mut debug: Debug, - boot: oneshot::Receiver<Boot<C>>, mut event_receiver: mpsc::UnboundedReceiver<Event<Action<P::Message>>>, mut control_sender: mpsc::UnboundedSender<Control>, is_daemon: bool, + graphics_settings: graphics::Settings, + default_fonts: Vec<Cow<'static, [u8]>>, ) where P: Program + 'static, C: Compositor<Renderer = P::Renderer> + 'static, - P::Theme: DefaultStyle, + P::Theme: theme::Base, { use winit::event; use winit::event_loop::ControlFlow; - let Boot { mut compositor } = boot.await.expect("Receive boot"); - let mut window_manager = WindowManager::new(); let mut is_window_opening = !is_daemon; + let mut compositor = None; let mut events = Vec::new(); let mut messages = Vec::new(); let mut actions = 0; @@ -691,12 +577,35 @@ async fn run_instance<P, C>( let mut ui_caches = FxHashMap::default(); let mut user_interfaces = ManuallyDrop::new(FxHashMap::default()); let mut clipboard = Clipboard::unconnected(); + let mut compositor_receiver: Option<oneshot::Receiver<_>> = None; debug.startup_finished(); loop { + let event = if compositor_receiver.is_some() { + let compositor_receiver = + compositor_receiver.take().expect("Waiting for compositor"); + + match compositor_receiver.await { + Ok(Ok((new_compositor, event))) => { + compositor = Some(new_compositor); + + Some(event) + } + Ok(Err(error)) => { + control_sender + .start_send(Control::Crash( + Error::GraphicsCreationFailed(error), + )) + .expect("Send control action"); + break; + } + Err(error) => { + panic!("Compositor initialization failed: {error}") + } + } // Empty the queue if possible - let event = if let Ok(event) = event_receiver.try_next() { + } else if let Ok(event) = event_receiver.try_next() { event } else { event_receiver.next().await @@ -714,11 +623,63 @@ async fn run_instance<P, C>( make_visible, on_open, } => { + if compositor.is_none() { + let (compositor_sender, new_compositor_receiver) = + oneshot::channel(); + + compositor_receiver = Some(new_compositor_receiver); + + let create_compositor = { + let default_fonts = default_fonts.clone(); + + async move { + let mut compositor = + C::new(graphics_settings, window.clone()).await; + + if let Ok(compositor) = &mut compositor { + for font in default_fonts { + compositor.load_font(font.clone()); + } + } + + compositor_sender + .send(compositor.map(|compositor| { + ( + compositor, + Event::WindowCreated { + id, + window, + exit_on_close_request, + make_visible, + on_open, + }, + ) + })) + .ok() + .expect("Send compositor"); + } + }; + + #[cfg(not(target_arch = "wasm32"))] + crate::futures::futures::executor::block_on( + create_compositor, + ); + + #[cfg(target_arch = "wasm32")] + { + wasm_bindgen_futures::spawn_local(create_compositor); + } + + continue; + } + let window = window_manager.insert( id, - Arc::new(window), + window, &program, - &mut compositor, + compositor + .as_mut() + .expect("Compositor must be initialized"), exit_on_close_request, ); @@ -758,12 +719,23 @@ async fn run_instance<P, C>( } Event::EventLoopAwakened(event) => { match event { + event::Event::NewEvents(event::StartCause::Init) => { + for (_id, window) in window_manager.iter_mut() { + window.raw.request_redraw(); + } + } event::Event::NewEvents( - event::StartCause::Init - | event::StartCause::ResumeTimeReached { .. }, + event::StartCause::ResumeTimeReached { .. }, ) => { + let now = Instant::now(); + for (_id, window) in window_manager.iter_mut() { - window.raw.request_redraw(); + if let Some(redraw_at) = window.redraw_at { + if redraw_at <= now { + window.raw.request_redraw(); + window.redraw_at = None; + } + } } } event::Event::PlatformSpecific( @@ -801,17 +773,49 @@ async fn run_instance<P, C>( event: event::WindowEvent::RedrawRequested, .. } => { + let Some(compositor) = &mut compositor else { + continue; + }; + let Some((id, window)) = window_manager.get_mut_alias(id) else { continue; }; - // 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 physical_size = window.state.physical_size(); + + if physical_size.width == 0 || physical_size.height == 0 + { + continue; + } + + if window.viewport_version + != window.state.viewport_version() + { + let logical_size = window.state.logical_size(); + + debug.layout_started(); + let ui = user_interfaces + .remove(&id) + .expect("Remove user interface"); + + let _ = user_interfaces.insert( + id, + ui.relayout(logical_size, &mut window.renderer), + ); + debug.layout_finished(); + + compositor.configure_surface( + &mut window.surface, + physical_size.width, + physical_size.height, + ); + + window.viewport_version = + window.state.viewport_version(); + } + let redraw_event = core::Event::Window( window::Event::RedrawRequested(Instant::now()), ); @@ -857,81 +861,18 @@ async fn run_instance<P, C>( status: core::event::Status::Ignored, }); - 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 => { - window.raw.request_redraw(); - - ControlFlow::Wait - } - window::RedrawRequest::At(at) => { - ControlFlow::WaitUntil(at) - } - }, - _ => ControlFlow::Wait, - }, - )); - - let physical_size = window.state.physical_size(); - - if physical_size.width == 0 || physical_size.height == 0 - { - continue; - } - - if window.viewport_version - != window.state.viewport_version() + if let user_interface::State::Updated { + redraw_request: Some(redraw_request), + } = ui_state { - let logical_size = window.state.logical_size(); - - debug.layout_started(); - let ui = user_interfaces - .remove(&id) - .expect("Remove user interface"); - - let _ = user_interfaces.insert( - id, - ui.relayout(logical_size, &mut window.renderer), - ); - debug.layout_finished(); - - debug.draw_started(); - let new_mouse_interaction = user_interfaces - .get_mut(&id) - .expect("Get user interface") - .draw( - &mut window.renderer, - window.state.theme(), - &renderer::Style { - text_color: window.state.text_color(), - }, - window.state.cursor(), - ); - debug.draw_finished(); - - if new_mouse_interaction != window.mouse_interaction - { - window.raw.set_cursor( - conversion::mouse_interaction( - new_mouse_interaction, - ), - ); - - window.mouse_interaction = - new_mouse_interaction; + match redraw_request { + window::RedrawRequest::NextFrame => { + window.raw.request_redraw(); + } + window::RedrawRequest::At(at) => { + window.redraw_at = Some(at); + } } - - compositor.configure_surface( - &mut window.surface, - physical_size.width, - physical_size.height, - ); - - window.viewport_version = - window.state.viewport_version(); } debug.render_started(); @@ -995,6 +936,13 @@ async fn run_instance<P, C>( if matches!( window_event, + winit::event::WindowEvent::Resized(_) + ) { + window.raw.request_redraw(); + } + + if matches!( + window_event, winit::event::WindowEvent::CloseRequested ) && window.exit_on_close_request { @@ -1031,7 +979,10 @@ async fn run_instance<P, C>( } } event::Event::AboutToWait => { - if events.is_empty() && messages.is_empty() { + if events.is_empty() + && messages.is_empty() + && window_manager.is_idle() + { continue; } @@ -1065,13 +1016,27 @@ async fn run_instance<P, C>( &mut messages, ); + #[cfg(feature = "unconditional-rendering")] window.raw.request_redraw(); - if !uis_stale { - uis_stale = matches!( - ui_state, - user_interface::State::Outdated - ); + match ui_state { + #[cfg(not( + feature = "unconditional-rendering" + ))] + user_interface::State::Updated { + redraw_request: Some(redraw_request), + } => match redraw_request { + window::RedrawRequest::NextFrame => { + window.raw.request_redraw(); + } + window::RedrawRequest::At(at) => { + window.redraw_at = Some(at); + } + }, + user_interface::State::Outdated => { + uis_stale = true; + } + user_interface::State::Updated { .. } => {} } for (event, status) in window_events @@ -1139,6 +1104,17 @@ async fn run_instance<P, C>( actions = 0; } } + + if let Some(redraw_at) = window_manager.redraw_at() { + let _ = + control_sender.start_send(Control::ChangeFlow( + ControlFlow::WaitUntil(redraw_at), + )); + } else { + let _ = control_sender.start_send( + Control::ChangeFlow(ControlFlow::Wait), + ); + } } _ => {} } @@ -1159,7 +1135,7 @@ fn build_user_interface<'a, P: Program>( id: window::Id, ) -> UserInterface<'a, P::Message, P::Theme, P::Renderer> where - P::Theme: DefaultStyle, + P::Theme: theme::Base, { debug.view_started(); let view = program.view(id); @@ -1178,7 +1154,7 @@ fn update<P: Program, E: Executor>( debug: &mut Debug, messages: &mut Vec<P::Message>, ) where - P::Theme: DefaultStyle, + P::Theme: theme::Base, { for message in messages.drain(..) { debug.log_message(&message); @@ -1199,7 +1175,7 @@ fn update<P: Program, E: Executor>( fn run_action<P, C>( action: Action<P::Message>, program: &P, - compositor: &mut C, + compositor: &mut Option<C>, events: &mut Vec<(window::Id, core::Event)>, messages: &mut Vec<P::Message>, clipboard: &mut Clipboard, @@ -1215,7 +1191,7 @@ fn run_action<P, C>( ) where P: Program, C: Compositor<Renderer = P::Renderer> + 'static, - P::Theme: DefaultStyle, + P::Theme: theme::Base, { use crate::runtime::clipboard; use crate::runtime::system; @@ -1267,6 +1243,10 @@ fn run_action<P, C>( core::Event::Window(core::window::Event::Closed), )); } + + if window_manager.is_empty() { + *compositor = None; + } } window::Action::GetOldest(channel) => { let id = @@ -1285,6 +1265,13 @@ fn run_action<P, C>( let _ = window.raw.drag_window(); } } + window::Action::DragResize(id, direction) => { + if let Some(window) = window_manager.get_mut(id) { + let _ = window.raw.drag_resize_window( + conversion::resize_direction(direction), + ); + } + } window::Action::Resize(id, size) => { if let Some(window) = window_manager.get_mut(id) { let _ = window.raw.request_inner_size( @@ -1295,6 +1282,41 @@ fn run_action<P, C>( ); } } + window::Action::SetMinSize(id, size) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_min_inner_size(size.map(|size| { + winit::dpi::LogicalSize { + width: size.width, + height: size.height, + } + })); + } + } + window::Action::SetMaxSize(id, size) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_max_inner_size(size.map(|size| { + winit::dpi::LogicalSize { + width: size.width, + height: size.height, + } + })); + } + } + window::Action::SetResizeIncrements(id, increments) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_resize_increments(increments.map(|size| { + winit::dpi::LogicalSize { + width: size.width, + height: size.height, + } + })); + } + } + window::Action::SetResizable(id, resizable) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_resizable(resizable); + } + } window::Action::GetSize(id, channel) => { if let Some(window) = window_manager.get_mut(id) { let size = window @@ -1329,7 +1351,7 @@ fn run_action<P, C>( if let Some(window) = window_manager.get(id) { let position = window .raw - .inner_position() + .outer_position() .map(|position| { let position = position .to_logical::<f32>(window.raw.scale_factor()); @@ -1358,7 +1380,7 @@ fn run_action<P, C>( ); } } - window::Action::ChangeMode(id, mode) => { + window::Action::SetMode(id, mode) => { if let Some(window) = window_manager.get_mut(id) { window.raw.set_visible(conversion::visible(mode)); window.raw.set_fullscreen(conversion::fullscreen( @@ -1367,7 +1389,7 @@ fn run_action<P, C>( )); } } - window::Action::ChangeIcon(id, icon) => { + window::Action::SetIcon(id, icon) => { if let Some(window) = window_manager.get_mut(id) { window.raw.set_window_icon(conversion::icon(icon)); } @@ -1405,7 +1427,7 @@ fn run_action<P, C>( window.raw.focus_window(); } } - window::Action::ChangeLevel(id, level) => { + window::Action::SetLevel(id, level) => { if let Some(window) = window_manager.get_mut(id) { window .raw @@ -1443,19 +1465,20 @@ fn run_action<P, C>( } window::Action::Screenshot(id, channel) => { 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(), - ); + if let Some(compositor) = compositor { + let bytes = compositor.screenshot( + &mut window.renderer, + window.state.viewport(), + window.state.background_color(), + &debug.overlay(), + ); - let _ = channel.send(window::Screenshot::new( - bytes, - window.state.physical_size(), - window.state.viewport().scale_factor(), - )); + let _ = channel.send(core::window::Screenshot::new( + bytes, + window.state.physical_size(), + window.state.viewport().scale_factor(), + )); + } } } window::Action::EnableMousePassthrough(id) => { @@ -1473,14 +1496,16 @@ fn run_action<P, C>( system::Action::QueryInformation(_channel) => { #[cfg(feature = "system")] { - let graphics_info = compositor.fetch_information(); + if let Some(compositor) = compositor { + let graphics_info = compositor.fetch_information(); - let _ = std::thread::spawn(move || { - let information = - crate::system::information(graphics_info); + let _ = std::thread::spawn(move || { + let information = + crate::system::information(graphics_info); - let _ = _channel.send(information); - }); + let _ = _channel.send(information); + }); + } } } }, @@ -1504,10 +1529,12 @@ fn run_action<P, C>( } } Action::LoadFont { bytes, channel } => { - // TODO: Error handling (?) - compositor.load_font(bytes.clone()); + if let Some(compositor) = compositor { + // TODO: Error handling (?) + compositor.load_font(bytes.clone()); - let _ = channel.send(Ok(())); + let _ = channel.send(Ok(())); + } } Action::Exit => { control_sender @@ -1526,7 +1553,7 @@ pub fn build_user_interfaces<'a, P: Program, C>( ) -> FxHashMap<window::Id, UserInterface<'a, P::Message, P::Theme, P::Renderer>> where C: Compositor<Renderer = P::Renderer>, - P::Theme: DefaultStyle, + P::Theme: theme::Base, { cached_user_interfaces .drain() |