diff options
Diffstat (limited to 'glutin/src')
| -rw-r--r-- | glutin/src/multi_window.rs | 264 | ||||
| -rw-r--r-- | glutin/src/multi_window/state.rs | 241 | 
2 files changed, 491 insertions, 14 deletions
diff --git a/glutin/src/multi_window.rs b/glutin/src/multi_window.rs index c3b9e74f..4949219f 100644 --- a/glutin/src/multi_window.rs +++ b/glutin/src/multi_window.rs @@ -1,15 +1,18 @@  //! Create interactive, native cross-platform applications. +mod state; + +pub use state::State; +  use crate::mouse;  use crate::{Error, Executor, Runtime}; -pub use iced_winit::multi_window::{ -    self, Application, Event, State, StyleSheet, -}; +pub use iced_winit::multi_window::{self, Application, StyleSheet};  use iced_winit::conversion;  use iced_winit::futures;  use iced_winit::futures::channel::mpsc;  use iced_winit::renderer; +use iced_winit::settings;  use iced_winit::user_interface;  use iced_winit::window;  use iced_winit::{Clipboard, Command, Debug, Proxy, Settings}; @@ -238,7 +241,7 @@ async fn run_instance<A, E, C>(      {          let state = states.get(&window::Id::MAIN).unwrap(); -        multi_window::run_command( +        run_command(              &application,              &mut cache,              state, @@ -324,7 +327,7 @@ async fn run_instance<A, E, C>(                                  .collect();                          // Update application -                        multi_window::update( +                        update(                              &mut application,                              &mut cache,                              state, @@ -343,15 +346,13 @@ async fn run_instance<A, E, C>(                          let should_exit = application.should_exit(); -                        interfaces = ManuallyDrop::new( -                            multi_window::build_user_interfaces( -                                &application, -                                &mut renderer, -                                &mut debug, -                                &states, -                                pure_states, -                            ), -                        ); +                        interfaces = ManuallyDrop::new(build_user_interfaces( +                            &application, +                            &mut renderer, +                            &mut debug, +                            &states, +                            pure_states, +                        ));                          if should_exit {                              break 'main; @@ -571,3 +572,238 @@ async fn run_instance<A, E, C>(      // Manually drop the user interface      // drop(ManuallyDrop::into_inner(user_interface));  } + +/// TODO(derezzedex): +// This is the an wrapper around the `Application::Message` associate type +// to allows the `shell` to create internal messages, while still having +// the current user specified custom messages. +#[derive(Debug)] +pub enum Event<Message> { +    /// An [`Application`] generated message +    Application(Message), + +    /// TODO(derezzedex) +    // Create a wrapper variant of `window::Event` type instead +    // (maybe we should also allow users to listen/react to those internal messages?) +    NewWindow(window::Id, settings::Window), +    /// TODO(derezzedex) +    CloseWindow(window::Id), +    /// TODO(derezzedex) +    WindowCreated(window::Id, glutin::window::Window), +} + +/// Updates an [`Application`] by feeding it the provided messages, spawning any +/// resulting [`Command`], and tracking its [`Subscription`]. +pub fn update<A: Application, E: Executor>( +    application: &mut A, +    cache: &mut user_interface::Cache, +    state: &State<A>, +    renderer: &mut A::Renderer, +    runtime: &mut Runtime<E, Proxy<Event<A::Message>>, Event<A::Message>>, +    clipboard: &mut Clipboard, +    proxy: &mut glutin::event_loop::EventLoopProxy<Event<A::Message>>, +    debug: &mut Debug, +    messages: &mut Vec<A::Message>, +    windows: &HashMap<window::Id, glutin::window::Window>, +    graphics_info: impl FnOnce() -> iced_graphics::compositor::Information + Copy, +) where +    <A::Renderer as crate::Renderer>::Theme: StyleSheet, +{ +    for message in messages.drain(..) { +        debug.log_message(&message); + +        debug.update_started(); +        let command = runtime.enter(|| application.update(message)); +        debug.update_finished(); + +        run_command( +            application, +            cache, +            state, +            renderer, +            command, +            runtime, +            clipboard, +            proxy, +            debug, +            windows, +            graphics_info, +        ); +    } + +    let subscription = application.subscription().map(Event::Application); +    runtime.track(subscription); +} + +/// Runs the actions of a [`Command`]. +pub fn run_command<A, E>( +    application: &A, +    cache: &mut user_interface::Cache, +    state: &State<A>, +    renderer: &mut A::Renderer, +    command: Command<A::Message>, +    runtime: &mut Runtime<E, Proxy<Event<A::Message>>, Event<A::Message>>, +    clipboard: &mut Clipboard, +    proxy: &mut glutin::event_loop::EventLoopProxy<Event<A::Message>>, +    debug: &mut Debug, +    windows: &HashMap<window::Id, glutin::window::Window>, +    _graphics_info: impl FnOnce() -> iced_graphics::compositor::Information + Copy, +) where +    A: Application, +    E: Executor, +    <A::Renderer as crate::Renderer>::Theme: StyleSheet, +{ +    use iced_native::command; +    use iced_native::system; +    use iced_native::window; +    use iced_winit::clipboard; +    use iced_winit::futures::FutureExt; + +    for action in command.actions() { +        match action { +            command::Action::Future(future) => { +                runtime.spawn(Box::pin(future.map(Event::Application))); +            } +            command::Action::Clipboard(action) => match action { +                clipboard::Action::Read(tag) => { +                    let message = tag(clipboard.read()); + +                    proxy +                        .send_event(Event::Application(message)) +                        .expect("Send message to event loop"); +                } +                clipboard::Action::Write(contents) => { +                    clipboard.write(contents); +                } +            }, +            command::Action::Window(id, action) => { +                let window = windows.get(&id).expect("No window found"); + +                match action { +                    window::Action::Resize { width, height } => { +                        window.set_inner_size(glutin::dpi::LogicalSize { +                            width, +                            height, +                        }); +                    } +                    window::Action::Move { x, y } => { +                        window.set_outer_position( +                            glutin::dpi::LogicalPosition { x, y }, +                        ); +                    } +                    window::Action::SetMode(mode) => { +                        window.set_visible(conversion::visible(mode)); +                        window.set_fullscreen(conversion::fullscreen( +                            window.primary_monitor(), +                            mode, +                        )); +                    } +                    window::Action::FetchMode(tag) => { +                        let mode = if window.is_visible().unwrap_or(true) { +                            conversion::mode(window.fullscreen()) +                        } else { +                            window::Mode::Hidden +                        }; + +                        proxy +                            .send_event(Event::Application(tag(mode))) +                            .expect("Send message to event loop"); +                    } +                } +            } +            command::Action::System(action) => match action { +                system::Action::QueryInformation(_tag) => { +                    #[cfg(feature = "iced_winit/system")] +                    { +                        let graphics_info = _graphics_info(); +                        let proxy = proxy.clone(); + +                        let _ = std::thread::spawn(move || { +                            let information = +                                crate::system::information(graphics_info); + +                            let message = _tag(information); + +                            proxy +                                .send_event(Event::Application(message)) +                                .expect("Send message to event loop") +                        }); +                    } +                } +            }, +            command::Action::Widget(action) => { +                use crate::widget::operation; + +                let mut current_cache = std::mem::take(cache); +                let mut current_operation = Some(action.into_operation()); + +                let mut user_interface = multi_window::build_user_interface( +                    application, +                    current_cache, +                    renderer, +                    state.logical_size(), +                    debug, +                    window::Id::MAIN, // TODO(derezzedex): run the operation on every widget tree +                ); + +                while let Some(mut operation) = current_operation.take() { +                    user_interface.operate(renderer, operation.as_mut()); + +                    match operation.finish() { +                        operation::Outcome::None => {} +                        operation::Outcome::Some(message) => { +                            proxy +                                .send_event(Event::Application(message)) +                                .expect("Send message to event loop"); +                        } +                        operation::Outcome::Chain(next) => { +                            current_operation = Some(next); +                        } +                    } +                } + +                current_cache = user_interface.into_cache(); +                *cache = current_cache; +            } +        } +    } +} + +/// TODO(derezzedex) +pub fn build_user_interfaces<'a, A>( +    application: &'a A, +    renderer: &mut A::Renderer, +    debug: &mut Debug, +    states: &HashMap<window::Id, State<A>>, +    mut pure_states: HashMap<window::Id, user_interface::Cache>, +) -> HashMap< +    window::Id, +    iced_winit::UserInterface< +        'a, +        <A as Application>::Message, +        <A as Application>::Renderer, +    >, +> +where +    A: Application + 'static, +    <A::Renderer as crate::Renderer>::Theme: StyleSheet, +{ +    let mut interfaces = HashMap::new(); + +    for (id, pure_state) in pure_states.drain() { +        let state = &states.get(&id).unwrap(); + +        let user_interface = multi_window::build_user_interface( +            application, +            pure_state, +            renderer, +            state.logical_size(), +            debug, +            id, +        ); + +        let _ = interfaces.insert(id, user_interface); +    } + +    interfaces +} diff --git a/glutin/src/multi_window/state.rs b/glutin/src/multi_window/state.rs new file mode 100644 index 00000000..163f46bd --- /dev/null +++ b/glutin/src/multi_window/state.rs @@ -0,0 +1,241 @@ +use crate::application::{self, StyleSheet as _}; +use crate::conversion; +use crate::multi_window::{Application, Event}; +use crate::window; +use crate::{Color, Debug, Point, Size, Viewport}; + +use glutin::event::{Touch, WindowEvent}; +use glutin::event_loop::EventLoopProxy; +use glutin::window::Window; +use std::collections::HashMap; +use std::marker::PhantomData; + +/// The state of a windowed [`Application`]. +#[allow(missing_debug_implementations)] +pub struct State<A: Application> +where +    <A::Renderer as crate::Renderer>::Theme: application::StyleSheet, +{ +    title: String, +    scale_factor: f64, +    viewport: Viewport, +    viewport_changed: bool, +    cursor_position: glutin::dpi::PhysicalPosition<f64>, +    modifiers: glutin::event::ModifiersState, +    theme: <A::Renderer as crate::Renderer>::Theme, +    appearance: iced_winit::application::Appearance, +    application: PhantomData<A>, +} + +impl<A: Application> State<A> +where +    <A::Renderer as crate::Renderer>::Theme: application::StyleSheet, +{ +    /// Creates a new [`State`] for the provided [`Application`] and window. +    pub fn new(application: &A, window: &Window) -> Self { +        let title = application.title(); +        let scale_factor = application.scale_factor(); +        let theme = application.theme(); +        let appearance = theme.appearance(application.style()); + +        let viewport = { +            let physical_size = window.inner_size(); + +            Viewport::with_physical_size( +                Size::new(physical_size.width, physical_size.height), +                window.scale_factor() * scale_factor, +            ) +        }; + +        Self { +            title, +            scale_factor, +            viewport, +            viewport_changed: false, +            // TODO: Encode cursor availability in the type-system +            cursor_position: glutin::dpi::PhysicalPosition::new(-1.0, -1.0), +            modifiers: glutin::event::ModifiersState::default(), +            theme, +            appearance, +            application: PhantomData, +        } +    } + +    /// Returns the current [`Viewport`] of the [`State`]. +    pub fn viewport(&self) -> &Viewport { +        &self.viewport +    } + +    /// TODO(derezzedex) +    pub fn viewport_changed(&self) -> bool { +        self.viewport_changed +    } + +    /// 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_position(&self) -> Point { +        conversion::cursor_position( +            self.cursor_position, +            self.viewport.scale_factor(), +        ) +    } + +    /// Returns the current keyboard modifiers of the [`State`]. +    pub fn modifiers(&self) -> glutin::event::ModifiersState { +        self.modifiers +    } + +    /// Returns the current theme of the [`State`]. +    pub fn theme(&self) -> &<A::Renderer as crate::Renderer>::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_changed = true; +            } +            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_changed = true; +            } +            WindowEvent::CursorMoved { position, .. } +            | WindowEvent::Touch(Touch { +                location: position, .. +            }) => { +                self.cursor_position = *position; +            } +            WindowEvent::CursorLeft { .. } => { +                // TODO: Encode cursor availability in the type-system +                self.cursor_position = +                    glutin::dpi::PhysicalPosition::new(-1.0, -1.0); +            } +            WindowEvent::ModifiersChanged(new_modifiers) => { +                self.modifiers = *new_modifiers; +            } +            #[cfg(feature = "debug")] +            WindowEvent::KeyboardInput { +                input: +                    glutin::event::KeyboardInput { +                        virtual_keycode: +                            Some(glutin::event::VirtualKeyCode::F12), +                        state: glutin::event::ElementState::Pressed, +                        .. +                    }, +                .. +            } => _debug.toggle(), +            _ => {} +        } +    } + +    /// Synchronizes the [`State`] with its [`Application`] and its respective +    /// window. +    /// +    /// Normally an [`Application`] should be synchronized with its [`State`] +    /// and window after calling [`Application::update`]. +    /// +    /// [`Application::update`]: crate::Program::update +    pub fn synchronize( +        &mut self, +        application: &A, +        windows: &HashMap<window::Id, Window>, +        proxy: &EventLoopProxy<Event<A::Message>>, +    ) { +        let new_windows = application.windows(); + +        // Check for windows to close +        for window_id in windows.keys() { +            if !new_windows.iter().any(|(id, _)| id == window_id) { +                proxy +                    .send_event(Event::CloseWindow(*window_id)) +                    .expect("Failed to send message"); +            } +        } + +        // Check for windows to spawn +        for (id, settings) in new_windows { +            if !windows.contains_key(&id) { +                proxy +                    .send_event(Event::NewWindow(id, settings)) +                    .expect("Failed to send message"); +            } +        } + +        let window = windows.values().next().expect("No window found"); + +        // Update window title +        let new_title = application.title(); + +        if self.title != new_title { +            window.set_title(&new_title); + +            self.title = new_title; +        } + +        // Update scale factor +        let new_scale_factor = application.scale_factor(); + +        if self.scale_factor != new_scale_factor { +            let size = window.inner_size(); + +            self.viewport = Viewport::with_physical_size( +                Size::new(size.width, size.height), +                window.scale_factor() * new_scale_factor, +            ); + +            self.scale_factor = new_scale_factor; +        } + +        // Update theme and appearance +        self.theme = application.theme(); +        self.appearance = self.theme.appearance(application.style()); +    } +}  | 
