在上一步中,我们创建了一个新的空的 Cargo 包。在这一步中,我们将把这个空包变成一个最小的 Makepad 应用,仅包含一个空窗口。
在这一节中,你将学习:
💡 如果你不想跟着手动输入,可以直接查看这一步的完整代码:GitHub 链接
我们从向项目添加 Makepad 依赖开始。
Makepad 的顶层 crate 是 makepad-widgets
,它包含了构建应用所需的一切。
运行以下命令来添加依赖:
接下来,我们将添加所需的代码。
将以下文件复制到src
目录中(我们随后会解释代码的作用):
app.rs:
lib.rs:
main.rs:
确保你在项目目录下,运行:
如果一切正常,你应该会看到一个空窗口:
以下代码定义了一个live design块:
Live Design 块用于定义 Makepad 的DSL 代码。DSL 在 Makepad 中用于描述布局和样式,类似 CSS。
让我们逐行拆解:
use link::widgets::*';
导入内置组件,比如 Root
,Window
和 View
。
App = {{App}} { ... }
定义了一个 App
。
{{App}}
语法表示该 DSL 定义与 Rust 中的 App
结构体相关联。
在 App 中,我们将 ui
字段定义为组件树的根节点:
<Root> { ... }
是我们的顶层容器。
<Window> { ... }
是屏幕上的一个窗口。
body = <View> {}
是一个命名为 body
的空视图。
当我们运行应用程序时,Makepad 使用一种叫做 Live 设计系统 的机制,将 DSL 代码与 Rust 代码进行桥接。
其核心思想如下:
应用的布局和样式是通过 DSL 代码定义的。
Live 设计系统会将 DSL 中的定义匹配到对应的 Rust 结构体,并用 DSL 中的值初始化这些结构体。
如果稍后在 Makepad Studio 中修改了 DSL 代码,相关的 Rust 结构体会在运行时自动更新,无需重新启动应用程序。
Live 设计系统使 Makepad 拥有强大的 运行时样式编辑能力。你可以在 Makepad Studio 中动态调整 UI 的布局、颜色以及其他样式属性,并且能在应用运行时立即看到修改效果 —— 无需重新编译!
App
结构体以下代码定义了 App
结构体:
App 结构体是我们应用程序的核心。现在它仅包含我们组件树(widget tree)的根节点。稍后我们还会在其中添加应用所需的各种状态。
注意,我们为 App 结构体派生了两个 trait:Live
和 LiveHook
。接下来我们将更详细地了解这两个 trait。
Live
TraitLive
trait 使结构体能够与 Live 设计系统进行交互。
要为某个结构体派生 Live
trait,该结构体中的每个字段都需要标注以下属性之一:
#[live]
属性表示该字段属于 Live 设计系统的一部分。#[rust]
属性表示该字段是普通的 Rust 字段。当 Live 设计系统遇到标有 #[live]
属性的字段时,它会在 DSL(领域特定语言)代码中查找该字段的匹配定义。如果找到了,就使用 DSL 中的定义来实例化该字段;如果没有找到,则会使用默认值来实例化该字段。
相对地,当系统遇到标有 #[rust]
属性的字段时,它会使用该字段类型的 Default::default
构造函数来实例化这个字段。
在我们的例子中,我们用 #[live]
属性标记了 App
结构体中的 ui
字段。由于我们在 DSL 中为该字段提供了相应的定义,Live 设计系统会自动根据 DSL 中的定义填充 ui
字段,构建出组件树:一个包含一个空 View
的 Window
,由 Root
组件包裹。
LiveHook
TraitLiveHook
trait 提供了一些可被重写(overridable)的方法,这些方法会在应用程序生命周期的不同阶段被调用。
目前我们暂时用不到这些方法,因此可以为 App
结构体定义一个空的 LiveHook
实现:
或者,也可以像我们现在这样为 App
结构体派生(derive)LiveHook trait
。这样会自动生成与上面相同的实现,从而省去了手动编写的工作。
等到我们真正需要用到这些方法时,再来详细解释 LiveHook
trait 的具体作用。
AppMain
Trait以下代码为 App
结构体实现了 AppMain
trait:
AppMain
trait 用于将 App
结构体的一个实例挂接(hook)到主事件循环中。在我们的实现中,我们只是简单地将所有事件转发给组件树的根节点,由它来进行适当的处理。
在 Makepad 中,Scope(作用域) 是一个容器,用于在事件传递过程中携带应用级别的数据以及组件特定的属性。
目前我们还没有任何状态,因此暂时使用 Scope::empty()
来为每个事件创建一个空的作用域。关于作用域的更多内容,我们将在后面进一步介绍。
LiveRegister
Trait以下代码为 App
结构体实现了 LiveRegister
trait:
LiveRegister
trait 用于注册 DSL 代码。通过注册 DSL 代码,我们可以让整个应用访问这些定义。
前面我们提到过,live design 块用于定义 DSL 代码。在底层,每个 live design 块都会生成一个 live_design
函数,当这个函数被调用时,会将该块中的 DSL 代码注册进系统。而我们正是在 LiveRegister
trait 的实现中调用这些 live_design
函数。
在我们的实现中,我们调用了 makepad_widgets::live_design
函数,用于注册 makepad_widgets crate 中的 DSL 代码。如果不调用这个函数,我们将无法使用任何内置组件,比如之前看到的 Root
、Window
和 View
等组件。
注意:由
app.rs
文件顶部的live_design
宏生成的live_design
函数是一个特殊函数。我们不需要在这里手动调用它,因为它会被app_main
函数自动调用。而这个app_main
函数是由app_main
宏生成的,我们接下来会讲解它。
app_main
宏以下代码调用了 app_main
宏。
app_main
宏会生成一个 app_main
函数,里面包含启动应用所需的所有代码。
app_main
函数的功能如下:
LiveRegister
trait 注册额外的 DSL 代码。App
结构体。AppMain
trait 将该 App
结构体实例挂接到主事件循环中。我们现在有了一个最简的 Makepad 应用程序,包含一个空窗口。接下来的步骤中,我们将开始为应用构建一个图片网格。