「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)。たくさんコードを書きますが、簡単ですね。
「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」メニューを実行した際のメッセージ送信設定をします。