Files
endorphin-tessera-gui/src/render/scene.rs
T

120 lines
6.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 合成整个原生窗口的显示列表:页面画面、宿主侧栏和开发工具绘制。
//! 窗口逻辑尺寸与设备像素比例在这里衔接。页面内容先应用其视角并裁剪,宿主控件的区域和命中布局另外返回。
//! shell/app 为每个 OS 窗口用各自已发布文档调用 build_scene;本模块只组装设备命令,不进入 VM 或刷新源协议。
//!
use crate::{
activity::Activity,
developer_tools::{self, DeveloperToolsLayout, Workspace as DeveloperToolsState},
display_list::{DisplayList, DrawCommand},
document::RenderDocument,
geometry::{Point, Rect},
};
#[derive(Clone, Debug, Default)]
/// 宿主管理区域的几何布局,供窗口事件分辨页面与工具命中。
pub struct SceneLayout {
// 开发工具的逻辑坐标布局;页面业务对象的命中几何仍由 RenderDocument 管理。
pub developer_tools: DeveloperToolsLayout,
}
/// 组合页面、开发工具和侧栏的设备像素显示列表,同时返回供命中使用的宿主布局。
/// shell/app 的窗口绘制路径调用此入口;width/height 是整窗设备像素,scale 将逻辑坐标换成设备坐标。
/// 返回的显示列表交给窗口渲染,layout 则保留同帧开发工具命中区域,避免输入使用另一帧的几何。
pub fn build_scene(
width: u32,
height: u32,
scale: f32,
document: &RenderDocument,
developer_tools_state: &mut DeveloperToolsState,
activity: &Activity,
sidebar: &mut crate::sidebar::State,
) -> DisplayScene {
// 活动标签的 developer_tool 标记决定管理页类型,不靠当前窗口标题猜测;同步工具状态后再合成。
let tool = document
.frame
.tabs
.iter()
.find(|tab| tab.active)
.and_then(|tab| tab.developer_tool.as_deref());
// 先切换当前工具,再通知文档变化;工具绘制由此读取与活动标签一致的状态。
developer_tools_state.select(tool);
developer_tools_state.document_updated(document);
// left 是侧栏占用的逻辑宽度,surface 保存业务页面的 pan/zoom;二者属于不同坐标层。
let left = sidebar.left();
let surface = &document.frame.surface;
// width/height 为设备像素,sidebar.left 为逻辑单位,先乘 scale 得到页面可用裁剪区域。
let content = Rect::new(
left * scale,
0.0,
(width as f32 - left * scale).max(0.0),
height as f32,
);
// 管理页使用单位页面缩放,避免业务 Page 相机改变宿主工具字号;普通页叠加旧 Surface zoom。
// Mount/逐命令相机投影已在文档内,Surface 是外层旧 Page 视角,设备 scale 则属于整个 OS 窗口,三者不能重复应用。
let zoom = if tool.is_some() { 1.0 } else { surface.zoom };
// 显示列表转换失败时降级为空默认文档;这只保护原生绘制,不把无效命令伪装成成功执行。
let mut page = document.display_list(scale * zoom).unwrap_or_else(|_| {
RenderDocument::default()
.display_list(scale)
.expect("default document is valid")
});
// 页面显示列表先已乘设备比例/zoom,再加入侧栏偏移和世界 pan 的设备像素平移,保持缩放顺序一致。
page.translate(Point::new(
(left - surface.pan.x * zoom) * scale,
-surface.pan.y * zoom * scale,
));
// 从空列表开始按背景、页面、工具、侧栏的顺序叠加;后加入的命令绘制在前面内容之上。
let mut display_list = DisplayList::default();
// 全窗只做一次 Clear,后续跳过页面列表自身 Clear,防止其覆盖侧栏或突破页面裁剪。
display_list.push(DrawCommand::Clear(
crate::document::parse_color(&document.frame.background)
.unwrap_or(crate::system_ui::theme::BG),
));
display_list.push(DrawCommand::PushClip(content));
// 按页面原有顺序复制命令;页面自身的嵌套裁剪仍处于 content 这个外层裁剪之内。
for command in page.commands() {
// Clear 是整目标缓冲清屏操作,只有非 Clear 命令可以并入已建立背景的窗口列表。
if !matches!(command, DrawCommand::Clear(_)) {
display_list.push(command.clone());
}
}
display_list.push(DrawCommand::PopClip);
// 工具先在扣除侧栏的独立画布内构建,宽度用饱和减法防止窄窗口发生无符号下溢。
let (mut developer_list, mut developer_layout) = developer_tools::build(
width.saturating_sub((left * scale) as u32),
height,
scale,
document,
developer_tools_state,
activity,
);
// 工具命令在设备空间平移,交互布局在逻辑空间平移,两者比例不同但实际对齐同一页面起点。
// 输入路由保留的 developer_layout 不能再乘 scale;shell 的 pointer 已转换为逻辑单位,只有像素绘制列表使用设备比例。
developer_list.translate(Point::new(left * scale, 0.0));
developer_layout.translate(left, 0.0);
display_list.extend(developer_list);
// 侧栏最后覆盖合成,内部按逻辑尺寸绘制后统一设备缩放,不受页面相机影响。
let mut chrome = sidebar.draw(
&document.frame.tabs,
height as f32 / scale,
developer_tools_state.accent,
);
chrome.scale(scale);
display_list.extend(chrome);
// 把绘制输出与这次构建产生的交互布局绑定返回,调用方无需重新计算工具位置。
DisplayScene {
display_list,
layout: SceneLayout {
developer_tools: developer_layout,
},
}
}
/// 整窗合成结果:显示列表与同帧管理交互几何一起返回。
pub struct DisplayScene {
// 已完成缩放与平移的整窗绘制命令,供 shell 的渲染路径消费。
pub display_list: DisplayList,
// 保留逻辑坐标供宿主输入路由使用,不能直接当成设备像素命中区域。
pub layout: SceneLayout,
}