summaryrefslogblamecommitdiffstats
path: root/runtime/src/user_interface.rs
blob: 9b396c6939a7be2ee4db8f4b199e9bf160b58601 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                            





                                      
                  
                                                                            
  
                   
 

                                                                       
                                

                                                                          
                                                                 
   
             

                                                                                
   
                                                                                   
                                       

                                                        
                       
                        
                                  
                 

 
                                                                              
     
                                    
 




                                                                         


                                                                              
                                                    

                 
                         
                                     

           



                                              
                                                                                             
                                                
           

                                                                

                                

                                         
                                                     
                                               
                                                       







                                                      
                            
                      
                              








                                                         
                                                                 
                
                     
                     
                                
               
                               
 


                                        
                                           
                       


                                                     
 
                       
                 
                 
                  
                          
                   


         




                                                                               
                 

                                                                      

                 
                         
                                     

           



                                              
                                                                                             
                                                
           


                                          
                                                                

                                
                                         
                                                     
                                               
                                                       
                                                  
                                            


                                       
                                      

              
                                      


                                                          
                            
                      
                              

              
                                        
                                                                
                        
                       
                              
                               

                             



                                                
                                               



                                        

                  
                         
                              
                                
                                      
                                    
                                      

                                   
                                 
                                                             
                                                     
 


                                                   





                                            

                                           
 
                                                                           
                                     

                                                               
                                                              
                                                
 
                                 

                                                     
                               

                                         
                           




                               
                                                          

                                                                            
 


                                                                     
                                                             
                                        
                                 
                                                                      

                      






                                                        
                                             


                                                       
 





                                                               
                                                
                                                                  
                       
                 
 
                                                
                                    

                 
 









                                                             
                  





                                          
 
                                        
 
                                         
                
                                                                

          

                                                         

                                                         
                                   
                   
                                  
                                            



                                                                      

                                                     
                                                 
                                    
                          
                                            
                                

                              
                               
                              
                  
 
                                                                    


                                        

                                                                            
 
                                            
                                                             
                                        
                                 


                                                                      


                                        
                                                
                                    

                 
                                                          
              

                       



                               

                                   
                                 
                 


                           

     

                                                                   

                                                                            
       
                                           





                                                              
                         
                                     
                                  

           



                                              
                                                                                  
                                                
           
                                          
                                      
                                         
                                                
                                                                

                                         
                                         
                                                     
                                               
                                                       
                                                  
                                            
                                    
                                      
                                         

              
                                      


                                                          
                            
                      
                              

              


                                                       
                       
                              
                               

                             

                                      
                                                                                                                    


                                                
                                               






                                            


                                
                      
                                
                              
                             


                                        

                                                         
                                                         

                            





                                        
                                      
         



                                                                          
 


                                        




                                                     

                                    
             
                                          
                    
                      
              


                                                
                  
                
                  
          
 

                                   
                     
                  
                  




                                    

                                                                       
                                    
                        
                      
                     















                                                                             
                                
                                    





                                          
                                              
                                        
                                                                            
                                                
                                   

                                      
                          
 






                                                
 




                                                        
                                             



                                                    
                         




                                               

                                        

     



                                                                 
                                              
       


                                      
                     

                      
 


                                       





                                        

                                      
                                       
                                                                           
             


                                                            
                         

                          


         

                                                                         
                                                                          
                                                                             

     

                                                                           
                                      
                                   


     
                                                  



                        

            



                                                                                
                           


                                         







                           
 
                                                           
                       
                
                                                                  
             


                                                                     
             



                                                                                       
      
 
//! Implement your own event loop to drive a user interface.
use crate::core::event::{self, Event};
use crate::core::layout;
use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget;
use crate::core::window;
use crate::core::{
    Clipboard, Element, InputMethod, Layout, Rectangle, Shell, Size, Vector,
};
use crate::overlay;

/// A set of interactive graphical elements with a specific [`Layout`].
///
/// It can be updated and drawn.
///
/// Iced tries to avoid dictating how to write your event loop. You are in
/// charge of using this type in your system in any way you want.
///
/// # Example
/// The [`integration`] example uses a [`UserInterface`] to integrate Iced in an
/// existing graphical application.
///
/// [`integration`]: https://github.com/iced-rs/iced/tree/0.13/examples/integration
#[allow(missing_debug_implementations)]
pub struct UserInterface<'a, Message, Theme, Renderer> {
    root: Element<'a, Message, Theme, Renderer>,
    base: layout::Node,
    state: widget::Tree,
    overlay: Option<layout::Node>,
    bounds: Size,
}

impl<'a, Message, Theme, Renderer> UserInterface<'a, Message, Theme, Renderer>
where
    Renderer: crate::core::Renderer,
{
    /// Builds a user interface for an [`Element`].
    ///
    /// It is able to avoid expensive computations when using a [`Cache`]
    /// obtained from a previous instance of a [`UserInterface`].
    ///
    /// # Example
    /// Imagine we want to build a [`UserInterface`] for
    /// [the counter example that we previously wrote](index.html#usage). Here
    /// is naive way to set up our application loop:
    ///
    /// ```no_run
    /// # mod iced_wgpu {
    /// #     pub type Renderer = ();
    /// # }
    /// #
    /// # pub struct Counter;
    /// #
    /// # impl Counter {
    /// #     pub fn new() -> Self { Counter }
    /// #     pub fn view(&self) -> iced_core::Element<(), (), Renderer> { unimplemented!() }
    /// #     pub fn update(&mut self, _: ()) {}
    /// # }
    /// use iced_runtime::core::Size;
    /// use iced_runtime::user_interface::{self, UserInterface};
    /// use iced_wgpu::Renderer;
    ///
    /// // Initialization
    /// let mut counter = Counter::new();
    /// let mut cache = user_interface::Cache::new();
    /// let mut renderer = Renderer::default();
    /// let mut window_size = Size::new(1024.0, 768.0);
    ///
    /// // Application loop
    /// loop {
    ///     // Process system events here...
    ///
    ///     // Build the user interface
    ///     let user_interface = UserInterface::build(
    ///         counter.view(),
    ///         window_size,
    ///         cache,
    ///         &mut renderer,
    ///     );
    ///
    ///     // Update and draw the user interface here...
    ///     // ...
    ///
    ///     // Obtain the cache for the next iteration
    ///     cache = user_interface.into_cache();
    /// }
    /// ```
    pub fn build<E: Into<Element<'a, Message, Theme, Renderer>>>(
        root: E,
        bounds: Size,
        cache: Cache,
        renderer: &mut Renderer,
    ) -> Self {
        let root = root.into();

        let Cache { mut state } = cache;
        state.diff(root.as_widget());

        let base = root.as_widget().layout(
            &mut state,
            renderer,
            &layout::Limits::new(Size::ZERO, bounds),
        );

        UserInterface {
            root,
            base,
            state,
            overlay: None,
            bounds,
        }
    }

    /// Updates the [`UserInterface`] by processing each provided [`Event`].
    ///
    /// It returns __messages__ that may have been produced as a result of user
    /// interactions. You should feed these to your __update logic__.
    ///
    /// # Example
    /// Let's allow our [counter](index.html#usage) to change state by
    /// completing [the previous example](#example):
    ///
    /// ```no_run
    /// # mod iced_wgpu {
    /// #     pub type Renderer = ();
    /// # }
    /// #
    /// # pub struct Counter;
    /// #
    /// # impl Counter {
    /// #     pub fn new() -> Self { Counter }
    /// #     pub fn view(&self) -> iced_core::Element<(), (), Renderer> { unimplemented!() }
    /// #     pub fn update(&mut self, _: ()) {}
    /// # }
    /// use iced_runtime::core::clipboard;
    /// use iced_runtime::core::mouse;
    /// use iced_runtime::core::Size;
    /// use iced_runtime::user_interface::{self, UserInterface};
    /// use iced_wgpu::Renderer;
    ///
    /// let mut counter = Counter::new();
    /// let mut cache = user_interface::Cache::new();
    /// let mut renderer = Renderer::default();
    /// let mut window_size = Size::new(1024.0, 768.0);
    /// let mut cursor = mouse::Cursor::default();
    /// let mut clipboard = clipboard::Null;
    ///
    /// // Initialize our event storage
    /// let mut events = Vec::new();
    /// let mut messages = Vec::new();
    ///
    /// loop {
    ///     // Obtain system events...
    ///
    ///     let mut user_interface = UserInterface::build(
    ///         counter.view(),
    ///         window_size,
    ///         cache,
    ///         &mut renderer,
    ///     );
    ///
    ///     // Update the user interface
    ///     let (state, event_statuses) = user_interface.update(
    ///         &events,
    ///         cursor,
    ///         &mut renderer,
    ///         &mut clipboard,
    ///         &mut messages
    ///     );
    ///
    ///     cache = user_interface.into_cache();
    ///
    ///     // Process the produced messages
    ///     for message in messages.drain(..) {
    ///         counter.update(message);
    ///     }
    /// }
    /// ```
    pub fn update(
        &mut self,
        events: &[Event],
        cursor: mouse::Cursor,
        renderer: &mut Renderer,
        clipboard: &mut dyn Clipboard,
        messages: &mut Vec<Message>,
    ) -> (State, Vec<event::Status>) {
        use std::mem::ManuallyDrop;

        let mut outdated = false;
        let mut redraw_request = window::RedrawRequest::Wait;
        let mut input_method = InputMethod::Disabled;

        let mut manual_overlay = ManuallyDrop::new(
            self.root
                .as_widget_mut()
                .overlay(
                    &mut self.state,
                    Layout::new(&self.base),
                    renderer,
                    Vector::ZERO,
                )
                .map(overlay::Nested::new),
        );

        let (base_cursor, overlay_statuses) = if manual_overlay.is_some() {
            let bounds = self.bounds;

            let mut overlay = manual_overlay.as_mut().unwrap();
            let mut layout = overlay.layout(renderer, bounds);
            let mut event_statuses = Vec::new();

            for event in events {
                let mut shell = Shell::new(messages);

                overlay.update(
                    event,
                    Layout::new(&layout),
                    cursor,
                    renderer,
                    clipboard,
                    &mut shell,
                );

                event_statuses.push(shell.event_status());
                redraw_request = redraw_request.min(shell.redraw_request());
                input_method.merge(shell.input_method());

                if shell.is_layout_invalid() {
                    let _ = ManuallyDrop::into_inner(manual_overlay);

                    self.base = self.root.as_widget().layout(
                        &mut self.state,
                        renderer,
                        &layout::Limits::new(Size::ZERO, self.bounds),
                    );

                    manual_overlay = ManuallyDrop::new(
                        self.root
                            .as_widget_mut()
                            .overlay(
                                &mut self.state,
                                Layout::new(&self.base),
                                renderer,
                                Vector::ZERO,
                            )
                            .map(overlay::Nested::new),
                    );

                    if manual_overlay.is_none() {
                        break;
                    }

                    overlay = manual_overlay.as_mut().unwrap();

                    shell.revalidate_layout(|| {
                        layout = overlay.layout(renderer, bounds);
                    });
                }

                if shell.are_widgets_invalid() {
                    outdated = true;
                }
            }

            let base_cursor = if manual_overlay
                .as_mut()
                .and_then(|overlay| {
                    cursor.position().map(|cursor_position| {
                        overlay.is_over(
                            Layout::new(&layout),
                            renderer,
                            cursor_position,
                        )
                    })
                })
                .unwrap_or_default()
            {
                mouse::Cursor::Unavailable
            } else {
                cursor
            };

            self.overlay = Some(layout);

            (base_cursor, event_statuses)
        } else {
            (cursor, vec![event::Status::Ignored; events.len()])
        };

        let viewport = Rectangle::with_size(self.bounds);

        let _ = ManuallyDrop::into_inner(manual_overlay);

        let event_statuses = events
            .iter()
            .zip(overlay_statuses)
            .map(|(event, overlay_status)| {
                if matches!(overlay_status, event::Status::Captured) {
                    return overlay_status;
                }

                let mut shell = Shell::new(messages);

                self.root.as_widget_mut().update(
                    &mut self.state,
                    event,
                    Layout::new(&self.base),
                    base_cursor,
                    renderer,
                    clipboard,
                    &mut shell,
                    &viewport,
                );

                if shell.event_status() == event::Status::Captured {
                    self.overlay = None;
                }

                redraw_request = redraw_request.min(shell.redraw_request());
                input_method.merge(shell.input_method());

                shell.revalidate_layout(|| {
                    self.base = self.root.as_widget().layout(
                        &mut self.state,
                        renderer,
                        &layout::Limits::new(Size::ZERO, self.bounds),
                    );

                    self.overlay = None;
                });

                if shell.are_widgets_invalid() {
                    outdated = true;
                }

                shell.event_status().merge(overlay_status)
            })
            .collect();

        (
            if outdated {
                State::Outdated
            } else {
                State::Updated {
                    redraw_request,
                    input_method,
                }
            },
            event_statuses,
        )
    }

    /// Draws the [`UserInterface`] with the provided [`Renderer`].
    ///
    /// It returns the current [`mouse::Interaction`]. You should update the
    /// icon of the mouse cursor accordingly in your system.
    ///
    /// [`Renderer`]: crate::core::Renderer
    ///
    /// # Example
    /// We can finally draw our [counter](index.html#usage) by
    /// [completing the last example](#example-1):
    ///
    /// ```no_run
    /// # mod iced_wgpu {
    /// #     pub type Renderer = ();
    /// #     pub type Theme = ();
    /// # }
    /// #
    /// # pub struct Counter;
    /// #
    /// # impl Counter {
    /// #     pub fn new() -> Self { Counter }
    /// #     pub fn view(&self) -> Element<(), (), Renderer> { unimplemented!() }
    /// #     pub fn update(&mut self, _: ()) {}
    /// # }
    /// use iced_runtime::core::clipboard;
    /// use iced_runtime::core::mouse;
    /// use iced_runtime::core::renderer;
    /// use iced_runtime::core::{Element, Size};
    /// use iced_runtime::user_interface::{self, UserInterface};
    /// use iced_wgpu::{Renderer, Theme};
    ///
    /// let mut counter = Counter::new();
    /// let mut cache = user_interface::Cache::new();
    /// let mut renderer = Renderer::default();
    /// let mut window_size = Size::new(1024.0, 768.0);
    /// let mut cursor = mouse::Cursor::default();
    /// let mut clipboard = clipboard::Null;
    /// let mut events = Vec::new();
    /// let mut messages = Vec::new();
    /// let mut theme = Theme::default();
    ///
    /// loop {
    ///     // Obtain system events...
    ///
    ///     let mut user_interface = UserInterface::build(
    ///         counter.view(),
    ///         window_size,
    ///         cache,
    ///         &mut renderer,
    ///     );
    ///
    ///     // Update the user interface
    ///     let event_statuses = user_interface.update(
    ///         &events,
    ///         cursor,
    ///         &mut renderer,
    ///         &mut clipboard,
    ///         &mut messages
    ///     );
    ///
    ///     // Draw the user interface
    ///     let mouse_interaction = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor);
    ///
    ///     cache = user_interface.into_cache();
    ///
    ///     for message in messages.drain(..) {
    ///         counter.update(message);
    ///     }
    ///
    ///     // Update mouse cursor icon...
    ///     // Flush rendering operations...
    /// }
    /// ```
    pub fn draw(
        &mut self,
        renderer: &mut Renderer,
        theme: &Theme,
        style: &renderer::Style,
        cursor: mouse::Cursor,
    ) -> mouse::Interaction {
        // TODO: Move to shell level (?)
        renderer.clear();

        let viewport = Rectangle::with_size(self.bounds);

        let base_cursor = if let Some(mut overlay) = self
            .root
            .as_widget_mut()
            .overlay(
                &mut self.state,
                Layout::new(&self.base),
                renderer,
                Vector::ZERO,
            )
            .map(overlay::Nested::new)
        {
            let overlay_layout = self
                .overlay
                .take()
                .unwrap_or_else(|| overlay.layout(renderer, self.bounds));

            let cursor = if cursor
                .position()
                .map(|cursor_position| {
                    overlay.is_over(
                        Layout::new(&overlay_layout),
                        renderer,
                        cursor_position,
                    )
                })
                .unwrap_or_default()
            {
                mouse::Cursor::Unavailable
            } else {
                cursor
            };

            self.overlay = Some(overlay_layout);

            cursor
        } else {
            cursor
        };

        self.root.as_widget().draw(
            &self.state,
            renderer,
            theme,
            style,
            Layout::new(&self.base),
            base_cursor,
            &viewport,
        );

        let base_interaction = self.root.as_widget().mouse_interaction(
            &self.state,
            Layout::new(&self.base),
            base_cursor,
            &viewport,
            renderer,
        );

        let Self {
            overlay,
            root,
            base,
            ..
        } = self;

        // TODO: Currently, we need to call Widget::overlay twice to
        // implement the painter's algorithm properly.
        //
        // Once we have a proper persistent widget tree, we should be able to
        // avoid this additional call.
        overlay
            .as_ref()
            .and_then(|layout| {
                root.as_widget_mut()
                    .overlay(
                        &mut self.state,
                        Layout::new(base),
                        renderer,
                        Vector::ZERO,
                    )
                    .map(overlay::Nested::new)
                    .map(|mut overlay| {
                        let overlay_interaction = overlay.mouse_interaction(
                            Layout::new(layout),
                            cursor,
                            &viewport,
                            renderer,
                        );

                        overlay.draw(
                            renderer,
                            theme,
                            style,
                            Layout::new(layout),
                            cursor,
                        );

                        if cursor
                            .position()
                            .map(|cursor_position| {
                                overlay.is_over(
                                    Layout::new(layout),
                                    renderer,
                                    cursor_position,
                                )
                            })
                            .unwrap_or_default()
                        {
                            overlay_interaction
                        } else {
                            base_interaction
                        }
                    })
            })
            .unwrap_or(base_interaction)
    }

    /// Applies a [`widget::Operation`] to the [`UserInterface`].
    pub fn operate(
        &mut self,
        renderer: &Renderer,
        operation: &mut dyn widget::Operation,
    ) {
        self.root.as_widget().operate(
            &mut self.state,
            Layout::new(&self.base),
            renderer,
            operation,
        );

        if let Some(mut overlay) = self
            .root
            .as_widget_mut()
            .overlay(
                &mut self.state,
                Layout::new(&self.base),
                renderer,
                Vector::ZERO,
            )
            .map(overlay::Nested::new)
        {
            if self.overlay.is_none() {
                self.overlay = Some(overlay.layout(renderer, self.bounds));
            }

            overlay.operate(
                Layout::new(self.overlay.as_ref().unwrap()),
                renderer,
                operation,
            );
        }
    }

    /// Relayouts and returns a new  [`UserInterface`] using the provided
    /// bounds.
    pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self {
        Self::build(self.root, bounds, Cache { state: self.state }, renderer)
    }

    /// Extract the [`Cache`] of the [`UserInterface`], consuming it in the
    /// process.
    pub fn into_cache(self) -> Cache {
        Cache { state: self.state }
    }
}

/// Reusable data of a specific [`UserInterface`].
#[derive(Debug)]
pub struct Cache {
    state: widget::Tree,
}

impl Cache {
    /// Creates an empty [`Cache`].
    ///
    /// You should use this to initialize a [`Cache`] before building your first
    /// [`UserInterface`].
    pub fn new() -> Cache {
        Cache {
            state: widget::Tree::empty(),
        }
    }
}

impl Default for Cache {
    fn default() -> Cache {
        Cache::new()
    }
}

/// The resulting state after updating a [`UserInterface`].
#[derive(Debug, Clone)]
pub enum State {
    /// The [`UserInterface`] is outdated and needs to be rebuilt.
    Outdated,

    /// The [`UserInterface`] is up-to-date and can be reused without
    /// rebuilding.
    Updated {
        /// The [`window::RedrawRequest`] describing when a redraw should be performed.
        redraw_request: window::RedrawRequest,
        /// The current [`InputMethod`] strategy of the user interface.
        input_method: InputMethod,
    },
}