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: f32
curve: f32
fn get_color(self) -> vec4 { ... }
: 可覆盖的颜色逻辑。DrawLine
: 绘制线条 (继承自 DrawQuad
)。
color: #RRGGBBAA
line_width: f64
(在 Rust 中设置)DrawScrollShadow
: 绘制滚动阴影。
shadow_size: f32
Sdf2d
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: bool
draw_bg: <DrawColor>
(或覆盖为其他 Draw* 类型)walk
, layout
scroll_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
, layout
enabled: bool
<LinkLabel>
: 带下划线的按钮,用于链接。
<Button>
。url: "string"
(Rust 中设置)open_in_place: bool
<TextInput>
: 文本输入框。
text: "string"
empty_text: "string"
is_password
, is_read_only
, is_numeric_only: bool
draw_text
, draw_bg
, draw_cursor
, draw_selection
walk
, layout
<CheckBox>
/ <Toggle>
: 复选框/开关。
text: "string"
draw_bg: <DrawCheckBox>
(可设置 check_type: Check/Radio/Toggle/None
)bind: "path.to.data"
<RadioButton>
: 单选按钮。
text: "string"
value: EnumVariant
draw_bg: <DrawRadioButton>
(可设置 radio_type: Round/Tab
)<View>
或 <ButtonGroup>
内使用。<Slider>
: 滑块。
text: "string"
min
, max
, default
, step
, precision
bind: "path.to.data"
text_input: <TextInput>
(内置文本输入)draw_bg: <DrawSlider>
<DropDown>
: 下拉菜单。
labels: ["...", ...]
values: [Enum::Val1, ...]
selected_item: usize
popup_menu: <PopupMenu>
popup_menu_position: OnSelected/BelowInput
bind: "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: bool
scroll_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/Vertical
align: FromA/FromB/Weighted
a: <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)
的位置(相对于父容器的左上角)。总之,归一化相对坐标提供了一种与分辨率无关、基于比例的方式来定义子元素在父容器可用空间内的对齐位置。