「Ace」を使って「TAURI」で「テキストエディタ」アプリを作ろう

メニューの実行
これまでの連載ではJavaScript側からRustを呼び出していましたが、今回は図3のようにRust側からJavaScriptを呼び出します。それにはJavaScriptでRust側からのメッセージ呼び出しの受け取り状態をセット(listen)し、RustからのメッセージをJavaScriptで受け取ったときに処理を実行します。
ここではバックエンドのRust側で新たな機能として「メニューバー」を実装し、「メニュー」が実行された際にフロントエンドのWebページのJavaScriptにメッセージを送ります。
JavaScript側でメッセージを受け取る
まず、例として「New」メニューを実行したらJavaScriptに「new-file」メッセージを送り、Aceエディタの文字列を空(から)にして初期化します。そのためにはJavaScript側でTAURIのイベントの「listen」メソッドを使います。文字通りlistenメソッドで聞き耳を立てておくわけです。
なお、メッセージが呼ばれる前にメッセージの受け取り状態をセットしておかなければ、Rustからメッセージが送られてきても受け取れないため何も処理されません。
・「src」→「main.js」ファイルのサンプルコードconst { invoke } = window.__TAURI__.tauri;
let editor;
window.addEventListener("DOMContentLoaded", () => {
editor = ace.edit('text_edit');
editor.focus();
editor.setFontSize(16);
//Newメニューから呼ばれる設定
setNew();
editor.session.setMode('ace/mode/html');
});
//Newメニューから呼ばれるセットをする関数
async function setNew() {
await window.__TAURI__.event.listen('new-file', () => {
editor.session.getDocument().setValue("");
});
}
【サンプルコードの解説】
DOMが全て読み込み完了したら「setNew」関数を呼び出します。
setNew関数で、Rustからの「new-file」メッセージを受け取った際に、Aceエディタのドキュメントの値を「""」(空)にするようにセットします。
Rustからメニューを実行
「New」と「Quit」のカスタムメニューを作成して「File」サブメニューに追加し、さらにメニューにも追加すればメニューバーの準備は完了です。TAURIビルダーの「menu」メソッドでメニューバーをウィンドウにセットします。「on_menu_event」メソッドでメニューがクリックされてメニューイベントを実行します。"new"メニューが実行されたらJavaScript側に"new-file"メッセージを送り(emit_all)ます。"quit"メニューが実行されたならプロセスから「exit」します。
お気づきかと思いますが、基本的にサンプルコードにコメントがある部分やその関数内、繋がりのあるコードなどが追記部分です。主にRust(main.rs)やJavaScript(main.js)のコードだけコメントしています(追記するコメントが抜けていたら申し訳ありません)。
・「src-tauri」→「src」→「main.rs」ファイルのサンプルコード#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
//メニューを扱うクレート
use tauri::{CustomMenuItem,Menu,MenuItem,Submenu,Manager};
fn main() {
//Newメニュー
let new_file = CustomMenuItem::new(
"new".to_string(), "New");
//Quitメニュー
let quit = CustomMenuItem::new(
"quit".to_string(), "Quit");
//Fileメニュー
let menu_file = Submenu::new(
"File",
Menu::new()
.add_item(new_file)
.add_native_item(MenuItem::Separator)
.add_item(quit));
//全メニューにFileメニューを追加
let menu = Menu::new()
.add_submenu(menu_file);
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![])
//メニューのセット
.menu(menu)
.on_menu_event(|event| {
match event.menu_item_id() {
//Newメニューを実行する時
"new" => {
event
.window()
.emit_all("new-file", "")
.unwrap();
}
//Quitメニューを実行する時
"quit" => {
std::process::exit(0);
}
_ => {}
}
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
【サンプルコードの解説】
「use」文で「CustomMenuItem」「Menu」「MenuItem」「Submenu」「Manager」構造体を使えるようにします。
「CustomMenuItem」構造体でサブメニュー内の「New」「Quit」カスタムメニューのインスタンスを作成します。
「Submenu」構造体でメニュー内の「File」サブメニューのインスタンスを作成し、カスタムメニューやセパレータを追加します。
「Menu」構造体でメニューバーに「File」サブメニューを追加したインスタンスを生成し「menu」変数に代入します。
TAURIビルダーの「menu」メソッドで「menu」変数をメニューバーにセットします。
TAURIビルダーの「on_menu_event」メソッドで「new」「quit」メニューを実行するメッセージの送信設定をします。
「Font」「Mode」「Theme」メニュー
先ほど「New」メニューを実装した要領で「Font」「Mode」「Theme」メニューも実装します。「Font」メニューにはフォントサイズ「12」「16」「24」ピクセルのメニュー、「Mode」メニューには「Text」「HTML」「JavaScript」メニュー、「Theme」メニューには「dracula」「terminal」「chrome」メニューがあります。
「New」メニューと同様のやり方なので、ここでは一気にまとめて「Font」「Mode」「Theme」メニューも実装します(図4)。たくさんコードを書きますが、簡単ですね。
図4:本文のサンプルコードを開いたmain.jsファイルをキャプチャしたフォントサイズ24、JavaScriptモード、draculaテーマのAceエディタ(図のソースコードにある「フォントサイズ16」や「ace/mode/html」は起動時の値)
「Font」「Mode」「Theme」メニューのJavaScript
「Font」メニューの「12」「16」「24」メニューの'font-size'メッセージを受け取ったら、Aceエディタの「setFontSize」メソッドでメニュー名の数値と同じフォントサイズをセットします。
「Mode」メニューの「Text」「HTML」「JavaScript」メニューの'mode'メッセージを受け取ったら、Aceエディタの「setMode」メソッドでシンタックスハイライトのモードをセットします。
「Theme」メニューの「dracula」「terminal」「chrome」メニューの'theme'メッセージを受け取ったら、Aceエディタの「setTheme」メソッドで背景色などの色の組み合わせのテーマをセットします。
・「src」→「main.js」ファイルのサンプルコードconst { invoke } = window.__TAURI__.tauri;
let editor;
window.addEventListener("DOMContentLoaded", () => {
editor = ace.edit('text_edit');
editor.focus();
editor.setFontSize(16);
//Fontサイズメニューが呼ばれる設定
setFontSize();
setNew();
//Modeメニューが呼ばれる設定
setMode();
//Themeメニューが呼ばれる設定
setTheme();
editor.session.setMode('ace/mode/html');
});
async function setNew() {
await window.__TAURI__.event.listen('new-file', () => {
editor.session.getDocument().setValue("");
});
}
//Fontサイズメニューが呼ばれるセットをする関数
async function setFontSize() {
await window.__TAURI__.event.listen('font-size', event => {
editor.setFontSize(event.payload);
});
}
//Modeメニューが呼ばれるセットをする関数
async function setMode() {
await window.__TAURI__.event.listen('mode', event => {
editor.session.setMode('ace/mode/'+event.payload);
});
}
//Themeメニューが呼ばれるセットをする関数
async function setTheme() {
await window.__TAURI__.event.listen('theme', event => {
editor.setTheme('ace/theme/'+event.payload);
});
}
【サンプルコードの解説】
DOMが全て読み込み完了したら「setFontSize」「setMode」「setTheme」関数を呼び出します。
setFontSize関数でRustからの「font-size」メッセージを受け取った際に、Aceエディタのフォントサイズをメニューから受け取った引数の値(event.payload)をそのままセットします。
setMode関数でRustからの「mode」メッセージを受け取った際に、Aceエディタのモードを'ace/mode/'にメニューから受け取った引数の値(event.payload)をそのまま繋げてセットします。
setTheme関数でRustからの「font-size」メッセージを受け取った際に、Aceエディタのテーマを'ace/theme/'にメニューから受け取った引数の値(event.payload)をそのまま繋げてセットします。
「Font」「Mode」「Theme」メニューの実行
「New」メニューと同様に「Font」メニューの「12」「16」「24」メニュー、「Mode」メニューの「Text」「HTML」「JavaScript」メニュー、「Theme」メニューの「dracula」「terminal」「chrome」メニューを一気に実装します。
フォントサイズは任意の正の整数でもOKで、モードも「CSS」など様々なファイル形式に対応しており、テーマも「xcode」などいろいろあります。メニューを増やして改造してみると良いでしょう。
・「src-tauri」→「src」→「main.rs」ファイルのサンプルコード#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use tauri::{CustomMenuItem, Menu, MenuItem, Submenu,Manager};
fn main() {
let new_file = CustomMenuItem::new(
"new".to_string(), "New");
let quit = CustomMenuItem::new(
"quit".to_string(), "Quit");
//フォントサイズ12メニュー
let font12 = CustomMenuItem::new(
"font12".to_string(), "12");
//フォントサイズ16メニュー
let font16 = CustomMenuItem::new(
"font16".to_string(), "16");
//フォントサイズ24メニュー
let font24 = CustomMenuItem::new(
"font24".to_string(), "24");
//Textモードメニュー
let mode_text = CustomMenuItem::new(
"mode_text".to_string(), "Text");
//HTMLモードメニュー
let mode_html = CustomMenuItem::new(
"mode_html".to_string(), "HTML");
//JavaScriptモードメニュー
let mode_js = CustomMenuItem::new(
"mode_js".to_string(), "JavaScript");
//draculaテーマメニュー
let theme_dracula = CustomMenuItem::new(
"theme_dracula".to_string(), "dracula");
//terminalテーマメニュー
let theme_terminal = CustomMenuItem::new(
"theme_terminal".to_string(), "terminal");
//chromeテーマメニュー
let theme_chrome = CustomMenuItem::new(
"theme_chrome".to_string(), "chrome");
let menu_file = Submenu::new(
"File",
Menu::new()
.add_item(new_file)
.add_native_item(MenuItem::Separator)
.add_item(quit));
//Fontサイズメニュー
let menu_font = Submenu::new(
"Font",
Menu::new()
.add_item(font12)
.add_item(font16)
.add_item(font24));
//Modeメニュー
let menu_mode = Submenu::new(
"Mode",
Menu::new()
.add_item(mode_text)
.add_item(mode_html)
.add_item(mode_js));
//Themeメニュー
let menu_theme = Submenu::new(
"Theme",
Menu::new()
.add_item(theme_dracula)
.add_item(theme_terminal)
.add_item(theme_chrome));
//メニューバーにFile・Font・Mode・Themeメニューを追加
let menu = Menu::new()
.add_submenu(menu_file)
.add_submenu(menu_font)
.add_submenu(menu_mode)
.add_submenu(menu_theme);
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![])
.menu(menu)
.on_menu_event(|event| {
match event.menu_item_id() {
"new" => {
event
.window()
.emit_all("new-file", "")
.unwrap();
}
"quit" => {
std::process::exit(0);
}
//Font12メニューを実行する時
"font12" => {
event
.window()
.emit_all("font-size", 12)
.unwrap();
}
//Font16メニューを実行する時
"font16" => {
event
.window()
.emit_all("font-size", 16)
.unwrap();
}
//Font24メニューを実行する時
"font24" => {
event
.window()
.emit_all("font-size", 24)
.unwrap();
}
//Textモードメニューを実行する時
"mode_text" => {
event
.window()
.emit_all("mode", "text")
.unwrap();
}
//HTMLモードメニューを実行する時
"mode_html" => {
event
.window()
.emit_all("mode", "html")
.unwrap();
}
//JavaScriptモードメニューを実行する時
"mode_js" => {
event
.window()
.emit_all("mode", "javascript")
.unwrap();
}
//draculaテーマメニューを実行する時
"theme_dracula" => {
event
.window()
.emit_all("theme", "dracula")
.unwrap();
}
//terminalテーマメニューを実行する時
"theme_terminal" => {
event
.window()
.emit_all("theme", "terminal")
.unwrap();
}
//chromeテーマメニューを実行する時
"theme_chrome" => {
event
.window()
.emit_all("theme", "chrome")
.unwrap();
}
_ => {}
}
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
【サンプルコードの解説】
「CustomMenuItem」構造体でサブメニュー内の「12」「16」「24」「Text」「HTML」「JavaScript」「dracula」「terminal」「chrome」カスタムメニューのインスタンスを作成します。
「Submenu」構造体でメニュー内の「Font」「Mode」「Theme」サブメニューのインスタンスを作成し、カスタムメニューを追加します。
「Menu」構造体でメニューバーに「Font」「Mode」「Theme」サブメニューを追加するインスタンスを生成し「menu」変数に代入します。
TAURIビルダーの「menu」メソッドで「menu」変数をセットします。
TAURIビルダーの「on_menu_event」メソッドで「font12」「font16」「font24」「mode_text」「mode_html」「mode_js」「theme_dracula」「theme_terminal」「theme_chrome」メニューを実行した際のメッセージ送信設定をします。
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- 「TAURI」でExcelのデータを読み書きしてWebページに表示してみよう
- 「TAURI」で「画像ビューア」のサンプルアプリを作ろう
- 「TAURI」で「丸アートお絵描き」アプリを作ろう
- 「TAURI」で「簡易RSSリーダー」を開発してみよう
- 「TAURI」で気象庁の「CSVデータ」を解析する
- 「TAURI」で「ピアノ音楽」アプリを作ろう
- 「TAURI」にも必要な「Rust」の「クレート」を使う
- 「TAURI」の基礎知識を押さえ、簡単なTAURIアプリ開発を体験してみよう
- 「TAURI」でデータベースを使ってみよう
- WebGPUを使うための「HTML5+CSS+JavaScript」の文法を学ぼう



