diff options
Diffstat (limited to '')
| -rw-r--r-- | runtime/src/multi_window.rs | 6 | ||||
| -rw-r--r-- | runtime/src/multi_window/program.rs | 32 | ||||
| -rw-r--r-- | runtime/src/multi_window/state.rs | 280 | 
3 files changed, 318 insertions, 0 deletions
diff --git a/runtime/src/multi_window.rs b/runtime/src/multi_window.rs new file mode 100644 index 00000000..cf778a20 --- /dev/null +++ b/runtime/src/multi_window.rs @@ -0,0 +1,6 @@ +//! A multi-window application. +pub mod program; +pub mod state; + +pub use program::Program; +pub use state::State; diff --git a/runtime/src/multi_window/program.rs b/runtime/src/multi_window/program.rs new file mode 100644 index 00000000..591b3e9a --- /dev/null +++ b/runtime/src/multi_window/program.rs @@ -0,0 +1,32 @@ +//! Build interactive programs using The Elm Architecture. +use crate::core::text; +use crate::core::window; +use crate::core::{Element, Renderer}; +use crate::Command; + +/// The core of a user interface for a multi-window application following The Elm Architecture. +pub trait Program: Sized { +    /// The graphics backend to use to draw the [`Program`]. +    type Renderer: Renderer + text::Renderer; + +    /// The type of __messages__ your [`Program`] will produce. +    type Message: std::fmt::Debug + Send; + +    /// Handles a __message__ and updates the state of the [`Program`]. +    /// +    /// This is where you define your __update logic__. All the __messages__, +    /// produced by either user interactions or commands, will be handled by +    /// this method. +    /// +    /// Any [`Command`] returned will be executed immediately in the +    /// background by shells. +    fn update(&mut self, message: Self::Message) -> Command<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::Renderer>; +} diff --git a/runtime/src/multi_window/state.rs b/runtime/src/multi_window/state.rs new file mode 100644 index 00000000..49f72c39 --- /dev/null +++ b/runtime/src/multi_window/state.rs @@ -0,0 +1,280 @@ +//! The internal state of a multi-window [`Program`]. +use crate::core::event::{self, Event}; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::widget::operation::{self, Operation}; +use crate::core::{Clipboard, Size}; +use crate::user_interface::{self, UserInterface}; +use crate::{Command, Debug, Program}; + +/// The execution state of a multi-window [`Program`]. It leverages caching, event +/// processing, and rendering primitive storage. +#[allow(missing_debug_implementations)] +pub struct State<P> +where +    P: Program + 'static, +{ +    program: P, +    caches: Option<Vec<user_interface::Cache>>, +    queued_events: Vec<Event>, +    queued_messages: Vec<P::Message>, +    mouse_interaction: mouse::Interaction, +} + +impl<P> State<P> +where +    P: Program + 'static, +{ +    /// Creates a new [`State`] with the provided [`Program`], initializing its +    /// primitive with the given logical bounds and renderer. +    pub fn new( +        program: P, +        bounds: Size, +        renderer: &mut P::Renderer, +        debug: &mut Debug, +    ) -> Self { +        let user_interface = build_user_interface( +            &program, +            user_interface::Cache::default(), +            renderer, +            bounds, +            debug, +        ); + +        let caches = Some(vec![user_interface.into_cache()]); + +        State { +            program, +            caches, +            queued_events: Vec::new(), +            queued_messages: Vec::new(), +            mouse_interaction: mouse::Interaction::Idle, +        } +    } + +    /// Returns a reference to the [`Program`] of the [`State`]. +    pub fn program(&self) -> &P { +        &self.program +    } + +    /// Queues an event in the [`State`] for processing during an [`update`]. +    /// +    /// [`update`]: Self::update +    pub fn queue_event(&mut self, event: Event) { +        self.queued_events.push(event); +    } + +    /// Queues a message in the [`State`] for processing during an [`update`]. +    /// +    /// [`update`]: Self::update +    pub fn queue_message(&mut self, message: P::Message) { +        self.queued_messages.push(message); +    } + +    /// Returns whether the event queue of the [`State`] is empty or not. +    pub fn is_queue_empty(&self) -> bool { +        self.queued_events.is_empty() && self.queued_messages.is_empty() +    } + +    /// Returns the current [`mouse::Interaction`] of the [`State`]. +    pub fn mouse_interaction(&self) -> mouse::Interaction { +        self.mouse_interaction +    } + +    /// Processes all the queued events and messages, rebuilding and redrawing +    /// the widgets of the linked [`Program`] if necessary. +    /// +    /// Returns a list containing the instances of [`Event`] that were not +    /// captured by any widget, and the [`Command`] obtained from [`Program`] +    /// after updating it, only if an update was necessary. +    pub fn update( +        &mut self, +        bounds: Size, +        cursor: mouse::Cursor, +        renderer: &mut P::Renderer, +        theme: &<P::Renderer as iced_core::Renderer>::Theme, +        style: &renderer::Style, +        clipboard: &mut dyn Clipboard, +        debug: &mut Debug, +    ) -> (Vec<Event>, Option<Command<P::Message>>) { +        let mut user_interfaces = build_user_interfaces( +            &self.program, +            self.caches.take().unwrap(), +            renderer, +            bounds, +            debug, +        ); + +        debug.event_processing_started(); +        let mut messages = Vec::new(); + +        let uncaptured_events = user_interfaces.iter_mut().fold( +            vec![], +            |mut uncaptured_events, ui| { +                let (_, event_statuses) = ui.update( +                    &self.queued_events, +                    cursor, +                    renderer, +                    clipboard, +                    &mut messages, +                ); + +                uncaptured_events.extend( +                    self.queued_events +                        .iter() +                        .zip(event_statuses) +                        .filter_map(|(event, status)| { +                            matches!(status, event::Status::Ignored) +                                .then_some(event) +                        }) +                        .cloned(), +                ); +                uncaptured_events +            }, +        ); + +        self.queued_events.clear(); +        messages.append(&mut self.queued_messages); +        debug.event_processing_finished(); + +        let commands = if messages.is_empty() { +            debug.draw_started(); + +            for ui in &mut user_interfaces { +                self.mouse_interaction = +                    ui.draw(renderer, theme, style, cursor); +            } + +            debug.draw_finished(); + +            self.caches = Some( +                user_interfaces +                    .drain(..) +                    .map(UserInterface::into_cache) +                    .collect(), +            ); + +            None +        } else { +            let temp_caches = user_interfaces +                .drain(..) +                .map(UserInterface::into_cache) +                .collect(); + +            drop(user_interfaces); + +            let commands = Command::batch(messages.into_iter().map(|msg| { +                debug.log_message(&msg); + +                debug.update_started(); +                let command = self.program.update(msg); +                debug.update_finished(); + +                command +            })); + +            let mut user_interfaces = build_user_interfaces( +                &self.program, +                temp_caches, +                renderer, +                bounds, +                debug, +            ); + +            debug.draw_started(); +            for ui in &mut user_interfaces { +                self.mouse_interaction = +                    ui.draw(renderer, theme, style, cursor); +            } +            debug.draw_finished(); + +            self.caches = Some( +                user_interfaces +                    .drain(..) +                    .map(UserInterface::into_cache) +                    .collect(), +            ); + +            Some(commands) +        }; + +        (uncaptured_events, commands) +    } + +    /// Applies widget [`Operation`]s to the [`State`]. +    pub fn operate( +        &mut self, +        renderer: &mut P::Renderer, +        operations: impl Iterator<Item = Box<dyn Operation<P::Message>>>, +        bounds: Size, +        debug: &mut Debug, +    ) { +        let mut user_interfaces = build_user_interfaces( +            &self.program, +            self.caches.take().unwrap(), +            renderer, +            bounds, +            debug, +        ); + +        for operation in operations { +            let mut current_operation = Some(operation); + +            while let Some(mut operation) = current_operation.take() { +                for ui in &mut user_interfaces { +                    ui.operate(renderer, operation.as_mut()); +                } + +                match operation.finish() { +                    operation::Outcome::None => {} +                    operation::Outcome::Some(message) => { +                        self.queued_messages.push(message); +                    } +                    operation::Outcome::Chain(next) => { +                        current_operation = Some(next); +                    } +                }; +            } +        } + +        self.caches = Some( +            user_interfaces +                .drain(..) +                .map(UserInterface::into_cache) +                .collect(), +        ); +    } +} + +fn build_user_interfaces<'a, P: Program>( +    program: &'a P, +    mut caches: Vec<user_interface::Cache>, +    renderer: &mut P::Renderer, +    size: Size, +    debug: &mut Debug, +) -> Vec<UserInterface<'a, P::Message, P::Renderer>> { +    caches +        .drain(..) +        .map(|cache| { +            build_user_interface(program, cache, renderer, size, debug) +        }) +        .collect() +} + +fn build_user_interface<'a, P: Program>( +    program: &'a P, +    cache: user_interface::Cache, +    renderer: &mut P::Renderer, +    size: Size, +    debug: &mut Debug, +) -> UserInterface<'a, P::Message, P::Renderer> { +    debug.view_started(); +    let view = program.view(); +    debug.view_finished(); + +    debug.layout_started(); +    let user_interface = UserInterface::build(view, size, cache, renderer); +    debug.layout_finished(); + +    user_interface +}  | 
