292 lines
11 KiB
Rust
292 lines
11 KiB
Rust
//! 定义从托管对象提取的不可变视觉树。共享业务对象可以有多次展示,树节点与 Mount 出现的身份不是同一概念。
|
|
//! 节点保存排版、绘制和协议投影信息;浅比较用于决定是否复用已有快照。
|
|
//!
|
|
//! Owned immutable visual derivation; no managed VM handles.
|
|
use std::sync::Arc;
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
/// 不可变视觉节点。block_id 关联业务身份,slots 保留子项出现的稳定组成;kind 和盒模型字段用于派生排版与绘制。
|
|
pub struct UiNode {
|
|
// 只有页面根带兼容 Page 相机;其余字段是普通有限盒子的布局策略。
|
|
pub surface: Option<crate::page::Surface>,
|
|
pub flex: f32,
|
|
pub flex_policy: crate::layout::FlexPolicy,
|
|
pub main_align: crate::layout::MainAlign,
|
|
pub cross_align: crate::layout::CrossAlign,
|
|
pub align_self: Option<crate::layout::CrossAlign>,
|
|
pub margin: crate::layout::Insets,
|
|
pub sizing: crate::box_model::Sizing,
|
|
// 单边可选覆盖值;None 表示沿用 kind 的统一 padding,不代表边距为零。
|
|
pub padding_edges: [Option<f32>; 4],
|
|
pub border: crate::layout::Insets,
|
|
pub border_color: String,
|
|
pub clip_children: bool,
|
|
// 这些字符串只是派生身份/协议版本键,不是可以操作托管堆的 ObjectRef。
|
|
pub block_id: String,
|
|
pub protocol_key: String,
|
|
// slot 保持组成身份;数组次序确定当前绘制/布局顺序,二者不是同一概念。
|
|
pub slots: Vec<u64>,
|
|
// 从按钮、点击连接或自定义事件等能力推导的交互资格,供命中宽阶段使用,不代表最终 hitTest 必然成功。
|
|
pub interactive: bool,
|
|
pub kind: UiKind,
|
|
}
|
|
|
|
impl UiNode {
|
|
/// 按节点种类返回其视觉子项;叶节点使用空切片,避免为遍历另建组成。
|
|
pub fn children(&self) -> &[Arc<UiNode>] {
|
|
match &self.kind {
|
|
UiKind::Column { children, .. }
|
|
| UiKind::Overlay { children, .. }
|
|
| UiKind::Canvas { children, .. }
|
|
| UiKind::Scene2D { children, .. }
|
|
| UiKind::CanvasViewport { children, .. }
|
|
| UiKind::Viewport3D { children, .. }
|
|
| UiKind::Row { children, .. }
|
|
| UiKind::Button { children, .. }
|
|
| UiKind::Scroll { children, .. } => children,
|
|
_ => &[],
|
|
}
|
|
}
|
|
|
|
/// 判断是否需要对子项执行弹性主轴分配。
|
|
pub fn has_flex(&self) -> bool {
|
|
// 不仅旧 flex 数值,非默认 FlexPolicy 或任一轴 Fill 也要求父节点执行分配,不能仅按自然尺寸排布。
|
|
self.flex > 0.0
|
|
|| self.flex_policy != crate::layout::FlexPolicy::default()
|
|
|| self.sizing.width == crate::box_model::Dimension::Fill
|
|
|| self.sizing.height == crate::box_model::Dimension::Fill
|
|
}
|
|
|
|
/// 判断当前节点是否会为子项指定实际分配范围,供源布局选择安排路径。
|
|
pub fn allocates_children(&self) -> bool {
|
|
// 任何孩子需要弹性或拉伸即进入分配路径;align_self 优先于父 cross_align。
|
|
self.children().iter().any(|child| {
|
|
child.has_flex()
|
|
|| child.align_self.unwrap_or(self.cross_align)
|
|
== crate::layout::CrossAlign::Stretch
|
|
})
|
|
}
|
|
|
|
/// 比较节点自身配置和直接子项身份,决定能否复用旧 Arc;不是深拷贝业务图。
|
|
pub fn shallow_eq(&self, other: &Self) -> bool {
|
|
// 先比较所有公共视觉/协议配置,任一变化都不能复用旧节点 Arc,即使子内容完全相同。
|
|
if self.surface != other.surface
|
|
|| self.flex_policy != other.flex_policy
|
|
|| self.flex != other.flex
|
|
|| self.main_align != other.main_align
|
|
|| self.cross_align != other.cross_align
|
|
|| self.align_self != other.align_self
|
|
|| self.margin != other.margin
|
|
|| self.sizing != other.sizing
|
|
|| self.padding_edges != other.padding_edges
|
|
|| self.border != other.border
|
|
|| self.border_color != other.border_color
|
|
|| self.clip_children != other.clip_children
|
|
|| self.block_id != other.block_id
|
|
|| self.protocol_key != other.protocol_key
|
|
|| self.slots != other.slots
|
|
|| self.interactive != other.interactive
|
|
{
|
|
return false;
|
|
}
|
|
// 直接孩子按 Arc 指针比较而不是递归深比较;依赖提取保证未变子树复用 Arc,父级只需验证其身份。
|
|
let same_children = || {
|
|
self.children().len() == other.children().len()
|
|
&& self
|
|
.children()
|
|
.iter()
|
|
.zip(other.children())
|
|
.all(|(a, b)| Arc::ptr_eq(a, b))
|
|
};
|
|
// 同类变体再比较其专有数据和直接子项;变体不同必为 false,避免布局种类切换后误复用缓存。
|
|
match (&self.kind, &other.kind) {
|
|
(
|
|
UiKind::Viewport3D {
|
|
width: a,
|
|
height: b,
|
|
view: c,
|
|
controls: g,
|
|
panels: i,
|
|
..
|
|
},
|
|
UiKind::Viewport3D {
|
|
width: d,
|
|
height: e,
|
|
view: f,
|
|
controls: h,
|
|
panels: j,
|
|
..
|
|
},
|
|
) => a == d && b == e && c == f && g == h && i == j && same_children(),
|
|
(
|
|
UiKind::CanvasViewport {
|
|
width: a,
|
|
height: b,
|
|
camera: c,
|
|
..
|
|
},
|
|
UiKind::CanvasViewport {
|
|
width: d,
|
|
height: e,
|
|
camera: f,
|
|
..
|
|
},
|
|
) => a == d && b == e && c == f && same_children(),
|
|
(UiKind::Scene2D { positions: a, .. }, UiKind::Scene2D { positions: b, .. }) => {
|
|
a == b && same_children()
|
|
}
|
|
(UiKind::Overlay { padding: a, .. }, UiKind::Overlay { padding: b, .. }) => {
|
|
a == b && same_children()
|
|
}
|
|
(UiKind::Canvas { positions: a, .. }, UiKind::Canvas { positions: b, .. }) => {
|
|
a == b && same_children()
|
|
}
|
|
(
|
|
UiKind::Text {
|
|
value: a,
|
|
size: sa,
|
|
color: ca,
|
|
},
|
|
UiKind::Text {
|
|
value: b,
|
|
size: sb,
|
|
color: cb,
|
|
},
|
|
) => a == b && sa == sb && ca == cb,
|
|
(
|
|
UiKind::Rect {
|
|
width: a,
|
|
height: b,
|
|
radius: c,
|
|
color: d,
|
|
},
|
|
UiKind::Rect {
|
|
width: e,
|
|
height: f,
|
|
radius: g,
|
|
color: h,
|
|
},
|
|
) => (a, b, c, d) == (e, f, g, h),
|
|
(UiKind::Spacer(a), UiKind::Spacer(b)) => a == b,
|
|
(
|
|
UiKind::Scroll {
|
|
axis: aa,
|
|
width: a,
|
|
height: b,
|
|
padding: pa,
|
|
gap: ga,
|
|
children: _,
|
|
},
|
|
UiKind::Scroll {
|
|
axis: ab,
|
|
width: c,
|
|
height: d,
|
|
padding: pb,
|
|
gap: gb,
|
|
children: _,
|
|
},
|
|
) => aa == ab && a == c && b == d && pa == pb && ga == gb && same_children(),
|
|
(
|
|
UiKind::Column {
|
|
padding: a, gap: b, ..
|
|
},
|
|
UiKind::Column {
|
|
padding: c, gap: d, ..
|
|
},
|
|
)
|
|
| (
|
|
UiKind::Row {
|
|
padding: a, gap: b, ..
|
|
},
|
|
UiKind::Row {
|
|
padding: c, gap: d, ..
|
|
},
|
|
)
|
|
| (
|
|
UiKind::Button {
|
|
padding: a, gap: b, ..
|
|
},
|
|
UiKind::Button {
|
|
padding: c, gap: d, ..
|
|
},
|
|
) => a == c && b == d && same_children(),
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
/// 视觉内容的原生分类。叶节点保存绘制数据,容器保存子树,场景视口另外保存相机与世界投影。
|
|
pub enum UiKind {
|
|
// 有限 3D 视口包含不可变场景视图和面板 UI 孩子;panels 与 children/slots 按顺序对应。
|
|
Viewport3D {
|
|
width: f32,
|
|
height: f32,
|
|
view: Arc<crate::scene3d::View>,
|
|
controls: bool,
|
|
panels: Vec<crate::layout::Size>,
|
|
children: Vec<Arc<UiNode>>,
|
|
},
|
|
// 二维世界组成保留孩子的显式位置,不使用行列的累计偏移定位。
|
|
Scene2D {
|
|
positions: Vec<[f32; 2]>,
|
|
children: Vec<Arc<UiNode>>,
|
|
},
|
|
// 有限布局盒子观察独立 Scene2D,相机仅影响投影,不改变场景节点原始逻辑位置。
|
|
CanvasViewport {
|
|
width: f32,
|
|
height: f32,
|
|
camera: crate::viewport::CameraView,
|
|
children: Vec<Arc<UiNode>>,
|
|
},
|
|
// 孩子重叠布置,顺序同时决定叠放次序;普通有限布局与世界 Canvas 不同。
|
|
Overlay {
|
|
padding: f32,
|
|
children: Vec<Arc<UiNode>>,
|
|
},
|
|
// 旧 Page Canvas 模式的兼容投影,位置数组与孩子顺序一致,页面级 Surface 保存其相机。
|
|
Canvas {
|
|
positions: Vec<[f32; 2]>,
|
|
children: Vec<Arc<UiNode>>,
|
|
},
|
|
// 按钮是有子内容的容器,不把标签文本硬编码进 UiKind;交互资格另由 UiNode 标志表达。
|
|
Button {
|
|
padding: f32,
|
|
gap: f32,
|
|
children: Vec<Arc<UiNode>>,
|
|
},
|
|
// 文字叶节点保留源字号等级与颜色文本;排版及最终设备比例由后续布局/显示列表转换处理。
|
|
Text {
|
|
value: String,
|
|
size: u32,
|
|
color: String,
|
|
},
|
|
Rect {
|
|
width: f32,
|
|
height: f32,
|
|
radius: f32,
|
|
color: String,
|
|
},
|
|
Column {
|
|
padding: f32,
|
|
gap: f32,
|
|
children: Vec<Arc<UiNode>>,
|
|
},
|
|
Row {
|
|
padding: f32,
|
|
gap: f32,
|
|
children: Vec<Arc<UiNode>>,
|
|
},
|
|
// 无视觉内容的间隔叶节点,提供布局占用尺寸。
|
|
Spacer(f32),
|
|
// 滚动盒保存有限分配尺寸、允许轴和普通内容;实际偏移按 Mount 出现分别维护,不放到共享节点里。
|
|
Scroll {
|
|
axis: crate::scroll::Axis,
|
|
width: f32,
|
|
height: f32,
|
|
padding: f32,
|
|
gap: f32,
|
|
children: Vec<Arc<UiNode>>,
|
|
},
|
|
}
|