2025. 5. 7. 15:04ㆍTauri/Tutorial
기능
- open window 버튼을 누르면 새 창 띄우기
- 메뉴 버튼 만들기
프론트엔드
전체 코드
src/index.html
<html>
<head></head>
<div>
<button onclick="openWindow()">Open Window</button>
<div id="data"></div>
</div>
<script>
const invoke = window.__TAURI__.core.invoke;
const listen = window.__TAURI__.event.listen;
async function openWindow() {
await invoke("open_window");
}
function init() {
listen("data", data => {
document.querySelector("#data").innerHTML = data.payload;
});
}
init();
</script>
</html>
src/sub_window.html
<html>
<head></head>
<div>
<input type="text" id="data" />
<button onclick="sendData()">Send Data</button>
</div>
<script>
const invoke = window.__TAURI__.core.invoke;
async function sendData() {
await invoke("send_data", {
data : document.querySelector("#data").value
});
}
</script>
</html>
프론트엔드 코드는 이제 베리 이지 하니까 넘어가겠다.
잘 모르겠으면 이전 글을 참고하시길!!
백엔드
전체 코드
src-tauri/src/lib.rs
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
use tauri::{
menu::{MenuBuilder, MenuItemBuilder, SubmenuBuilder},
AppHandle, Manager, Emitter,
};
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.setup(|app| {
let open_menu = MenuItemBuilder::new("open")
.id("open")
.build(app)?;
let save_menu = MenuItemBuilder::new("save")
.id("save")
.build(app)?;
let file_sub_menu = SubmenuBuilder::new(app,"file")
.items(&[&open_menu, &save_menu])
.build()?;
let menu = MenuBuilder::new(app)
.items(&[&file_sub_menu])
.build()?;
let open_id = open_menu.id().clone();
app.on_menu_event(move |_, event| {
if *event.id() == open_id {
println!("open menu clicked");
}
});
app.set_menu(menu)?;
Ok(())
})
.invoke_handler(tauri::generate_handler![open_window, send_data])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
#[tauri::command]
fn open_window(app : AppHandle) {
std::thread::spawn(move || {
let option = app.get_webview_window("sub_window");
if let Some(window) = option {
let _ = window.show();
let _ = window.set_focus();
}
});
}
#[tauri::command]
fn send_data(app : AppHandle, data : String) {
app.emit("data", data).unwrap();
}
src-tauri/tauri.conf.json의 일부
"app": {
"withGlobalTauri": true,
"windows": [
{
"title": "rust_tauri",
"width": 800,
"height": 600,
"resizable": true,
"fullscreen": false
},
{
"title": "rust_tauri",
"label": "sub_window",
"url": "sub_window.html",
"visible": false,
"width": 800,
"height": 600,
"resizable": true,
"fullscreen": false
}
],
"security": {
"csp": null
}
},
코드 분석
새 창 띄우기
프론트엔드의 open window 버튼과 invoke 된 rust 함수는 open_window()이다.
#[tauri::command]
fn open_window(app : AppHandle) {
std::thread::spawn(move || {
let option = app.get_webview_window("sub_window");
if let Some(window) = option {
let _ = window.show();
let _ = window.set_focus();
}
});
}
thread를 분리하여 안정성을 높이고, app.get_webview_window 함수를 이용하여 새 창을 열었다.
새 창의 이름은 sub_window로, tauri.conf.json에 label과 url을 입력하여 연결해 주었다.
(다른 방법도 있으나 다른 글에서 다루도록 하겠다.)
결국 sub_window.html => sub_window(label) => get_webview_window("sub_window"); => open_window()의 흐름이다!
그럼 아래 코드는 어떤 의미일까?
if let Some(window) = option {
let _ = window.show();
let _ = window.set_focus();
}
이미 새 창이 열려있다면 그 창에 포커스 하는 것이다.
tauri.conf.json에서 visible이 false이므로 처음에는 새 창이 안 보이는데, 버튼을 누르면 보이는 것이다.
만약 if문이 없다면 새 창이 열린 상태로 버튼을 누를 때 백엔드에서 오류를 일으키게 된다.
메뉴 버튼 만들기
tauri::Builder::default()
.setup(|app| {
let open_menu = MenuItemBuilder::new("open")
.id("open")
.build(app)?;
let save_menu = MenuItemBuilder::new("save")
.id("save")
.build(app)?;
let file_sub_menu = SubmenuBuilder::new(app,"file")
.items(&[&open_menu, &save_menu])
.build()?;
let menu = MenuBuilder::new(app)
.items(&[&file_sub_menu])
.build()?;
let open_id = open_menu.id().clone();
app.on_menu_event(move |_, event| {
if *event.id() == open_id {
println!("open menu clicked");
}
});
app.set_menu(menu)?;
Ok(())
})
tauri::Builder에 메뉴를 추가하면 된다.
setup 내에 |app|을 통해 app을 클로저로 불러온다.
let open_menu = MenuItemBuilder::new("open")
.id("open")
.build(app)?;
let save_menu = MenuItemBuilder::new("save")
.id("save")
.build(app)?;
MenuItemBuilder::new() 함수를 통해 메뉴 항목을 만든다.
.build(app)을 통해 실제 MenuItem 객체로 빌드한다.
에러 전파를 위한 ?를 사용한다.
서브메뉴와 최상위 메뉴도 같다.
let file_sub_menu = SubmenuBuilder::new(app,"file")
.items(&[&open_menu, &save_menu])
.build()?;
let menu = MenuBuilder::new(app)
.items(&[&file_sub_menu])
.build()?;
여기서 .items는 조금 복잡한데, 소유권 이동의 영역이다.
배열 전체를 참조하기 위해 [ ] 앞에 &를 붙이고,
개별 객체를 참조하기 위해 내부에 &를 각각 붙인다.
┌───────────────┐ & ┌───────────────────────────────┐
│ [ &file_sub_menu ] │──►│ slice: &[ &dyn IsMenuItem ] │
└───────────────┘ └───────────────────────────────┘
│
│ & (trait object coercion)
▼
┌───────────────┐
│ &Submenu │ ──► &dyn IsMenuItem
└───────────────┘
- 내부 & : Submenu → &Submenu → &dyn IsMenuItem
- 외부 & : [&dyn IsMenuItem] → &[&dyn IsMenuItem]
이런 느낌... 어렵다 ㅎㅎ...
사실 원래 코딩할 때 내부 &는 안 넣었는데, cargo한테 혼나고 넣었다.
컴파일러가 너무 성능이 좋다...
let open_id = open_menu.id().clone(); // "open"
app.on_menu_event(move |_, event| {
if *event.id() == open_id {
println!("open menu clicked");
}
});
open 메뉴가 클릭되면 log 출력
open menu clicked 로그가 잘 출력된다.
app.set_menu(menu)?;
Ok(())
app.set_menu(menu)?;를 통해 빌드한 메뉴 버튼을 앱에 장착하고,
Ok(())를 통해 모든 초기화가 성공했음을 알린다!
'Tauri > Tutorial' 카테고리의 다른 글
[4] Tauri로 Text Editor 만들기 (0) | 2025.05.07 |
---|---|
[3] Tauri Tutorial (button을 통해 프론트엔드와 백엔드를 연결해보자) (0) | 2025.05.03 |
[2] Tauri 설치 및 환경 설정 방법 (0) | 2025.05.03 |
[1] Tauri란? 간단하게 알아보자 (vs Electron) (0) | 2025.05.03 |