普通视图

发现新文章,点击刷新页面。
昨天 — 2025年1月17日首页

Tauri(五)——实现托盘菜单和图标切换功能

2025年1月17日 22:47

前言

在桌面端应用中,托盘图标是常见的功能,本文将以 Tauri V2 框架为例,展示如何实现托盘菜单以及根据主题切换托盘图标的功能。以下是效果截图和详细实现步骤和代码说明。

image.png

1. 修改 Cargo.toml 添加依赖

首先,在 src-tauri/Cargo.toml 文件中添加如下依赖:

[dependencies]
tauri = { version = "2.0.6", features = ["tray-icon", "image-png"] }
  • tray-icon: 启用托盘图标功能。
  • image-png: 支持自定义 PNG 图标。

2. 实现托盘菜单功能

在 Rust 中,我们创建一个 enable_tray 函数,用于初始化托盘菜单及其事件。

enable_tray 函数

fn enable_tray(app: &mut tauri::App) {
    use tauri::{
        image::Image,
        menu::{MenuBuilder, MenuItem},
        tray::TrayIconBuilder,
    };
    
    // 退出按钮
    let quit_i = MenuItem::with_id(app, "quit", "Quit Coco", true, None::<&str>).unwrap();
    // 设置按钮
    let settings_i = MenuItem::with_id(app, "settings", "Settings...", true, None::<&str>).unwrap();
    // 打开按钮
    let open_i = MenuItem::with_id(app, "open", "Open Coco", true, None::<&str>).unwrap();
    // 关于按钮
    let about_i = MenuItem::with_id(app, "about", "About Coco", true, None::<&str>).unwrap();
    // 隐藏按钮
    let hide_i = MenuItem::with_id(app, "hide", "Hide Coco", true, None::<&str>).unwrap();
    // ......

    // 按照一定顺序 把按钮 放到 菜单里
    let menu = MenuBuilder::new(app)
        .item(&open_i)
        .separator() // 分割线
        .item(&hide_i)
        .item(&about_i)
        .item(&settings_i)
        .separator() // 分割线
        .item(&quit_i)
        .build()
        .unwrap();

    let _tray = TrayIconBuilder::with_id("tray")
        // .icon(app.default_window_icon().unwrap().clone()) // 默认的图片
        .icon(Image::from_bytes(include_bytes!("../icons/light@2x.png")).expect("REASON")) // 自定义的图片
        .menu(&menu)
        .on_menu_event(|app, event| match event.id.as_ref() {
            "open" => {
                handle_open_coco(app);  // 打开事件
            }
            "hide" => {
                handle_hide_coco(app);
            }
            "about" => {
                let _ = app.emit("open_settings", "about");
            }
            "settings" => {
                // windows failed to open second window, issue: https://github.com/tauri-apps/tauri/issues/11144 https://github.com/tauri-apps/tauri/issues/8196
                //#[cfg(windows)]
                let _ = app.emit("open_settings", "");

                // #[cfg(not(windows))]
                // open_settings(&app);
            }
            "quit" => {
                println!("quit menu item was clicked");
                app.exit(0);
            }
            _ => {
                println!("menu item {:?} not handled", event.id);
            }
        })
        .build(app)
        .unwrap();
}

功能说明

  • 菜单项创建:使用 MenuItem::with_id 方法创建菜单项并设置唯一 ID 和显示文本。
  • 菜单构建:通过 MenuBuilder 组合菜单项并添加分隔符。
  • 托盘图标构建:通过 TrayIconBuilder 设置图标、菜单及点击事件。
  • 事件监听:在 on_menu_event 中根据菜单项 ID 处理对应事件。

3. 注册托盘菜单

在 Tauri 应用启动时,调用 enable_tray 注册托盘菜单。

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    let mut ctx = tauri::generate_context!();

    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![
            switch_tray_icon, // 切换托盘图标
        ])
        .setup(|app| {
            init(app.app_handle());

            enable_tray(app); // 注册事件

            Ok(())
        })
        .run(ctx)
        .expect("error while running tauri application");
}

4. 实现托盘图标切换

为了根据主题切换托盘图标,我们需要创建一个 switch_tray_icon 命令。

switch_tray_icon 命令

#[tauri::command]
fn switch_tray_icon(app: tauri::AppHandle, is_dark_mode: bool) {
    let app_handle = app.app_handle();

    println!("is_dark_mode: {}", is_dark_mode);

    const DARK_ICON_PATH: &[u8] = include_bytes!("../icons/dark@2x.png");
    const LIGHT_ICON_PATH: &[u8] = include_bytes!("../icons/light@2x.png");

    // 根据 app 的主题切换 图标
    let icon_path: &[u8] = if is_dark_mode {
        DARK_ICON_PATH
    } else {
        LIGHT_ICON_PATH
    };

    // 获取托盘
    let tray = match app_handle.tray_by_id("tray") {
        Some(tray) => tray,
        None => {
            eprintln!("Tray with ID 'tray' not found");
            return;
        }
    };

    // 设置图标
    if let Err(e) = tray.set_icon(Some(
        tauri::image::Image::from_bytes(icon_path)
            .unwrap_or_else(|e| panic!("Failed to load icon from bytes: {}", e)),
    )) {
        eprintln!("Failed to set tray icon: {}", e);
    }
}

代码说明

  • 动态加载图标:根据 is_dark_mode 参数决定使用亮色或暗色图标。
  • 更新托盘图标:通过 set_icon 方法更新图标。
  • 错误处理:在托盘实例不存在或图标加载失败时记录错误日志。

5. 前端调用 Rust 命令

前端可以通过 Tauri 的 invoke API 调用 switch_tray_icon 命令。

示例代码

import { invoke } from "@tauri-apps/api/core";

async function switchTrayIcon(value: "dark" | "light") {
    try {
      // invoke  switch_tray_icon 事件名 isDarkMode 参数名
      await invoke("switch_tray_icon", { isDarkMode: value === "dark" });
    } catch (err) {
      console.error("Failed to switch tray icon:", err);
    }
  }

在主题切换时调用 switchTrayIcon 即可实现图标动态切换。

小结

通过本文的实现,我们完成了以下功能:

  1. 创建自定义托盘菜单。(更丰富的菜单内容可以自行扩展了)
  2. 响应托盘菜单事件。
  3. 根据主题动态切换托盘图标。(不仅仅可以主题切换图标,还可以依据 app 行为修改对应的图标)

这种方式为 Tauri 应用提供了更加友好的用户体验。如果有其他需求,可以在菜单事件中扩展更多功能。

参考

  1. v2.tauri.app/learn/syste…
  2. github.com/infinilabs/…

开源

最近,我正在基于 Tauri 开发一款项目,名为 Coco。目前已开源,项目仍在不断完善中,欢迎大家前往支持并为项目点亮免费的 star 🌟!

作为个人的第一个 Tauri 项目,开发过程中也是边探索边学习。希望能与志同道合的朋友一起交流经验、共同成长!

代码中如有问题或不足之处,期待小伙伴们的宝贵建议和指导!

非常感谢您的支持与关注!

❌
❌