249 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			249 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
/* Copyright (C) 2020 Purism SPC
 | 
						||
 * SPDX-License-Identifier: GPL-3.0+
 | 
						||
 */
 | 
						||
 | 
						||
/*! Centrally manages the shape of the UI widgets, and the choice of layout.
 | 
						||
 * 
 | 
						||
 * Coordinates this based on information collated from all possible sources.
 | 
						||
 */
 | 
						||
 | 
						||
use std::cmp::min;
 | 
						||
use ::outputs::c::OutputHandle;
 | 
						||
 | 
						||
pub mod c {
 | 
						||
    use super::*;
 | 
						||
    use std::os::raw::c_void;
 | 
						||
    use ::util::c::Wrapped;
 | 
						||
    
 | 
						||
    /// ServerContextService*
 | 
						||
    #[repr(transparent)]
 | 
						||
    pub struct UIManager(*const c_void);
 | 
						||
 | 
						||
    extern "C" {
 | 
						||
        pub fn server_context_service_update_visible(imservice: *const UIManager, active: u32);
 | 
						||
        pub fn server_context_service_release_visibility(imservice: *const UIManager);
 | 
						||
    }
 | 
						||
 | 
						||
    #[no_mangle]
 | 
						||
    pub extern "C"
 | 
						||
    fn squeek_visman_new() -> Wrapped<VisibilityManager> {
 | 
						||
        Wrapped::new(VisibilityManager {
 | 
						||
            ui_manager: None,
 | 
						||
            visibility_state: VisibilityFactors {
 | 
						||
                im_active: false,
 | 
						||
                physical_keyboard_present: false,
 | 
						||
            }
 | 
						||
        })
 | 
						||
    }
 | 
						||
    
 | 
						||
    /// Use to initialize the UI reference
 | 
						||
    #[no_mangle]
 | 
						||
    pub extern "C"
 | 
						||
    fn squeek_visman_set_ui(visman: Wrapped<VisibilityManager>, ui_manager: *const UIManager) {
 | 
						||
        let visman = visman.clone_ref();
 | 
						||
        let mut visman = visman.borrow_mut();
 | 
						||
        visman.set_ui_manager(Some(ui_manager))
 | 
						||
    }
 | 
						||
 | 
						||
    #[no_mangle]
 | 
						||
    pub extern "C"
 | 
						||
    fn squeek_visman_set_keyboard_present(visman: Wrapped<VisibilityManager>, present: u32) {
 | 
						||
        let visman = visman.clone_ref();
 | 
						||
        let mut visman = visman.borrow_mut();
 | 
						||
        visman.set_keyboard_present(present != 0)
 | 
						||
    }
 | 
						||
 | 
						||
    #[no_mangle]
 | 
						||
    pub extern "C"
 | 
						||
    fn squeek_uiman_new() -> Wrapped<Manager> {
 | 
						||
        Wrapped::new(Manager { output: None })
 | 
						||
    }
 | 
						||
 | 
						||
    /// Used to size the layer surface containing all the OSK widgets.
 | 
						||
    #[no_mangle]
 | 
						||
    pub extern "C"
 | 
						||
    fn squeek_uiman_get_perceptual_height(
 | 
						||
        uiman: Wrapped<Manager>,
 | 
						||
    ) -> u32 {
 | 
						||
        let uiman = uiman.clone_ref();
 | 
						||
        let uiman = uiman.borrow();
 | 
						||
        // TODO: what to do when there's no output?
 | 
						||
        uiman.get_perceptual_height().unwrap_or(0)
 | 
						||
    }
 | 
						||
 | 
						||
    #[no_mangle]
 | 
						||
    pub extern "C"
 | 
						||
    fn squeek_uiman_set_output(
 | 
						||
        uiman: Wrapped<Manager>,
 | 
						||
        output: OutputHandle,
 | 
						||
    ) {
 | 
						||
        let uiman = uiman.clone_ref();
 | 
						||
        let mut uiman = uiman.borrow_mut();
 | 
						||
        uiman.output = Some(output);
 | 
						||
    }
 | 
						||
}
 | 
						||
 | 
						||
/// Stores current state of all things influencing what the UI should look like.
 | 
						||
pub struct Manager {
 | 
						||
    /// Shared output handle, current state updated whenever it's needed.
 | 
						||
    // TODO: Stop assuming that the output never changes.
 | 
						||
    // (There's no way for the output manager to update the ui manager.)
 | 
						||
    // FIXME: Turn into an OutputState and apply relevant connections elsewhere.
 | 
						||
    // Otherwise testability and predictablity is low.
 | 
						||
    output: Option<OutputHandle>,
 | 
						||
    //// Pixel size of the surface. Needs explicit updating.
 | 
						||
    //surface_size: Option<Size>,
 | 
						||
}
 | 
						||
 | 
						||
impl Manager {
 | 
						||
    fn get_perceptual_height(&self) -> Option<u32> {
 | 
						||
        let output_info = (&self.output).as_ref()
 | 
						||
            .and_then(|o| o.get_state())
 | 
						||
            .map(|os| (os.scale as u32, os.get_pixel_size()));
 | 
						||
        match output_info {
 | 
						||
            Some((scale, Some(px_size))) => Some({
 | 
						||
                let height = if (px_size.width < 720) & (px_size.width > 0) {
 | 
						||
                    px_size.width * 7 / 12 // to match 360×210
 | 
						||
                } else if px_size.width < 1080 {
 | 
						||
                    360 + (1080 - px_size.width) * 60 / 360 // smooth transition
 | 
						||
                } else {
 | 
						||
                    360
 | 
						||
                };
 | 
						||
 | 
						||
                // Don't exceed half the display size
 | 
						||
                min(height, px_size.height / 2) / scale
 | 
						||
            }),
 | 
						||
            Some((scale, None)) => Some(360 / scale),
 | 
						||
            None => None,
 | 
						||
        }
 | 
						||
    }
 | 
						||
}
 | 
						||
 | 
						||
#[derive(PartialEq, Debug)]
 | 
						||
enum Visibility {
 | 
						||
    Hidden,
 | 
						||
    Visible,
 | 
						||
}
 | 
						||
 | 
						||
#[derive(Debug)]
 | 
						||
enum VisibilityTransition {
 | 
						||
    /// Hide immediately
 | 
						||
    Hide,
 | 
						||
    /// Hide if no show request comes soon
 | 
						||
    Release,
 | 
						||
    /// Show instantly
 | 
						||
    Show,
 | 
						||
    /// Don't do anything
 | 
						||
    NoTransition,
 | 
						||
}
 | 
						||
 | 
						||
/// Contains visibility policy
 | 
						||
#[derive(Clone, Debug)]
 | 
						||
struct VisibilityFactors {
 | 
						||
    im_active: bool,
 | 
						||
    physical_keyboard_present: bool,
 | 
						||
}
 | 
						||
 | 
						||
impl VisibilityFactors {
 | 
						||
    /// Static policy.
 | 
						||
    /// Use when transitioning from an undefined state (e.g. no UI before).
 | 
						||
    fn desired(&self) -> Visibility {
 | 
						||
        match self {
 | 
						||
            VisibilityFactors {
 | 
						||
                im_active: true,
 | 
						||
                physical_keyboard_present: false,
 | 
						||
            } => Visibility::Visible,
 | 
						||
            _ => Visibility::Hidden,
 | 
						||
        }
 | 
						||
    }
 | 
						||
    /// Stateful policy
 | 
						||
    fn transition_to(&self, next: &Self) -> VisibilityTransition {
 | 
						||
        use self::Visibility::*;
 | 
						||
        let im_deactivation = self.im_active && !next.im_active;
 | 
						||
        match (self.desired(), next.desired(), im_deactivation) {
 | 
						||
            (Visible, Hidden, true) => VisibilityTransition::Release,
 | 
						||
            (Visible, Hidden, _) => VisibilityTransition::Hide,
 | 
						||
            (Hidden, Visible, _) => VisibilityTransition::Show,
 | 
						||
            _ => VisibilityTransition::NoTransition,
 | 
						||
        }
 | 
						||
    }
 | 
						||
}
 | 
						||
 | 
						||
// Temporary struct for migration. Should be integrated with Manager eventually.
 | 
						||
pub struct VisibilityManager {
 | 
						||
    /// Owned reference. Be careful, it's shared with C at large
 | 
						||
    ui_manager: Option<*const c::UIManager>,
 | 
						||
    visibility_state: VisibilityFactors,
 | 
						||
}
 | 
						||
 | 
						||
impl VisibilityManager {
 | 
						||
    fn set_ui_manager(&mut self, ui_manager: Option<*const c::UIManager>) {
 | 
						||
        let new = VisibilityManager {
 | 
						||
            ui_manager,
 | 
						||
            ..unsafe { self.clone() }
 | 
						||
        };
 | 
						||
        self.apply_changes(new);
 | 
						||
    }
 | 
						||
 | 
						||
    fn apply_changes(&mut self, new: Self) {
 | 
						||
        if let Some(ui) = &new.ui_manager {
 | 
						||
            if self.ui_manager.is_none() {
 | 
						||
                // Previous state was never applied, so effectively undefined.
 | 
						||
                // Just apply the new one.
 | 
						||
                let new_state = new.visibility_state.desired();
 | 
						||
                unsafe {
 | 
						||
                    c::server_context_service_update_visible(
 | 
						||
                        *ui,
 | 
						||
                        (new_state == Visibility::Visible) as u32,
 | 
						||
                    );
 | 
						||
                }
 | 
						||
            } else {
 | 
						||
                match self.visibility_state.transition_to(&new.visibility_state) {
 | 
						||
                    VisibilityTransition::Hide => unsafe {
 | 
						||
                        c::server_context_service_update_visible(*ui, 0);
 | 
						||
                    },
 | 
						||
                    VisibilityTransition::Show => unsafe {
 | 
						||
                        c::server_context_service_update_visible(*ui, 1);
 | 
						||
                    },
 | 
						||
                    VisibilityTransition::Release => unsafe {
 | 
						||
                        c::server_context_service_release_visibility(*ui);
 | 
						||
                    },
 | 
						||
                    VisibilityTransition::NoTransition => {}
 | 
						||
                }
 | 
						||
            }
 | 
						||
        }
 | 
						||
        *self = new;
 | 
						||
    }
 | 
						||
 | 
						||
    pub fn set_im_active(&mut self, im_active: bool) {
 | 
						||
        let new = VisibilityManager {
 | 
						||
            visibility_state: VisibilityFactors {
 | 
						||
                im_active,
 | 
						||
                ..self.visibility_state.clone()
 | 
						||
            },
 | 
						||
            ..unsafe { self.clone() }
 | 
						||
        };
 | 
						||
        self.apply_changes(new);
 | 
						||
    }
 | 
						||
 | 
						||
    pub fn set_keyboard_present(&mut self, keyboard_present: bool) {
 | 
						||
        let new = VisibilityManager {
 | 
						||
            visibility_state: VisibilityFactors {
 | 
						||
                physical_keyboard_present: keyboard_present,
 | 
						||
                ..self.visibility_state.clone()
 | 
						||
            },
 | 
						||
            ..unsafe { self.clone() }
 | 
						||
        };
 | 
						||
        self.apply_changes(new);
 | 
						||
    }
 | 
						||
 | 
						||
    /// The struct is not really safe to clone due to the ui_manager reference.
 | 
						||
    /// This is only a helper for getting desired visibility.
 | 
						||
    unsafe fn clone(&self) -> Self {
 | 
						||
        VisibilityManager {
 | 
						||
            ui_manager: self.ui_manager.clone(),
 | 
						||
            visibility_state: self.visibility_state.clone(),
 | 
						||
        }
 | 
						||
    }
 | 
						||
}
 |