Announcing tauri-egui 0.1.0
Tauri 團隊很高興宣布 tauri-egui 的首次發佈。
egui
是一個用 Rust 撰寫的 GUI 函式庫。它透過 glutin 利用 OpenGL 環境。
tauri-egui 是一個 Tauri 外掛程式,它與 Tauri 執行時事件迴圈連線,讓您能夠透過我們的 glutin 分支 建立 glutin 視窗,並透過我們的 egui-tao 整合使用 egui。
設定
第一步是將 crate 新增至 Cargo.toml 中的依賴項
[dependencies]tauri-egui = "0.1"
現在您需要啟用此外掛程式
fn main() { tauri::Builder::default() .setup(|app| { app.wry_plugin(tauri_egui::EguiPluginBuilder::new(app.handle())); Ok(()) })}
建立 egui 佈局
若要使用 egui,您只需要實作 tauri_egui::eframe::App
特徵,以使用 egui API 渲染元素。在以下範例中,我們將建立一個登入佈局。
- 定義將用於渲染佈局的 struct
use std::sync::mpsc::{channel, Receiver, Sender};use tauri_egui::{eframe, egui};
pub struct LoginLayout { heading: String, users: Vec<String>, user: String, password: String, password_checker: Box<dyn Fn(&str) -> bool + Send + 'static>, tx: Sender<String>, texture: Option<egui::TextureHandle>,}
impl LoginLayout { pub fn new( password_checker: Box<dyn Fn(&str) -> bool + Send + 'static>, users: Vec<String>, ) -> (Self, Receiver<String>) { let (tx, rx) = channel(); let initial_user = users.iter().next().cloned().unwrap_or_else(String::new); ( Self { heading: "Sign in".into(), users, user: initial_user, password: "".into(), password_checker, tx, texture: None, }, rx, ) }}
- 實作
tauri_egui::eframe::App
以使用 egui API
impl eframe::App for LoginLayout { // Called each time the UI needs repainting // see https://docs.rs/eframe/latest/eframe/trait.App.html#tymethod.update for more details fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { let Self { heading, users, user, password, password_checker, tx, .. } = self;
let size = egui::Vec2 { x: 320., y: 240. }; // set the window size frame.set_window_size(size);
// adds a panel that covers the remainder of the screen egui::CentralPanel::default().show(ctx, |ui| { // our layout will be top-down and centered ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| { // we will start adding elements here in the next sections }); }); }}
- 定義我們將使用的一些輔助函式
fn logo_and_heading(ui: &mut egui::Ui, logo: egui::Image, heading: &str) { let original_item_spacing_y = ui.style().spacing.item_spacing.y; ui.style_mut().spacing.item_spacing.y = 8.; ui.add(logo); ui.style_mut().spacing.item_spacing.y = 16.; ui.heading(egui::RichText::new(heading)); ui.style_mut().spacing.item_spacing.y = original_item_spacing_y;}
fn control_label(ui: &mut egui::Ui, label: &str) { let original_item_spacing_y = ui.style().spacing.item_spacing.y; ui.style_mut().spacing.item_spacing.y = 8.; ui.label(label); ui.style_mut().spacing.item_spacing.y = original_item_spacing_y;}
- 載入影像、將其配置為紋理並將其新增至 UI (需要
png
依賴項)
let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| { let mut reader = png::Decoder::new(std::io::Cursor::new(include_bytes!("icons/32x32.png"))) .read_info() .unwrap(); let mut buffer = Vec::new(); while let Ok(Some(row)) = reader.next_row() { buffer.extend(row.data()); } let icon_size = [reader.info().width as usize, reader.info().height as usize]; // Load the texture only once. ctx.load_texture( "icon", egui::ColorImage::from_rgba_unmultiplied(icon_size, &buffer), egui::TextureFilter::Linear, )});logo_and_heading( ui, egui::Image::new(texture, texture.size_vec2()), heading.as_str(),);
- 新增使用者選擇 ComboBox
ui.with_layout(egui::Layout::top_down(egui::Align::Min), |ui| { control_label(ui, "User"); egui::ComboBox::from_id_source("user") .width(ui.available_width() - 8.) .selected_text(egui::RichText::new(user.clone()).family(egui::FontFamily::Monospace)) .show_ui(ui, move |ui| { for user_name in users { ui.selectable_value(user, user_name.clone(), user_name.clone()); } }) .response;});
- 為密碼輸入新增一個條目
ui.style_mut().spacing.item_spacing.y = 20.;
let textfield = ui .with_layout(egui::Layout::top_down(egui::Align::Min), |ui| { ui.style_mut().spacing.item_spacing.y = 0.; control_label(ui, "Password"); ui.horizontal_wrapped(|ui| { let field = ui.add_sized( [ui.available_width(), 18.], egui::TextEdit::singleline(password).password(true), ); field }) .inner }) .inner;
- 新增提交按鈕
let mut button = ui.add_enabled(!password.is_empty(), egui::Button::new("Unlock"));button.rect.min.x = 100.;button.rect.max.x = 100.;
- 處理提交
if (textfield.lost_focus() && ui.input().key_pressed(egui::Key::Enter)) || button.clicked(){ if password_checker(&password) { let _ = tx.send(password.clone()); password.clear(); frame.close(); } else { *heading = "Invalid password".into(); textfield.request_focus(); }}
現在我們已經建立佈局,讓我們將其放在視窗上並在 Tauri 應用程式中顯示它
use tauri::Manager;fn main() { tauri::Builder::default() .setup(|app| { app.wry_plugin(tauri_egui::EguiPluginBuilder::new(app.handle()));
// the closure that is called when the submit button is clicked - validate the password let password_checker: Box<dyn Fn(&str) -> bool + Send> = Box::new(|s| s == "tauri-egui-released");
let (egui_app, rx) = LoginLayout::new( password_checker, vec!["John".into(), "Jane".into(), "Joe".into()], ); let native_options = tauri_egui::eframe::NativeOptions { resizable: false, ..Default::default() };
app .state::<tauri_egui::EguiPluginHandle>() .create_window( "login".to_string(), Box::new(|_cc| Box::new(egui_app)), "Sign in".into(), native_options, ) .unwrap();
// wait for the window to be closed with the user data on another thread // you don't need to spawn a thread when using e.g. an async command std::thread::spawn(move || { if let Ok(signal) = rx.recv() { dbg!(signal); } });
Ok(()) }) .run(tauri::generate_context!()) .expect("error while running tauri application")}
以下是它在所有平台上的外觀
若要自訂 egui 應用程式的外觀和風格,請查看 Context#set_style API。
© 2025 Tauri Contributors. CC-BY / MIT