Skip to content

Commit 63848a4

Browse files
committed
docs(guide): add component concept guide
Add English and Chinese guides describing component concepts, composition, container usage, measure-place layout, input handling, and window callbacks.
1 parent 95cdc20 commit 63848a4

File tree

2 files changed

+336
-0
lines changed

2 files changed

+336
-0
lines changed

docs/en/guide/component-concept.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
---
2+
title: "Component"
3+
order: 2
4+
---
5+
6+
# Component
7+
8+
In `tessera`, a component (Component) is the basic building block of the user interface. It allows developers to create complex UIs by composing these components.
9+
10+
`tessera` expresses components as functions, usually by marking functions with the `#[tessera]` macro. Below we also refer to components as "tessera".
11+
12+
## Defining components
13+
14+
Defining a component is very simple: just create a function and mark it with the `#[tessera]` macro — it becomes a `tessera` component.
15+
16+
```rust
17+
use tessera_ui::tessera;
18+
19+
#[tessera]
20+
fn component() {
21+
22+
}
23+
```
24+
25+
Component functions can accept parameters with no special restrictions. As mentioned above, a component function is fundamentally an ordinary Rust function.
26+
27+
```rust
28+
use tessera_ui::tessera;
29+
use tessera_ui_basic_components::text::text;
30+
31+
#[tessera]
32+
fn component(name: String, age: u32) {
33+
text(format!("Hello, {}! You are {} years old.", name, age));
34+
}
35+
```
36+
37+
## Composition
38+
39+
To compose components, simply call one component function from another component function.
40+
41+
```rust
42+
use tessera_ui::tessera;
43+
44+
#[tessera]
45+
fn parent_component() {
46+
child_component();
47+
}
48+
49+
#[tessera]
50+
fn child_component() {
51+
// child component content
52+
}
53+
```
54+
55+
By default, this renders the child component at the top-left corner of the parent. For more advanced layouts see the "measure-place" layout system below.
56+
57+
## Container
58+
59+
Often we don't know what the child component will be and cannot call it directly. In such cases we can pass the child as a closure.
60+
61+
```rust
62+
use tessera_ui::tessera;
63+
64+
#[tessera]
65+
fn parent_component(child: impl FnOnce()) {
66+
child();
67+
}
68+
69+
#[tessera]
70+
fn child_component() {
71+
// child component content
72+
}
73+
74+
fn app() {
75+
parent_component(|| {
76+
child_component();
77+
});
78+
}
79+
```
80+
81+
Such components are called containers.
82+
83+
## Measure - Place
84+
85+
For more complex layouts you need to dive into tessera's measure-place layout system.
86+
87+
Tessera's layout system is split into two phases: measure and place. The measure phase determines the component's size, while the place phase determines the component's position on the screen.
88+
89+
### Custom layout
90+
91+
To override the default measurement behavior, call the `measure` function inside the component and provide a closure:
92+
93+
```rust
94+
use tessera_ui::tessera;
95+
96+
#[tessera]
97+
fn component() {
98+
measure(Box::new(|input| {
99+
// measurement logic here
100+
}))
101+
}
102+
```
103+
104+
The `measure` function accepts a closure that receives a `MeasureInput` and returns a `Size`. Its type is defined as:
105+
106+
```rust
107+
pub type MeasureFn =
108+
dyn Fn(&MeasureInput<'_>) -> Result<ComputedData, MeasurementError> + Send + Sync;
109+
```
110+
111+
`MeasureInput` contains layout information such as child component ids and parent constraints, while the returned `ComputedData` is the component's size — the measurement result.
112+
113+
Note that `measure` does not need to be imported; it is injected into the function component's context by the `#[tessera]` macro and can be considered part of the component API.
114+
115+
For detailed information about `MeasureInput` and `ComputedData`, see the documentation on docs.rs. Here is a simple measurement example: it measures the first child's size, places the child at the top-left (relative coordinate `(0, 0)`), and returns its own size as the child's size plus 10 pixels.
116+
117+
```rust
118+
#[tessera]
119+
fn component(child: impl FnOnce()) {
120+
child(); // compose child component
121+
measure(Box::new(|input| {
122+
let child_node_id = input.children_ids[0];
123+
let child_size = input.measure_child(child_node_id, input.parent_constraint)?;
124+
input.place_child(child_node_id, PxPosition::new(Px(0), Px(0)));
125+
Ok(
126+
ComputedData {
127+
width: child_size.width + Px(10),
128+
height: child_size.height + Px(10),
129+
}
130+
)
131+
}))
132+
}
133+
```
134+
135+
This simple example demonstrates what a component must do to perform layout:
136+
137+
1. Execute child component functions
138+
2. Measure child sizes
139+
3. Place child components
140+
4. Return its own size
141+
142+
::: warning
143+
If you place children without measuring them, or measure children but do not place them, the renderer will panic at runtime.
144+
:::
145+
146+
You may notice earlier examples did not include any explicit layout. That's because tessera provides a default layout behavior for components without a custom layout: it measures child components and places them at the top-left. This default behavior is adequate in many cases.
147+
148+
### Input handling
149+
150+
Similar to `measure`, the `#[tessera]` macro injects an `input_handler` function into the component for handling external input events.
151+
152+
The following example shows a component that prevents components below it from receiving mouse events:
153+
154+
```rust
155+
#[tessera]
156+
fn component() {
157+
input_handler(Box::new(|mut input | {
158+
input.block_cursor();
159+
}))
160+
}
161+
```
162+
163+
### Window callbacks
164+
165+
The `#[tessera]` macro also injects functions to register window callbacks. Currently available callbacks include:
166+
167+
- `on_minimize(Box<dyn Fn(bool) + Send + Sync>)` – window minimize callback
168+
- `on_close(Box<dyn Fn() + Send + Sync>)` – window close callback
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
---
2+
title: "组件"
3+
order: 2
4+
---
5+
6+
# 组件
7+
8+
`tessera` 中,组件(Component)是构建用户界面的基本单元。使得开发者可以通过组合这些组件来创建复杂的用户界面。
9+
10+
`tessera` 使用函数来表达组件,这一般通过 `#[tessera]` 宏标记函数来实现。所以下文中我们也将组件称为 `tessera`
11+
12+
## 定义组件
13+
14+
定义一个组件非常简单,只需创建一个函数并使用 `#[tessera]` 宏标记它,它就从一个普通函数变成了一个 `tessera` 组件。
15+
16+
```rust
17+
use tessera_ui::tessera;
18+
19+
#[tessera]
20+
fn component() {
21+
22+
}
23+
```
24+
25+
组件函数可以接受参数,对参数没有任何限制,就像上文说的,组件函数本质上还是普通的 Rust 函数。
26+
27+
```rust
28+
use tessera_ui::tessera;
29+
use tessera_ui_basic_components::text::text;
30+
31+
#[tessera]
32+
fn component(name: String, age: u32) {
33+
text(format!("Hello, {}! You are {} years old.", name, age));
34+
}
35+
```
36+
37+
## 组件的组合
38+
39+
想要组合组件,只需在一个组件函数中调用另一个组件函数即可。
40+
41+
```rust
42+
use tessera_ui::tessera;
43+
44+
#[tessera]
45+
fn parent_component() {
46+
child_component();
47+
}
48+
49+
#[tessera]
50+
fn child_component() {
51+
// 这里是子组件的内容
52+
}
53+
```
54+
55+
默认的,这会将子组件渲染在父组件的左上角。如果需要更复杂的布局见后文关于`测量-放置`布局系统的介绍。
56+
57+
## 容器
58+
59+
很多时候,我们没法直接知道子组件是什么,无法直接调用它们。这时我们可以使用闭包传入子组件。
60+
61+
```rust
62+
use tessera_ui::tessera;
63+
64+
#[tessera]
65+
fn parent_component(child: impl FnOnce()) {
66+
child();
67+
}
68+
69+
#[tessera]
70+
fn child_component() {
71+
// 这里是子组件的内容
72+
}
73+
74+
fn app() {
75+
parent_component(|| {
76+
child_component();
77+
});
78+
}
79+
```
80+
81+
这种组件,我们称之为容器(Container)。
82+
83+
## 测量-放置
84+
85+
如果需要更加复杂的布局,我们就需要深入`tessera`的测量-放置布局系统。
86+
87+
`tessera`的布局系统分为两个阶段:`测量(measure)``放置(place)`。其中测量阶段是为了确定组件的大小,而放置阶段是为了确定组件在屏幕上的位置。
88+
89+
### 自定义布局
90+
91+
如果需要覆盖默认的测量行为,则需要在组件函数中调用 `measure` 函数设置
92+
93+
```rust
94+
use tessera_ui::tessera;
95+
96+
#[tessera]
97+
fn component() {
98+
measure(Box::new(|input| {
99+
// 这里是测量逻辑
100+
}))
101+
}
102+
```
103+
104+
`measure`函数接受一个闭包作为参数,这个闭包接受一个`MeasureInput`类型的参数,并返回一个`Size`类型的值。它的类型定义如下:
105+
106+
```rust
107+
pub type MeasureFn =
108+
dyn Fn(&MeasureInput<'_>) -> Result<ComputedData, MeasurementError> + Send + Sync;
109+
```
110+
111+
`MeasureInput` 包含了子组件组件 id,父组件约束等布局信息,而返回的 `ComputedData` 为本组件的大小,即测量结果。
112+
113+
注意,`measure` 函数是不需要导入的,它由 `#[tessera]` 宏注入到函数组件上下文,可以理解为函数组件的 api。
114+
115+
有关`MeasureInput``ComputedData`的详细信息,请参考[docs.rs](https://docs.rs/tessera-ui/latest/tessera_ui/type.MeasureFn.html)上的文档。这里给出一个简单的测量例子,它测量第一个子组件的大小,将子组件放在左上角(即相对坐标的`(0, 0)`),然后返回自己的大小为子组件的大小加上 10 个像素的结果。
116+
117+
```rust
118+
#[tessera]
119+
fn component(child: impl FnOnce()) {
120+
child(); // 组合子组件
121+
measure(Box::new(|input| {
122+
let child_node_id = input.children_ids[0];
123+
let child_size = input.measure_child(child_node_id, input.parent_constraint)?;
124+
input.place_child(child_node_id, PxPosition::new(Px(0), Px(0)));
125+
Ok(
126+
ComputedData {
127+
width: child_size.width + Px(10),
128+
height: child_size.height + Px(10),
129+
}
130+
)
131+
}))
132+
}
133+
```
134+
135+
这个简单的例子展示了布局一个组件必须要做的事情:
136+
137+
1. 执行子组件函数
138+
2. 测量子组件大小
139+
3. 放置子组件
140+
4. 返回自己的大小
141+
142+
::: warning
143+
如果不测量子组件就放置它们,或者测量了子组件但是没有放置,渲染器会在运行时崩溃。
144+
:::
145+
146+
读者可能注意到,本段之前给出的组件没有任何布局实现,这其实是因为`tessera`为未实现布局的组件提供了一个默认的布局行为:测量子组件的大小,并将它们放置在左上角。这在很多情况下已经足够了。
147+
148+
### 输入处理
149+
150+
类似 `measure` 函数,`#[tessera]` 宏还会为组件注入一个 `input_handler` 函数,用于处理外部输入事件。
151+
152+
以下例子展示一个会阻止它下面的组件接收鼠标事件的组件:
153+
154+
```rust
155+
#[tessera]
156+
fn component() {
157+
input_handler(Box::new(|mut input | {
158+
input.block_cursor();
159+
}))
160+
}
161+
```
162+
163+
### 窗口回调
164+
165+
`#[tessera]` 宏还会为组件注入一些可以注册窗口回调的函数,目前有下面这些,在未来可能会有所变化:
166+
167+
- `on_minimize(Box<dyn Fn(bool) + Send + Sync>)` – 窗口最小化回调
168+
- `on_close(Box<dyn Fn() + Send + Sync>)` – 窗口关闭回调

0 commit comments

Comments
 (0)