live_design!{ ... }: 定义 UI 结构、样式和组件的宏。use link::widgets::*;: 导入 Makepad 内置 Widget 库。use link::theme::*;: 导入当前主题定义的常量(颜色、字体大小、间距等)。use link::shaders::*;: 导入标准 Shader 库(如 Sdf2d, Pal, Math)。ComponentName = {{RustStructName}} { ... }: 将 DSL 组件与 Rust 结构体绑定。ComponentName 是在 DSL 中使用的名字,RustStructName 是对应的 Rust 结构体名。instance_name = <ComponentName> { ... }: 实例化一个组件。instance_name 是这个实例的 LiveId,可以在 Rust 代码中通过 id!(instance_name) 引用。<BaseComponent> { ... }: 继承自 BaseComponent,并覆盖或添加属性。propertyName: value: 设置属性值。propertyName = { ... }: 设置嵌套属性(通常用于 draw_bg, draw_text, layout, walk, animator 等)。id!(...): (在 Rust 代码中) 用于获取 LiveId。live_id!(...): (在 DSL 中) 用于引用已定义的 LiveId(不常用,通常直接用名字)。(CONSTANT_NAME): 引用在主题或其他地方定义的常量。dep("path/to/resource"): 声明一个资源依赖(字体、图片、SVG)。路径通常相对于 Cargo.toml 或使用 crate://self/,打包的时候需要注意指定资源的真实路径。Walk & Layout)walk: 控制元素自身在父容器中的布局。
layout: 控制元素内部子元素的布局。
flow: (Flow) 子元素的布局流向。
Right (默认): 水平从左到右。Down: 垂直从上到下。Overlay: 堆叠在一起。RightWrap: 水平排列,空间不足时换行。spacing: value: 子元素之间的间距(根据 flow 方向)。line_spacing: value: (RightWrap, Down flow) 行间距。padding: (Padding) 内边距。
{top: v, right: v, bottom: v, left: v}v (简写,四边相等)<THEME_MSPACE_2> (引用主题常量)align: (Align) 子元素在父容器中的对齐方式。
{x: 0.0, y: 0.0} (左上){x: 0.5, y: 0.5} (居中){x: 1.0, y: 1.0} (右下)clip_x: bool, clip_y: bool: (默认 true) 是否裁剪超出边界的内容。scroll: vec2(x, y): 内容的滚动偏移量。Live DSL 中的对齐坐标 (align)
作用域: layout 属性块内,用于控制父容器如何对齐其子元素。
类型: Align { x: f64, y: f64 }
坐标系: 归一化相对坐标 (Normalized Relative Coordinates),范围通常是 0.0 到 1.0。
含义:
align.x: 控制子元素在父容器可用水平空间中的对齐方式。
0.0: 左对齐 (Left Align)0.5: 水平居中 (Center Align)1.0: 右对齐 (Right Align)align.y: 控制子元素在父容器可用垂直空间中的对齐方式。
0.0: 顶对齐 (Top Align)0.5: 垂直居中 (Center Align)1.0: 底对齐 (Bottom Align)关键点:
align 作用于子元素作为一个整体(或多个子元素作为一个组,取决于 flow)。它决定的是子元素在父容器剩余空间中的位置。align 才有可见效果。如果子元素设置了 width: Fill 或 height: Fill,那么在该方向上通常没有剩余空间,对齐也就没有意义了。clip_x/clip_y: false)。示例:
Draw* 类型)DrawColor: 绘制纯色背景。
color: #RRGGBBAA 或 (THEME_COLOR_...)fn pixel(self) -> vec4 { ... }: 可覆盖的像素着色器。DrawQuad: 绘制四边形的基础,其他 Draw* 通常继承自它。包含基本的顶点变换和裁剪逻辑。
draw_depth: f32 (默认 1.0): 控制绘制深度/层级。draw_zbias: f32 (通常内部使用): 微调深度以避免 Z-fighting。DrawText / DrawText2: 绘制文本。
text_style: <TextStyleName> { ... }: 设置文本样式。color: #RRGGBBAA 或 (THEME_COLOR_...)wrap: Word / Line / Ellipsis (Ellipsis 可能未完全支持)font_scale: f64 (默认 1.0)brightness: f32 (旧版,可能已弃用)curve: f32 (旧版,可能已弃用)fn get_color(self) -> vec4 { ... }: 可覆盖的颜色逻辑(常用于动画)。DrawIcon: 绘制 SVG 图标。
svg_file: dep("...") 或 svg_path: "..."color: #RRGGBBAA 或 (THEME_COLOR_...) (图标颜色)brightness: f32curve: f32fn get_color(self) -> vec4 { ... }: 可覆盖的颜色逻辑。DrawLine: 绘制线条 (继承自 DrawQuad)。
color: #RRGGBBAAline_width: f64 (在 Rust 中设置)DrawScrollShadow: 绘制滚动阴影。
shadow_size: f32Sdf2d in fn pixel)在 fn pixel 中使用 Sdf2d 进行矢量绘制。
let sdf = Sdf2d::viewport(self.pos * self.rect_size);SDF 绘制 (Sdf2d in fn pixel) - 基本形状
在 fn pixel 函数中,你可以使用 Sdf2d 对象来定义和组合形状。以下是常用的基本形状函数及其说明:
sdf.circle(cx: float, cy: float, radius: float)
cx, cy: 圆心的 x 和 y 坐标。radius: 圆的半径。sdf.dist 为当前像素点到该圆形边界的有符号距离。内部为负,外部为正。sdf.shape 会被更新为 min(sdf.shape, sdf.dist)。sdf.rect(x: float, y: float, w: float, h: float)
x, y: 矩形左下角的 x 和 y 坐标。w, h: 矩形的宽度和高度。sdf.dist 为当前像素点到该矩形边界的有符号距离。sdf.shape 会被更新为 min(sdf.shape, sdf.dist)。sdf.box(x: float, y: float, w: float, h: float, radius: float)
x, y: 矩形左下角的 x 和 y 坐标。w, h: 矩形的宽度和高度。radius: 所有四个角的圆角半径。sdf.dist 为当前像素点到该圆角矩形边界的有符号距离。sdf.shape 会被更新为 min(sdf.shape, sdf.dist)。sdf.box_x(x: float, y: float, w: float, h: float, r_left: float, r_right: float)
r_left,右上和右下角使用 r_right。x, y: 矩形左下角的 x 和 y 坐标。w, h: 矩形的宽度和高度。r_left: 左侧两个角的圆角半径。r_right: 右侧两个角的圆角半径。sdf.dist 为当前像素点到该特定圆角矩形边界的有符号距离。sdf.shape 会被更新为 min(sdf.shape, sdf.dist)。sdf.box_y(x: float, y: float, w: float, h: float, r_top: float, r_bottom: float)
r_top,左下和右下角使用 r_bottom。x, y: 矩形左下角的 x 和 y 坐标。w, h: 矩形的宽度和高度。r_top: 顶部两个角的圆角半径。r_bottom: 底部两个角的圆角半径。sdf.dist 为当前像素点到该特定圆角矩形边界的有符号距离。sdf.shape 会被更新为 min(sdf.shape, sdf.dist)。sdf.box_all(x: float, y: float, w: float, h: float, r_left_top: float, r_right_top: float, r_right_bottom: float, r_left_bottom: float)
x, y: 矩形左下角的 x 和 y 坐标。w, h: 矩形的宽度和高度。r_left_top: 左上角的圆角半径。r_right_top: 右上角的圆角半径。r_right_bottom: 右下角的圆角半径。r_left_bottom: 左下角的圆角半径。sdf.dist 为当前像素点到该高度自定义圆角矩形边界的有符号距离。sdf.shape 会被更新为 min(sdf.shape, sdf.dist)。sdf.hexagon(cx: float, cy: float, radius: float)
cx, cy: 六边形中心的 x 和 y 坐标。radius: 从中心到任一顶点的距离。sdf.dist 为当前像素点到该六边形边界的有符号距离。sdf.shape 会被更新为 min(sdf.shape, sdf.dist)。sdf.hline(y: float, half_thickness: float)
y: 水平线的中心 y 坐标。half_thickness: 线条厚度的一半(总厚度为 2 * half_thickness)。sdf.dist 为当前像素点到该水平线边界的有符号距离。sdf.shape 会被更新为 min(sdf.shape, sdf.dist)。sdf.move_to(x: float, y: float)
(x, y) 坐标,不绘制任何线条。它会更新 sdf.start_pos 和 sdf.last_pos。x, y: 新路径起点的坐标。line_to 或 close_path。sdf.line_to(x: float, y: float)
sdf.last_pos) 绘制一条直线段到指定的 (x, y) 坐标。这条线段会成为当前形状的一部分。它会更新 sdf.last_pos。x, y: 线段终点的坐标。sdf.dist 和 sdf.shape 以反映到这条线段的距离。同时,它还会更新 sdf.clip 用于路径填充。sdf.close_path()
sdf.last_pos) 回到当前子路径起点 (sdf.start_pos) 的直线段来闭合当前的子路径。sdf.arc2(cx: float, cy: float, radius: float, start_angle: float, end_angle: float)
cx, cy: 圆弧圆心的 x 和 y 坐标。radius: 圆弧的半径。start_angle: 圆弧的起始角度(弧度)。end_angle: 圆弧的结束角度(弧度)。sdf.dist 为当前像素点到该圆弧边界的有符号距离。sdf.shape 会被更新为 min(sdf.shape, sdf.dist)。重要提示:
这些函数仅仅是定义形状的边界(通过计算 Signed Distance Field)。
要实际看到这些形状,你必须在定义形状之后调用一个绘制函数,如 sdf.fill(color), sdf.stroke(color, width), sdf.glow(color, width) 等。
sdf.shape 存储了当前已定义形状的最小 SDF 值(用于 union),而 sdf.old_shape 用于布尔运算(intersect, subtract, gloop)。
sdf.dist 存储了最新定义的那个基本形状的 SDF 值。
布尔运算: (通常在绘制形状后调用)
sdf.union(): 合并当前形状和 old_shape。sdf.intersect(): 取当前形状和 old_shape 的交集。sdf.subtract(): 从 old_shape 中减去当前形状。填充/描边:
sdf.fill(color): 填充当前形状并重置形状状态。sdf.fill_keep(color): 填充当前形状,保留形状状态。sdf.fill_premul(premultiplied_color): 使用预乘 Alpha 填充并重置。sdf.fill_keep_premul(premultiplied_color): 使用预乘 Alpha 填充并保留状态。sdf.stroke(color, width): 描边当前形状并重置形状状态。sdf.stroke_keep(color, width): 描边当前形状,保留形状状态。效果:
sdf.glow(color, width): 添加辉光并重置形状状态。sdf.glow_keep(color, width): 添加辉光,保留形状状态。sdf.gloop(k): 与 old_shape 进行平滑混合 (Metaball 效果)。变换: (影响后续绘制)
sdf.translate(dx, dy)sdf.rotate(angle_rad, pivot_x, pivot_y)sdf.scale(factor, pivot_x, pivot_y)清除: sdf.clear(color): 用指定颜色覆盖当前结果。
结果: sdf.result (最终的 vec4 颜色)。
Shader 中的坐标 (self.pos, self.geom_pos, Sdf2d::viewport 参数)
作用域: draw_* 块内的 fn pixel 和 fn vertex 函数。
类型: vec2 (通常是 f32 类型)
坐标系: 归一化局部坐标 (Normalized Local Coordinates),范围通常是 0.0 到 1.0。
含义:
self.pos (在 fn pixel 中): 表示当前正在计算的像素点,在其所属的绘制矩形 (self.rect_size) 内的归一化坐标。
vec2(0.0, 0.0)。vec2(1.0, 1.0)。self.geom_pos (在 fn vertex 中): 表示当前正在处理的顶点,在其所属的输入几何体(通常是一个单位四边形)内的归一化坐标。
GeometryQuad2D,顶点通常是 (0,0), (1,0), (1,1), (0,1)。rect_pos 和 rect_size 定义的矩形区域。Sdf2d::viewport(pos: vec2) 的参数: 这个 pos 参数通常是像素坐标或相对于某个局部原点的坐标,而不是归一化的。
Sdf2d::viewport(self.pos * self.rect_size)。这里 self.pos 是 0-1 的归一化坐标,乘以 self.rect_size 将其转换为相对于当前绘制矩形左上角的像素级局部坐标。SDF 函数(如 sdf.circle(cx, cy, r))期望接收这种像素级的局部坐标。关键点:
vertex 函数的返回值和变换矩阵决定)。self.pos 在 fn pixel 中非常重要,因为它允许你根据像素在 Widget 矩形内的相对位置来计算颜色、图案或形状。self.geom_pos 在 fn vertex 中用于将基础几何体(通常是 0-1 的四边形)映射到屏幕上的目标矩形。Sdf2d 函数的坐标通常是相对于 Sdf2d::viewport 创建时所基于的那个坐标系的(通常是 self.pos * self.rect_size 产生的局部像素坐标)。示例:
总结:
| 概念 | 作用域 | 类型 | 坐标系 | 含义 |
|---|---|---|---|---|
align | layout 块 | Align | 归一化相对 (0-1) | 控制子元素在父容器剩余空间中的对齐 |
self.pos | fn pixel | vec2 | 归一化局部 (0-1) | 当前像素在自身绘制矩形内的相对位置 |
self.geom_pos | fn vertex | vec2 | 归一化几何体 (0-1) | 当前顶点在输入几何体内的相对位置 |
Sdf2d 参数 | fn pixel | float/vec2 | 局部像素/任意(取决于 viewport) | 用于定义 SDF 形状的坐标 |
理解这两种坐标系的区别对于精确控制 Makepad UI 的布局和视觉效果至关重要。align 用于宏观布局,而 Shader 中的 self.pos 用于微观的像素级绘制。
animator)animator 块用于定义 Widget 的状态以及这些状态之间的过渡动画。它允许你以声明式的方式创建交互式的视觉反馈。
animator: 包含所有动画定义的块。track_name: 一个动画轨迹的名称(LiveId),通常对应一个逻辑状态(如 hover, focus, open, selected, visible 等)。这个名称将在 Rust 代码中通过 id!(track_name) 引用。state_name = { ... })在每个 track_name 内部,通常定义至少两个状态,最常见的是 on 和 off,也可以是自定义的状态名。
default: on | off: 指定 Widget 初始化时此动画轨迹的默认状态。state_name = { ... }: 定义一个具体状态(如 on, off, down, open, close 等)。在每个具体的状态定义块(如 on = { ... })内部,可以设置以下参数来控制动画如何进入该状态:
from: { ... }: 定义从其他状态转换到当前状态时的动画行为。
all: PlayMode: 为所有未明确指定的来源状态设置默认的过渡动画。other_state_name: PlayMode: 为从特定的 other_state_name 状态转换到当前状态设置动画。这会覆盖 all 的设置。PlayMode: 定义动画的播放方式和时长:
Forward { duration: seconds }: 从当前值正向动画到目标值,持续 seconds 秒。Backward { duration: seconds }: 从目标值反向动画到当前值(较少直接使用,通常由 Forward 自动处理反向)。Snap: 立即跳转到目标值,没有动画。Loop { duration: seconds, end: value }: 循环播放动画,duration 是一个周期的时长,end 是循环点(通常是 1.0)。ReverseLoop { ... }: 反向循环。BounceLoop { ... }: 来回循环(乒乓效果)。ease: EaseType { ... }: 指定缓动函数,控制动画的速度曲线。
Linear, InQuad, OutQuad, InOutQuad, InExp, OutExp, InOutExp, ExpDecay {d1:v, d2:v} (模拟物理衰减)。In/Out/InOut + Cubic, Quart, Quint, Sine, Circ, Elastic, Back, Bounce。Pow { begin: v, end: v }, Bezier { cp0: v, cp1: v, cp2: v, cp3: v }。apply: { ... }: 定义当动画到达这个状态时,哪些 #[live] 属性应该具有的目标值。
property: value: 将属性 property 的最终值设置为 value。property: [{ time: t, value: v }, ...]: 定义关键帧动画。
time: 归一化时间 (0.0 到 1.0)。value: 在该时间点属性应具有的值。time 通常是 0.0,代表动画开始时的值(通常是 from 状态的值,但可以覆盖)。time 通常是 1.0,代表动画结束时的值。draw_bg: { hover: 1.0 } 或 walk: { margin: { left: 10.0 } }。redraw: true: 标记这个动画轨迹在播放时需要不断重绘 Widget。对于视觉上会改变的动画,这通常是必需的。cursor: MouseCursor: (通常在 on 状态中) 当动画进入此状态时,设置鼠标光标样式(如 Hand, Default)。handle_event 中,根据用户交互(如 Hit::FingerHoverIn, Hit::FingerDown)或其他逻辑,调用 self.animator_play(cx, id!(track_name.state_name)) 或 self.animator_toggle(cx, condition, animate, id!(track.on), id!(track.off)) 来触发状态转换。on)的定义,并根据当前状态查找对应的 from 规则来确定动画的 duration 和 ease。duration 和 ease 函数,计算出 apply 块中指定的每个属性在当前帧应该具有的中间值。#[live] 字段。redraw: true 或 animator_handle_event 返回 must_redraw(),则请求重绘 Widget。draw_walk 中,Widget 使用被动画更新后的 #[live] 字段值进行绘制。示例:按钮的 Hover 和 Down 状态
在 Makepad 中,实现平滑的视觉动画(例如 UI 元素的位置、大小、颜色、透明度等的渐变)强烈推荐使用 NextFrame 事件机制,而不是 Timer。
Animator 系统内部就是基于 NextFrame 来驱动动画更新和重绘的。
NextFrame (用于动画)
self.redraw(cx) 或 self.area.redraw(cx) 时,Makepad 会在下一帧渲染循环开始时发送一个 Event::NextFrame 事件给该 Widget(以及其他需要重绘的 Widget)。handle_event 中捕获这个 NextFrame 事件 (if let Some(ne) = self.next_frame.is_event(event)), 根据时间差 (ne.time) 计算动画的下一状态,更新 #[live] 属性或 instance 变量,然后再次调用 self.redraw(cx) 来请求再下一帧的更新。Animator 系统自动化了这个过程:animator_handle_event 内部检查动画是否在进行,如果是,则计算下一帧的值,应用它们,并返回 must_redraw() 为 true,这通常会触发外部调用 self.redraw(cx)。NextFrame 事件与 Makepad 的渲染循环紧密耦合,确保动画更新与屏幕刷新同步,从而获得最平滑的视觉效果。NextFrame(除非有其他原因需要重绘)。animator_play),并在 NextFrame 事件中更新状态(或让 Animator 处理),然后请求重绘即可。Timer (用于定时逻辑)
cx.start_timer(id, interval, repeats) 或 cx.start_timeout(id, interval) 启动一个定时器。Event::Timer 事件,其中包含触发的定时器 ID。handle_event 中捕获这个 Timer 事件 (if self.my_timer.is_event(event).is_some()) 并执行相应的逻辑。核心思想:区分“动画过程”和“延时触发”
动画过程 (Animation Process): 指的是一个属性值从 A 平滑地过渡到 B 的整个过程。例如:
abs_pos: vec2(-1000.0, 10.0)) 滑动到屏幕内 (abs_pos: vec2(60.0, 10.0))。对于这种平滑的、逐帧变化的视觉过渡,你应该依赖 Animator 和 NextFrame。Animator 会在每一帧(由 NextFrame 触发)计算出属性的中间值,并更新它,然后请求下一帧重绘,直到动画完成。你不应该使用 Timer 来手动控制这种逐帧更新,因为 Timer 的触发间隔与屏幕刷新率无关,会导致动画卡顿。
延时触发 (Delayed Trigger): 指的是在一段时间之后执行一个一次性的操作。例如:
对于这种在特定时间点触发某个动作的需求,你应该使用 Timer。cx.start_timeout(duration) 就是为此设计的。当定时器触发时 (my_timer.is_event(event).is_some()),你执行相应的操作,比如调用 self.close(cx) 来启动关闭动画。
比如:robrix 项目中 RobrixPopupNotification 组件弹框动画:
self.open(cx) 时,你使用 self.animator.animator_play(cx, id!(mode.open)) 来启动滑动动画。这个动画的过程是由 Animator 和 NextFrame 驱动的。这里不需要 Timer。self.open(cx) 时,你使用 self.view(id!(content.progress)).animator_play(cx, id!(mode.progress)) 来启动进度条动画。这个动画的过程也是由 Animator 和 NextFrame 驱动的。这里不需要 Timer。self.open(cx) 中启动一个 Timer (self.animation_timer = cx.start_timeout(2.5);) 是正确的。当这个 Timer 在 2.5 秒后触发时 (if self.animation_timer.is_event(event).is_some()),你调用 self.close(cx)。self.close(cx) 时(无论是被定时器触发还是被点击关闭按钮触发),你使用 self.animator.animator_play(cx, id!(mode.close)) 来启动滑出动画。这个动画的过程同样是由 Animator 和 NextFrame 驱动的。这里不需要 Timer。因此
Animator + NextFrame 实现平滑的视觉过渡动画(滑动、渐变、缩放等)。Timer (特别是 cx.start_timeout) 来实现在固定延迟后触发某个一次性动作(比如启动一个关闭动画)。最佳实践:
hover, focus, open, selected)。default: 设置好初始状态。from 规则: 定义不同状态转换时的动画行为,使过渡更自然。redraw: true: 确保视觉变化能够被渲染。instance 变量: 在 draw_* shader 中定义 instance 变量(如 instance hover: float),并在 animator 的 apply 块中修改这些变量,然后在 fn pixel 或 fn get_color 中使用 mix() 或其他逻辑来根据这些 instance 变量改变外观。NextFrame 机制。 最简单的方式是利用 Makepad 内置的 Animator 系统,它封装了基于 NextFrame 的动画逻辑。Timer。 例如:
PopupNotification 使用 Timer 在固定时间后自动关闭,这是 Timer 的一个合适用例。| 错误信息模式 (Error Message Pattern) | 可能的原因 (Possible Causes) | 常见场景与提示 (Common Scenarios & Tips) |
|---|---|---|
Enum variant X not found for field Y | 1. 枚举变体名称拼写错误或大小写错误。2. 使用了该枚举类型不支持的变体。 | 检查 Flow (Right, Down, Overlay, RightWrap), Size (Fill, Fit, Fixed, All), Axis (Horizontal, Vertical), CheckType, RadioType, ImageFit 等枚举的拼写和可用值。查阅文档或 Rust 定义。 |
Identifier X not found | 1. 引用的 LiveId(组件实例名、常量名)拼写错误或大小写错误。2. 引用的 LiveId 未在当前或父作用域定义。3. 忘记 use link::... 导入。 | 检查实例名、常量名(如 THEME_COLOR_TEXT)是否正确。确保在使用前已定义。检查 use 语句。 |
field X not found on struct Y | 1. 字段名 X 拼写错误或大小写错误。2. 组件 Y (或其基类) 没有名为 X 的 #[live] 字段。3. 尝试在错误的层级设置属性(例如,直接在 <Button> 上设置 text_style 而不是在 draw_text 内部)。 | 检查属性名的拼写。查阅组件的 Rust 定义或文档确认可用属性及其层级。例如,文本样式通常在 draw_text = { text_style = { ... } } 内部。 |
Cannot find value X to override | 1. 尝试覆盖 (<Base> { X: ... }) 一个在基类 Base 中不存在的属性 X。2. 属性 X 的路径或名称错误。 | 确认基类中确实存在你要覆盖的属性,并且路径正确。例如,覆盖背景色可能是 <Base> { draw_bg: { color: ... } }。 |
Value type mismatch for field X | 为属性 X 赋了错误类型的值。 | 检查赋值类型:数字 (100, 12.5),颜色 (#f00, (THEME_COLOR_...)),字符串 ("text"), 枚举 (Fill, Down),布尔 (true, false),依赖 (dep("...")),对象 ({...})。 |
Cannot cast value X to type Y | 同上,类型不匹配。 | 同上。 |
Expected type X found Y | 同上,类型不匹配。 | 同上。 |
Unexpected token / Expected '}' / Expected ':' / Expected ',' | DSL 语法错误。 | 检查括号 {} () <> 是否匹配,属性间是否有逗号 ,,属性名和值之间是否有冒号 :。利用编辑器语法高亮。 |
Cannot parse value | 赋给属性的值格式不正确(例如,颜色格式错误、数字格式错误、枚举名称错误)。 | 检查颜色格式 (#RGB, #RGBA, #RRGGBB, #RRGGBBAA),数字格式,枚举拼写。 |
Unexpected field X | 1. 在组件内部使用了它不支持的字段名 X。2. 结构层级错误,将属性放在了错误的对象内。 | 确认组件支持该字段。检查花括号 {} 的层级是否正确。 |
Cannot find widget class Y | 1. 组件类型 <Y> 名称拼写错误或大小写错误。2. 忘记 use link::widgets::*; 或其他包含该组件的 use 语句。3. 自定义组件未在 Rust 中正确注册 (live_register)。 | 检查组件名称拼写。确认已导入 widgets。如果是自定义组件,确保已在 live_register 中注册。 |
Expected object value for field Z | 字段 Z 期望一个对象值(用 {...} 包裹),但赋了其他类型的值(如数字、字符串)。 | 通常发生在 layout, walk, draw_bg, draw_text, animator 等属性上,确保使用 {} 包裹其内部属性。 |
Cannot find dependency / File not found at path X | dep("...") 中的资源路径错误或文件不存在。 | 检查 crate://self/ 前缀是否正确。确认文件路径和文件名无误,且文件存在于项目中。 |
| (Shader 编译错误) | fn pixel 或 fn vertex 中的 MPSL 代码错误。 | 检查 Shader 语法、变量名、函数调用、类型。参考 GLSL 语法和 Makepad 内置函数 (Sdf2d, Pal, Math)。 |
Name collision between X splat and Y | #[derive(Live)] 宏检测到命名冲突。通常是一个 #[live] 字段与一个由属性宏(如 #[walk])隐式处理的字段同名。 | 移除或重命名冲突的 #[live] 字段。例如,如果使用了 #[walk] walk: Walk,就不要再定义 #[live] abs_pos: DVec2。 |
the trait bound X: LiveApply is not satisfied | 通常是 #[derive(Live)] 失败的连锁反应,导致 LiveApply trait 未被实现,进而影响 #[derive(Widget)]。 | 解决导致 #[derive(Live)] 失败的根本错误(通常是命名冲突或其他宏处理错误)。 |
no function or associated item named '...' found for struct X | 同上,#[derive(Live)] 失败导致 LiveNew trait 的方法(如 new, live_design_with, live_type_info)未实现。 | 解决导致 #[derive(Live)] 失败的根本错误。 |
调试技巧:
#[derive(Live)] 部分) 是最权威的方式。<View>: 基础容器。
show_bg: booldraw_bg: <DrawColor> (或覆盖为其他 Draw* 类型)walk, layoutscroll_bars: <ScrollBars>optimize: None/DrawList/Texture<Label>: 显示文本。
text: "string"draw_text: <DrawText2>walk, layout (通常 width:Fit, height:Fit)<Button>: 可点击按钮。
text: "string"draw_text: <DrawText2>draw_icon: <DrawIcon>draw_bg: <DrawQuad> (或覆盖)icon_walk, label_walk, walk, layoutenabled: bool<LinkLabel>: 带下划线的按钮,用于链接。
<Button>。url: "string" (Rust 中设置)open_in_place: bool<TextInput>: 文本输入框。
text: "string"empty_text: "string"is_password, is_read_only, is_numeric_only: booldraw_text, draw_bg, draw_cursor, draw_selectionwalk, layout<CheckBox> / <Toggle>: 复选框/开关。
text: "string"draw_bg: <DrawCheckBox> (可设置 check_type: Check/Radio/Toggle/None)bind: "path.to.data"<RadioButton>: 单选按钮。
text: "string"value: EnumVariantdraw_bg: <DrawRadioButton> (可设置 radio_type: Round/Tab)<View> 或 <ButtonGroup> 内使用。<Slider>: 滑块。
text: "string"min, max, default, step, precisionbind: "path.to.data"text_input: <TextInput> (内置文本输入)draw_bg: <DrawSlider><DropDown>: 下拉菜单。
labels: ["...", ...]values: [Enum::Val1, ...]selected_item: usizepopup_menu: <PopupMenu>popup_menu_position: OnSelected/BelowInputbind: "path.to.data"<Image>: 显示位图。
source: dep("...")fit: Stretch/Horizontal/Vertical/Smallest/Biggest/Size<Icon>: 显示 SVG 图标。
draw_icon: { svg_file: dep("..."), color: ... }icon_walk<ScrollBars>: 滚动条容器(通常在 <View> 的 scroll_bars 属性中使用)。
show_scroll_x, show_scroll_y: boolscroll_bar_x, scroll_bar_y: <ScrollBar><PortalList> / <PortalList2>: 高性能列表。
next_visible_item 和 item。auto_tail: bool<Html> / <Markdown>: 渲染格式化文本。
body: "string"<PageFlip> / <SlidesView>: 页面/幻灯片切换器。
active_page: live_id!(page_id) / current_slide: value<Dock>: 可停靠面板系统。
root, Tabs, Tab 结构。<Splitter>: 可拖动分隔器。
axis: Horizontal/Verticalalign: FromA/FromB/Weighteda: <Widget>, b: <Widget><Modal>: 模态对话框。
content: <Widget><PopupNotification> / RobrixPopupNotification: 非模态弹出通知。
content: <Widget><AdaptiveView>: 响应式视图切换。
Desktop = <Widget>, Mobile = <Widget><CachedWidget>: 缓存子 Widget。
child_id = <Widget><View> /<ViewBase> 及其变体高级用法:
draw_bg 中编写 fn pixel 或 fn vertex 来创建复杂的背景效果、渐变、图案或响应状态的视觉变化。optimize: Texture 或 DrawList): 用于包含大量静态或不经常变化内容的 View,可以显著提高性能。Texture 模式将内容绘制到纹理上,DrawList 模式缓存绘制命令。flow, align, spacing, padding, margin 以及 Size 模式 (Fill, Fit, Fixed) 来构建复杂的、响应式的布局结构。View 本身不直接处理很多交互,但可以捕获 FingerDown, FingerHover 等事件,并结合 animator 改变 draw_bg 中的 instance 变量,实现视觉反馈。<ScrollXView>, <ScrollYView>, <ScrollXYView> 或在普通 <View> 中添加 scroll_bars: <ScrollBars> {} 来创建滚动区域。示例 (自定义 Shader & 优化):
<View> 是唯一可以设置 event_order 属性的内置 Widget。
<View> 组件:
DSL/Widgets/View.md) 明确列出了 event_order ([EventOrder](#eventorder)) 作为一个字段。EventOrder 枚举允许你指定事件在其子组件中的传播顺序:
Up (默认): 从最后一个子组件到第一个(类似 HTML DOM 的冒泡)。Down: 从第一个子组件到最后一个。List(Vec<LiveId>): 按照列表中指定的 LiveId 顺序。其他内置组件:
<Button>, <Label>, <TextInput>, <Slider> 等)通常不直接暴露 event_order 属性 让用户在 DSL 中设置。<View> 作为其基础结构的一部分,但它们自身的事件处理逻辑通常是固定的,或者依赖于父级 <View> 的 event_order。<Button> 内部可能有一个 <View> 来布局图标和文本,但你不能直接在 <Button> 上设置 event_order 来改变图标和文本接收事件的顺序。你需要修改 <Button> 的内部 live_design! 定义(如果它是自定义的)或者接受其默认行为。<Dock>, <PortalList>, <Window> 这样的复杂容器 Widget,它们有自己特定的事件分发逻辑来管理其子项(Tabs, List Items, Child Windows),通常不通过简单的 event_order 属性控制。总结:
<View> 是唯一可以直接在 live_design! 中配置 event_order 的核心内置 Widget。event_order 的 <View> 中来间接控制它们的事件处理顺序。<ButtonFlat>, <H1> 等),它们通常继承自基础 Widget(如 <Button>, <Label>),其事件处理顺序也主要由其内部结构和父容器决定。如果你需要对特定组件(非 <View>)的子元素事件顺序进行精细控制,你可能需要:
<View> 中,并设置该 <View> 的 event_order。live_design! 定义中调整结构或使用带有特定 event_order 的 <View>。<Label>高级用法:
draw_text 中覆盖 fn get_color 或 fn pixel 来实现文本颜色渐变、特殊效果或基于状态的颜色变化。<Html> 或 <Markdown> Widget,它们内部使用 <TextFlow>,而 <TextFlow> 又使用 <Label> (或其 DrawText) 进行底层绘制。hover_actions_enabled: true,然后在 Rust 代码中监听 LabelAction::HoverIn/HoverOut,通常用于显示 Tooltip。示例 (自定义颜色 Shader):
<Button>高级用法:
draw_bg, draw_text, draw_icon 中的 fn pixel 或 fn get_color,可以完全改变按钮的外观和状态反馈。<Button> 内部使用 <View> 和其他 Widget 来创建包含多个元素(如图标、文本、状态指示器)的复杂按钮布局(需要 Button 的 flow 不是 Overlay,或者自定义绘制逻辑)。animator 创建复杂的按下、悬停、禁用等状态的视觉过渡效果。ButtonAction::Clicked/Pressed/Released,并根据 action_data 执行不同的逻辑。示例 (自定义背景和动画): (参考文档中的 Advanced 示例)
<LinkLabel>
高级用法:
<Button>,可以像 Button 一样覆盖 draw_bg, draw_text 来改变下划线样式、文本颜色和悬停/按下效果。url 属性。clicked 动作,除了打开 URL 外,还可以执行其他应用逻辑。示例 (自定义下划线和颜色):
<TextInput>高级用法:
draw_bg, draw_text, draw_cursor, draw_selection 来完全控制输入框的视觉样式,包括背景、边框、光标、选区高亮等。is_numeric_only,但更复杂的验证(如邮箱格式、最大长度)需要在 Rust 的 handle_actions 中监听 TextInputAction::Changed 并进行处理。bind 属性将输入框的值与 Rust 数据结构同步。TextInputAction::KeyDownUnhandled 来处理未被 TextInput 内部处理的按键事件。set_is_read_only 或 set_is_password。示例 (自定义背景和光标): (参考文档中的 Advanced 示例)
<CheckBox> /<Toggle>高级用法:
draw_bg 中的 fn pixel,可以绘制任意形状或图标来代替默认的勾选标记、圆形或开关滑块。check_type: None 可以完全移除默认绘制,让你用 draw_icon 或其他子 Widget 来表示状态。<CheckBoxCustom> 中,通过覆盖 draw_icon 的 fn get_color 或 fn pixel,可以根据 active (选中状态)、hover、focus 实例变量来改变图标的颜色或外观。bind 属性将复选框的布尔状态与数据模型同步。示例 (自定义 Toggle 外观):
<RadioButton>高级用法:
draw_bg 的 fn pixel 来创建自定义的单选按钮外观(例如,不同的选中标记、背景形状)。radio_type: Tab 用于创建标签页样式的单选按钮。<View> 下,并在 Rust 代码中监听 RadioButtonAction::Clicked,然后手动取消选中同一组中的其他 RadioButton (使用 RadioButtonSet 或手动迭代)。<RadioButtonCustom> 或 <RadioButtonImage> 变体,并配置 draw_icon 或 image 属性。可以结合 animator 根据 active 状态改变图标/图片的外观。bind 和 value 将选中的值同步到数据模型。示例 (Tab 样式与状态管理):
<Slider>高级用法:
draw_bg 的 fn pixel 来完全改变滑块轨道和滑块手柄的外观,包括形状、颜色、渐变等。可以使用 slide_pos (0.0-1.0) 实例变量来确定绘制位置。draw_bg: { bipolar: 1.0 } 可以让滑块的值条从中间向两边绘制,适用于表示 -1 到 1 或类似范围的值。text_input 的样式,或者在 Rust 中监听 SliderAction::Slide 并手动更新一个独立的 <Label> 或 <TextInput>。step 和 precision 来控制滑块的离散值和显示格式。示例 (自定义 Rotary 外观): (参考文档中的 RotarySolid 示例)
<DropDown>高级用法:
draw_bg 和 draw_text 来改变按钮本身的样式。popup_menu: <PopupMenu> { ... } 来完全自定义弹出菜单的背景、边框以及菜单项 (menu_item: <PopupMenuItem> { ... }) 的外观(背景、文本、选中标记)。labels 和 values 列表,然后调用 redraw(cx) 来更新下拉选项。bind 将选中的 value 同步到数据模型。示例 (自定义弹出菜单项):
<Image> /<Icon> /<ImageBlend>高级用法:
draw_bg (Image) 或 draw_icon (Icon) 的 fn pixel 来应用滤镜、颜色调整、混合效果或其他图像处理。animator 改变 opacity, image_scale, image_pan (DrawImage/DrawWebView) 或 draw_icon 中的 instance 变量来实现淡入淡出、缩放、平移或颜色动画。ImageBlend 内置了 blend 动画器用于交叉淡入淡出。load_image_dep_by_path, load_image_file_by_path, load_image_file_by_path_async 或 set_texture 来动态更改显示的图像。示例 (ImageBlend 切换): (参考文档中的 ImageBlend 示例和 App 代码)
<PortalList> /<PortalList2>live_design! 中定义多个不同的列表项模板 (如 ListItemTypeA, ListItemTypeB),然后在 Rust 的 next_visible_item 循环中,根据数据决定为每个 item_id 调用 list.item(cx, item_id, live_id!(ListItemTypeA)) 还是 list.item(cx, item_id, live_id!(ListItemTypeB))。handle_event 中检测滚动条接近末尾(通过 ScrollBarsAction 或比较 first_id 和 range_end),然后异步加载更多数据并更新 range_end。max_pull_down 和对 first_scroll > 0.0 的检测来实现下拉刷新交互。item() 返回已存在项时,这些状态被保留。如果使用了 reuse_items: true,则需要在获取到重用项时重置其状态。<Dock> /<Splitter> /<Tab> /<TabBar>Dock 的 dock_items 状态(添加/移除 Tab 和 Tabs 定义),然后调用 redraw(cx)。需要仔细管理 LiveId。Tab 组件的 draw_bg, draw_name, draw_icon 样式。TabBar 的 draw_bg, draw_fill, draw_drag 样式。Splitter 的 draw_bg 样式。Dock 的 dock_items HashMap 来保存和恢复用户自定义的布局。需要处理 LiveId 冲突(如 PR 中所示)。<Html> /<Markdown> /<TextFlow>draw_normal, draw_italic, draw_bold 等 DrawText2 属性,以及 draw_block (DrawFlowBlock) 的颜色和 fn pixel 来改变渲染样式。code_layout, quote_layout, list_item_layout 等 Layout 属性。link: <MyLink> 来使用自定义的链接组件(需要继承自 LinkLabel 或 Button)。Html 或 Markdown 中嵌入自定义 Widget(如 <Button>),并在 Rust 中处理它们的事件。这通常需要在 draw_walk 中手动处理自定义标签。<Modal> /<PopupNotification>Modal 的 bg_view 或 PopupNotification 的 draw_bg 来改变背景外观(例如,不同的模糊效果、颜色或完全透明)。content 可以是任何复杂的 Widget 组合。animator 来改变弹出/消失的动画效果(例如,淡入淡出、缩放、不同的缓动函数)。open(cx) 和 close(cx)。handle_actions 中监听 Modal/Popup 内容区域发出的动作。<AdaptiveView>set_variant_selector 提供复杂的逻辑来根据多种因素(不仅仅是屏幕尺寸,还可以是平台、设备特性、应用状态等)选择要显示的视图变体。retain_unused_variants: true 来保留非活动视图的状态,避免在切换回来时重新初始化。需要注意内存使用。<CachedWidget>最佳实践通常是在 DSL 中尽可能多地进行声明式定义,并将复杂的逻辑和状态管理保留在 Rust 代码中。
重点关注在 live_design! 的 draw_* 块中 fn pixel 和 fn vertex 函数内可用的特性和内置函数。
fn pixel(self) -> vec4: 像素着色器函数。计算并返回当前像素的最终颜色 (RGBA)。self 包含了 uniform, instance, varying 变量以及内置变量如 pos (归一化坐标 0-1), rect_pos, rect_size。fn vertex(self) -> vec4: 顶点着色器函数。计算并返回顶点的最终裁剪空间位置 (Clip Space)。通常需要设置 varying 变量传递给 pixel 函数。self 包含 uniform, instance, varying 以及内置变量如 geom_pos (几何体归一化坐标 0-1), rect_pos, rect_size, camera_projection, camera_view, view_transform, draw_clip, view_clip, draw_zbias, draw_depth。self: 在 shader 函数内部,self 包含了所有可用的 uniform, instance, varying 变量以及内置变量。可以直接通过 self.propertyName 访问。float: 单精度浮点数。vec2, vec3, vec4: 2/3/4 维浮点向量。mat2, mat3, mat4: 2x2, 3x3, 4x4 浮点矩阵。int, ivec2, ivec3, ivec4: 整数及向量。bool, bvec2, bvec3, bvec4: 布尔值及向量。texture2d: 2D 纹理采样器。textureOES: (Android 特定) OES 纹理采样器,通常用于视频。self.pos (vec2, pixel shader): 当前像素在 rect_size 内的归一化坐标 (0.0 到 1.0)。self.geom_pos (vec2, vertex shader): 输入几何体(通常是四边形)的归一化顶点坐标 (0.0 到 1.0)。self.rect_pos (vec2): 当前绘制矩形的左上角屏幕坐标。self.rect_size (vec2): 当前绘制矩形的尺寸(宽、高)。self.draw_clip (vec4): 绘制裁剪区域 (xy=min, zw=max)。self.view_clip (vec4): 视图裁剪区域 (xy=min, zw=max)。self.view_shift (vec2): 视图滚动偏移。self.camera_projection (mat4): 摄像机投影矩阵。self.camera_view (mat4): 摄像机视图矩阵。self.view_transform (mat4): 视图变换矩阵。self.draw_depth (float): 基础绘制深度。self.draw_zbias (float): 深度偏移。self.dpi_factor (float): 当前 DPI 因子。uniform name: type: 在 DSL 中定义,所有实例共享(可覆盖)。instance name: type: 在 DSL 中定义,每个实例独立(常用于动画/状态)。varying name: type: 在 DSL 中定义,用于在 vertex 和 pixel 之间传递数据。let name: type = value;: 在函数内部声明局部变量。var name: type = value;: 在函数内部声明可变局部变量。#RGB, #RGBA, #RRGGBB, #RRGGBBAA: 十六进制颜色。vec4(r, g, b, a): 0.0 到 1.0 范围的 RGBA。(THEME_COLOR_...): 引用主题颜色常量。Sdf2d, Pal, Math, GaussShadow)Sdf2d (Signed Distance Field 2D): 用于矢量绘图。
Sdf2d::viewport(pos: vec2) -> Self: 创建 SDF 上下文。sdf.clear(color: vec4): 用颜色覆盖结果。sdf.dist 和 sdf.shape)
sdf.circle(cx, cy, radius)sdf.rect(x, y, w, h)sdf.box(x, y, w, h, radius)sdf.box_x(x, y, w, h, r_left, r_right)sdf.box_y(x, y, w, h, r_top, r_bottom)sdf.box_all(x, y, w, h, r_lt, r_rt, r_rb, r_lb)sdf.hexagon(cx, cy, radius)sdf.hline(y, half_thickness)sdf.move_to(x, y)sdf.line_to(x, y)sdf.close_path()sdf.arc2(cx, cy, radius, start_angle_rad, end_angle_rad)sdf.arc_round_caps(cx, cy, radius, start_angle, end_angle, thickness)sdf.arc_flat_caps(...)sdf.union(): shape = min(dist, old_shape)sdf.intersect(): shape = max(dist, old_shape)sdf.subtract(): shape = max(-dist, old_shape)sdf.fill(color: vec4) -> vec4: 填充并重置 shape。sdf.fill_keep(color: vec4) -> vec4: 填充并保留 shape。sdf.fill_premul(premultiplied_color: vec4) -> vec4: 预乘 Alpha 填充并重置。sdf.fill_keep_premul(premultiplied_color: vec4) -> vec4: 预乘 Alpha 填充并保留。sdf.stroke(color: vec4, width: float) -> vec4: 描边并重置 shape。sdf.stroke_keep(color: vec4, width: float) -> vec4: 描边并保留 shape。sdf.glow(color: vec4, width: float) -> vec4: 辉光并重置 shape。sdf.glow_keep(color: vec4, width: float) -> vec4: 辉光并保留 shape。sdf.gloop(k: float): 平滑混合 (Metaball)。sdf.blend(k: float): 线性混合。sdf.translate(dx, dy)sdf.rotate(angle_rad, pivot_x, pivot_y)sdf.scale(factor, pivot_x, pivot_y)sdf.result (最终 vec4 颜色)。Pal (Palette): 颜色处理函数。
Pal::premul(color: vec4) -> vec4: 转换为预乘 Alpha。Pal::hsv2rgb(hsv: vec4) -> vec4: HSV 转 RGB。Pal::rgb2hsv(rgb: vec4) -> vec4: RGB 转 HSV。Pal::iqX(t: float) -> vec3: Inigo Quilez 的调色板函数 (X=0..7)。Math: 数学函数。
Math::rotate_2d(v: vec2, angle_rad: float) -> vec2: 旋转 2D 向量。Math::random_2d(v: vec2) -> float: 基于输入向量生成伪随机数 (0-1)。GaussShadow: 高斯模糊阴影计算。
GaussShadow::box_shadow(lower: vec2, upper: vec2, point: vec2, sigma: float) -> float: 计算矩形阴影的模糊值。GaussShadow::rounded_box_shadow(lower: vec2, upper: vec2, point: vec2, sigma: float, corner: float) -> float: 计算圆角矩形阴影的模糊值。(基于文档中 builtin 目录下的文件)
abs, ceil, clamp, degrees, distance, dot, exp, exp2, faceforward, floor, fract, inversesqrt, length, log, log2, max, min, mix, mod, normalize, pow, radians, reflect, refract, sign, smoothstep, sqrt, step。acos, asin, atan, cos, sin, tan。cross, matrixCompMult, transpose, inverse。all, any, equal, greaterThan, greaterThanEqual, lessThan, lessThanEqual, not, notEqual。sample2d(sampler: texture2d, coord: vec2) -> vec4: 对 2D 纹理进行采样。sample2d_rt(sampler: texture2d, coord: vec2) -> vec4: (Makepad 特定) 可能用于渲染目标纹理的采样,行为可能与 sample2d 类似或有特定优化。sample2dOES(sampler: textureOES, coord: vec2) -> vec4: (Android 特定) 对 OES 纹理进行采样。dFdx(p), dFdy(p): 计算变量 p 相对于屏幕 x/y 坐标的偏导数(仅 Fragment Shader)。self: 知道 self 中有哪些可用的内置变量 (pos, rect_pos, rect_size 等) 和你在 DSL 中定义的 uniform/instance/varying 变量。self.pos: 通常是 0-1 的归一化坐标,相对于 self.rect_size。self.geom_pos: 0-1 的几何体坐标,通常用于顶点着色器。Sdf2d::viewport(self.pos * self.rect_size): 将归一化坐标转换为相对于当前绘制矩形的像素级坐标,这是 SDF 绘图的常用起点。Sdf2d: 对于矢量图形,优先使用 Sdf2d 提供的函数,它们通常比手动计算更方便、更优化。fill/stroke 会重置 sdf.shape,而 fill_keep/stroke_keep 不会。布尔运算 (union, intersect, subtract) 会更新 sdf.shape 和 sdf.old_shape。Pal::premul 用于转换,fill_premul/fill_keep_premul 直接处理预乘颜色。直接返回 sdf.result 通常已经是预乘的。pixel 函数中进行过于复杂的计算或循环。varying 在 vertex 中计算可以在像素间插值的值。instance 变量接收来自 animator 的值,并在 pixel 或 vertex 函数中使用 mix() 或其他逻辑来根据这些值改变外观。return #f00;) 来测试代码块是否执行。return vec4(value, 0.0, 0.0, 1.0);)。View 的 debug 属性。核心思想:比例定位
想象一个父容器,比如一个 <View>,它有一定的宽度和高度。当你在里面放置一个子元素时,如果父容器的空间比子元素大,子元素就需要知道放在这个“剩余空间”的哪个位置。
“归一化相对坐标”就是一种描述这个位置的方式,它不使用具体的像素值,而是使用比例。
0.0 代表一端的开始(左边或顶部)。1.0 代表另一端的结束(右边或底部)。0.5 代表正中间。具体到 align: {x: f64, y: f64}:
align.x (水平方向):
0.0: 将子元素的左边缘与父容器可用空间的左边缘对齐。0.5: 将子元素的水平中心与父容器可用空间的水平中心对齐。1.0: 将子元素的右边缘与父容器可用空间的右边缘对齐。0.25: 将子元素放置在从左边算起 1/4 处的位置。0.75: 将子元素放置在从左边算起 3/4 处的位置。align.y (垂直方向):
0.0: 将子元素的上边缘与父容器可用空间的上边缘对齐。0.5: 将子元素的垂直中心与父容器可用空间的垂直中心对齐。1.0: 将子元素的下边缘与父容器可用空间的下边缘对齐。0.25: 将子元素放置在从顶部算起 1/4 处的位置。0.75: 将子元素放置在从顶部算起 3/4 处的位置。可用空间 (Available Space):
这是理解相对坐标的关键。可用空间是指父容器在放置完所有非对齐子元素(例如,按 flow: Right 或 flow: Down 排列的元素)以及考虑了自身的 padding 之后,剩余的用于放置对齐子元素(通常是那些 width: Fit, height: Fit 或 width: Fixed, height: Fixed 的元素)的空间。
flow: Right: 可用水平空间是父容器宽度减去所有子元素宽度、间距和父容器左右 padding 后的剩余宽度。可用垂直空间通常是父容器的高度减去其上下 padding。flow: Down: 可用垂直空间是父容器高度减去所有子元素高度、间距和父容器上下 padding 后的剩余高度。可用水平空间通常是父容器的宽度减去其左右 padding。flow: Overlay: 可用空间通常就是父容器减去 padding 后的整个内部区域。为什么使用归一化相对坐标?
Size::Fill 的关系: 当子元素使用 Size::Fill 时,它会占据所有可用空间,此时 align 在该方向上通常不起作用,因为没有剩余空间可以用来对齐。示例理解:
在这个例子中:
align: {x: 0.5, y: 0.5} 意味着按钮的中心点应该对齐到可用空间的中心点。align.x = 10 + 120 * 0.5 = 70。align.y = 10 + 50 * 0.5 = 35。(70, 35) 的位置(相对于父容器的左上角)。总之,归一化相对坐标提供了一种与分辨率无关、基于比例的方式来定义子元素在父容器可用空间内的对齐位置。