adabraka-gpui 0.4.0

Adabraka's GPU-accelerated UI framework (fork of GPUI from Zed - github.com/zed-industries/zed)
Documentation
#![allow(dead_code)]

use std::sync::{Arc, Mutex};

use crate::platform::TrayMenuItem;
use crate::{SharedString, TrayIconEvent};

type TrayActionCallback = Arc<Mutex<Option<Box<dyn Fn(SharedString) + Send>>>>;
type TrayClickCallback = Arc<Mutex<Option<Box<dyn Fn(TrayIconEvent) + Send>>>>;

struct GpuiTray {
    icon_data: Vec<u8>,
    tooltip: String,
    menu_items: Vec<TrayMenuItem>,
    action_callback: TrayActionCallback,
    click_callback: TrayClickCallback,
}

impl ksni::Tray for GpuiTray {
    fn icon_pixmap(&self) -> Vec<ksni::Icon> {
        if self.icon_data.is_empty() {
            return vec![];
        }
        if let Ok(img) = image::load_from_memory(&self.icon_data) {
            let rgba = img.to_rgba8();
            let width = rgba.width() as i32;
            let height = rgba.height() as i32;
            let raw = rgba.as_raw();
            let mut argb_data = Vec::with_capacity(raw.len());
            for pixel in raw.chunks_exact(4) {
                argb_data.push(pixel[3]);
                argb_data.push(pixel[0]);
                argb_data.push(pixel[1]);
                argb_data.push(pixel[2]);
            }
            vec![ksni::Icon {
                width,
                height,
                data: argb_data,
            }]
        } else {
            vec![]
        }
    }

    fn title(&self) -> String {
        self.tooltip.clone()
    }

    fn tool_tip(&self) -> ksni::ToolTip {
        ksni::ToolTip {
            title: self.tooltip.clone(),
            ..Default::default()
        }
    }

    fn activate(&mut self, _x: i32, _y: i32) {
        if let Ok(guard) = self.click_callback.lock() {
            if let Some(ref cb) = *guard {
                cb(TrayIconEvent::LeftClick);
            }
        }
    }

    fn secondary_activate(&mut self, _x: i32, _y: i32) {
        if let Ok(guard) = self.click_callback.lock() {
            if let Some(ref cb) = *guard {
                cb(TrayIconEvent::RightClick);
            }
        }
    }

    fn menu(&self) -> Vec<ksni::MenuItem<Self>> {
        self.menu_items
            .iter()
            .map(|item| convert_menu_item(item, &self.action_callback))
            .collect()
    }
}

fn convert_menu_item(
    item: &TrayMenuItem,
    action_callback: &TrayActionCallback,
) -> ksni::MenuItem<GpuiTray> {
    match item {
        TrayMenuItem::Action { label, id } => {
            let id_clone = id.clone();
            let cb = action_callback.clone();
            ksni::MenuItem::Standard(ksni::menu::StandardItem {
                label: label.to_string(),
                activate: Box::new(move |_tray: &mut GpuiTray| {
                    if let Ok(guard) = cb.lock() {
                        if let Some(ref callback) = *guard {
                            callback(id_clone.clone());
                        }
                    }
                }),
                ..Default::default()
            })
        }
        TrayMenuItem::Separator => ksni::MenuItem::Separator,
        TrayMenuItem::Submenu { label, items } => ksni::MenuItem::SubMenu(ksni::menu::SubMenu {
            label: label.to_string(),
            submenu: items
                .iter()
                .map(|i| convert_menu_item(i, action_callback))
                .collect(),
            ..Default::default()
        }),
        TrayMenuItem::Toggle { label, checked, id } => {
            let id_clone = id.clone();
            let cb = action_callback.clone();
            ksni::MenuItem::Standard(ksni::menu::StandardItem {
                label: label.to_string(),
                icon_name: if *checked {
                    "checkbox-checked-symbolic".to_string()
                } else {
                    String::new()
                },
                activate: Box::new(move |_tray: &mut GpuiTray| {
                    if let Ok(guard) = cb.lock() {
                        if let Some(ref callback) = *guard {
                            callback(id_clone.clone());
                        }
                    }
                }),
                ..Default::default()
            })
        }
    }
}

pub struct LinuxTray {
    handle: Option<ksni::Handle<GpuiTray>>,
    action_callback: TrayActionCallback,
    click_callback: TrayClickCallback,
}

impl LinuxTray {
    pub fn new() -> Self {
        Self {
            handle: None,
            action_callback: Arc::new(Mutex::new(None)),
            click_callback: Arc::new(Mutex::new(None)),
        }
    }

    fn ensure_started(&mut self) {
        if self.handle.is_some() {
            return;
        }
        let tray = GpuiTray {
            icon_data: Vec::new(),
            tooltip: String::new(),
            menu_items: Vec::new(),
            action_callback: self.action_callback.clone(),
            click_callback: self.click_callback.clone(),
        };
        let service = ksni::TrayService::new(tray);
        self.handle = Some(service.handle());
        service.spawn();
    }

    pub fn set_icon(&mut self, icon_data: Option<&[u8]>) {
        self.ensure_started();
        if let Some(handle) = &self.handle {
            let data = icon_data.unwrap_or(&[]).to_vec();
            handle.update(move |tray: &mut GpuiTray| {
                tray.icon_data = data.clone();
            });
        }
    }

    pub fn set_tooltip(&mut self, tooltip: &str) {
        self.ensure_started();
        if let Some(handle) = &self.handle {
            let tooltip = tooltip.to_string();
            handle.update(move |tray: &mut GpuiTray| {
                tray.tooltip = tooltip.clone();
            });
        }
    }

    pub fn set_menu(&mut self, items: Vec<TrayMenuItem>) {
        self.ensure_started();
        if let Some(handle) = &self.handle {
            handle.update(move |tray: &mut GpuiTray| {
                tray.menu_items = items.clone();
            });
        }
    }

    pub fn set_on_menu_action(&self, callback: Box<dyn Fn(SharedString) + Send>) {
        if let Ok(mut guard) = self.action_callback.lock() {
            *guard = Some(callback);
        }
    }

    pub fn set_on_click(&self, callback: Box<dyn Fn(TrayIconEvent) + Send>) {
        if let Ok(mut guard) = self.click_callback.lock() {
            *guard = Some(callback);
        }
    }

    pub fn shutdown(&mut self) {
        if let Some(handle) = self.handle.take() {
            handle.shutdown();
        }
    }
}

impl Drop for LinuxTray {
    fn drop(&mut self) {
        self.shutdown();
    }
}