Files
endorphin-tessera-gui/src/bridge/ui_bridge/tree.rs
T

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>>,
},
}