//! 面向设备像素的绘制指令集合。它承载矩形、文字、裁剪与 3D 表面等原语,不直接操作 VM。 //! 平移和缩放统一作用于命令几何;文字样式和裁剪也要使用一致的设备比例。 //! 调用路径:RenderDocument.display_list 将已验证逻辑命令转为这些设备原语;shell/system_ui 也可直接组装。 //! SoftwareRenderer 顺序消费 commands;合成页面/工具面板时用 translate/extend,不能把共享场景数据本身平移。 //! use crate::geometry::{Point, Rect}; use crate::text::TextStyle; #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] /// 设备绘制使用的 RGBA 字节通道,区别于源代码的颜色文本。 pub struct Color { // 红通道字节;源十六进制颜色由 document.parse_color 解码。 pub red: u8, // 绿通道字节,颜色本身不带坐标或设备比例。 pub green: u8, // 蓝通道字节,与输出软件像素通道组合。 pub blue: u8, // 透明度 0..255,混合方式由具体原语渲染器解释。 pub alpha: u8, } impl Color { /// 从 RGB 通道构造不透明颜色值。 pub const fn rgb(red: u8, green: u8, blue: u8) -> Self { Self { red, green, blue, alpha: 255, } } /// 从 RGBA 通道构造带透明度的颜色值。 pub const fn rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Self { Self { red, green, blue, alpha, } } } #[derive(Clone, Debug)] /// 原生画布最终顺序执行的绘制原语,已经使用对应设备比例。 pub enum DrawCommand { /// Immutable RGBA image mapped from its local rectangle by rotation. Image { // 不可变 RGBA 资源 Arc,窗口渲染无需访问源对象或重新解码 PNG。 image: std::sync::Arc, // 设备坐标旋转原点,平移只移动它,不改局部 rect 的偏移。 origin: Point, // 相对 origin 的局部采样矩形,旋转时绕 origin 映射。 rect: Rect, // 旋转角参数,平移/同比缩放不改变它。 angle: f32, // 整体不透明度系数,与资源像素 alpha 在采样时组合。 opacity: f32, // 可选染色通道值,不属于位置/尺寸变换。 tint: Option, }, // 在 rect 内绘制不可变 3D 视图;平移/缩放改变视口矩形,不改写共享场景或相机。 Scene3D { rect: Rect, view: std::sync::Arc, }, // 清除整个目标缓冲;不携带几何,因此列表平移和缩放不会改变它。 Clear(Color), // 将矩形压入裁剪栈;后续命令受裁剪约束,必须由对应 PopClip 恢复。 PushClip(Rect), // 恢复上层裁剪。合并列表保持原顺序,调用方需保证裁剪栈配对。 PopClip, // 填充圆角矩形;半径与矩形尺寸在设备缩放时使用同一比例。 FillRoundedRect { rect: Rect, radius: f32, color: Color, }, // 描边圆角矩形;描边宽度也是长度,必须随几何一起缩放。 StrokeRoundedRect { rect: Rect, radius: f32, width: f32, color: Color, }, // 在有限矩形内进行文本布局;缩放同时作用于矩形和文字样式,保持换行比例。 WrappedText { rect: Rect, value: String, style: TextStyle, color: Color, }, // 以原点定位文本,文本布局使用 TextStyle;与 WrappedText 不同,不额外携带有限文本框。 Text { origin: Point, value: String, style: TextStyle, color: Color, }, } impl DrawCommand { /// 返回平移后的绘制命令,并同步移动命令中的相关几何。 pub fn translated(mut self, delta: Point) -> Self { // 平移只改变位置,保留尺寸、圆角、文字样式和颜色;消费 self 后返回修改后的命令。 match &mut self { // 这些原语的定位直接在 rect 上,移动矩形原点即可,尺寸保持不变。 Self::WrappedText { rect, .. } | Self::Scene3D { rect, .. } | Self::PushClip(rect) | Self::FillRoundedRect { rect, .. } | Self::StrokeRoundedRect { rect, .. } => { rect.x += delta.x; rect.y += delta.y; } // 文字与图片使用 origin 定位;图片 rect 是局部偏移,不能在此跟着重复平移。 Self::Text { origin, .. } | Self::Image { origin, .. } => { origin.x += delta.x; origin.y += delta.y; } // Clear/PopClip 无定位几何,平移不产生任何变化。 _ => {} } self } } #[derive(Clone, Debug, Default)] /// 有序绘制原语容器;命令顺序同时约束叠放和裁剪栈行为。 pub struct DisplayList { // 拥有按执行顺序排列的原语、字符串与原生资源 Arc,列表本身不引用 VM。 commands: Vec, } impl DisplayList { /// 按设备比例缩放命令,文字、边框和矩形不能分别使用不一致比例。 pub fn scale(&mut self, scale: f32) { // 原地累计缩放:重复调用会相乘,并非设置绝对缩放值。调用方负责提供有效设备比例。 for command in &mut self.commands { // 先处理各变体独有的长度(字体、圆角、描边),再把公共矩形交给下方统一缩放。 let rect = match command { // 有限文字框交给公共矩形缩放,样式先同步乘比例,避免换行框与字形不一致。 DrawCommand::WrappedText { rect, style, .. } => { *style = style.scaled(scale); Some(rect) } // 裁剪属于设备几何,和所裁内容使用相同比例。 DrawCommand::PushClip(rect) => Some(rect), // 只改三维显示区域,不缩放共享世界网格和相机数值。 DrawCommand::Scene3D { rect, .. } => Some(rect), // 圆角作为长度先缩放,再返回矩形供统一处理。 DrawCommand::FillRoundedRect { rect, radius, .. } => { *radius *= scale; Some(rect) } // 半径与描边宽也要缩放,不能只改外框而保留旧粗细。 DrawCommand::StrokeRoundedRect { rect, radius, width, .. } => { *radius *= scale; *width *= scale; Some(rect) } // 图片 origin 和局部 rect 均在设备单位,分别缩放后返回 None,防止公共分支再缩放一次。 DrawCommand::Image { origin, rect, .. } => { origin.x *= scale; origin.y *= scale; rect.x *= scale; rect.y *= scale; rect.width *= scale; rect.height *= scale; None } // 无框文字单独缩放原点和样式,没有矩形需要返回公共路径。 DrawCommand::Text { origin, style, .. } => { origin.x *= scale; origin.y *= scale; *style = style.scaled(scale); None } // Clear/PopClip 不含长度数据,保持原样。 _ => None, }; // 矩形的原点和宽高一起缩放,保证裁剪范围、文本框与实际绘制内容对齐。 if let Some(rect) = rect { rect.x *= scale; rect.y *= scale; rect.width *= scale; rect.height *= scale; } } } /// 按绘制顺序追加一条原语,后加入的内容在合成顺序中位于后面。 pub fn push(&mut self, command: DrawCommand) { self.commands.push(command); } /// 平移当前显示列表中的命令,供整块页面或管理面板放入窗口坐标。 // 与单条 translated 相同,但直接修改整个列表;Clear/PopClip 没有位置需要移动。 pub fn translate(&mut self, delta: Point) { // 保持原语顺序逐项移动,不能按种类分组而打乱覆盖关系和裁剪栈。 for command in &mut self.commands { // 与单条 translated 保持同一空间规则:rect 型移原点,图片只移旋转 origin。 match command { DrawCommand::WrappedText { rect, .. } | DrawCommand::Scene3D { rect, .. } | DrawCommand::PushClip(rect) | DrawCommand::FillRoundedRect { rect, .. } | DrawCommand::StrokeRoundedRect { rect, .. } => { rect.x += delta.x; rect.y += delta.y; } DrawCommand::Text { origin, .. } | DrawCommand::Image { origin, .. } => { origin.x += delta.x; origin.y += delta.y; } // 无几何的状态原语保留顺序但不做坐标更新。 DrawCommand::Clear(_) | DrawCommand::PopClip => {} } } } /// 借用按绘制顺序排列的原生原语,不复制命令集合。 pub fn commands(&self) -> &[DrawCommand] { &self.commands } /// 按原顺序合并另一组绘制命令,不在这里重排层次。 // 消费另一个列表并移动其命令,不复制字符串或场景数据,也不在连接处补裁剪指令。 pub fn extend(&mut self, other: DisplayList) { self.commands.extend(other.commands); } }