diff options
| author | 2020-11-08 13:31:08 +0100 | |
|---|---|---|
| committer | 2020-11-08 13:31:08 +0100 | |
| commit | da1a3eed1e6e67c2c0507e3b939e4aacdc06c19a (patch) | |
| tree | 65eb9b90b1e9fb07912296d36b604caea96b3b15 /winit | |
| parent | ef76f16900801c764292674280b0150e039d1ca3 (diff) | |
| parent | 631c9e4a215d8f044d76ea926117f9a9bdd24d5d (diff) | |
| download | iced-da1a3eed1e6e67c2c0507e3b939e4aacdc06c19a.tar.gz iced-da1a3eed1e6e67c2c0507e3b939e4aacdc06c19a.tar.bz2 iced-da1a3eed1e6e67c2c0507e3b939e4aacdc06c19a.zip | |
Merge pull request #597 from hecrj/improvement/reuse-view-in-event-loop
Rebuild widget tree only after an application update
Diffstat (limited to '')
| -rw-r--r-- | winit/src/application.rs | 488 | ||||
| -rw-r--r-- | winit/src/application/state.rs | 235 | 
2 files changed, 498 insertions, 225 deletions
| diff --git a/winit/src/application.rs b/winit/src/application.rs index 12f92053..c1d86471 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,13 +1,22 @@  //! Create interactive, native cross-platform applications. +mod state; + +pub use state::State; +  use crate::conversion;  use crate::mouse;  use crate::{      Clipboard, Color, Command, Debug, Error, Executor, Mode, Proxy, Runtime,      Settings, Size, Subscription,  }; + +use iced_futures::futures; +use iced_futures::futures::channel::mpsc;  use iced_graphics::window; -use iced_graphics::Viewport; -use iced_native::program::{self, Program}; +use iced_native::program::Program; +use iced_native::{Cache, UserInterface}; + +use std::mem::ManuallyDrop;  /// An interactive, native cross-platform application.  /// @@ -116,279 +125,270 @@ where      E: Executor + 'static,      C: window::Compositor<Renderer = A::Renderer> + 'static,  { -    use winit::{ -        event, -        event_loop::{ControlFlow, EventLoop}, -    }; +    use futures::task; +    use futures::Future; +    use winit::event_loop::EventLoop;      let mut debug = Debug::new();      debug.startup_started(); +    let (compositor, renderer) = C::new(compositor_settings)?; +      let event_loop = EventLoop::with_user_event(); +      let mut runtime = { -        let executor = E::new().map_err(Error::ExecutorCreationFailed)?;          let proxy = Proxy::new(event_loop.create_proxy()); +        let executor = E::new().map_err(Error::ExecutorCreationFailed)?;          Runtime::new(executor, proxy)      }; -    let flags = settings.flags; -    let (application, init_command) = runtime.enter(|| A::new(flags)); -    runtime.spawn(init_command); +    let (application, init_command) = { +        let flags = settings.flags; + +        runtime.enter(|| A::new(flags)) +    };      let subscription = application.subscription(); -    runtime.track(subscription); -    let mut title = application.title(); -    let mut mode = application.mode(); -    let mut background_color = application.background_color(); -    let mut scale_factor = application.scale_factor(); +    runtime.spawn(init_command); +    runtime.track(subscription);      let window = settings          .window -        .into_builder(&title, mode, event_loop.primary_monitor()) +        .into_builder( +            &application.title(), +            application.mode(), +            event_loop.primary_monitor(), +        )          .build(&event_loop)          .map_err(Error::WindowCreationFailed)?; -    let clipboard = Clipboard::new(&window); -    // TODO: Encode cursor availability in the type-system -    let mut cursor_position = winit::dpi::PhysicalPosition::new(-1.0, -1.0); -    let mut mouse_interaction = mouse::Interaction::default(); -    let mut modifiers = winit::event::ModifiersState::default(); +    let (mut sender, receiver) = mpsc::unbounded(); -    let physical_size = window.inner_size(); -    let mut viewport = Viewport::with_physical_size( -        Size::new(physical_size.width, physical_size.height), -        window.scale_factor() * scale_factor, -    ); -    let mut resized = false; +    let mut instance = Box::pin(run_instance::<A, E, C>( +        application, +        compositor, +        renderer, +        window, +        runtime, +        debug, +        receiver, +    )); -    let (mut compositor, mut renderer) = C::new(compositor_settings)?; +    let mut context = task::Context::from_waker(task::noop_waker_ref()); -    let surface = compositor.create_surface(&window); +    event_loop.run(move |event, _, control_flow| { +        use winit::event_loop::ControlFlow; -    let mut swap_chain = compositor.create_swap_chain( -        &surface, -        physical_size.width, -        physical_size.height, -    ); +        if let ControlFlow::Exit = control_flow { +            return; +        } -    let mut state = program::State::new( -        application, -        viewport.logical_size(), -        conversion::cursor_position(cursor_position, viewport.scale_factor()), -        &mut renderer, -        &mut debug, -    ); -    debug.startup_finished(); +        if let Some(event) = event.to_static() { +            sender.start_send(event).expect("Send event"); -    event_loop.run(move |event, _, control_flow| match event { -        event::Event::MainEventsCleared => { -            if state.is_queue_empty() { -                return; -            } +            let poll = instance.as_mut().poll(&mut context); -            let command = runtime.enter(|| { -                state.update( -                    viewport.logical_size(), -                    conversion::cursor_position( -                        cursor_position, -                        viewport.scale_factor(), -                    ), -                    clipboard.as_ref().map(|c| c as _), -                    &mut renderer, -                    &mut debug, -                ) -            }); +            *control_flow = match poll { +                task::Poll::Pending => ControlFlow::Wait, +                task::Poll::Ready(_) => ControlFlow::Exit, +            }; +        } +    }); +} -            // If the application was updated -            if let Some(command) = command { -                runtime.spawn(command); +async fn run_instance<A, E, C>( +    mut application: A, +    mut compositor: C, +    mut renderer: A::Renderer, +    window: winit::window::Window, +    mut runtime: Runtime<E, Proxy<A::Message>, A::Message>, +    mut debug: Debug, +    mut receiver: mpsc::UnboundedReceiver<winit::event::Event<'_, A::Message>>, +) where +    A: Application + 'static, +    E: Executor + 'static, +    C: window::Compositor<Renderer = A::Renderer> + 'static, +{ +    use iced_futures::futures::stream::StreamExt; +    use winit::event; -                let program = state.program(); +    let surface = compositor.create_surface(&window); +    let clipboard = Clipboard::new(&window); -                // Update subscriptions -                let subscription = program.subscription(); -                runtime.track(subscription); +    let mut state = State::new(&application, &window); +    let mut viewport_version = state.viewport_version(); +    let mut swap_chain = { +        let physical_size = state.physical_size(); -                // Update window title -                let new_title = program.title(); +        compositor.create_swap_chain( +            &surface, +            physical_size.width, +            physical_size.height, +        ) +    }; -                if title != new_title { -                    window.set_title(&new_title); +    let mut user_interface = ManuallyDrop::new(build_user_interface( +        &mut application, +        Cache::default(), +        &mut renderer, +        state.logical_size(), +        &mut debug, +    )); -                    title = new_title; -                } +    let mut primitive = +        user_interface.draw(&mut renderer, state.cursor_position()); +    let mut mouse_interaction = mouse::Interaction::default(); -                // Update window mode -                let new_mode = program.mode(); +    let mut events = Vec::new(); +    let mut external_messages = Vec::new(); -                if mode != new_mode { -                    window.set_fullscreen(conversion::fullscreen( -                        window.current_monitor(), -                        new_mode, -                    )); +    debug.startup_finished(); -                    mode = new_mode; +    while let Some(event) = receiver.next().await { +        match event { +            event::Event::MainEventsCleared => { +                if events.is_empty() && external_messages.is_empty() { +                    continue;                  } -                // Update background color -                background_color = program.background_color(); +                debug.event_processing_started(); +                let mut messages = user_interface.update( +                    &events, +                    state.cursor_position(), +                    clipboard.as_ref().map(|c| c as _), +                    &mut renderer, +                ); -                // Update scale factor -                let new_scale_factor = program.scale_factor(); +                messages.extend(external_messages.drain(..)); +                events.clear(); +                debug.event_processing_finished(); -                if scale_factor != new_scale_factor { -                    let size = window.inner_size(); +                if !messages.is_empty() { +                    let cache = +                        ManuallyDrop::into_inner(user_interface).into_cache(); -                    viewport = Viewport::with_physical_size( -                        Size::new(size.width, size.height), -                        window.scale_factor() * new_scale_factor, +                    // Update application +                    update( +                        &mut application, +                        &mut runtime, +                        &mut debug, +                        messages,                      ); -                    // We relayout the UI with the new logical size. -                    // The queue is empty, therefore this will never produce -                    // a `Command`. -                    // -                    // TODO: Properly queue `WindowResized` -                    let _ = state.update( -                        viewport.logical_size(), -                        conversion::cursor_position( -                            cursor_position, -                            viewport.scale_factor(), -                        ), -                        clipboard.as_ref().map(|c| c as _), +                    // Update window +                    state.synchronize(&application, &window); + +                    user_interface = ManuallyDrop::new(build_user_interface( +                        &mut application, +                        cache,                          &mut renderer, +                        state.logical_size(),                          &mut debug, -                    ); - -                    scale_factor = new_scale_factor; +                    ));                  } + +                debug.draw_started(); +                primitive = +                    user_interface.draw(&mut renderer, state.cursor_position()); +                debug.draw_finished(); + +                window.request_redraw();              } +            event::Event::UserEvent(message) => { +                external_messages.push(message); +            } +            event::Event::RedrawRequested(_) => { +                debug.render_started(); +                let current_viewport_version = state.viewport_version(); + +                if viewport_version != current_viewport_version { +                    let physical_size = state.physical_size(); +                    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(); -            window.request_redraw(); -        } -        event::Event::UserEvent(message) => { -            state.queue_message(message); -        } -        event::Event::RedrawRequested(_) => { -            debug.render_started(); +                    debug.draw_started(); +                    primitive = user_interface +                        .draw(&mut renderer, state.cursor_position()); +                    debug.draw_finished(); -            if resized { -                let physical_size = viewport.physical_size(); +                    swap_chain = compositor.create_swap_chain( +                        &surface, +                        physical_size.width, +                        physical_size.height, +                    ); -                swap_chain = compositor.create_swap_chain( -                    &surface, -                    physical_size.width, -                    physical_size.height, -                ); +                    viewport_version = current_viewport_version; +                } -                resized = false; -            } +                let new_mouse_interaction = compositor.draw( +                    &mut renderer, +                    &mut swap_chain, +                    state.viewport(), +                    state.background_color(), +                    &primitive, +                    &debug.overlay(), +                ); -            let new_mouse_interaction = compositor.draw( -                &mut renderer, -                &mut swap_chain, -                &viewport, -                background_color, -                state.primitive(), -                &debug.overlay(), -            ); +                debug.render_finished(); -            debug.render_finished(); +                if new_mouse_interaction != mouse_interaction { +                    window.set_cursor_icon(conversion::mouse_interaction( +                        new_mouse_interaction, +                    )); -            if new_mouse_interaction != mouse_interaction { -                window.set_cursor_icon(conversion::mouse_interaction( -                    new_mouse_interaction, -                )); +                    mouse_interaction = new_mouse_interaction; +                } -                mouse_interaction = new_mouse_interaction; +                // TODO: Handle animations! +                // Maybe we can use `ControlFlow::WaitUntil` for this.              } +            event::Event::WindowEvent { +                event: window_event, +                .. +            } => { +                if requests_exit(&window_event, state.modifiers()) { +                    break; +                } -            // TODO: Handle animations! -            // Maybe we can use `ControlFlow::WaitUntil` for this. -        } -        event::Event::WindowEvent { -            event: window_event, -            .. -        } => { -            handle_window_event( -                &window_event, -                &window, -                scale_factor, -                control_flow, -                &mut cursor_position, -                &mut modifiers, -                &mut viewport, -                &mut resized, -                &mut debug, -            ); - -            if let Some(event) = conversion::window_event( -                &window_event, -                viewport.scale_factor(), -                modifiers, -            ) { -                state.queue_event(event.clone()); -                runtime.broadcast(event); +                state.update(&window, &window_event, &mut debug); + +                if let Some(event) = conversion::window_event( +                    &window_event, +                    state.scale_factor(), +                    state.modifiers(), +                ) { +                    events.push(event.clone()); +                    runtime.broadcast(event); +                }              } +            _ => {}          } -        _ => { -            *control_flow = ControlFlow::Wait; -        } -    }) +    } + +    // Manually drop the user interface +    drop(ManuallyDrop::into_inner(user_interface));  } -/// Handles a `WindowEvent` and mutates the provided control flow, keyboard -/// modifiers, viewport, and resized flag accordingly. -pub fn handle_window_event( +/// Returns true if the provided event should cause an [`Application`] to +/// exit. +/// +/// [`Application`]: trait.Application.html +pub fn requests_exit(      event: &winit::event::WindowEvent<'_>, -    window: &winit::window::Window, -    scale_factor: f64, -    control_flow: &mut winit::event_loop::ControlFlow, -    cursor_position: &mut winit::dpi::PhysicalPosition<f64>, -    modifiers: &mut winit::event::ModifiersState, -    viewport: &mut Viewport, -    resized: &mut bool, -    _debug: &mut Debug, -) { -    use winit::{event::WindowEvent, event_loop::ControlFlow}; +    _modifiers: winit::event::ModifiersState, +) -> bool { +    use winit::event::WindowEvent;      match event { -        WindowEvent::Resized(new_size) => { -            let size = Size::new(new_size.width, new_size.height); - -            *viewport = Viewport::with_physical_size( -                size, -                window.scale_factor() * scale_factor, -            ); -            *resized = true; -        } -        WindowEvent::ScaleFactorChanged { -            scale_factor: new_scale_factor, -            new_inner_size, -        } => { -            let size = Size::new(new_inner_size.width, new_inner_size.height); - -            *viewport = Viewport::with_physical_size( -                size, -                new_scale_factor * scale_factor, -            ); -            *resized = true; -        } -        WindowEvent::CloseRequested => { -            *control_flow = ControlFlow::Exit; -        } -        WindowEvent::CursorMoved { position, .. } => { -            *cursor_position = *position; -        } -        WindowEvent::CursorLeft { .. } => { -            // TODO: Encode cursor availability in the type-system -            *cursor_position = winit::dpi::PhysicalPosition::new(-1.0, -1.0); -        } -        WindowEvent::ModifiersChanged(new_modifiers) => { -            *modifiers = *new_modifiers; -        } +        WindowEvent::CloseRequested => true,          #[cfg(target_os = "macos")]          WindowEvent::KeyboardInput {              input: @@ -398,19 +398,57 @@ pub fn handle_window_event(                      ..                  },              .. -        } if modifiers.logo() => { -            *control_flow = ControlFlow::Exit; -        } -        #[cfg(feature = "debug")] -        WindowEvent::KeyboardInput { -            input: -                winit::event::KeyboardInput { -                    virtual_keycode: Some(winit::event::VirtualKeyCode::F12), -                    state: winit::event::ElementState::Pressed, -                    .. -                }, -            .. -        } => _debug.toggle(), -        _ => {} +        } if _modifiers.logo() => true, +        _ => false, +    } +} + +/// Builds a [`UserInterface`] for the provided [`Application`], logging +/// [`Debug`] information accordingly. +/// +/// [`UserInterface`]: struct.UserInterface.html +/// [`Application`]: trait.Application.html +/// [`Debug`]: struct.Debug.html +pub fn build_user_interface<'a, A: Application>( +    application: &'a mut A, +    cache: Cache, +    renderer: &mut A::Renderer, +    size: Size, +    debug: &mut Debug, +) -> UserInterface<'a, A::Message, A::Renderer> { +    debug.view_started(); +    let view = application.view(); +    debug.view_finished(); + +    debug.layout_started(); +    let user_interface = UserInterface::build(view, size, cache, renderer); +    debug.layout_finished(); + +    user_interface +} + +/// Updates an [`Application`] by feeding it the provided messages, spawning any +/// resulting [`Command`], and tracking its [`Subscription`]. +/// +/// [`Application`]: trait.Application.html +/// [`Command`]: struct.Command.html +/// [`Subscription`]: struct.Subscription.html +pub fn update<A: Application, E: Executor>( +    application: &mut A, +    runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>, +    debug: &mut Debug, +    messages: Vec<A::Message>, +) { +    for message in messages { +        debug.log_message(&message); + +        debug.update_started(); +        let command = runtime.enter(|| application.update(message)); +        debug.update_finished(); + +        runtime.spawn(command);      } + +    let subscription = application.subscription(); +    runtime.track(subscription);  } diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs new file mode 100644 index 00000000..4c0bfd34 --- /dev/null +++ b/winit/src/application/state.rs @@ -0,0 +1,235 @@ +use crate::conversion; +use crate::{Application, Color, Debug, Mode, Point, Size, Viewport}; + +use std::marker::PhantomData; +use winit::event::WindowEvent; +use winit::window::Window; + +/// The state of a windowed [`Application`]. +/// +/// [`Application`]: ../trait.Application.html +#[derive(Debug, Clone)] +pub struct State<A: Application> { +    title: String, +    mode: Mode, +    background_color: Color, +    scale_factor: f64, +    viewport: Viewport, +    viewport_version: usize, +    cursor_position: winit::dpi::PhysicalPosition<f64>, +    modifiers: winit::event::ModifiersState, +    application: PhantomData<A>, +} + +impl<A: Application> State<A> { +    /// Creates a new [`State`] for the provided [`Application`] and window. +    /// +    /// [`State`]: struct.State.html +    /// [`Application`]: ../trait.Application.html +    pub fn new(application: &A, window: &Window) -> Self { +        let title = application.title(); +        let mode = application.mode(); +        let background_color = application.background_color(); +        let scale_factor = application.scale_factor(); + +        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, +            mode, +            background_color, +            scale_factor, +            viewport, +            viewport_version: 0, +            // TODO: Encode cursor availability in the type-system +            cursor_position: winit::dpi::PhysicalPosition::new(-1.0, -1.0), +            modifiers: winit::event::ModifiersState::default(), +            application: PhantomData, +        } +    } + +    /// Returns the current background [`Color`] of the [`State`]. +    /// +    /// [`Color`]: ../struct.Color.html +    /// [`State`]: struct.State.html +    pub fn background_color(&self) -> Color { +        self.background_color +    } + +    /// Returns the current [`Viewport`] of the [`State`]. +    /// +    /// [`Viewport`]: ../struct.Viewport.html +    /// [`State`]: struct.State.html +    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. +    /// +    /// [`Viewport`]: ../struct.Viewport.html +    /// [`State`]: struct.State.html +    pub fn viewport_version(&self) -> usize { +        self.viewport_version +    } + +    /// Returns the physical [`Size`] of the [`Viewport`] of the [`State`]. +    /// +    /// [`Size`]: ../struct.Size.html +    /// [`Viewport`]: ../struct.Viewport.html +    /// [`State`]: struct.State.html +    pub fn physical_size(&self) -> Size<u32> { +        self.viewport.physical_size() +    } + +    /// Returns the logical [`Size`] of the [`Viewport`] of the [`State`]. +    /// +    /// [`Size`]: ../struct.Size.html +    /// [`Viewport`]: ../struct.Viewport.html +    /// [`State`]: struct.State.html +    pub fn logical_size(&self) -> Size<f32> { +        self.viewport.logical_size() +    } + +    /// Returns the current scale factor of the [`Viewport`] of the [`State`]. +    /// +    /// [`Viewport`]: ../struct.Viewport.html +    /// [`State`]: struct.State.html +    pub fn scale_factor(&self) -> f64 { +        self.viewport.scale_factor() +    } + +    /// Returns the current cursor position of the [`State`]. +    /// +    /// [`State`]: struct.State.html +    pub fn cursor_position(&self) -> Point { +        conversion::cursor_position( +            self.cursor_position, +            self.viewport.scale_factor(), +        ) +    } + +    /// Returns the current keyboard modifiers of the [`State`]. +    /// +    /// [`State`]: struct.State.html +    pub fn modifiers(&self) -> winit::event::ModifiersState { +        self.modifiers +    } + +    /// Processes the provided window event and updates the [`State`] +    /// accordingly. +    /// +    /// [`State`]: struct.State.html +    pub fn update( +        &mut self, +        window: &Window, +        event: &WindowEvent<'_>, +        _debug: &mut Debug, +    ) { +        match event { +            WindowEvent::Resized(new_size) => { +                let size = Size::new(new_size.width, new_size.height); + +                self.viewport = Viewport::with_physical_size( +                    size, +                    window.scale_factor() * self.scale_factor, +                ); + +                self.viewport_version = self.viewport_version.wrapping_add(1); +            } +            WindowEvent::ScaleFactorChanged { +                scale_factor: new_scale_factor, +                new_inner_size, +            } => { +                let size = +                    Size::new(new_inner_size.width, new_inner_size.height); + +                self.viewport = Viewport::with_physical_size( +                    size, +                    new_scale_factor * self.scale_factor, +                ); + +                self.viewport_version = self.viewport_version.wrapping_add(1); +            } +            WindowEvent::CursorMoved { position, .. } => { +                self.cursor_position = *position; +            } +            WindowEvent::CursorLeft { .. } => { +                // TODO: Encode cursor availability in the type-system +                self.cursor_position = +                    winit::dpi::PhysicalPosition::new(-1.0, -1.0); +            } +            WindowEvent::ModifiersChanged(new_modifiers) => { +                self.modifiers = *new_modifiers; +            } +            #[cfg(feature = "debug")] +            WindowEvent::KeyboardInput { +                input: +                    winit::event::KeyboardInput { +                        virtual_keycode: Some(winit::event::VirtualKeyCode::F12), +                        state: winit::event::ElementState::Pressed, +                        .. +                    }, +                .. +            } => _debug.toggle(), +            _ => {} +        } +    } + +    /// Synchronizes the [`State`] with its [`Application`] and its respective +    /// window. +    /// +    /// Normally an [`Application`] should be synchronized with its [`State`] +    /// and window after calling [`Application::update`]. +    /// +    /// [`State`]: struct.State.html +    /// [`Application`]: ../trait.Application.html +    /// [`Application::update`]: ../trait.Application.html#tymethod.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 window mode +        let new_mode = application.mode(); + +        if self.mode != new_mode { +            window.set_fullscreen(conversion::fullscreen( +                window.current_monitor(), +                new_mode, +            )); + +            self.mode = new_mode; +        } + +        // Update background color +        self.background_color = application.background_color(); + +        // Update scale factor +        let new_scale_factor = application.scale_factor(); + +        if self.scale_factor != new_scale_factor { +            let size = window.inner_size(); + +            self.viewport = Viewport::with_physical_size( +                Size::new(size.width, size.height), +                window.scale_factor() * new_scale_factor, +            ); + +            self.scale_factor = new_scale_factor; +        } +    } +} | 
