//! 合成整个原生窗口的显示列表:页面画面、宿主侧栏和开发工具绘制。 //! 窗口逻辑尺寸与设备像素比例在这里衔接。页面内容先应用其视角并裁剪,宿主控件的区域和命中布局另外返回。 //! 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。 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_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, }