nativeshell 0.1.16

NativeShell Rust package
Documentation
use std::{
    cell::RefCell,
    collections::HashMap,
    rc::{Rc, Weak},
};

use crate::{
    codec::{
        value::{from_value, to_value},
        MethodCall, MethodCallReply, MethodInvoker, Value,
    },
    util::{Late, OkLog},
    Error, Result,
};

use super::{
    api_constants::*,
    api_model::{MenuAction, MenuCreateRequest, MenuDestroyRequest, MenuOpen, SetMenuRequest},
    platform::menu::{PlatformMenu, PlatformMenuManager},
    Context, EngineHandle, MethodCallHandler, MethodInvokerProvider, RegisteredMethodCallHandler,
    WindowMethodCallResult,
};

struct MenuEntry {
    engine: EngineHandle,
    platform_menu: Rc<PlatformMenu>,
}

pub struct MenuManager {
    context: Context,
    platform_menu_map: HashMap<MenuHandle, MenuEntry>,
    platform_menu_manager: Rc<PlatformMenuManager>,
    next_handle: MenuHandle,
    weak_self: Late<Weak<RefCell<MenuManager>>>,
    invoker_provider: Late<MethodInvokerProvider>,
}

#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct MenuHandle(pub(crate) i64);

pub trait MenuDelegate {
    fn on_menu_open(&self, menu_handle: MenuHandle);
    fn on_menu_action(&self, menu_handle: MenuHandle, id: i64);
    fn get_platform_menu(&self, menu: MenuHandle) -> Result<Rc<PlatformMenu>>;
    fn move_to_previous_menu(&self, menu_handle: MenuHandle);
    fn move_to_next_menu(&self, menu_handle: MenuHandle);
}

impl MenuManager {
    pub(super) fn new(context: Context) -> RegisteredMethodCallHandler<Self> {
        let platform_manager = Rc::new(PlatformMenuManager::new(context.clone()));
        platform_manager.assign_weak_self(Rc::downgrade(&platform_manager));
        Self {
            context: context.clone(),
            platform_menu_map: HashMap::new(),
            platform_menu_manager: platform_manager,
            next_handle: MenuHandle(1),
            weak_self: Late::new(),
            invoker_provider: Late::new(),
        }
        .register(context, channel::MENU_MANAGER)
    }

    pub fn get_platform_menu_manager(&self) -> &PlatformMenuManager {
        &self.platform_menu_manager
    }

    fn on_create_or_update(
        &mut self,
        request: MenuCreateRequest,
        engine: EngineHandle,
    ) -> Result<MenuHandle> {
        let handle = request.handle.unwrap_or_else(|| {
            let res = self.next_handle;
            self.next_handle.0 += 1;
            res
        });
        let entry = self.platform_menu_map.entry(handle);
        let context = self.context.clone();
        let weak_self = self.weak_self.clone();
        let platform_menu = entry
            .or_insert_with(|| {
                let platform_menu = Rc::new(PlatformMenu::new(context, handle, weak_self));
                platform_menu.assign_weak_self(Rc::downgrade(&platform_menu));
                MenuEntry {
                    engine,
                    platform_menu,
                }
            })
            .platform_menu
            .clone();
        platform_menu
            .update_from_menu(request.menu, self)
            .map_err(Error::from)?;

        Ok(handle)
    }

    fn invoker_for_menu(&self, menu_handle: MenuHandle) -> Option<MethodInvoker<Value>> {
        self.platform_menu_map.get(&menu_handle).map(|e| {
            self.invoker_provider
                .get_method_invoker_for_engine(e.engine)
        })
    }

    fn map_result<T>(result: Result<T>) -> WindowMethodCallResult
    where
        T: serde::Serialize,
    {
        result.map(|v| to_value(v).unwrap()).map_err(|e| e.into())
    }
}

impl MethodCallHandler for MenuManager {
    fn on_method_call(
        &mut self,
        call: MethodCall<Value>,
        reply: MethodCallReply<Value>,
        engine: EngineHandle,
    ) {
        match call.method.as_str() {
            method::menu::CREATE_OR_UPDATE => {
                let request: MenuCreateRequest = from_value(&call.args).unwrap();
                let res = self.on_create_or_update(request, engine);
                reply.send(Self::map_result(res));
            }
            method::menu::DESTROY => {
                let request: MenuDestroyRequest = from_value(&call.args).unwrap();
                self.platform_menu_map.remove(&request.handle);
                reply.send_ok(Value::Null);
            }
            method::menu::SET_APP_MENU => {
                let request: SetMenuRequest = from_value(&call.args).unwrap();
                match request.handle {
                    Some(handle) => {
                        let menu = self.platform_menu_map.get(&handle);
                        match menu {
                            Some(menu) => reply.send(Self::map_result(
                                self.platform_menu_manager
                                    .set_app_menu(Some(menu.platform_menu.clone()))
                                    .map_err(|e| e.into()),
                            )),
                            None => {
                                reply.send(Self::map_result::<()>(Err(Error::InvalidMenuHandle)));
                            }
                        }
                    }
                    None => reply.send(Self::map_result(
                        self.platform_menu_manager
                            .set_app_menu(None)
                            .map_err(|e| e.into()),
                    )),
                }
            }
            _ => {}
        };
    }

    fn assign_weak_self(&mut self, weak_self: Weak<RefCell<Self>>) {
        self.weak_self.set(weak_self);
    }

    fn assign_invoker_provider(&mut self, provider: MethodInvokerProvider) {
        self.invoker_provider.set(provider);
    }
}

impl MenuDelegate for MenuManager {
    fn on_menu_action(&self, menu_handle: MenuHandle, id: i64) {
        if let Some(invoker) = self.invoker_for_menu(menu_handle) {
            invoker
                .call_method(
                    method::menu::ON_ACTION,
                    to_value(&MenuAction {
                        handle: menu_handle,
                        id,
                    })
                    .unwrap(),
                    |_| {},
                )
                .ok_log();
        }
    }

    fn on_menu_open(&self, menu_handle: MenuHandle) {
        if let Some(invoker) = self.invoker_for_menu(menu_handle) {
            invoker
                .call_method(
                    method::menu::ON_OPEN,
                    to_value(&MenuOpen {
                        handle: menu_handle,
                    })
                    .unwrap(),
                    |_| {},
                )
                .ok_log();
        }
    }

    fn get_platform_menu(&self, menu: MenuHandle) -> Result<Rc<PlatformMenu>> {
        self.platform_menu_map
            .get(&menu)
            .map(|c| c.platform_menu.clone())
            .ok_or(Error::InvalidMenuHandle)
    }

    fn move_to_previous_menu(&self, menu_handle: MenuHandle) {
        if let Some(invoker) = self.invoker_for_menu(menu_handle) {
            invoker
                .call_method(method::menu_bar::MOVE_TO_PREVIOUS_MENU, Value::Null, |_| {})
                .ok_log();
        }
    }

    fn move_to_next_menu(&self, menu_handle: MenuHandle) {
        if let Some(invoker) = self.invoker_for_menu(menu_handle) {
            invoker
                .call_method(method::menu_bar::MOVE_TO_NEXT_MENU, Value::Null, |_| {})
                .ok_log();
        }
    }
}