11 - 添加更多状态
在前几步中,我们为应用创建了幻灯片的初步实现。
为了保持初步实现的简单性,我们的幻灯片只显示了占位图片,还不能响应用户事件。
在接下来的几步中,我们将去除这些限制,让幻灯片变得动态。
我们的计划如下:
- 首先,我们会给应用添加更多状态。
- 然后,我们会使用这些状态来响应用户事件。
在这一步,我们将开始添加更多状态。
注意:如果你不想跟着敲代码,可以在这里找到本步骤的全部代码:https://github.com/makepad/image_viewer/tree/main/step_11
你将学到的内容
在这一步中,你将学到:
更新 State 结构体
让我们从更新 State 结构体开始,使其包含幻灯片放映所需的状态。
在 app.rs
文件中,用下面的代码替换 State 结构体的定义:
1#[derive(Debug)]
2pub struct State {
3 image_paths: Vec<PathBuf>,
4 max_images_per_row: usize,
5 current_image_idx: Option<usize>,
6}
这为 State 结构体增加了以下字段:
current_image_idx
:包含幻灯片中当前图片的索引。
这就是我们绘制幻灯片所需的全部状态。
更新 State 结构体的 Default Trait 实现
我们还需要更新 State 结构体的 Default trait 实现,以反映这个新字段。
在 app.rs
文件中,用下面的代码替换 State 结构体的 Default trait 实现:
1impl Default for State {
2 fn default() -> Self {
3 Self {
4 image_paths: Vec::new(),
5 max_images_per_row: 4,
6 current_image_idx: None,
7 }
8 }
9}
引用依赖项
在接下来的代码中,我们将从 Rust 代码中引用作为资源打包到应用中的占位图片。
回想一下,像占位图片这样的打包资源在 Makepad 中称为依赖项(dependencies),我们可以在 DSL 代码中使用如下表达式来引用它们:
1dep("crate://self/resources/placeholder.jpg")
但是我们如何在 Rust 代码中引用依赖项呢?为此,我们需要使用一些小技巧。
我们先从给 App 结构体添加一个字段开始。
在 app.rs
文件中,用下面的代码替换 App 结构体的定义:
1#[derive(Live)]
2pub struct App {
3 #[live]
4 placeholder: LiveDependency,
5 #[live]
6 ui: WidgetRef,
7 #[rust]
8 state: State,
9}
这为 App 结构体增加了以下字段:
注意,我们给该字段加上了 #[live]
属性,这意味着它会从 DSL 代码中初始化。为了使其生效,我们需要在 DSL 代码中为该字段添加相应的定义。
在 app.rs
文件的 live design 块中,用下面的代码替换 App 的定义:
1App = {{App}} {
2 placeholder: (PLACEHOLDER),
3
4 ui: <Root> {
5 <Window> {
6 body = <View> {
7 slideshow = <Slideshow> {}
8 }
9 }
10 }
11 }
这为 App 结构体的 placeholder 字段添加了定义。
有了这个定义,live design 系统会自动用正确的路径初始化 App 结构体中的 placeholder 字段。正如我们在下一节将看到的,我们可以在 Rust 代码中使用这个路径来加载占位图片。
添加辅助函数
既然我们已经有了从 Rust 代码引用占位图片的方法,现在是时候为 App 结构体添加一些辅助函数,并更新一些已有函数了。这会让后续的代码更容易编写。
添加 set_current_image 方法
我们先定义一个 set_current_image
方法。该方法用于更改幻灯片当前显示的图片。
在 app.rs
文件中,添加以下代码到 App 结构体的 impl 块中:
1fn set_current_image(&mut self, cx: &mut Cx, image_idx: Option<usize>) {
2 self.state.current_image_idx = image_idx;
3
4 let image = self.ui.image(id!(slideshow.image));
5 if let Some(image_idx) = self.state.current_image_idx {
6 let image_path = &self.state.image_paths[image_idx];
7 image
8 .load_image_file_by_path_async(cx, &image_path)
9 .unwrap();
10 } else {
11 image
12 .load_image_dep_by_path(cx, self.placeholder.as_str())
13 .unwrap();
14 }
15 self.ui.redraw(cx);
16 }
这段代码的作用是:
- 将
current_image_idx
设置为新值。
- 获取 Slideshow 中的 Image 引用。
- 如果
current_image_idx
是 Some(image_idx)
:
- 获取对应图片的路径。
- 使用该路径重新加载 Image。
- 否则:
- 调用
self.ui.redraw(cx)
来安排界面重绘。
注意,当使用占位图片重新加载 Image 时,set_current_image
方法会使用之前定义的 App 结构体中的 placeholder
字段。
更新 load_image_paths 方法
接下来,我们将更新已有的 load_image_paths
方法,使其在加载一组新图片时,调用 set_current_image
方法设置当前显示的图片。
在 app.rs
文件中,替换 App 结构体中 load_image_paths
方法的定义为以下代码:
1fn load_image_paths(&mut self, cx: &mut Cx, path: &Path) {
2 self.state.image_paths.clear();
3 for entry in path.read_dir().unwrap() {
4 let entry = entry.unwrap();
5 let path = entry.path();
6 if !path.is_file() {
7 continue;
8 }
9 self.state.image_paths.push(path);
10 }
11
12 if self.state.image_paths.is_empty() {
13 self.set_current_image(cx, None);
14 } else {
15 self.set_current_image(cx, Some(0));
16 }
17 }
这里唯一变化的是结尾新增的四行代码:
1if self.state.image_paths.is_empty() {
2 self.set_current_image(cx, None);
3 } else {
4 self.set_current_image(cx, Some(0));
5 }
这段代码的作用是:
- 如果图片列表为空,则将当前图片设置为
None
。
- 否则,将当前图片设置为列表中的第一张图片。
此更改确保幻灯片始终显示当前图片,或者在没有图片时显示占位图片。
检查到目前为止的进度
让我们检查一下当前的进度。
确保你在你的包目录下,然后运行:
如果一切正常,你应该会看到和之前一样的幻灯片,不过这次它会显示一张真实的图片:

下一步
我们现在已经拥有了幻灯片所需的状态。下一步,我们将使用这些状态来响应用户事件。