11 - 添加更多状态

在前几步中,我们为应用创建了幻灯片的初步实现。

为了保持初步实现的简单性,我们的幻灯片只显示了占位图片,还不能响应用户事件。

在接下来的几步中,我们将去除这些限制,让幻灯片变得动态。

我们的计划如下:

  • 首先,我们会给应用添加更多状态。
  • 然后,我们会使用这些状态来响应用户事件。

在这一步,我们将开始添加更多状态。

注意:如果你不想跟着敲代码,可以在这里找到本步骤的全部代码:https://github.com/makepad/image_viewer/tree/main/step_11

你将学到的内容

在这一步中,你将学到:

  • 如何在 Rust 代码中引用依赖项。

更新 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 结构体增加了以下字段:

  • placeholder:包含对占位图片的引用。

注意,我们给该字段加上了 #[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_idxSome(image_idx)
    • 获取对应图片的路径。
    • 使用该路径重新加载 Image。
  • 否则:
    • 使用占位图片重新加载 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
  • 否则,将当前图片设置为列表中的第一张图片。

此更改确保幻灯片始终显示当前图片,或者在没有图片时显示占位图片。

检查到目前为止的进度

让我们检查一下当前的进度。

确保你在你的包目录下,然后运行:

1cargo run --release

如果一切正常,你应该会看到和之前一样的幻灯片,不过这次它会显示一张真实的图片:

下一步

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