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