Skip to content
Tauri 中文网

宣布 tauri-egui 0.1.0

Tauri 团队很高兴地宣布 tauri-egui 的第一个版本发布。

🌐 The Tauri team is pleased to announce the first release of tauri-egui.

egui 是一个用 Rust 编写的 GUI 库。它通过 glutin 利用 OpenGL 上下文。

tauri-egui 是一个 Tauri 插件,它连接 Tauri 运行时事件循环,允许你通过我们的 glutin 分支 创建 glutin 窗口,并通过我们的 egui-tao 集成使用 egui。

🌐 tauri-egui is a Tauri plugin that connects with the Tauri runtime event loop to allow you to create glutin windows via our glutin fork and use egui through our egui-tao integration.

🌐 Setup

第一步是将该包添加到你在 Cargo.toml 中的依赖中:

🌐 The first step is to add the crate to your dependencies in Cargo.toml:

[dependencies]
tauri-egui = "0.1"

现在你需要启用插件:

🌐 Now you need to enable the plugin:

fn main() {
tauri::Builder::default()
.setup(|app| {
app.wry_plugin(tauri_egui::EguiPluginBuilder::new(app.handle()));
Ok(())
})
}

🌐 Create an egui layout

要使用 egui,你所需要做的就是实现 tauri_egui::eframe::App trait 来使用 egui API 渲染元素。在下面的例子中,我们将创建一个登录布局。

🌐 To use egui, all you need to do is implement the tauri_egui::eframe::App trait to render elements using the egui API. In the following example, we will create a login layout.

  • 定义将用于呈现布局的结构体:
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(),
);
  • 添加用户选择下拉框:
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 应用中显示:

🌐 Now that we have created the layout, let’s put it on a window and show it in the Tauri application:

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")
}

所有平台上的显示效果如下:

🌐 Here’s how it will look on all platforms:

tauri_egui layout

要自定义你的 egui 应用的外观和感觉,请查看 Context#set_style API

🌐 To customize the look and feel of your egui application, check out the Context#set_style API.


Tauri 中文网 - 粤ICP备13048890号
Nodejs.cn 旗下网站