Rust Slint实现弹出式菜单栏源码分享
一、源码分享
1、效果展示



2、源码分享
2.1、工程搭建
参考我这篇博文:【Rust 使用Slint库开发UI界面工程搭建详细教程】
2.2、工程结构

2.3、main.rs
use slint::{PlatformError};
slint::include_modules!();
use tokio;
#[tokio::main]
async fn main() -> Result<(), PlatformError> {
let app: MainWindow = MainWindow::new()?;
let weak: slint::Weak<MainWindow> = app.as_weak();
app.global::<DataAdapter>().on_btn_clicked({
let weak: slint::Weak<MainWindow> = weak.clone();
move |value| {
if let Some(strong) = weak.upgrade() {
let _adapter: DataAdapter<'_> = strong.global::<DataAdapter>();
}
}
});
let _ = app.run();
Ok(())
}
2.4、main.slint
import { AboutSlint, VerticalBox, LineEdit, HorizontalBox, Button, GroupBox, GridBox,
ComboBox, Spinner, Slider, ListView, Palette, ProgressIndicator, CheckBox, Switch, StandardListView,ScrollView } from "std-widgets.slint";
import { DataAdapter} from "models.slint";
export { DataAdapter}
import "images/01 Digitall.ttf";
component Dot {
in property <bool> isBig: false;
in property <length> radius;
in property <color> color: #06bddd;
width: 0;
height: 0;
Rectangle {
y: isBig? radius - 6px: radius;
x: -(self.width / 2);
width: isBig? 5px: 3px;
height: isBig? 14px: 6px;
background: color;
border-radius: self.width / 2;
}
}
component ScaleNumText {
in-out property <string> text <=> t.text;
in property <int> value;
in property <length> radius;
in property <brush> color <=>t.color;
width: 0;
height: 0;
t := Text {
y: radius;
width: 36px;
font-size: 18px;
transform-rotation: -root.transform-rotation - 30deg;
horizontal-alignment: center;
}
}
component ValueTextDigit inherits Text {
in property <bool> active: true;
in property <int> digit;
text: active ? digit : 0;
color: active ? white : #555454;
width: 36px;
font-size: 52px;
font-family: "01 Digitall";
}
component ValueText {
in property <int> value;
in property <string> unit:"%";
HorizontalLayout {
property <length> default-width: 18px;
ValueTextDigit {
digit: (value / 100).floor();
active: value > 99;
}
ValueTextDigit {
digit: (value.mod(100) / 10).floor();
active: value > 9;
}
ValueTextDigit {
digit: value.mod(10);
}
Text {
y:self.height/2;
text: root.unit;
color: white;
font-size: 18px;
font-family: "01 Digitall";
}
}
}
component DynamicDial inherits Rectangle{
width: 500px;
height: 500px;
// background: #046621;
out property <int> value: 0;
in property <int> minimum: 0;
in property <int> maximum: 100;
property <int> total_dots: 51;
private property <angle> start_angle: 0deg;
private property <angle> end_angle: 300deg;
private property <angle> current_angle: start_angle;
callback value_changed(val:int);
Rectangle {
width: parent.width;
height: parent.height;
transform-rotation: 30deg;
for i in total_dots : Dot {
isBig: Math.mod(i, 5) == 0;
radius:root.width / 2 - 20px;
transform-rotation: 306deg * (i / total_dots);
}
}
Rectangle {
transform-rotation: 30deg;
property <int> per : (Math.abs(minimum) + Math.abs(maximum)) / 10;
for i in 11: ScaleNumText {
text: per*i;
radius:root.width / 2 - 50px;
color: white;
transform-rotation: 330deg * (i / 11);
}
}
Image {
width: parent.width*0.77;
height: self.width;
source: @image-url("images/dial.png");
Image {
width: parent.width*0.823;
height: self.width;
source: @image-url("images/lines.png");
colorize: #ffffff42;
transform-rotation: root.current_angle;
opacity: 1;
}
ta := TouchArea {
property <length> centerX: self.width / 2;
property <length> centerY: self.height / 2;
property <length> relativeX;
property <length> relativeY;
property <angle> newAngle;
property <angle> deltaDegrees;
property <bool> firstTouch: false;
width: parent.width;
height: parent.height;
enabled: true;
pure public function normalizeAngle(angle: angle) -> angle {
return (angle + 360deg).mod(360deg);
}
public function map(x:int,in_min:int,in_max:int,out_min:int,out_max:int) -> int {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
changed pressed => {
if !self.pressed {
firstTouch = false;
}
else{
relativeX = ta.mouse-x - centerX;
relativeY = ta.mouse-y - centerY;
newAngle = normalizeAngle(atan2(relativeY / 1px, relativeX / 1px));
root.current_angle = normalizeAngle(newAngle - 120deg).clamp(start_angle, end_angle);
root.value = map(root.current_angle/1deg,root.start_angle/1deg,root.end_angle/1deg,root.minimum,root.maximum);
root.value_changed(root.value);
}
}
moved => {
relativeX = ta.mouse-x - centerX;
relativeY = ta.mouse-y - centerY;
newAngle = normalizeAngle(atan2(relativeY / 1px, relativeX / 1px));
newAngle -= 120deg;
if newAngle < root.start_angle && newAngle > -30deg {
root.current_angle = root.start_angle;
}
else if newAngle >root.end_angle && newAngle < 330deg {
root.current_angle = root.end_angle;
}
else{
root.current_angle = normalizeAngle(newAngle).clamp(start_angle, end_angle);
}
root.value = map(root.current_angle/1deg,root.start_angle/1deg,root.end_angle/1deg,root.minimum,root.maximum);
root.value_changed(root.value);
}
}
Image{
x: parent.width/2 + parent.width/2*0.73 * (root.current_angle+120deg).cos() - self.width/2;
y: parent.height/2 + parent.height/2*0.73 * (root.current_angle+120deg).sin() - self.height/2;
source: @image-url("images/notch.png");
}
}
ValueText {
value: root.value;
}
}
export component MainWindow inherits Window {
width: 800px;
height: 600px;
background: #34373F;
dial:=DynamicDial {
value_changed(val) => {
debug(val)
}
}
}
2.5、models.slint
export global DataAdapter {
callback btn_clicked(string);
}
2.6、Cargo.toml
[package]
name = "ttt"
version = "0.1.0"
edition = "2024"
author = "<peng.xu@sf-express.com>"
[dependencies]
rand = "0.9.2"
slint = "1.14.1"
slint-interpreter = "1.14.1"
tokio = { version = "1.48.0", features = ["full"] }
[build-dependencies]
slint-build = "1.14.1"
2.7、源码资源下载
文章顶部下载。
二、Slint详解
Slint 是一个现代的、声明式的原生 GUI 框架,旨在高效、简洁地构建跨平台的用户界面。它特别注重性能和资源消耗,非常适合嵌入式设备和桌面应用程序。下面将从多个角度深入解析:
1、核心概念与设计哲学
- 声明式 UI: Slint 使用一种自定义的、基于标记的声明式语言(
.slint文件)来描述用户界面。开发者专注于定义 UI 的结构、外观和交互逻辑(状态、属性、绑定、回调),而不是编写命令式的绘图代码。这类似于 React 的 JSX 或 QML。 - 原生性能: Slint 渲染引擎使用平台原生的图形 API(如 OpenGL, Vulkan, DirectX, Metal 或软件渲染)进行高效绘制,确保流畅的用户体验。
- 轻量级: Slint 的运行时库非常小巧,编译后的应用程序体积也小,特别适合资源受限的环境(如微控制器)。
- 跨平台: Slint 支持 Windows, macOS, Linux (X11/Wayland), WebAssembly 以及多种嵌入式平台(如运行 FreeRTOS 的微控制器)。
- 与 Rust 深度集成: 虽然 Slint 的核心是用 C++ 编写的,但其 Rust API 是一等公民。开发者可以使用 Rust 的强大功能(安全性、并发性、生态)来编写应用程序逻辑。
2、Slint UI 描述语言 (.slint 文件)
这是定义 UI 的主要方式。该语言结构清晰:
import { VerticalBox, Button } from "std-widgets.slint";
export component MainWindow inherits Window {
// 属性 (Property):定义组件的状态或可配置项
property <int> counter: 0; // 定义一个整数属性 `counter`,初始值为 0
// 回调 (Callback):定义可以被 Rust 代码或 UI 元素触发的信号
callback increment(); // 定义一个无参数回调
// 布局和子组件
VerticalBox {
Text { text: "Count: " + counter; } // 绑定:文本内容与 `counter` 属性同步
Button {
text: "Increment";
clicked => { // 处理按钮点击事件
increment(); // 触发回调
counter += 1; // 直接修改属性(绑定会自动更新 UI)
}
}
}
}
关键元素:
component: 定义一个可重用的 UI 组件(类似于控件或窗口)。property: 定义组件的状态变量。类型可以是内置类型(int,float,string,bool,color,length等)或自定义结构体。属性是响应式的,它们的改变会自动触发依赖它们的 UI 部分更新。callback: 定义事件信号。UI 事件(如按钮点击)或 Rust 代码可以触发它们。Rust 代码可以注册处理函数来响应这些回调。- 绑定 (
=>和=):property => expression:单向绑定。当expression结果改变时,更新property。property = expression:初始化赋值。property1 <=> property2:双向绑定(较少用)。- 表达式可以包含算术运算、逻辑运算、字符串连接、三元运算符等。
- 布局组件: 如
VerticalBox,HorizontalBox,GridLayout等,用于组织子组件的位置。 - 内置组件:
Text,Button,Slider,CheckBox,LineEdit等。 - 自定义组件: 开发者可以创建自己的
component以实现复用。 - 动画: 支持简单的属性动画(如
animate x { duration: 250ms; ... })。
3、Rust 集成与交互
Rust 代码负责应用程序的业务逻辑、数据处理、网络通信等。它与 Slint UI 通过以下方式交互:
- 加载
.slint文件: 使用slint::load_file("ui/main.slint")或slint::load_from_string(...)加载 UI 定义。 - 获取组件实例: 加载后,可以获取 UI 根组件(如
MainWindow)的实例:let main_window = MainWindow::new().unwrap(); - 访问和修改属性: 通过生成的 getter 和 setter 方法:
// 读取属性 let current_count = main_window.get_counter(); // 修改属性 (会触发 UI 更新) main_window.set_counter(current_count + 1); - 注册回调处理函数: 使用
on_<callback_name>方法注册 Rust 函数来处理 UI 事件:main_window.on_increment(|| { println!("Increment callback triggered from UI!"); // 可以在这里执行 Rust 逻辑 }); - 触发回调: Rust 代码也可以主动触发 UI 中定义的回调,通知 UI 状态变化:
main_window.increment(); // 触发定义在 .slint 中的 `increment` 回调 - 运行事件循环: 最后,调用
run()启动主事件循环,显示窗口并处理用户输入:main_window.run().unwrap();
4、架构概览
+---------------------------------------+
| Your Rust Application Code |
| (Business Logic, Data, Network, etc.) |
+------------------+--------------------+
| (Interact via Properties & Callbacks)
v
+------------------+--------------------+
| Slint Runtime (Rust API) |
| - Loads .slint files |
| - Manages component instances |
| - Bridges UI events to Rust |
| - Handles property bindings |
+------------------+--------------------+
| (Internal)
v
+------------------+--------------------+
| Slint Compiler / Interpreter |
| - Parses .slint DSL |
| - Evaluates expressions & bindings |
| - Handles layout |
+------------------+--------------------+
| (Render Commands)
v
+------------------+--------------------+
| Slint Renderer |
| - Uses native graphics APIs |
| (OpenGL/Vulkan/DX/Metal/Software) |
| - Efficiently draws UI elements |
+---------------------------------------+
5、优点
- 高效开发: 声明式语言简化 UI 构建。
- 高性能: 原生渲染,低资源占用。
- 跨平台: 广泛支持各种操作系统和设备。
- Rust 集成: 无缝结合 Rust 的安全性和性能。
- 响应式: 基于属性的自动 UI 更新。
- 嵌入式友好: 极小的内存和 CPU 占用。
6、学习资源与工具
- 官方文档: https://slint.dev/docs 是最权威的资源,包含语言参考、Rust API 文档和教程。
- 示例仓库: https://github.com/slint-ui/slint/tree/master/examples 包含各种功能的示例代码。
- Slint VS Code 扩展: 提供语法高亮、代码片段、实时预览等功能,极大提升开发效率。
slint-viewer工具: 可以快速预览.slint文件的效果,无需编译整个 Rust 项目。
7、适用场景
- 嵌入式设备 GUI (MCU)
- 桌面应用程序 (Windows, macOS, Linux)
- 需要原生性能和轻量级的应用
- 希望使用 Rust 构建 UI 的项目
总结: Rust Slint 是一个强大的工具,它通过声明式语言、高效的渲染引擎和与 Rust 的深度集成,为开发者提供了一种构建高性能、跨平台原生 GUI 的现代解决方案。对于追求性能、效率和 Rust 生态集成的项目来说,它是一个非常值得考虑的选择。

848

被折叠的 条评论
为什么被折叠?



