在过去的几周里,我一直在探索 Rust 中的 GUI 应用开发,并发现 Iced 是一个非常简洁且极简主义的框架,非常适合创建轻量级 GUI 应用。在本文中,我将深入探讨 Iced 的架构,并使用该框架构建一个简单的 GUI 应用。
架构解析
在 GUI 中,有些控件(Widget)是交互式的,例如按钮;而另一些则不是,例如标签。当按下按钮时,它会触发一个交互,该交互可以驱动后端执行某些操作,例如更新标签中显示的文本。这种交互是有状态的,因为它会改变控件的状态。
我们的用户界面中有三个要素:
控件:界面的可视元素。 交互:某些控件可能触发的操作。 状态:界面的底层信息。
这些元素相互连接,形成一个紧密的反馈回路。
“控件在用户与其交互时产生交互。然后,这些交互会改变应用程序的状态。更改后的状态会传播并指示必须显示的新控件。
GUI 三要素
模型:应用程序的状态。 消息:应用程序的交互。 更新逻辑:消息如何改变状态。 视图逻辑:状态如何决定控件。
当我们逐步完成 Hello World GUI 应用的实现时,这些概念将变得更加清晰。
您可以阅读 Iced 作者对此架构的解释:https://book.iced.rs/architecture.html (本节是我个人的理解)。
代码实现
让我们创建一个最小的 Iced 应用。
cargo new - bin iced-simple-sandbox-app
将 Iced 依赖项添加到 cargo.toml 文件中。
[package]
name = "iced-simple-sandbox-app"
version = "0.1.0"
edition = "2021"
[dependencies]
iced = {version = "0.12.1"}
该应用程序包含一个标签,用于显示变量(状态)的值,并包含两个按钮:“递增”和“递减”。当用户按下“递增”按钮时,变量的值增加 1。类似地,按下“递减”按钮会将值减少 1。
在 Iced 术语中,我们的模型封装了应用程序的状态,即递增或递减的变量。
struct AppState {
index: i32,
}
消息对应于用户与应用程序的交互,例如按下“递增”或“递减”按钮。
#[derive(Debug, Clone)]
enum Message {
IncrementButtonPressed,
DecrementButtonPressed,
}
Iced 提供了一个名为 Sandbox 的特征(trait),我们的应用程序将实现该特征。Iced 运行时使用此特征调用适当的函数并运行应用程序。以下是 Sandbox 特征。
请注意,_Sandbox_ 特征支持的功能最少。还有一个名为 Application 的特征,它提供了更灵活的功能,例如运行异步操作。我们将在以后的文章中介绍 _Application_。
pub trait Sandbox {
type Message: Debug + Send;
// 必需方法
fn new() -> Self;
fn title(&self) -> String;
fn update(&mut self, message: Self::Message);
fn view(&self) -> Element<'_, Self::Message>;
// 提供的方法
fn theme(&self) -> Theme { ... }
fn style(&self) -> Application { ... }
fn scale_factor(&self) -> f64 { ... }
fn run(settings: Settings<()>) -> Result<(), Error>
where Self: 'static + Sized { ... }
}
new() 方法在应用程序初始化时调用。 每次 UI 更新时都会调用 title() 方法。它返回一个用作应用程序标题的 _String_。 每当发生用户交互时,都会触发 update() 方法,其中 Message 参数指示交互的类型。这将更新应用程序的状态。 view() 方法在应用程序首次运行或用户与其交互时调用。它返回一个泛型类型 _Element_。Element 是可组合的。我们将在其他文章中讨论这一点。
Sandbox 特征中的其他方法都有默认实现,因此我们不需要提供它们。但是,我包含了 theme() 方法的实现,因为深色主题增添了时尚、现代的感觉。
状态图如下所示
让我们实现应用程序。
impl Sandbox for AppState {
type Message = Message;
fn new() -> AppState {
println!("{:?} Sandbox::new()", std::thread::current().id());
Self {
index: 0,
}
}
fn title(&self) -> String {
println!("{:?} Sandbox::title()", std::thread::current().id());
String::from("AppState")
}
fn update(&mut self, message: Self::Message) {
println!("{:?} Sandbox::update({:?})",
std::thread::current().id(), message);
self.index = match message {
Message::IncrementButtonPressed => {self.index + 1},
Message::DecrementButtonPressed => {self.index - 1},
};
}
fn view(&self) -> Element<Self::Message> {
println!("{:?} Sandbox::view()", std::thread::current().id());
let incerement_btn = button(
"Increment"
)
.width(150)
.on_press(Message::IncrementButtonPressed);
let label_text = text("Value:").width(75);
let index_text = text(self.index.to_string()).width(75);
let decrement_btn = button(
"Decrement"
)
.width(150)
.on_press(Message::DecrementButtonPressed);
let content = column![
incerement_btn,
row![label_text, index_text,],
decrement_btn,
]
.width(Length::Fill)
.align_items(Alignment::Center)
.spacing(10);
container(scrollable(content))
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
.into()
}
}
应用程序如下所示
感谢您的阅读!关注公众号并回复 ”iced“ 获取源码
点击关注并扫码添加进交流群