字玩FontPlayer开发笔记7 Tauri2动态切换菜单enbaled状态

发布于:2025-02-11 ⋅ 阅读:(35) ⋅ 点赞:(0)

字玩FontPlayer开发笔记7 Tauri2动态切换菜单enbaled状态

字玩FontPlayer是笔者开源的一款字体设计工具,使用Vue3 + ElementUI开发,源代码:
github: https://github.com/HiToysMaker/fontplayer
gitee: https://gitee.com/toysmaker/fontplayer

笔记

字玩目前是用Electron进行桌面端应用打包,但是性能体验不太好,一直想替换成Tauri。Tauri的功能和Electron类似,都可以把前端代码打包生成桌面端(比如Windows和Mac)应用。Tauri只使用系统提供的WebView,不像Electron一样内置Chromium和Node.js,性能体验更佳。

前两天初步完成了Tauri配置和菜单设置,今天继续将原有Electron代码替换成Tauri。在字玩的设计中,菜单按钮的enbaled状态需要动态切换,比如导出图片只有在编辑模式下才能点击,在列表状态下是禁用的状态。原有基本逻辑是,在前端使用一个ref变量editStatus记录状态,使用watch监听editStatus改变,每当editStatus改变时,给Electron端发送消息,Electron端监听到消息后,根据设定好的规则更新菜单状态。虽然只是一个小功能,但是由于笔者对Rust和Tauri的生疏,还是费了不少功夫,在此记录一下。

定义禁用规则

在Rust端,对每个菜单按钮定义禁用规则,传入参数是前端发送过来的edit_status,根据不同的edit_status状态定义禁用规则。关于如何监听前端消息在下面会具体说明。

fn enable(edit_status: &str) -> bool {
  true
}

fn enable_at_edit(edit_status: &str) -> bool {
  match edit_status {
    "edit" | "glyph" => true,
    _ => false,
  }
}

fn enable_at_list(edit_status: &str) -> bool {
  match edit_status {
    "edit" | "glyph" | "pic" => false,
    _ => true,
  }
}

fn template_enable(edit_status: &str) -> bool {
  match edit_status {
    "edit" | "glyph" | "pic" => false,
    _ => true,
  }
}

// 定义用于启用/禁用菜单项的映射
fn build_menu_enabled_map() -> HashMap<String, Box<dyn Fn(&str) -> bool>> {
  let menu_enabled_map: HashMap<String, Box<dyn Fn(&str) -> bool>> = HashMap::from([
    ("about".to_string(), Box::new(enable) as Box<dyn Fn(&str) -> bool>),
    ("create-file".to_string(), Box::new(enable_at_list) as Box<dyn Fn(&str) -> bool>),
    ("open-file".to_string(), Box::new(enable_at_list) as Box<dyn Fn(&str) -> bool>),
    ("save-file".to_string(), Box::new(enable) as Box<dyn Fn(&str) -> bool>),
    ("save-as".to_string(), Box::new(enable) as Box<dyn Fn(&str) -> bool>),
    ("undo".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("redo".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("cut".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("copy".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("paste".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("delete".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("import-font-file".to_string(), Box::new(enable_at_list) as Box<dyn Fn(&str) -> bool>),
    ("import-glyphs".to_string(), Box::new(enable_at_list) as Box<dyn Fn(&str) -> bool>),
    ("import-pic".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("import-svg".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("export-font-file".to_string(), Box::new(enable) as Box<dyn Fn(&str) -> bool>),
    ("export-glyphs".to_string(), Box::new(enable_at_list) as Box<dyn Fn(&str) -> bool>),
    ("export-jpeg".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("export-png".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("export-svg".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("add-character".to_string(), Box::new(enable_at_list) as Box<dyn Fn(&str) -> bool>),
    ("add-icon".to_string(), Box::new(enable_at_list) as Box<dyn Fn(&str) -> bool>),
    ("font-settings".to_string(), Box::new(enable) as Box<dyn Fn(&str) -> bool>),
    ("preference-settings".to_string(), Box::new(enable) as Box<dyn Fn(&str) -> bool>),
    ("language-settings".to_string(), Box::new(enable) as Box<dyn Fn(&str) -> bool>),
    ("template-1".to_string(), Box::new(template_enable) as Box<dyn Fn(&str) -> bool>),
    ("remove-overlap".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
  ]);
  menu_enabled_map
}
定义更新菜单状态方法

接下来定义更新菜单的方法,以供前端调用:

#[tauri::command]
fn toggle_menu_disabled(app: AppHandle, edit_status: String) {
  let map = build_menu_enabled_map();
  let window = app.get_webview_window("main").unwrap();
  let menu = window.menu().unwrap();
  for submenu in menu.items().unwrap() {
    match submenu {
      // 如果是 Submenu 类型,调用 items() 获取子菜单项
      MenuItemKind::Submenu(submenu) => {
        // 获取并遍历子菜单中的菜单项
        for item in submenu.items().unwrap() {
          match item {
            MenuItemKind::MenuItem(item) => {
              let id: String = item.id().0.clone();
              let status: String = edit_status.clone();
              let enabled: bool = map.get(&id).expect("Error")(&status);
              item.set_enabled(enabled);
            }
      
            _ => {
              // 如果是其他未处理的类型,使用 `_` 捕获
            }
          }
        }
      }

      _ => {
        // 如果是其他未处理的类型,使用 `_` 捕获
      }
    }
  }
}

另外需要设置invoke_handler注册函数,这样前端才能调用:

.invoke_handler(tauri::generate_handler![toggle_menu_disabled])
前端调用Rust toggle_menu_disabled方法

在前端,监听editStatus变化,每当editStatus改变时,更新菜单enbaled状态。

const editStatusToString = (status: Status) => {
	if (editStatus.value === Status.Edit) {
		return 'edit'
	} else if (editStatus.value === Status.Glyph) {
		return 'glyph'
	} else if (editStatus.value === Status.Pic) {
		return 'pic'
	}
	return 'list'
}

watch(editStatus, () => {
	invoke('toggle_menu_disabled', { editStatus: editStatusToString(editStatus.value) });
})
附完整Rust端代码

src-tauri/lib.rs

#![allow(unused)]

use tauri::{Manager, Window};
use tauri::Size;
use tauri::{AppHandle, Emitter};
use tauri::menu::{Menu, MenuItem, PredefinedMenuItem, Submenu, MenuItemBuilder, MenuItemKind};
use std::collections::HashMap;

#[tauri::command]
fn test(app: AppHandle) {
  app.emit("create-file", ()).unwrap();
}

fn enable(edit_status: &str) -> bool {
  true
}

fn enable_at_edit(edit_status: &str) -> bool {
  match edit_status {
    "edit" | "glyph" => true,
    _ => false,
  }
}

fn enable_at_list(edit_status: &str) -> bool {
  match edit_status {
    "edit" | "glyph" | "pic" => false,
    _ => true,
  }
}

fn template_enable(edit_status: &str) -> bool {
  match edit_status {
    "edit" | "glyph" | "pic" => false,
    _ => true,
  }
}

// 定义用于启用/禁用菜单项的映射
fn build_menu_enabled_map() -> HashMap<String, Box<dyn Fn(&str) -> bool>> {
  let menu_enabled_map: HashMap<String, Box<dyn Fn(&str) -> bool>> = HashMap::from([
    ("about".to_string(), Box::new(enable) as Box<dyn Fn(&str) -> bool>),
    ("create-file".to_string(), Box::new(enable_at_list) as Box<dyn Fn(&str) -> bool>),
    ("open-file".to_string(), Box::new(enable_at_list) as Box<dyn Fn(&str) -> bool>),
    ("save-file".to_string(), Box::new(enable) as Box<dyn Fn(&str) -> bool>),
    ("save-as".to_string(), Box::new(enable) as Box<dyn Fn(&str) -> bool>),
    ("undo".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("redo".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("cut".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("copy".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("paste".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("delete".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("import-font-file".to_string(), Box::new(enable_at_list) as Box<dyn Fn(&str) -> bool>),
    ("import-glyphs".to_string(), Box::new(enable_at_list) as Box<dyn Fn(&str) -> bool>),
    ("import-pic".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("import-svg".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("export-font-file".to_string(), Box::new(enable) as Box<dyn Fn(&str) -> bool>),
    ("export-glyphs".to_string(), Box::new(enable_at_list) as Box<dyn Fn(&str) -> bool>),
    ("export-jpeg".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("export-png".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("export-svg".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
    ("add-character".to_string(), Box::new(enable_at_list) as Box<dyn Fn(&str) -> bool>),
    ("add-icon".to_string(), Box::new(enable_at_list) as Box<dyn Fn(&str) -> bool>),
    ("font-settings".to_string(), Box::new(enable) as Box<dyn Fn(&str) -> bool>),
    ("preference-settings".to_string(), Box::new(enable) as Box<dyn Fn(&str) -> bool>),
    ("language-settings".to_string(), Box::new(enable) as Box<dyn Fn(&str) -> bool>),
    ("template-1".to_string(), Box::new(template_enable) as Box<dyn Fn(&str) -> bool>),
    ("remove-overlap".to_string(), Box::new(enable_at_edit) as Box<dyn Fn(&str) -> bool>),
  ]);
  menu_enabled_map
}

#[tauri::command]
fn toggle_menu_disabled(app: AppHandle, edit_status: String) {
  let map = build_menu_enabled_map();
  let window = app.get_webview_window("main").unwrap();
  let menu = window.menu().unwrap();
  for submenu in menu.items().unwrap() {
    match submenu {
      // 如果是 Submenu 类型,调用 items() 获取子菜单项
      MenuItemKind::Submenu(submenu) => {
        // 获取并遍历子菜单中的菜单项
        for item in submenu.items().unwrap() {
          match item {
            MenuItemKind::MenuItem(item) => {
              let id: String = item.id().0.clone();
              let status: String = edit_status.clone();
              let enabled: bool = map.get(&id).expect("Error")(&status);
              item.set_enabled(enabled);
            }
      
            _ => {
              // 如果是其他未处理的类型,使用 `_` 捕获
            }
          }
        }
      }

      _ => {
        // 如果是其他未处理的类型,使用 `_` 捕获
      }
    }
  }
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
  tauri::Builder::default()
    .setup(|app| {
      // 获取名为 "main" 的 Webview 窗口句柄
      let window = app.get_webview_window("main").unwrap();

      // 获取窗口尺寸
      let primary_display = window.inner_size().unwrap();
      let screen_width = primary_display.width;
      let screen_height = primary_display.height;

      // 获取主显示器的 DPI 缩放因子
      let scale_factor = window.scale_factor().unwrap();

      // 设置最大窗口尺寸
      let max_width = 1280;
      let max_height = 800;

      // 根据 DPI 缩放因子调整窗口尺寸
      let adjusted_width = (max_width as f64 * scale_factor) as u32;
      let adjusted_height = (max_height as f64 * scale_factor) as u32;

      // 计算窗口大小,确保窗口大小不超过屏幕大小
      let window_width = screen_width.min(adjusted_width);
      let window_height = screen_height.min(adjusted_height);

      // 获取窗口并设置尺寸
      window.set_size(Size::new(tauri::PhysicalSize::new(window_width as u32, window_height as u32))).unwrap();

      app.on_menu_event(move |app, event| {
        if event.id() == "about" {
          println!("about");
        } else if event.id() == "create-file" {
          test(app.app_handle().clone())
        }
      });

      if cfg!(debug_assertions) {
        app.handle().plugin(
          tauri_plugin_log::Builder::default()
            .level(log::LevelFilter::Info)
            .build(),
        )?;
      }
      Ok(())
    })
    .menu(|handle| Menu::with_items(handle, &[
      &Submenu::with_items(
        handle,
        "File",
        true,
        &[
          &MenuItemBuilder::with_id("about", "关于").build(handle).expect("Error")
        ],
      )?,
      &Submenu::with_items(
        handle,
        "文件",
        true,
        &[
          &MenuItemBuilder::with_id("create-file", "新建工程").build(handle).expect("Error"),
          &MenuItemBuilder::with_id("open-file", "打开工程").build(handle).expect("Error"),
          &MenuItemBuilder::with_id("save-file", "保存工程").build(handle).expect("Error"),
          &MenuItemBuilder::with_id("save-as", "另存为").build(handle).expect("Error"),
        ],
      )?,
      &Submenu::with_items(
        handle,
        "编辑",
        true,
        &[
          &MenuItemBuilder::with_id("undo", "撤销").enabled(false).build(handle).expect("Error"),
          &MenuItemBuilder::with_id("redo", "重做").enabled(false).build(handle).expect("Error"),
          &MenuItemBuilder::with_id("cut", "剪切").enabled(false).build(handle).expect("Error"),
          &MenuItemBuilder::with_id("paste", "粘贴").enabled(false).build(handle).expect("Error"),
          &MenuItemBuilder::with_id("copy", "复制").enabled(false).build(handle).expect("Error"),
          &MenuItemBuilder::with_id("delete", "删除").enabled(false).build(handle).expect("Error"),
        ],
      )?,
      &Submenu::with_items(
        handle,
        "导入",
        true,
        &[
          &MenuItemBuilder::with_id("import-font-file", "导入字体库").build(handle).expect("Error"),
          &MenuItemBuilder::with_id("import-glyphs", "导入字形").build(handle).expect("Error"),
          &MenuItemBuilder::with_id("import-pic", "识别图片").enabled(false).build(handle).expect("Error"),
          &MenuItemBuilder::with_id("import-svg", "导入SVG").enabled(false).build(handle).expect("Error"),
        ],
      )?,
      &Submenu::with_items(
        handle,
        "导出",
        true,
        &[
          &MenuItemBuilder::with_id("export-font-file", "导出字体库").build(handle).expect("Error"),
          &MenuItemBuilder::with_id("export-glyphs", "导出字形").build(handle).expect("Error"),
          &MenuItemBuilder::with_id("export-jpeg", "导出JPEG").enabled(false).build(handle).expect("Error"),
          &MenuItemBuilder::with_id("export-png", "导出PNG").enabled(false).build(handle).expect("Error"),
          &MenuItemBuilder::with_id("export-svg", "导出SVG").enabled(false).build(handle).expect("Error"),
        ],
      )?,
      &Submenu::with_items(
        handle,
        "字符与图标",
        true,
        &[
          &MenuItemBuilder::with_id("add-character", "添加字符").build(handle).expect("Error"),
          &MenuItemBuilder::with_id("add-icon", "添加图标").build(handle).expect("Error"),
        ],
      )?,
      &Submenu::with_items(
        handle,
        "设置",
        true,
        &[
          &MenuItemBuilder::with_id("font-settings", "字体设置").build(handle).expect("Error"),
          &MenuItemBuilder::with_id("preference-settings", "偏好设置").build(handle).expect("Error"),
          &MenuItemBuilder::with_id("language-settings", "语言设置").build(handle).expect("Error"),
        ],
      )?,
      &Submenu::with_items(
        handle,
        "模板",
        true,
        &[
          &MenuItemBuilder::with_id("template-1", "测试模板").build(handle).expect("Error"),
        ],
      )?,
      &Submenu::with_items(
        handle,
        "工具",
        true,
        &[
          &MenuItemBuilder::with_id("remove-overlap", "去除重叠").enabled(false).build(handle).expect("Error"),
        ],
      )?,
    ]))
    .invoke_handler(tauri::generate_handler![toggle_menu_disabled])
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}

网站公告

今日签到

点亮在社区的每一天
去签到