210 lines
7.5 KiB
Rust
210 lines
7.5 KiB
Rust
//! 面向设备像素的绘制指令集合。它承载矩形、文字、裁剪与 3D 表面等原语,不直接操作 VM。
|
|
//! 平移和缩放统一作用于命令几何;文字样式和裁剪也要使用一致的设备比例。
|
|
//!
|
|
use crate::geometry::{Point, Rect};
|
|
use crate::text::TextStyle;
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
/// 设备绘制使用的 RGBA 字节通道,区别于源代码的颜色文本。
|
|
pub struct Color {
|
|
pub red: u8,
|
|
pub green: u8,
|
|
pub blue: u8,
|
|
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 {
|
|
image: std::sync::Arc<crate::render::image::Image>,
|
|
origin: Point,
|
|
rect: Rect,
|
|
angle: f32,
|
|
opacity: f32,
|
|
tint: Option<Color>,
|
|
},
|
|
// 在 rect 内绘制不可变 3D 视图;平移/缩放改变视口矩形,不改写共享场景或相机。
|
|
Scene3D {
|
|
rect: Rect,
|
|
view: std::sync::Arc<crate::scene3d::View>,
|
|
},
|
|
// 清除整个目标缓冲;不携带几何,因此列表平移和缩放不会改变它。
|
|
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 {
|
|
Self::WrappedText { rect, .. }
|
|
| Self::Scene3D { rect, .. }
|
|
| Self::PushClip(rect)
|
|
| Self::FillRoundedRect { rect, .. }
|
|
| Self::StrokeRoundedRect { rect, .. } => {
|
|
rect.x += delta.x;
|
|
rect.y += delta.y;
|
|
}
|
|
Self::Text { origin, .. } | Self::Image { origin, .. } => {
|
|
origin.x += delta.x;
|
|
origin.y += delta.y;
|
|
}
|
|
_ => {}
|
|
}
|
|
self
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
/// 有序绘制原语容器;命令顺序同时约束叠放和裁剪栈行为。
|
|
pub struct DisplayList {
|
|
commands: Vec<DrawCommand>,
|
|
}
|
|
|
|
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)
|
|
}
|
|
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
|
|
}
|
|
_ => 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 {
|
|
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);
|
|
}
|
|
}
|