Skip to main content

rgpui/
platform.rs

1//! 平台抽象层:定义 `Platform` trait 和 `PlatformWindow` trait,供各平台 crate 实现。
2
3mod app_menu;
4mod keyboard;
5mod keystroke;
6
7/// 用于配置父窗口锚定弹出窗口的类型,如下拉菜单、弹出菜单和工具提示。
8pub mod popup;
9
10#[cfg(all(
11    any(test, feature = "test-support"),
12    any(target_os = "windows", target_os = "linux", target_family = "wasm")
13))]
14mod threaded_dispatcher;
15
16/// Wayland Layer Shell 支持 — 允许窗口作为覆盖层、面板或桌面背景渲染。
17#[cfg(all(target_os = "linux", feature = "wayland"))]
18pub mod layer_shell;
19
20#[cfg(any(test, feature = "test-support"))]
21mod test;
22
23#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
24mod visual_test;
25
26#[cfg(all(
27    feature = "screen-capture",
28    any(target_os = "windows", target_os = "linux", target_os = "freebsd",)
29))]
30pub mod scap_screen_capture;
31
32#[cfg(all(
33    any(target_os = "windows", target_os = "linux"),
34    feature = "screen-capture"
35))]
36pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame;
37#[cfg(not(feature = "screen-capture"))]
38pub(crate) type PlatformScreenCaptureFrame = ();
39#[cfg(all(target_os = "macos", feature = "screen-capture"))]
40pub(crate) type PlatformScreenCaptureFrame = core_video::image_buffer::CVImageBuffer;
41
42use crate::rgpui_util;
43use crate::scheduler::Instant;
44pub use crate::scheduler::RunnableMeta;
45use crate::{
46    Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
47    DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun,
48    ForegroundExecutor, GlyphId, GpuSpecs, Hsla, ImageSource, Keymap, LineLayout, Pixels,
49    PlatformInput, Point, Priority, RenderGlyphParams, RenderImage, RenderImageParams,
50    RenderSvgParams, Scene, ShapedGlyph, ShapedRun, SharedString, Size, SvgRenderer,
51    SystemWindowTab, Task, Window, WindowControlArea, hash, point, px, size,
52};
53use crate::{Tray, TrayIconEvent, TrayMenuItem};
54use anyhow::Result;
55#[cfg(any(target_os = "linux", target_os = "freebsd"))]
56use anyhow::bail;
57use async_task::Runnable;
58use futures::channel::oneshot;
59#[cfg(any(test, feature = "test-support"))]
60use image::RgbaImage;
61use image::codecs::gif::GifDecoder;
62use image::{AnimationDecoder as _, Frame};
63use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
64use schemars::JsonSchema;
65use seahash::SeaHasher;
66use serde::{Deserialize, Serialize};
67use smallvec::SmallVec;
68use std::borrow::Cow;
69use std::hash::{Hash, Hasher};
70use std::io::Cursor;
71use std::ops;
72use std::time::Duration;
73use std::{
74    fmt::{self, Debug},
75    ops::Range,
76    path::{Path, PathBuf},
77    rc::Rc,
78    sync::Arc,
79};
80use strum::EnumIter;
81use uuid::Uuid;
82
83pub use app_menu::*;
84pub use keyboard::*;
85pub use keystroke::*;
86
87#[cfg(any(test, feature = "test-support"))]
88pub(crate) use test::*;
89
90#[cfg(any(test, feature = "test-support"))]
91pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
92
93#[cfg(all(
94    any(test, feature = "test-support"),
95    any(target_os = "windows", target_os = "linux", target_family = "wasm")
96))]
97pub use threaded_dispatcher::ThreadedDispatcher;
98
99#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
100pub use visual_test::VisualTestPlatform;
101
102// TODO(jk): return an enum instead of a string
103/// 返回当前使用的合成器名称(猜测),
104/// 不会尝试连接到指定的合成器。
105#[cfg(any(target_os = "linux", target_os = "freebsd"))]
106#[inline]
107pub fn guess_compositor() -> &'static str {
108    if std::env::var_os("ZED_HEADLESS").is_some() {
109        return "Headless";
110    }
111
112    #[cfg(feature = "wayland")]
113    let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
114    #[cfg(not(feature = "wayland"))]
115    let wayland_display: Option<std::ffi::OsString> = None;
116
117    #[cfg(feature = "x11")]
118    let x11_display = std::env::var_os("DISPLAY");
119    #[cfg(not(feature = "x11"))]
120    let x11_display: Option<std::ffi::OsString> = None;
121
122    let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
123    let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
124
125    if use_wayland {
126        "Wayland"
127    } else if use_x11 {
128        "X11"
129    } else {
130        "Headless"
131    }
132}
133
134// ============================================================================
135// 缺失的系统类型定义
136// ============================================================================
137
138/// 系统电源事件
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum SystemPowerEvent {
141    /// 系统即将进入睡眠
142    Sleep,
143    /// 系统已从睡眠唤醒
144    WakeUp,
145}
146
147/// 电源阻止器类型
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub enum PowerSaveBlockerKind {
150    /// 阻止系统休眠
151    PreventSleep,
152    /// 阻止屏幕关闭
153    PreventDisplaySleep,
154}
155
156/// 操作系统信息
157#[derive(Debug, Clone)]
158pub struct OsInfo {
159    /// 操作系统名称
160    pub name: String,
161    /// 操作系统版本
162    pub version: String,
163}
164
165/// 权限状态
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167pub enum PermissionStatus {
168    /// 未确定
169    NotDetermined,
170    /// 已授权
171    Granted,
172    /// 已拒绝
173    Denied,
174    /// 不可用
175    Unavailable,
176}
177
178/// 权限类型(用于描述应用在系统中申请的权限类别)
179///
180/// 通常用于 macOS / Windows 等系统能力访问控制,例如辅助功能、屏幕录制、输入监控等。
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub enum PermissionType {
183    /// 辅助功能权限(Accessibility)
184    ///
185    /// 用于允许应用模拟用户操作、读取 UI 元素、控制系统界面等能力。
186    Accessibility,
187
188    /// 屏幕录制/屏幕捕获权限(Screen Capture)
189    ///
190    /// 用于获取屏幕内容,例如截图、录屏或远程桌面功能。
191    ScreenCapture,
192
193    /// 输入监控权限(Input Monitoring)
194    ///
195    /// 用于监听键盘和鼠标输入事件(如全局快捷键、输入记录等)。
196    InputMonitoring,
197}
198
199/// 网络状态
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201pub enum NetworkStatus {
202    /// 无法连接网络
203    Disconnected,
204    /// 已连接但不满足服务要求
205    ConnectedBelowRequired,
206    /// 已连接且满足服务要求
207    Connected,
208}
209
210/// 媒体键事件
211#[derive(Debug, Clone)]
212pub struct MediaKeyEvent {
213    /// 键码
214    pub key_code: u16,
215}
216
217/// 生物识别状态
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub enum BiometricStatus {
220    /// 不可用
221    Unavailable,
222    /// 已解锁
223    Unlocked,
224    /// 已锁定
225    Locked,
226}
227
228/// 用户注意力请求类型
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum AttentionType {
231    /// 请求非关键性注意(如弹跳 Dock 图标一次)
232    Informational,
233    /// 请求关键性注意(如弹跳 Dock 图标直到被激活)
234    Critical,
235}
236
237/// 对话框选项
238#[derive(Debug, Clone)]
239pub struct DialogOptions {
240    /// 对话框类型
241    pub dialog_type: DialogType,
242    /// 对话框标题
243    pub title: String,
244    /// 对话框消息
245    pub message: String,
246    /// 确认按钮文本
247    pub confirm_label: Option<String>,
248    /// 取消按钮文本
249    pub cancel_label: Option<String>,
250}
251
252/// 对话框类型
253#[derive(Debug, Clone, Copy, PartialEq, Eq)]
254pub enum DialogType {
255    /// 信息提示
256    Info,
257    /// 警告
258    Warning,
259    /// 错误
260    Error,
261}
262
263/// 聚焦窗口信息
264#[derive(Debug, Clone)]
265pub struct FocusedWindowInfo {
266    /// 窗口所属应用名称
267    pub app_name: String,
268    /// 窗口标题
269    pub window_title: String,
270    /// Bundle ID(macOS 特有)
271    pub bundle_id: Option<String>,
272    /// 进程 ID
273    pub pid: Option<u32>,
274}
275
276/// 语义化窗口位置,用于计算窗口的屏幕位置
277#[derive(Debug, Clone, Copy, PartialEq)]
278pub enum WindowPosition {
279    /// 在主显示区域居中
280    Center,
281    /// 在指定显示区域居中
282    CenterOnDisplay(DisplayId),
283    /// 在托盘图标上方居中
284    TrayCenter(Bounds<Pixels>),
285    /// 屏幕右上角(带边距)
286    TopRight {
287        /// 与屏幕边缘的距离
288        margin: Pixels,
289    },
290    /// 屏幕右下角(带边距)
291    BottomRight {
292        /// 与屏幕边缘的距离
293        margin: Pixels,
294    },
295    /// 屏幕左上角(带边距)
296    TopLeft {
297        /// 与屏幕边缘的距离
298        margin: Pixels,
299    },
300    /// 屏幕左下角(带边距)
301    BottomLeft {
302        /// 与屏幕边缘的距离
303        margin: Pixels,
304    },
305}
306
307/// 跨平台应用抽象层,由各平台 crate(rgpui-windows、rgpui-macos、rgpui-linux、rgpui-web)实现。
308///
309/// 提供应用生命周期、窗口管理、系统集成(托盘、快捷键、通知、电源等)的统一接口。
310/// 应用通过 [`rgpui_platform::application()`] 获取实现此 trait 的实例。
311pub trait Platform: 'static {
312    /// 返回后台线程执行器,用于调度异步任务。
313    fn background_executor(&self) -> BackgroundExecutor;
314    /// 返回主线程执行器,用于调度需要在 UI 线程运行的任务。
315    fn foreground_executor(&self) -> ForegroundExecutor;
316    /// 返回文本渲染系统实例,负责字体加载、文本布局和渲染。
317    fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
318
319    /// 启动应用主循环,`on_finish_launching` 在启动完成后回调。
320    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
321    /// 退出应用进程。
322    fn quit(&self);
323    /// 重启应用,可选指定新的二进制路径。
324    fn restart(&self, binary_path: Option<PathBuf>);
325    /// 激活应用(将窗口置于前台),`ignoring_other_apps` 在 macOS 下是否忽略其他应用。
326    fn activate(&self, ignoring_other_apps: bool);
327    /// 隐藏应用(macOS 下隐藏所有窗口,其他平台最小化)。
328    fn hide(&self);
329    /// 隐藏当前应用以外的所有其他应用的窗口。
330    fn hide_other_apps(&self);
331    /// 取消隐藏所有被 `hide_other_apps` 隐藏的应用。
332    fn unhide_other_apps(&self);
333
334    /// 返回所有可用显示器的列表。
335    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
336    /// 返回主显示器(包含任务栏/菜单栏的显示器)。
337    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
338    /// 返回当前获得焦点的窗口句柄。
339    fn active_window(&self) -> Option<AnyWindowHandle>;
340    /// 返回窗口栈(Z-order),从最顶层到最底层。
341    fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
342        None
343    }
344
345    /// 当前平台是否支持屏幕捕获功能。
346    fn is_screen_capture_supported(&self) -> bool {
347        false
348    }
349
350    /// 获取可用的屏幕捕获源列表(屏幕/窗口),通过 oneshot channel 异步返回。
351    fn screen_capture_sources(
352        &self,
353    ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
354        let (sources_tx, sources_rx) = oneshot::channel();
355        sources_tx
356            .send(Err(anyhow::anyhow!(
357                "rgpui was compiled without the screen-capture feature"
358            )))
359            .ok();
360        sources_rx
361    }
362
363    /// 根据窗口参数创建平台原生窗口,返回 `PlatformWindow` 实例。
364    fn open_window(
365        &self,
366        handle: AnyWindowHandle,
367        options: WindowParams,
368    ) -> anyhow::Result<Box<dyn PlatformWindow>>;
369
370    /// 返回应用窗口的外观模式(亮色/暗色)。
371    fn window_appearance(&self) -> WindowAppearance;
372
373    /// 返回窗口按钮布局配置(如 macOS 红绿灯位置、Windows 按钮顺序)。
374    fn button_layout(&self) -> Option<WindowButtonLayout> {
375        None
376    }
377
378    /// 在系统默认浏览器中打开 URL。
379    fn open_url(&self, url: &str);
380    /// 注册 URL scheme 回调,当应用通过自定义 URL scheme 打开时触发。
381    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
382    /// 注册自定义 URL scheme(如 `myapp://`),使系统将该 scheme 的 URL 分发到本应用。
383    fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
384
385    /// 打开文件选择对话框,返回用户选择的文件路径列表。
386    fn prompt_for_paths(
387        &self,
388        options: PathPromptOptions,
389    ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
390    /// 打开文件保存对话框,返回用户指定的保存路径。
391    fn prompt_for_new_path(
392        &self,
393        directory: &Path,
394        suggested_name: Option<&str>,
395    ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
396    /// 文件选择对话框是否支持同时选择文件和目录。
397    fn can_select_mixed_files_and_dirs(&self) -> bool;
398    /// 在系统文件管理器中显示(reveal)指定路径。
399    fn reveal_path(&self, path: &Path);
400    /// 使用系统默认应用打开指定路径。
401    fn open_with_system(&self, path: &Path);
402
403    /// 注册应用退出时的回调。
404    fn on_quit(&self, callback: Box<dyn FnMut()>);
405    /// 注册应用从后台恢复(macOS Dock 图标点击)时的回调。
406    fn on_reopen(&self, callback: Box<dyn FnMut()>);
407
408    /// 设置应用菜单栏(macOS 为全局菜单栏,其他平台为窗口菜单)。
409    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
410    /// 获取当前应用菜单的副本。
411    fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
412        None
413    }
414
415    /// 设置 macOS Dock 栏右键菜单。
416    fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
417    /// 执行 Dock 菜单中的操作。
418    fn perform_dock_menu_action(&self, _action: usize) {}
419    /// 将路径添加到最近打开文档列表。
420    fn add_recent_document(&self, _path: &Path) {}
421    /// 更新 Windows 跳转列表(任务栏右键菜单中的最近文档)。
422    fn update_jump_list(
423        &self,
424        _menus: Vec<MenuItem>,
425        _entries: Vec<SmallVec<[PathBuf; 2]>>,
426    ) -> Task<Vec<SmallVec<[PathBuf; 2]>>> {
427        Task::ready(Vec::new())
428    }
429    /// 注册应用菜单操作回调,当用户点击菜单项时触发。
430    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
431    /// 注册菜单即将打开时的回调(可用于动态更新菜单项状态)。
432    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
433    /// 注册菜单命令验证回调,返回 `false` 可禁用菜单项。
434    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
435
436    /// 返回系统热状态(正常、警告、临界)。
437    fn thermal_state(&self) -> ThermalState;
438    /// 注册系统热状态变化回调。
439    fn on_thermal_state_change(&self, callback: Box<dyn FnMut()>);
440
441    /// 返回合成器名称(如 "dwm"、"mutter"),用于诊断。
442    fn compositor_name(&self) -> &'static str {
443        ""
444    }
445    /// 返回应用自身可执行文件的路径。
446    fn app_path(&self) -> Result<PathBuf>;
447    /// 返回辅助可执行文件的路径(如子进程、插件)。
448    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
449
450    /// 设置鼠标光标样式(箭头、手型、文本光标等)。
451    fn set_cursor_style(&self, style: CursorStyle);
452
453    /// 隐藏鼠标光标,直到用户移动鼠标时自动恢复显示。
454    fn hide_cursor_until_mouse_moves(&self);
455
456    /// 返回鼠标光标当前是否可见。
457    fn is_cursor_visible(&self) -> bool;
458
459    /// 是否自动隐藏滚动条(鼠标靠近时才显示)。
460    fn should_auto_hide_scrollbars(&self) -> bool;
461
462    /// 从系统剪贴板读取内容。
463    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
464    /// 写入内容到系统剪贴板。
465    fn write_to_clipboard(&self, item: ClipboardItem);
466
467    /// 从 Linux/X11 主选择区(Primary Selection)读取内容。
468    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
469    fn read_from_primary(&self) -> Option<ClipboardItem>;
470    /// 写入内容到 Linux/X11 主选择区。
471    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
472    fn write_to_primary(&self, item: ClipboardItem);
473
474    /// 从 macOS 查找粘贴板(Find Pasteboard)读取内容。
475    #[cfg(target_os = "macos")]
476    fn read_from_find_pasteboard(&self) -> Option<ClipboardItem>;
477    /// 写入内容到 macOS 查找粘贴板。
478    #[cfg(target_os = "macos")]
479    fn write_to_find_pasteboard(&self, item: ClipboardItem);
480
481    /// 将凭据(URL、用户名、密码)写入系统密钥链。
482    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
483    /// 从系统密钥链读取指定 URL 的凭据。
484    fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
485    /// 从系统密钥链删除指定 URL 的凭据。
486    fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
487
488    /// 返回当前键盘布局信息。
489    fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
490    /// 返回键盘映射器,用于将原始按键事件转换为字符输入。
491    fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
492    /// 注册键盘布局变化回调(用户切换输入法/布局时触发)。
493    fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
494
495    /// 设置系统托盘图标、菜单和按键绑定。
496    fn set_tray(&self, _tray: Tray, _menus: Option<Vec<MenuItem>>, _keymap: &Keymap) {}
497    /// 更新系统托盘图标,`None` 表示移除图标。
498    fn set_tray_icon(&self, _icon: Option<&[u8]>) {}
499    /// 更新系统托盘右键菜单。
500    fn set_tray_menu(&self, _menu: Vec<TrayMenuItem>) {}
501    /// 设置鼠标悬停在托盘图标上时显示的工具提示文本。
502    fn set_tray_tooltip(&self, _tooltip: &str) {}
503    /// 设置托盘面板模式(Windows 下影响图标的显示行为)。
504    fn set_tray_panel_mode(&self, _enabled: bool) {}
505    /// 返回系统托盘图标在屏幕上的边界矩形。
506    fn get_tray_icon_bounds(&self) -> Option<Bounds<Pixels>> {
507        None
508    }
509    /// 注册托盘图标事件回调(单击、双击、右键等)。
510    fn on_tray_icon_event(&self, _callback: Box<dyn FnMut(TrayIconEvent)>) {}
511    /// 注册托盘菜单项操作回调。
512    fn on_tray_menu_action(&self, _callback: Box<dyn FnMut(SharedString)>) {}
513
514    /// 设置是否在所有窗口关闭后保持应用运行(仅显示托盘图标)。
515    fn set_keep_alive_without_windows(&self, _keep_alive: bool) {}
516
517    /// 注册全局系统快捷键,`id` 用于标识快捷键,`keystroke` 定义按键组合。
518    fn register_global_hotkey(&self, _id: u32, _keystroke: &Keystroke) -> Result<()> {
519        Ok(())
520    }
521    /// 取消注册全局系统快捷键。
522    fn unregister_global_hotkey(&self, _id: u32) {}
523    /// 注册全局快捷键触发回调,`id` 对应注册时的标识。
524    fn on_global_hotkey(&self, _callback: Box<dyn FnMut(u32)>) {}
525
526    /// 显示系统通知,返回 `Ok(())` 表示通知已发送。
527    fn show_notification(&self, _title: &str, _body: &str) -> Result<()> {
528        Ok(())
529    }
530
531    /// 设置开机自启动,`app_id` 为应用唯一标识。
532    fn set_auto_launch(&self, _app_id: &str, _enabled: bool) -> Result<()> {
533        Ok(())
534    }
535    /// 查询开机自启动是否已启用。
536    fn is_auto_launch_enabled(&self, _app_id: &str) -> bool {
537        false
538    }
539
540    /// 返回当前系统中获得焦点的窗口信息(标题、进程名等)。
541    fn focused_window_info(&self) -> Option<FocusedWindowInfo> {
542        None
543    }
544
545    /// 返回辅助功能(Accessibility)权限状态。
546    fn accessibility_status(&self) -> PermissionStatus {
547        PermissionStatus::Unavailable
548    }
549    /// 请求辅助功能权限(macOS 需要用户授权)。
550    fn request_accessibility_permission(&self) {}
551
552    /// 返回麦克风权限状态。
553    fn microphone_status(&self) -> PermissionStatus {
554        PermissionStatus::Unavailable
555    }
556    /// 请求麦克风权限,`callback` 收到授权结果。
557    fn request_microphone_permission(&self, _callback: Box<dyn FnOnce(bool)>) {}
558
559    /// 注册系统电源事件回调(电池状态变化、电源插拔等)。
560    fn on_system_power_event(&self, _callback: Box<dyn FnMut(SystemPowerEvent)>) {}
561
562    /// 注册系统唤醒时的回调函数。
563    fn on_system_wake(&self, _callback: Box<dyn FnMut()>) {}
564
565    /// 启动电源节省阻止器(阻止系统进入睡眠),返回阻止器 ID。
566    fn start_power_save_blocker(&self, _kind: PowerSaveBlockerKind) -> Option<u32> {
567        None
568    }
569    /// 停止指定的电源节省阻止器。
570    fn stop_power_save_blocker(&self, _id: u32) {}
571
572    /// 返回系统空闲时间(自上次用户输入以来的时长)。
573    fn system_idle_time(&self) -> Option<Duration> {
574        None
575    }
576
577    /// 返回当前网络连接状态。
578    fn network_status(&self) -> NetworkStatus {
579        NetworkStatus::Connected
580    }
581    /// 注册网络状态变化回调(在线/离线/连接变化)。
582    fn on_network_status_change(&self, _callback: Box<dyn FnMut(NetworkStatus)>) {}
583
584    /// 注册媒体键事件回调(播放/暂停/音量等)。
585    fn on_media_key_event(&self, _callback: Box<dyn FnMut(MediaKeyEvent)>) {}
586
587    /// 请求用户注意力(macOS Dock 图标弹跳、Windows 任务栏闪烁)。
588    fn request_user_attention(&self, _attention_type: AttentionType) {}
589    /// 取消用户注意力请求。
590    fn cancel_user_attention(&self) {}
591
592    /// 设置 macOS Dock 标签徽章文本(如未读消息数)。
593    fn set_dock_badge(&self, _label: Option<&str>) {}
594
595    /// 在指定位置显示右键上下文菜单。
596    fn show_context_menu(
597        &self,
598        _position: Point<Pixels>,
599        _items: Vec<TrayMenuItem>,
600        _callback: Box<dyn FnMut(SharedString)>,
601    ) {
602    }
603
604    /// 显示系统原生对话框(如确认、警告等),返回用户选择的按钮索引。
605    fn show_dialog(&self, _options: DialogOptions) -> oneshot::Receiver<usize> {
606        let (tx, rx) = oneshot::channel();
607        let _ = tx.send(0);
608        rx
609    }
610
611    /// 返回操作系统信息(名称、版本号)。
612    fn os_info(&self) -> OsInfo {
613        OsInfo {
614            name: String::new(),
615            version: String::new(),
616        }
617    }
618
619    /// 返回生物识别(指纹/面容 ID)硬件状态。
620    fn biometric_status(&self) -> BiometricStatus {
621        BiometricStatus::Unavailable
622    }
623    /// 触发生物识别认证,`reason` 为提示文本,`callback` 收到认证结果。
624    fn authenticate_biometric(&self, _reason: &str, _callback: Box<dyn FnOnce(bool)>) {}
625}
626
627/// 平台显示器句柄,代表一个物理显示器或笔记本屏幕。
628pub trait PlatformDisplay: Debug {
629    /// 获取显示器 ID。
630    fn id(&self) -> DisplayId;
631
632    /// 返回显示器的持久化唯一标识符,可在系统重启后继续使用。
633    fn uuid(&self) -> Result<Uuid>;
634
635    /// 获取显示器的边界区域(包含任务栏/Dock 区域)。
636    fn bounds(&self) -> Bounds<Pixels>;
637
638    /// 获取显示器的可见边界区域(排除任务栏/Dock 区域)。
639    /// 这是可放置窗口且不会被遮挡的可用区域。
640    /// 未覆盖时默认返回完整显示器边界。
641    fn visible_bounds(&self) -> Bounds<Pixels> {
642        self.bounds()
643    }
644
645    /// 获取显示器的默认窗口放置区域。
646    fn default_bounds(&self) -> Bounds<Pixels> {
647        let bounds = self.bounds();
648        let center = bounds.center();
649        let clipped_window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
650
651        let offset = clipped_window_size / 2.0;
652        let origin = point(center.x - offset.width, center.y - offset.height);
653        Bounds::new(origin, clipped_window_size)
654    }
655}
656
657/// 系统热状态
658#[derive(Debug, Clone, Copy, PartialEq, Eq)]
659pub enum ThermalState {
660    /// 系统无热限制
661    Nominal,
662    /// 系统轻微受限,应减少非必要工作
663    Fair,
664    /// 系统中度受限,应减少 CPU/GPU 密集型工作
665    Serious,
666    /// 系统严重受限,应最小化所有资源使用
667    Critical,
668}
669
670/// 屏幕捕获源的元数据
671#[derive(Clone)]
672pub struct SourceMetadata {
673    /// 屏幕的不透明标识符。
674    pub id: u64,
675    /// 人类可读的源标签。
676    pub label: Option<SharedString>,
677    /// 该源是否为主显示器。
678    pub is_main: Option<bool>,
679    /// 该源的视频分辨率。
680    pub resolution: Size<DevicePixels>,
681}
682
683/// 可被捕获的屏幕视频内容源。
684pub trait ScreenCaptureSource {
685    /// 返回该源的元数据。
686    fn metadata(&self) -> Result<SourceMetadata>;
687
688    /// 开始从该源捕获视频,每帧调用给定的回调函数。
689    fn stream(
690        &self,
691        foreground_executor: &ForegroundExecutor,
692        frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
693    ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
694}
695
696/// 从屏幕捕获的视频流。
697pub trait ScreenCaptureStream {
698    /// 返回该源的元数据。
699    fn metadata(&self) -> Result<SourceMetadata>;
700}
701
702/// 从屏幕捕获的视频帧。
703pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
704
705#[cfg(all(
706    any(target_os = "windows", target_os = "linux"),
707    feature = "screen-capture"
708))]
709impl ScreenCaptureFrame {
710    /// 获取帧宽度(像素)
711    pub fn width(&self) -> u32 {
712        match &self.0 {
713            scap::frame::Frame::YUVFrame(f) => f.width as u32,
714            scap::frame::Frame::RGB(f) => f.width as u32,
715            scap::frame::Frame::RGBx(f) => f.width as u32,
716            scap::frame::Frame::XBGR(f) => f.width as u32,
717            scap::frame::Frame::BGRx(f) => f.width as u32,
718            scap::frame::Frame::BGR0(f) => f.width as u32,
719            scap::frame::Frame::BGRA(f) => f.width as u32,
720        }
721    }
722
723    /// 获取帧高度(像素)
724    pub fn height(&self) -> u32 {
725        match &self.0 {
726            scap::frame::Frame::YUVFrame(f) => f.height as u32,
727            scap::frame::Frame::RGB(f) => f.height as u32,
728            scap::frame::Frame::RGBx(f) => f.height as u32,
729            scap::frame::Frame::XBGR(f) => f.height as u32,
730            scap::frame::Frame::BGRx(f) => f.height as u32,
731            scap::frame::Frame::BGR0(f) => f.height as u32,
732            scap::frame::Frame::BGRA(f) => f.height as u32,
733        }
734    }
735
736    /// 将帧转换为 RGBA 图像
737    ///
738    /// 支持所有 scap 输出格式:BGRA、BGR0、BGRx、XBGR、RGB、RGBx、YUV(NV12)。
739    /// YUV 格式使用 BT.601 标准进行色彩空间转换。
740    pub fn to_rgba(&self) -> Option<image::RgbaImage> {
741        match &self.0 {
742            scap::frame::Frame::BGRA(f) => {
743                let w = f.width as u32;
744                let h = f.height as u32;
745                let mut rgba = Vec::with_capacity((w * h * 4) as usize);
746                for chunk in f.data.chunks_exact(4) {
747                    rgba.push(chunk[2]); // R
748                    rgba.push(chunk[1]); // G
749                    rgba.push(chunk[0]); // B
750                    rgba.push(chunk[3]); // A
751                }
752                image::RgbaImage::from_raw(w, h, rgba)
753            }
754            scap::frame::Frame::BGR0(f) => {
755                let w = f.width as u32;
756                let h = f.height as u32;
757                let mut rgba = Vec::with_capacity((w * h * 4) as usize);
758                for chunk in f.data.chunks_exact(4) {
759                    rgba.push(chunk[2]); // R
760                    rgba.push(chunk[1]); // G
761                    rgba.push(chunk[0]); // B
762                    rgba.push(255); // A (不透明)
763                }
764                image::RgbaImage::from_raw(w, h, rgba)
765            }
766            scap::frame::Frame::BGRx(f) => {
767                let w = f.width as u32;
768                let h = f.height as u32;
769                let mut rgba = Vec::with_capacity((w * h * 4) as usize);
770                for chunk in f.data.chunks_exact(4) {
771                    rgba.push(chunk[2]); // R
772                    rgba.push(chunk[1]); // G
773                    rgba.push(chunk[0]); // B
774                    rgba.push(255); // A (不透明)
775                }
776                image::RgbaImage::from_raw(w, h, rgba)
777            }
778            scap::frame::Frame::XBGR(f) => {
779                let w = f.width as u32;
780                let h = f.height as u32;
781                let mut rgba = Vec::with_capacity((w * h * 4) as usize);
782                for chunk in f.data.chunks_exact(4) {
783                    rgba.push(chunk[3]); // R
784                    rgba.push(chunk[2]); // G
785                    rgba.push(chunk[1]); // B
786                    rgba.push(255); // A (不透明)
787                }
788                image::RgbaImage::from_raw(w, h, rgba)
789            }
790            scap::frame::Frame::RGB(f) => {
791                let w = f.width as u32;
792                let h = f.height as u32;
793                let mut rgba = Vec::with_capacity((w * h * 4) as usize);
794                for chunk in f.data.chunks_exact(3) {
795                    rgba.push(chunk[0]); // R
796                    rgba.push(chunk[1]); // G
797                    rgba.push(chunk[2]); // B
798                    rgba.push(255); // A (不透明)
799                }
800                image::RgbaImage::from_raw(w, h, rgba)
801            }
802            scap::frame::Frame::RGBx(f) => {
803                let w = f.width as u32;
804                let h = f.height as u32;
805                let mut rgba = Vec::with_capacity((w * h * 4) as usize);
806                for chunk in f.data.chunks_exact(4) {
807                    rgba.push(chunk[0]); // R
808                    rgba.push(chunk[1]); // G
809                    rgba.push(chunk[2]); // B
810                    rgba.push(255); // A (不透明)
811                }
812                image::RgbaImage::from_raw(w, h, rgba)
813            }
814            scap::frame::Frame::YUVFrame(f) => {
815                let w = f.width as u32;
816                let h = f.height as u32;
817                let mut rgba = Vec::with_capacity((w * h * 4) as usize);
818
819                // NV12 格式:Y 平面 + 交错 UV 平面
820                let y_plane = &f.luminance_bytes;
821                let uv_plane = &f.chrominance_bytes;
822                let y_stride = f.luminance_stride as usize;
823                let uv_stride = f.chrominance_stride as usize;
824
825                for row in 0..h as usize {
826                    for col in 0..w as usize {
827                        // 读取 Y 值(考虑步长)
828                        let y_idx = row * y_stride + col;
829                        let y = if y_idx < y_plane.len() {
830                            y_plane[y_idx] as i32
831                        } else {
832                            0
833                        };
834
835                        // 读取 U、V 值(UV 交错,每两个像素共享)
836                        let uv_row = row / 2;
837                        let uv_col = (col / 2) * 2;
838                        let uv_idx = uv_row * uv_stride + uv_col;
839                        let u = if uv_idx < uv_plane.len() {
840                            uv_plane[uv_idx] as i32
841                        } else {
842                            128
843                        };
844                        let v = if uv_idx + 1 < uv_plane.len() {
845                            uv_plane[uv_idx + 1] as i32
846                        } else {
847                            128
848                        };
849
850                        // BT.601 YUV → RGB 转换
851                        let c = 298 * (y - 16);
852                        let r = ((c + 409 * (v - 128) + 128) >> 8).clamp(0, 255) as u8;
853                        let g = ((c - 100 * (u - 128) - 208 * (v - 128) + 128) >> 8).clamp(0, 255)
854                            as u8;
855                        let b = ((c + 516 * (u - 128) + 128) >> 8).clamp(0, 255) as u8;
856
857                        rgba.push(r);
858                        rgba.push(g);
859                        rgba.push(b);
860                        rgba.push(255); // A (不透明)
861                    }
862                }
863                image::RgbaImage::from_raw(w, h, rgba)
864            }
865        }
866    }
867}
868
869#[cfg(all(target_os = "macos", feature = "screen-capture"))]
870impl ScreenCaptureFrame {
871    /// 获取帧宽度(像素)
872    pub fn width(&self) -> u32 {
873        0
874    }
875
876    /// 获取帧高度(像素)
877    pub fn height(&self) -> u32 {
878        0
879    }
880
881    /// 将帧转换为 RGBA 图像
882    pub fn to_rgba(&self) -> Option<image::RgbaImage> {
883        None
884    }
885}
886
887/// 硬件显示器的不透明标识符
888#[derive(PartialEq, Eq, Hash, Copy, Clone)]
889pub struct DisplayId(pub(crate) u64);
890
891impl DisplayId {
892    /// 从原始平台显示器标识符创建新的 `DisplayId`。
893    pub fn new(id: u64) -> Self {
894        Self(id)
895    }
896}
897
898impl From<u64> for DisplayId {
899    fn from(id: u64) -> Self {
900        Self(id)
901    }
902}
903
904impl From<DisplayId> for u64 {
905    fn from(id: DisplayId) -> Self {
906        id.0
907    }
908}
909
910impl Debug for DisplayId {
911    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
912        write!(f, "DisplayId({})", self.0)
913    }
914}
915
916/// 窗口调整大小的边缘方向
917#[derive(Debug, Clone, Copy, PartialEq, Eq)]
918pub enum ResizeEdge {
919    /// 上边缘
920    Top,
921    /// 右上角
922    TopRight,
923    /// 右边缘
924    Right,
925    /// 右下角
926    BottomRight,
927    /// 下边缘
928    Bottom,
929    /// 左下角
930    BottomLeft,
931    /// 左边缘
932    Left,
933    /// 左上角
934    TopLeft,
935}
936
937/// 描述窗口外观类型的枚举
938#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
939pub enum WindowDecorations {
940    #[default]
941    /// 服务端装饰
942    Server,
943    /// 客户端装饰
944    Client,
945}
946
947/// 描述窗口当前装饰配置的类型
948#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
949pub enum Decorations {
950    /// 窗口配置为使用服务端装饰
951    #[default]
952    Server,
953    /// 窗口配置为使用客户端装饰
954    Client {
955        /// 边缘平铺状态
956        tiling: Tiling,
957    },
958}
959
960/// 平台支持的窗口控件
961#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
962pub struct WindowControls {
963    /// 该平台是否支持全屏
964    pub fullscreen: bool,
965    /// 该平台是否支持最大化
966    pub maximize: bool,
967    /// 该平台是否支持最小化
968    pub minimize: bool,
969    /// 该平台是否支持窗口菜单
970    pub window_menu: bool,
971}
972
973impl Default for WindowControls {
974    fn default() -> Self {
975        // 默认假设所有功能都可用,除非另有说明
976        Self {
977            fullscreen: true,
978            maximize: true,
979            minimize: true,
980            window_menu: true,
981        }
982    }
983}
984
985/// [`WindowButtonLayout`] 中使用的窗口控制按钮类型。
986#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
987pub enum WindowButton {
988    /// 最小化按钮
989    Minimize,
990    /// 最大化按钮
991    Maximize,
992    /// 关闭按钮
993    Close,
994}
995
996impl WindowButton {
997    /// 返回该按钮渲染时使用的稳定元素 ID。
998    pub fn id(&self) -> &'static str {
999        match self {
1000            WindowButton::Minimize => "minimize",
1001            WindowButton::Maximize => "maximize",
1002            WindowButton::Close => "close",
1003        }
1004    }
1005
1006    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
1007    fn index(&self) -> usize {
1008        match self {
1009            WindowButton::Minimize => 0,
1010            WindowButton::Maximize => 1,
1011            WindowButton::Close => 2,
1012        }
1013    }
1014}
1015
1016/// 标题栏每侧最大的 [`WindowButton`] 数量。
1017pub const MAX_BUTTONS_PER_SIDE: usize = 3;
1018
1019/// 描述标题栏每侧出现的 [`WindowButton`]。
1020///
1021/// 在 Linux 上,此配置从桌面环境的配置中读取
1022/// (例如 GNOME 的 `gtk-decoration-layout` gsetting),通过 [`WindowButtonLayout::parse`] 解析。
1023#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1024pub struct WindowButtonLayout {
1025    /// 标题栏左侧的按钮。
1026    pub left: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
1027    /// 标题栏右侧的按钮。
1028    pub right: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
1029}
1030
1031#[cfg(any(target_os = "linux", target_os = "freebsd"))]
1032impl WindowButtonLayout {
1033    /// 返回 Zed 内置的 Linux 标题栏回退按钮布局。
1034    pub fn linux_default() -> Self {
1035        Self {
1036            left: [None; MAX_BUTTONS_PER_SIDE],
1037            right: [
1038                Some(WindowButton::Minimize),
1039                Some(WindowButton::Maximize),
1040                Some(WindowButton::Close),
1041            ],
1042        }
1043    }
1044
1045    /// 解析 GNOME 风格的 `button-layout` 字符串(如 `"close,minimize:maximize"`)。
1046    pub fn parse(layout_string: &str) -> Result<Self> {
1047        fn parse_side(
1048            s: &str,
1049            seen_buttons: &mut [bool; MAX_BUTTONS_PER_SIDE],
1050            unrecognized: &mut Vec<String>,
1051        ) -> [Option<WindowButton>; MAX_BUTTONS_PER_SIDE] {
1052            let mut result = [None; MAX_BUTTONS_PER_SIDE];
1053            let mut i = 0;
1054            for name in s.split(',') {
1055                let trimmed = name.trim();
1056                if trimmed.is_empty() {
1057                    continue;
1058                }
1059                let button = match trimmed {
1060                    "minimize" => Some(WindowButton::Minimize),
1061                    "maximize" => Some(WindowButton::Maximize),
1062                    "close" => Some(WindowButton::Close),
1063                    other => {
1064                        unrecognized.push(other.to_string());
1065                        None
1066                    }
1067                };
1068                if let Some(button) = button {
1069                    if seen_buttons[button.index()] {
1070                        continue;
1071                    }
1072                    if let Some(slot) = result.get_mut(i) {
1073                        *slot = Some(button);
1074                        seen_buttons[button.index()] = true;
1075                        i += 1;
1076                    }
1077                }
1078            }
1079            result
1080        }
1081
1082        let (left_str, right_str) = layout_string.split_once(':').unwrap_or(("", layout_string));
1083        let mut unrecognized = Vec::new();
1084        let mut seen_buttons = [false; MAX_BUTTONS_PER_SIDE];
1085        let layout = Self {
1086            left: parse_side(left_str, &mut seen_buttons, &mut unrecognized),
1087            right: parse_side(right_str, &mut seen_buttons, &mut unrecognized),
1088        };
1089
1090        if !unrecognized.is_empty()
1091            && layout.left.iter().all(Option::is_none)
1092            && layout.right.iter().all(Option::is_none)
1093        {
1094            bail!(
1095                "button layout string {:?} contains no valid buttons (unrecognized: {})",
1096                layout_string,
1097                unrecognized.join(", ")
1098            );
1099        }
1100
1101        Ok(layout)
1102    }
1103
1104    /// 将布局格式化为 GNOME 风格的 `button-layout` 字符串。
1105    #[cfg(test)]
1106    pub fn format(&self) -> String {
1107        fn format_side(buttons: &[Option<WindowButton>; MAX_BUTTONS_PER_SIDE]) -> String {
1108            buttons
1109                .iter()
1110                .flatten()
1111                .map(|button| match button {
1112                    WindowButton::Minimize => "minimize",
1113                    WindowButton::Maximize => "maximize",
1114                    WindowButton::Close => "close",
1115                })
1116                .collect::<Vec<_>>()
1117                .join(",")
1118        }
1119
1120        format!("{}:{}", format_side(&self.left), format_side(&self.right))
1121    }
1122}
1123
1124/// 描述窗口各边当前的平铺状态
1125#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
1126pub struct Tiling {
1127    /// 上边缘是否平铺
1128    pub top: bool,
1129    /// 左边缘是否平铺
1130    pub left: bool,
1131    /// 右边缘是否平铺
1132    pub right: bool,
1133    /// 下边缘是否平铺
1134    pub bottom: bool,
1135}
1136
1137impl Tiling {
1138    /// 创建一个所有边都平铺的 [`Tiling`] 实例。
1139    pub fn tiled() -> Self {
1140        Self {
1141            top: true,
1142            left: true,
1143            right: true,
1144            bottom: true,
1145        }
1146    }
1147
1148    /// 是否有任何边缘处于平铺状态
1149    pub fn is_tiled(&self) -> bool {
1150        self.top || self.left || self.right || self.bottom
1151    }
1152}
1153
1154/// 辅助功能适配器的回调函数。
1155pub struct A11yCallbacks {
1156    /// 适配器被激活时调用(屏幕阅读器连接)。
1157    pub activation: Box<dyn Fn() -> Option<accesskit::TreeUpdate> + Send + 'static>,
1158    /// 屏幕阅读器请求操作时调用。
1159    pub action: Box<dyn Fn(accesskit::ActionRequest) + Send + 'static>,
1160    /// 适配器被停用时调用(屏幕阅读器断开连接)。
1161    pub deactivation: Box<dyn Fn() + Send + 'static>,
1162}
1163
1164/// 帧请求选项,控制平台何时请求重绘。
1165#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
1166pub struct RequestFrameOptions {
1167    /// 是否需要呈现帧(提交到屏幕)。
1168    pub require_presentation: bool,
1169    /// 为 `true` 时强制刷新所有渲染状态。
1170    pub force_render: bool,
1171}
1172
1173/// 平台原生窗口抽象,由各平台 crate 实现,提供窗口管理、输入、渲染等能力。
1174///
1175/// 通过 [`Platform::open_window`] 创建实例,通过 [`PlatformWindow`] trait 与窗口交互。
1176pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
1177    /// 返回窗口在屏幕上的边界矩形(包含标题栏和边框)。
1178    fn bounds(&self) -> Bounds<Pixels>;
1179    /// 窗口是否处于最大化状态。
1180    fn is_maximized(&self) -> bool;
1181    /// 返回窗口当前边界状态(窗口化/最大化/全屏)。
1182    fn window_bounds(&self) -> WindowBounds;
1183    /// 返回窗口内容区域的尺寸(不含标题栏和边框)。
1184    fn content_size(&self) -> Size<Pixels>;
1185    /// 调整窗口内容区域的尺寸。
1186    fn resize(&mut self, size: Size<Pixels>);
1187    /// 返回窗口的显示缩放因子(如 1.0、1.5、2.0)。
1188    fn scale_factor(&self) -> f32;
1189    /// 返回窗口当前的外观模式(亮色/暗色)。
1190    fn appearance(&self) -> WindowAppearance;
1191    /// 返回窗口所在的显示器。
1192    fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
1193    /// 返回鼠标光标在窗口内的位置。
1194    fn mouse_position(&self) -> Point<Pixels>;
1195    /// 返回当前修饰键状态(Ctrl/Shift/Alt/Command)。
1196    fn modifiers(&self) -> Modifiers;
1197    /// 返回 Caps Lock 锁定状态。
1198    fn capslock(&self) -> Capslock;
1199    /// 设置输入处理器,用于处理 IME(输入法)组合文本。
1200    fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
1201    /// 取出并返回当前输入处理器(take 语义)。
1202    fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
1203    /// 显示模态提示对话框,返回用户选择的按钮索引。
1204    fn prompt(
1205        &self,
1206        level: PromptLevel,
1207        msg: &str,
1208        detail: Option<&str>,
1209        answers: &[PromptButton],
1210    ) -> Option<oneshot::Receiver<usize>>;
1211    /// 将窗口置于前台并获得焦点。
1212    fn activate(&self);
1213    /// 窗口是否当前处于活动(获得焦点)状态。
1214    fn is_active(&self) -> bool;
1215    /// 鼠标光标是否悬停在窗口上方。
1216    fn is_hovered(&self) -> bool;
1217    /// 返回窗口背景外观(透明/不透明/毛玻璃)。
1218    fn background_appearance(&self) -> WindowBackgroundAppearance;
1219    /// 设置窗口标题文本。
1220    fn set_title(&mut self, title: &str);
1221    /// 设置窗口背景外观模式。
1222    fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
1223    /// 最小化窗口。
1224    fn minimize(&self);
1225    /// 最大化窗口(macOS 下为缩放/zoom)。
1226    fn zoom(&self);
1227    /// 切换全屏状态。
1228    fn toggle_fullscreen(&self);
1229    /// 窗口是否处于全屏状态。
1230    fn is_fullscreen(&self) -> bool;
1231    /// 注册帧请求回调,平台在需要重绘时调用。
1232    fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
1233    /// 注册输入事件回调,处理键盘、鼠标、触摸等事件。
1234    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
1235    /// 注册窗口活动状态变化回调(获得/失去焦点时触发)。
1236    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
1237    /// 注册鼠标悬停状态变化回调(进入/离开窗口时触发)。
1238    fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
1239    /// 注册窗口尺寸变化回调,参数为新尺寸和缩放因子。
1240    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
1241    /// 注册窗口位置变化回调。
1242    fn on_moved(&self, callback: Box<dyn FnMut()>);
1243    /// 注册窗口关闭请求回调,返回 `false` 可阻止关闭。
1244    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
1245    /// 注册窗口控件区域命中测试回调(用于自定义标题栏拖拽区域)。
1246    fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
1247    /// 注册窗口关闭回调,窗口关闭时调用。
1248    fn on_close(&self, callback: Box<dyn FnOnce()>);
1249    /// 注册窗口外观变化回调(系统切换亮色/暗色主题时触发)。
1250    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
1251    /// 注册窗口按钮布局变化回调(macOS 全屏按钮位置变化等)。
1252    fn on_button_layout_changed(&self, _callback: Box<dyn FnMut()>) {}
1253    /// 将渲染场景提交到窗口进行绘制。
1254    fn draw(&self, scene: &Scene);
1255    /// 通知平台当前帧已完成渲染。
1256    fn completed_frame(&self) {}
1257    /// 返回精灵图集(用于图标、表情符号等位图渲染)。
1258    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1259    /// 是否支持亚像素渲染(ClearType 文本渲染)。
1260    fn is_subpixel_rendering_supported(&self) -> bool;
1261
1262    /// 该平台窗口是否支持 Web DOM 后端。
1263    ///
1264    /// 返回 `true` 时,核心会在每帧构建 DOM 树并通过 [`Self::dom_tree_update`] 交付,
1265    /// 桌面平台默认 `false` 以保持零开销。
1266    #[cfg(feature = "dom-backend")]
1267    fn supports_dom(&self) -> bool {
1268        false
1269    }
1270
1271    /// 交付当前帧的 DOM 树(每帧一次,仅当 `supports_dom()` 为真)。
1272    ///
1273    /// DOM 层渲染在 canvas 之上的覆盖层中(v1 接受双重绘制),
1274    /// 平台侧负责增量对账(见 `rgpui-dom` crate 的 reconcile)。
1275    #[cfg(feature = "dom-backend")]
1276    fn dom_tree_update(&self, _tree: &crate::dom::DomTree) {}
1277
1278    /// 注册 Web DOM 事件委托回调。
1279    ///
1280    /// 点击 DOM 覆盖层上的元素时,平台按 `data-gpui-id` 反查 DOM key 链并回调,
1281    /// 由核心按 key 链直接命中 hitbox(绕过坐标 hit-test)。桌面平台默认空实现。
1282    #[cfg(feature = "dom-backend")]
1283    fn on_dom_event(
1284        &self,
1285        _callback: Box<dyn FnMut(Vec<crate::DomNodeKey>, PlatformInput) -> DispatchEventResult>,
1286    ) {
1287    }
1288
1289    /// 由 DOM 后端在可滚动容器发生浏览器原生滚动(`scroll` 事件)后回调,
1290    /// 参数为事件链与滚动视口的 `scrollLeft`/`scrollTop`(向下/向右为正)。
1291    /// 默认空实现:仅 Web DOM 后端使用。
1292    #[cfg(feature = "dom-backend")]
1293    fn on_dom_scroll(&self, _callback: Box<dyn FnMut(Vec<crate::DomNodeKey>, f64, f64)>) {}
1294
1295    // macOS specific methods
1296    /// 返回窗口标题文本。
1297    fn get_title(&self) -> String {
1298        String::new()
1299    }
1300    /// 返回当前窗口的标签页组(macOS 标签页功能)。
1301    fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
1302        None
1303    }
1304    /// 标签页栏是否可见。
1305    fn tab_bar_visible(&self) -> bool {
1306        false
1307    }
1308    /// 标记文档是否已编辑(macOS 窗口标题栏红点标记)。
1309    fn set_edited(&mut self, _edited: bool) {}
1310    /// 设置文档路径(macOS 标题栏显示文件名)。
1311    fn set_document_path(&self, _path: Option<&std::path::Path>) {}
1312    /// 设置 macOS 红绿灯按钮位置。
1313    #[cfg(target_os = "macos")]
1314    fn set_traffic_light_position(&self, _position: Point<Pixels>) {}
1315    /// 显示系统字符面板(emoji、特殊符号)。
1316    fn show_character_palette(&self) {}
1317    /// 处理标题栏双击事件(可自定义行为,如最大化/缩放)。
1318    fn titlebar_double_click(&self, _is_resizable: bool, _is_minimizable: bool) {}
1319    /// 注册"将标签页移至新窗口"回调。
1320    fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
1321    /// 注册"合并所有窗口"回调。
1322    fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
1323    /// 注册"切换到上一个标签页"回调。
1324    fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
1325    /// 注册"切换到下一个标签页"回调。
1326    fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
1327    /// 注册"切换标签页栏可见性"回调。
1328    fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
1329    /// 合并所有窗口为一个窗口的标签页。
1330    fn merge_all_windows(&self) {}
1331    /// 将当前标签页移至新窗口。
1332    fn move_tab_to_new_window(&self) {}
1333    /// 切换标签页总览视图(macOS Exposé 风格)。
1334    fn toggle_window_tab_overview(&self) {}
1335    /// 设置窗口的 tabbing identifier(控制标签页分组)。
1336    fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
1337
1338    /// 返回窗口的原始 HWND 句柄(仅 Windows)。
1339    #[cfg(target_os = "windows")]
1340    fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
1341
1342    /// 返回窗口的内部边界(Linux 下包含 CSD 装饰区域)。
1343    fn inner_window_bounds(&self) -> WindowBounds {
1344        self.window_bounds()
1345    }
1346    /// 请求设置窗口装饰模式(客户端装饰/服务端装饰)。
1347    fn request_decorations(&self, _decorations: WindowDecorations) {}
1348    /// 在指定位置显示窗口系统菜单(Linux 右键标题栏)。
1349    fn show_window_menu(&self, _position: Point<Pixels>) {}
1350    /// 启动窗口拖拽移动(Linux CSD 模式下从自定义标题栏触发)。
1351    fn start_window_move(&self) {}
1352    /// 启动窗口边缘调整大小(Linux CSD 模式下从自定义边框触发)。
1353    fn start_window_resize(&self, _edge: ResizeEdge) {}
1354    /// 设置窗口输入区域(指定哪些区域接收鼠标事件,其余区域穿透)。
1355    fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
1356    /// 返回当前窗口装饰类型(客户端/服务端/无装饰)。
1357    fn window_decorations(&self) -> Decorations {
1358        Decorations::Server
1359    }
1360    /// 设置 Wayland app_id(用于窗口标识和桌面集成)。
1361    fn set_app_id(&mut self, _app_id: &str) {}
1362    /// 映射窗口(X11 下将窗口显示到屏幕)。
1363    fn map_window(&mut self) -> anyhow::Result<()> {
1364        Ok(())
1365    }
1366    /// 返回窗口控件信息(最小化/最大化/关闭按钮位置)。
1367    fn window_controls(&self) -> WindowControls {
1368        WindowControls::default()
1369    }
1370    /// 设置客户端区域的内边距(Wayland layer-shell 排除区域)。
1371    fn set_client_inset(&self, _inset: Pixels) {}
1372    /// 返回 GPU 硬件信息(设备名称、显存等)。
1373    fn gpu_specs(&self) -> Option<GpuSpecs>;
1374
1375    /// 更新输入法(IME)候选框的位置。
1376    fn update_ime_position(&self, _bounds: Bounds<Pixels>);
1377
1378    /// 播放系统提示音。
1379    fn play_system_bell(&self) {}
1380
1381    /// 初始化辅助功能适配器,注册辅助功能回调。
1382    fn a11y_init(&self, _callbacks: A11yCallbacks) {}
1383
1384    /// 向辅助功能适配器提供无障碍树更新数据(accesskit)。
1385    fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
1386
1387    /// 通知辅助功能适配器窗口边界已更新。
1388    fn a11y_update_window_bounds(&self) {}
1389
1390    /// 使用指定场景渲染到 RGBA 图像纹理(仅测试用途)。
1391    #[cfg(any(test, feature = "test-support"))]
1392    fn as_test(&mut self) -> Option<&mut TestWindow> {
1393        None
1394    }
1395
1396    /// 将给定场景渲染到纹理并返回 RGBA 像素数据(仅测试用途)。
1397    /// 不会将帧呈现到屏幕,用于视觉测试。
1398    #[cfg(any(test, feature = "test-support"))]
1399    fn render_to_image(&self, _scene: &Scene) -> Result<RgbaImage> {
1400        anyhow::bail!("render_to_image not implemented for this platform")
1401    }
1402
1403    /// 设置 Wayland layer-shell 独占区域大小(像素)。
1404    fn set_exclusive_zone(&self, _zone: Pixels) {}
1405    /// 设置 Wayland layer-shell 独占边缘(顶部/底部/左侧/右侧)。
1406    #[cfg(all(target_os = "linux", feature = "wayland"))]
1407    fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
1408
1409    /// 请求用户注意力(任务栏闪烁/弹跳,提示用户查看窗口)。
1410    fn request_attention(&self) {}
1411
1412    /// 设置窗口在屏幕上的位置。
1413    fn set_position(&mut self, _position: Point<Pixels>) {}
1414
1415    /// 隐藏窗口(从任务栏移除,托盘模式下使用)。
1416    fn hide(&self) {}
1417
1418    /// 设置鼠标事件是否穿透窗口(桌面宠物/覆盖层场景)。
1419    fn set_mouse_passthrough(&self, _passthrough: bool) {}
1420
1421    /// 返回 Windows 窗口扩展样式(WS_EX_* 标志位)。
1422    fn window_extended_style(&self) -> u32 {
1423        0
1424    }
1425    /// 设置 Windows 窗口扩展样式。
1426    fn set_window_extended_style(&self, _style: u32) {}
1427
1428    /// 设置标题栏是否可见(控制自定义标题栏/原生标题栏切换)。
1429    fn set_titlebar_visible(&self, _visible: bool) {}
1430
1431    /// 设置输入框的语义内容类型(如 `password`、`email`),
1432    /// 供系统输入法/自动填充识别。macOS 通过 `NSTextContent` 实现,其他平台为空操作。
1433    fn set_text_content_type(&self, _content_type: Option<&'static str>) {}
1434}
1435
1436/// 无头窗口渲染器,可生成真实渲染输出。
1437#[cfg(any(test, feature = "test-support"))]
1438pub trait PlatformHeadlessRenderer {
1439    /// 渲染场景并作为 RGBA 图像返回结果
1440    fn render_scene_to_image(
1441        &mut self,
1442        scene: &Scene,
1443        size: Size<DevicePixels>,
1444    ) -> Result<RgbaImage>;
1445
1446    /// 渲染场景到离屏目标,不读取结果
1447    ///
1448    /// 这是绘制到真实窗口的无头等效操作:它执行与绘制到真实窗口相同的 CPU 端场景编码和 GPU 提交,但不阻塞 GPU 完成或复制像素回来
1449    fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> Result<()>;
1450
1451    /// 返回此渲染器使用的精灵图集
1452    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1453}
1454
1455/// 带元数据的可运行任务类型别名。
1456/// 之前是单变体枚举,现在简化为直接类型别名。
1457#[doc(hidden)]
1458pub type RunnableVariant = Runnable<RunnableMeta>;
1459
1460#[doc(hidden)]
1461pub type TimerResolutionGuard = rgpui_util::Deferred<Box<dyn FnOnce() + Send>>;
1462
1463#[doc(hidden)]
1464pub enum TasksIncluded {
1465    OnlyCompleted,
1466    CompletedAndRunning,
1467}
1468
1469/// 此类型公开是为了测试宏可以生成和使用它,但不应视为公共 API 的一部分。
1470#[doc(hidden)]
1471pub trait PlatformDispatcher: Send + Sync {
1472    fn is_main_thread(&self) -> bool;
1473    fn dispatch(&self, runnable: RunnableVariant, priority: Priority);
1474    fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
1475    fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
1476
1477    fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>);
1478
1479    fn now(&self) -> Instant {
1480        Instant::now()
1481    }
1482
1483    fn increase_timer_resolution(&self) -> TimerResolutionGuard {
1484        rgpui_util::defer(Box::new(|| {}))
1485    }
1486
1487    #[cfg(any(test, feature = "test-support"))]
1488    fn as_test(&self) -> Option<&TestDispatcher> {
1489        None
1490    }
1491
1492    // 此 cfg 必须与 `threaded_dispatcher` 模块的匹配,该模块在编译时实现此方法
1493    #[cfg(all(
1494        any(test, feature = "test-support"),
1495        any(target_os = "windows", target_os = "linux", target_family = "wasm")
1496    ))]
1497    fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
1498        None
1499    }
1500}
1501
1502/// 平台文本系统抽象 — 提供字体加载、字形光栅化、文本排版等能力。各平台需实现此 trait。
1503pub trait PlatformTextSystem: Send + Sync {
1504    /// 加载指定的字体数据(TTF/OTF 字节流)。
1505    fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
1506    /// 获取所有可用的字体名称。
1507    fn all_font_names(&self) -> Vec<String>;
1508    /// 根据字体描述符获取字体 ID。
1509    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
1510    /// 获取字体的度量信息。
1511    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
1512    /// 获取字形的排版边界。
1513    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
1514    /// 获取字形的前进宽度。
1515    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
1516    /// 获取字符对应的字形 ID。
1517    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
1518    /// 获取字形的光栅化边界。
1519    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
1520    /// 光栅化字形。
1521    fn rasterize_glyph(
1522        &self,
1523        params: &RenderGlyphParams,
1524        raster_bounds: Bounds<DevicePixels>,
1525    ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
1526    /// 使用给定的字体运行(Font Run)排版一行文本。
1527    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
1528    /// 返回给定字体和大小的推荐文本渲染模式。
1529    fn recommended_rendering_mode(&self, _font_id: FontId, _font_size: Pixels)
1530    -> TextRenderingMode;
1531    /// 返回以给定颜色绘制字形时使用的膨胀级别。
1532    fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
1533        0
1534    }
1535}
1536
1537/// 空操作文本系统实现,所有方法返回默认值。用于测试或无文本系统需求的平台。
1538pub struct NoopTextSystem;
1539
1540impl NoopTextSystem {
1541    /// 创建一个新的空操作文本系统实例。
1542    pub fn new() -> Self {
1543        Self
1544    }
1545}
1546
1547impl PlatformTextSystem for NoopTextSystem {
1548    fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1549        Ok(())
1550    }
1551
1552    fn all_font_names(&self) -> Vec<String> {
1553        Vec::new()
1554    }
1555
1556    fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1557        Ok(FontId(1))
1558    }
1559
1560    fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1561        FontMetrics {
1562            units_per_em: 1000,
1563            ascent: 1025.0,
1564            descent: -275.0,
1565            line_gap: 0.0,
1566            underline_position: -95.0,
1567            underline_thickness: 60.0,
1568            cap_height: 698.0,
1569            x_height: 516.0,
1570            bounding_box: Bounds {
1571                origin: Point {
1572                    x: -260.0,
1573                    y: -245.0,
1574                },
1575                size: Size {
1576                    width: 1501.0,
1577                    height: 1364.0,
1578                },
1579            },
1580        }
1581    }
1582
1583    fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1584        Ok(Bounds {
1585            origin: Point { x: 54.0, y: 0.0 },
1586            size: size(392.0, 528.0),
1587        })
1588    }
1589
1590    fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1591        Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1592    }
1593
1594    fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1595        Some(GlyphId(ch.len_utf16() as u32))
1596    }
1597
1598    fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1599        Ok(Default::default())
1600    }
1601
1602    fn rasterize_glyph(
1603        &self,
1604        _params: &RenderGlyphParams,
1605        raster_bounds: Bounds<DevicePixels>,
1606    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1607        Ok((raster_bounds.size, Vec::new()))
1608    }
1609
1610    fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1611        let mut position = px(0.);
1612        let metrics = self.font_metrics(FontId(0));
1613        let em_width = font_size
1614            * self
1615                .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1616                .unwrap()
1617                .width
1618            / metrics.units_per_em as f32;
1619        let mut glyphs = Vec::new();
1620        for (ix, c) in text.char_indices() {
1621            if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1622                glyphs.push(ShapedGlyph {
1623                    id: glyph,
1624                    position: point(position, px(0.)),
1625                    index: ix,
1626                    is_emoji: glyph.0 == 2,
1627                });
1628                if glyph.0 == 2 {
1629                    position += em_width * 2.0;
1630                } else {
1631                    position += em_width;
1632                }
1633            } else {
1634                position += em_width
1635            }
1636        }
1637        let mut runs = Vec::default();
1638        if !glyphs.is_empty() {
1639            runs.push(ShapedRun {
1640                font_id: FontId(0),
1641                glyphs,
1642            });
1643        } else {
1644            position = px(0.);
1645        }
1646
1647        LineLayout {
1648            font_size,
1649            width: position,
1650            ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1651            descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1652            runs,
1653            len: text.len(),
1654        }
1655    }
1656
1657    fn recommended_rendering_mode(
1658        &self,
1659        _font_id: FontId,
1660        _font_size: Pixels,
1661    ) -> TextRenderingMode {
1662        TextRenderingMode::Grayscale
1663    }
1664}
1665
1666// Adapted from https://github.com/microsoft/terminal/blob/1283c0f5b99a2961673249fa77c6b986efb5086c/src/renderer/atlas/dwrite.cpp
1667// Copyright (c) Microsoft Corporation.
1668// Licensed under the MIT license.
1669/// 计算亚像素文本渲染的伽马校正比率。
1670pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
1671    const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
1672        [0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0], // gamma = 1.0
1673        [0.0166 / 4.0, -0.0807 / 4.0, 0.2227 / 4.0, -0.0751 / 4.0], // gamma = 1.1
1674        [0.0350 / 4.0, -0.1760 / 4.0, 0.4325 / 4.0, -0.1370 / 4.0], // gamma = 1.2
1675        [0.0543 / 4.0, -0.2821 / 4.0, 0.6302 / 4.0, -0.1876 / 4.0], // gamma = 1.3
1676        [0.0739 / 4.0, -0.3963 / 4.0, 0.8167 / 4.0, -0.2287 / 4.0], // gamma = 1.4
1677        [0.0933 / 4.0, -0.5161 / 4.0, 0.9926 / 4.0, -0.2616 / 4.0], // gamma = 1.5
1678        [0.1121 / 4.0, -0.6395 / 4.0, 1.1588 / 4.0, -0.2877 / 4.0], // gamma = 1.6
1679        [0.1300 / 4.0, -0.7649 / 4.0, 1.3159 / 4.0, -0.3080 / 4.0], // gamma = 1.7
1680        [0.1469 / 4.0, -0.8911 / 4.0, 1.4644 / 4.0, -0.3234 / 4.0], // gamma = 1.8
1681        [0.1627 / 4.0, -1.0170 / 4.0, 1.6051 / 4.0, -0.3347 / 4.0], // gamma = 1.9
1682        [0.1773 / 4.0, -1.1420 / 4.0, 1.7385 / 4.0, -0.3426 / 4.0], // gamma = 2.0
1683        [0.1908 / 4.0, -1.2652 / 4.0, 1.8650 / 4.0, -0.3476 / 4.0], // gamma = 2.1
1684        [0.2031 / 4.0, -1.3864 / 4.0, 1.9851 / 4.0, -0.3501 / 4.0], // gamma = 2.2
1685    ];
1686
1687    const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
1688    const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
1689
1690    let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
1691    let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
1692
1693    [
1694        ratios[0] * NORM13,
1695        ratios[1] * NORM24,
1696        ratios[2] * NORM13,
1697        ratios[3] * NORM24,
1698    ]
1699}
1700
1701/// 精灵图集的缓存键,标识一种可渲染的图元(字形、SVG 或图像)。
1702#[derive(PartialEq, Eq, Hash, Clone)]
1703pub enum AtlasKey {
1704    /// 字形图元
1705    Glyph(RenderGlyphParams),
1706    /// SVG 矢量图元
1707    Svg(RenderSvgParams),
1708    /// 位图图像图元
1709    Image(RenderImageParams),
1710}
1711
1712impl AtlasKey {
1713    /// 返回该图集键的纹理类型。
1714    pub fn texture_kind(&self) -> AtlasTextureKind {
1715        match self {
1716            AtlasKey::Glyph(params) => {
1717                if params.is_emoji {
1718                    AtlasTextureKind::Polychrome
1719                } else if params.subpixel_rendering {
1720                    AtlasTextureKind::Subpixel
1721                } else {
1722                    AtlasTextureKind::Monochrome
1723                }
1724            }
1725            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1726            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1727        }
1728    }
1729}
1730
1731impl From<RenderGlyphParams> for AtlasKey {
1732    fn from(params: RenderGlyphParams) -> Self {
1733        Self::Glyph(params)
1734    }
1735}
1736
1737impl From<RenderSvgParams> for AtlasKey {
1738    fn from(params: RenderSvgParams) -> Self {
1739        Self::Svg(params)
1740    }
1741}
1742
1743impl From<RenderImageParams> for AtlasKey {
1744    fn from(params: RenderImageParams) -> Self {
1745        Self::Image(params)
1746    }
1747}
1748
1749/// 平台精灵图集抽象 — 管理 GPU 纹理中的图元缓存(字形、SVG、图像)。
1750pub trait PlatformAtlas {
1751    /// 根据键获取图集瓦片,若不存在则通过 build 闭包创建并插入。
1752    fn get_or_insert_with<'a>(
1753        &self,
1754        key: &AtlasKey,
1755        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1756    ) -> Result<Option<AtlasTile>>;
1757    /// 从图集中移除指定键对应的瓦片。
1758    fn remove(&self, key: &AtlasKey);
1759    /// 测试断言用:图集是否包含指定键(默认实现恒返回 false)。
1760    #[cfg(any(test, feature = "test-support"))]
1761    fn contains(&self, _key: &AtlasKey) -> bool {
1762        false
1763    }
1764}
1765
1766#[doc(hidden)]
1767pub struct AtlasTextureList<T> {
1768    pub textures: Vec<Option<T>>,
1769    pub free_list: Vec<usize>,
1770}
1771
1772impl<T> Default for AtlasTextureList<T> {
1773    fn default() -> Self {
1774        Self {
1775            textures: Vec::default(),
1776            free_list: Vec::default(),
1777        }
1778    }
1779}
1780
1781impl<T> ops::Index<usize> for AtlasTextureList<T> {
1782    type Output = Option<T>;
1783
1784    fn index(&self, index: usize) -> &Self::Output {
1785        &self.textures[index]
1786    }
1787}
1788
1789impl<T> AtlasTextureList<T> {
1790    #[allow(unused)]
1791    pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1792        self.free_list.clear();
1793        self.textures.drain(..)
1794    }
1795
1796    pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1797        self.textures.iter_mut().flatten()
1798    }
1799}
1800
1801/// 精灵图集中的一块瓦片,描述其在纹理中的位置和边距。
1802#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1803#[repr(C)]
1804pub struct AtlasTile {
1805    /// 该瓦片所属的纹理。
1806    pub texture_id: AtlasTextureId,
1807    /// 该瓦片在其纹理内的唯一 ID。
1808    pub tile_id: TileId,
1809    /// 瓦片内容周围的像素边距。
1810    pub padding: u32,
1811    /// 该瓦片在纹理中的边界区域。
1812    pub bounds: Bounds<DevicePixels>,
1813}
1814
1815/// 图集纹理的唯一标识符,包含索引和内容类型。
1816#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1817#[repr(C)]
1818pub struct AtlasTextureId {
1819    // 使用 u32 而非 usize 以兼容 Metal Shader Language
1820    /// 该纹理在图集中的索引。
1821    pub index: u32,
1822    /// 该纹理中存储的内容类型。
1823    pub kind: AtlasTextureKind,
1824}
1825
1826#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1827#[repr(C)]
1828/// 图集纹理的内容类型,决定颜色格式和渲染路径。
1829pub enum AtlasTextureKind {
1830    /// 单色(灰度字形)
1831    Monochrome,
1832    /// 多色(彩色图像、Emoji)
1833    Polychrome,
1834    /// 亚像素渲染(LCD 抗锯齿字形)
1835    Subpixel,
1836}
1837
1838/// 图集瓦片的唯一标识符,封装 etagere 分配器的序列化 ID。
1839#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1840#[repr(C)]
1841pub struct TileId(pub u32);
1842
1843impl From<etagere::AllocId> for TileId {
1844    fn from(id: etagere::AllocId) -> Self {
1845        Self(id.serialize())
1846    }
1847}
1848
1849impl From<TileId> for etagere::AllocId {
1850    fn from(id: TileId) -> Self {
1851        Self::deserialize(id.0)
1852    }
1853}
1854
1855/// 平台输入处理器,封装异步窗口上下文和文本输入回调,处理选区、标记文本等 IME 操作。
1856pub struct PlatformInputHandler {
1857    cx: AsyncWindowContext,
1858    handler: Box<dyn InputHandler>,
1859}
1860
1861impl PlatformInputHandler {
1862    /// 创建新的输入处理器。
1863    pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1864        Self { cx, handler }
1865    }
1866
1867    /// 获取当前选中的文本范围(UTF-16 偏移)。
1868    pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1869        self.cx
1870            .update(|window, cx| {
1871                self.handler
1872                    .selected_text_range(ignore_disabled_input, window, cx)
1873            })
1874            .ok()
1875            .flatten()
1876    }
1877
1878    /// 获取当前标记(未确认)文本的范围。
1879    pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1880        self.cx
1881            .update(|window, cx| self.handler.marked_text_range(window, cx))
1882            .ok()
1883            .flatten()
1884    }
1885
1886    /// 获取指定 UTF-16 范围内的文本内容。
1887    pub fn text_for_range(
1888        &mut self,
1889        range_utf16: Range<usize>,
1890        adjusted: &mut Option<Range<usize>>,
1891    ) -> Option<String> {
1892        self.cx
1893            .update(|window, cx| {
1894                self.handler
1895                    .text_for_range(range_utf16, adjusted, window, cx)
1896            })
1897            .ok()
1898            .flatten()
1899    }
1900
1901    /// 替换指定范围内的文本。
1902    pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1903        self.cx
1904            .update(|window, cx| {
1905                self.handler
1906                    .replace_text_in_range(replacement_range, text, window, cx);
1907            })
1908            .ok();
1909    }
1910
1911    /// 替换指定范围内的文本并设置标记(IME 组合文本)。
1912    pub fn replace_and_mark_text_in_range(
1913        &mut self,
1914        range_utf16: Option<Range<usize>>,
1915        new_text: &str,
1916        new_selected_range: Option<Range<usize>>,
1917    ) {
1918        self.cx
1919            .update(|window, cx| {
1920                self.handler.replace_and_mark_text_in_range(
1921                    range_utf16,
1922                    new_text,
1923                    new_selected_range,
1924                    window,
1925                    cx,
1926                )
1927            })
1928            .ok();
1929    }
1930
1931    /// 清除标记文本(确认输入)。
1932    pub fn unmark_text(&mut self) {
1933        self.cx
1934            .update(|window, cx| self.handler.unmark_text(window, cx))
1935            .ok();
1936    }
1937
1938    /// 获取指定 UTF-16 范围在屏幕上的边界矩形。
1939    pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1940        self.cx
1941            .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1942            .ok()
1943            .flatten()
1944    }
1945
1946    /// macOS: 是否启用长按弹出字符面板功能。
1947    pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1948        self.handler.apple_press_and_hold_enabled()
1949    }
1950
1951    /// 直接分发文本输入(绕过 IME 组合流程)。
1952    pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1953        self.handler.replace_text_in_range(None, input, window, cx);
1954    }
1955
1956    /// 计算 IME 候选框的屏幕位置(基于标记文本范围和选区位置)。
1957    pub fn compute_ime_candidate_bounds(
1958        marked_range: Option<Range<usize>>,
1959        selection: &UTF16Selection,
1960        mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1961    ) -> Option<Bounds<Pixels>> {
1962        if let Some(marked_range) = marked_range {
1963            // Default to the start of the marked (composing) range.
1964            let mut line_start = marked_range.start;
1965
1966            // Walk backward from the caret looking for a line break. A change in
1967            // the Y coordinate means we crossed into the previous visual line, so
1968            // the line start is one position after the break point.
1969            let caret = selection.range.end;
1970            if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1971                for i in (marked_range.start..caret).rev() {
1972                    if let Some(b) = bounds_for_range(i..i) {
1973                        if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
1974                            line_start = i + 1;
1975                            break;
1976                        }
1977                    }
1978                }
1979            }
1980            bounds_for_range(line_start..line_start)
1981        } else {
1982            // No active composition  — use the selection endpoint.
1983            let offset = if selection.reversed {
1984                selection.range.start
1985            } else {
1986                selection.range.end
1987            };
1988            bounds_for_range(offset..offset)
1989        }
1990    }
1991
1992    /// 获取当前选中文本的边界框。
1993    pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1994        let marked_range = self.handler.marked_text_range(window, cx);
1995        let selection = self.handler.selected_text_range(true, window, cx)?;
1996        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1997            self.handler.bounds_for_range(range, window, cx)
1998        })
1999    }
2000
2001    /// 获取 IME 候选区域的边界框。
2002    pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
2003        let marked_range = self.marked_text_range();
2004        let selection = self.selected_text_range(true)?;
2005        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
2006            self.bounds_for_range(range)
2007        })
2008    }
2009
2010    /// 根据屏幕坐标返回最近的字符索引。
2011    #[allow(unused)]
2012    pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
2013        self.cx
2014            .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
2015            .ok()
2016            .flatten()
2017    }
2018
2019    /// 查询当前是否接受文本输入。
2020    pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
2021        self.handler.accepts_text_input(window, cx)
2022    }
2023
2024    /// 查询当前是否接受文本输入(异步版本)。
2025    pub fn query_accepts_text_input(&mut self) -> bool {
2026        self.cx
2027            .update(|window, cx| self.handler.accepts_text_input(window, cx))
2028            .unwrap_or(true)
2029    }
2030
2031    /// 参见 [`InputHandler::prefers_ime_for_printable_keys`]。
2032    ///
2033    /// 这不是对处理器简单的委托:当多按键绑定处于待处理状态时,无论处理器的偏好如何,
2034    /// 该函数都会返回 `false`,因为下一个可打印按键可能完成一个前缀已绕过 IME 的绑定。
2035    pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
2036        self.cx
2037            .update(|window, cx| {
2038                // 下一个可打印按键可能完成一个前缀已绕过 IME 的按键组合。
2039                !window.has_pending_keystrokes()
2040                    && self.handler.prefers_ime_for_printable_keys(window, cx)
2041            })
2042            .unwrap_or(false)
2043    }
2044}
2045
2046/// 表示文本缓冲区中的选区,以 UTF16 字符为单位。
2047/// 与 Range 不同,选区的头部可能在尾部之前。
2048#[derive(Debug)]
2049pub struct UTF16Selection {
2050    /// 该选区对应的文档中文本的范围(以 UTF16 字符为单位)。
2051    pub range: Range<usize>,
2052    /// 选区的头部是否在范围的起始位置(true)或结束位置(false)。
2053    pub reversed: bool,
2054}
2055
2056/// Zed 的平台 IME 系统文本输入处理接口。
2057/// 目前是 NSTextInputClient API 的 1:1 映射:
2058///
2059/// <https://developer.apple.com/documentation/appkit/nstextinputclient>
2060pub trait InputHandler: 'static {
2061    /// 获取用户当前选中文本的范围(如果有)。
2062    /// 对应 [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
2063    ///
2064    /// 返回值以 UTF-16 字符为单位,范围从 0 到文档长度。
2065    fn selected_text_range(
2066        &mut self,
2067        ignore_disabled_input: bool,
2068        window: &mut Window,
2069        cx: &mut App,
2070    ) -> Option<UTF16Selection>;
2071
2072    /// 获取当前标记(未确认)文本的范围(如果有)。
2073    /// 对应 [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
2074    ///
2075    /// 返回值以 UTF-16 字符为单位,范围从 0 到文档长度。
2076    fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
2077
2078    /// 获取给定文档范围内的文本(以 UTF-16 字符为单位)。
2079    /// 对应 [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
2080    ///
2081    /// range_utf16 以 UTF-16 字符为单位。
2082    fn text_for_range(
2083        &mut self,
2084        range_utf16: Range<usize>,
2085        adjusted_range: &mut Option<Range<usize>>,
2086        window: &mut Window,
2087        cx: &mut App,
2088    ) -> Option<String>;
2089
2090    /// 用给定文本替换文档中指定范围的文本。
2091    /// 对应 [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
2092    ///
2093    /// replacement_range 以 UTF-16 字符为单位。
2094    fn replace_text_in_range(
2095        &mut self,
2096        replacement_range: Option<Range<usize>>,
2097        text: &str,
2098        window: &mut Window,
2099        cx: &mut App,
2100    );
2101
2102    /// 用给定文本替换文档中指定范围的文本,
2103    /// 并将给定文本标记为 IME「组合」状态的一部分。
2104    /// 对应 [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
2105    ///
2106    /// range_utf16 以 UTF-16 字符为单位。
2107    /// new_selected_range 以 UTF-16 字符为单位。
2108    fn replace_and_mark_text_in_range(
2109        &mut self,
2110        range_utf16: Option<Range<usize>>,
2111        new_text: &str,
2112        new_selected_range: Option<Range<usize>>,
2113        window: &mut Window,
2114        cx: &mut App,
2115    );
2116
2117    /// 移除文档中的 IME「组合」状态。
2118    /// 对应 [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
2119    fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
2120
2121    /// 获取给定文档范围在屏幕坐标中的边界区域。
2122    /// 对应 [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
2123    ///
2124    /// 用于定位 IME 候选窗口。
2125    fn bounds_for_range(
2126        &mut self,
2127        range_utf16: Range<usize>,
2128        window: &mut Window,
2129        cx: &mut App,
2130    ) -> Option<Bounds<Pixels>>;
2131
2132    /// 获取给定点在 UTF16 字符中的字符偏移量。
2133    ///
2134    /// 对应 [characterIndexForPoint:](https://developer.apple.com/documentation/appkit/nstextinputclient/characterindex(for:))
2135    fn character_index_for_point(
2136        &mut self,
2137        point: Point<Pixels>,
2138        window: &mut Window,
2139        cx: &mut App,
2140    ) -> Option<usize>;
2141
2142    /// 允许输入上下文选择接收原始按键重复,而非将其发送到平台。
2143    /// TODO: 理想情况下应能通过 NSUserDefaults 设置 ApplePressAndHoldEnabled
2144    /// (iTerm 就是这样做的),但目前似乎不生效。
2145    fn apple_press_and_hold_enabled(&mut self) -> bool {
2146        true
2147    }
2148
2149    /// 返回此处理器是否接受要插入的文本输入。
2150    fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
2151        true
2152    }
2153
2154    /// 返回在非 ASCII 输入源(如日语、韩语、中文 IME)激活时,
2155    /// 可打印按键是否应在按键绑定匹配之前先路由到 IME。
2156    /// 这防止了 `jj` 等多击按键绑定拦截 IME 应该组合的按键。
2157    ///
2158    /// 默认为 `false`。编辑器根据是否期望字符输入来覆盖此值
2159    /// (例如 Vim 插入模式返回 `true`,正常模式返回 `false`)。
2160    /// 终端保持默认的 `false`,以便原始按键到达终端进程。
2161    fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
2162        false
2163    }
2164
2165    /// 设置输入中的选中文本范围。
2166    fn set_selected_text_range(
2167        &mut self,
2168        _range_utf16: Range<usize>,
2169        _window: &mut Window,
2170        _cx: &mut App,
2171    ) {
2172    }
2173
2174    /// 获取元素在屏幕坐标中的边界区域。
2175    fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
2176        None
2177    }
2178
2179    /// 获取文本的长度(以 UTF-16 字符为单位)。
2180    fn text_length_utf16(&mut self, _window: &mut Window, _cx: &mut App) -> Option<usize> {
2181        None
2182    }
2183}
2184
2185/// 创建窗口时可配置的变量
2186#[derive(Debug)]
2187pub struct WindowOptions {
2188    /// 指定窗口在屏幕坐标中的状态和边界。
2189    /// - `None`:继承边界。
2190    /// - `Some(WindowBounds)`:以对应的状态和恢复尺寸打开窗口。
2191    pub window_bounds: Option<WindowBounds>,
2192
2193    /// 窗口标题栏配置
2194    pub titlebar: Option<TitlebarOptions>,
2195
2196    /// 窗口创建时是否获取焦点
2197    pub focus: bool,
2198
2199    /// 窗口创建时是否显示
2200    pub show: bool,
2201
2202    /// 要创建的窗口类型
2203    pub kind: WindowKind,
2204
2205    /// 窗口是否可被用户拖拽移动
2206    pub is_movable: bool,
2207
2208    /// 窗口是否可被用户调整大小
2209    pub is_resizable: bool,
2210
2211    /// 窗口是否可被用户最小化
2212    pub is_minimizable: bool,
2213
2214    /// 在哪个显示器上创建窗口,若为 None,
2215    /// 则在主显示器上创建
2216    pub display_id: Option<DisplayId>,
2217
2218    /// 窗口背景外观。
2219    pub window_background: WindowBackgroundAppearance,
2220
2221    /// 窗口的应用标识符,桌面环境可用于将应用分组。
2222    pub app_id: Option<String>,
2223
2224    /// 窗口最小尺寸
2225    pub window_min_size: Option<Size<Pixels>>,
2226
2227    /// 使用客户端还是服务端装饰。仅 Wayland。
2228    /// 注意此设置可能被忽略。
2229    pub window_decorations: Option<WindowDecorations>,
2230
2231    /// 图标图片(仅 X11)
2232    pub icon: Option<Arc<image::RgbaImage>>,
2233
2234    /// 标签页组名称,允许在 macOS 10.12+ 上以原生标签页方式打开窗口。具有相同 tabbing identifier 的窗口将被分组在一起。
2235    pub tabbing_identifier: Option<String>,
2236
2237    /// macOS 专用:应用是否自行处理标题栏拖拽。当使用自定义标题栏时设置为 true,
2238    /// 使 AppKit 不拦截标题栏点击,由应用通过 `Window::start_window_move` 自行处理。
2239    pub app_owns_titlebar_drag: bool,
2240
2241    /// Windows/Linux:是否启用鼠标事件穿透(点击穿透到后面的窗口)。
2242    /// 用于桌面宠物、覆盖层等需要让鼠标点击穿透到下层窗口的场景。
2243    pub mouse_passthrough: bool,
2244}
2245
2246/// 创建窗口时的配置参数。
2247#[derive(Debug)]
2248pub struct WindowParams {
2249    /// 窗口初始位置和尺寸。
2250    pub bounds: Bounds<Pixels>,
2251
2252    /// 标题栏配置。
2253    pub titlebar: Option<TitlebarOptions>,
2254
2255    /// 窗口类型(普通窗口、覆盖层等)。
2256    pub kind: WindowKind,
2257
2258    /// 窗口是否可被用户拖拽移动。
2259    pub is_movable: bool,
2260
2261    /// 窗口是否可被用户调整大小。
2262    pub is_resizable: bool,
2263
2264    /// 窗口是否可被用户最小化。
2265    pub is_minimizable: bool,
2266
2267    /// 窗口打开后是否自动获取焦点。
2268    pub focus: bool,
2269
2270    /// 窗口打开后是否立即显示。
2271    pub show: bool,
2272
2273    /// 窗口图标(仅 X11 有效)。
2274    pub icon: Option<Arc<image::RgbaImage>>,
2275
2276    /// 指定显示在哪个显示器上(None 为默认)。
2277    pub display_id: Option<DisplayId>,
2278
2279    /// 应用标识符(主要用于 Wayland)。
2280    pub app_id: Option<String>,
2281
2282    /// 窗口最小尺寸限制。
2283    pub window_min_size: Option<Size<Pixels>>,
2284    /// macOS 标签页分组标识符,相同标识符的窗口可合并为标签页。
2285    #[cfg(target_os = "macos")]
2286    pub tabbing_identifier: Option<String>,
2287
2288    /// macOS only: 应用是否自行处理标题栏拖拽。
2289    /// 当使用自定义标题栏时设置为 true(macOS 专用,其他平台无效果)。
2290    pub app_owns_titlebar_drag: bool,
2291
2292    /// Windows/Linux: 是否启用鼠标事件穿透(点击穿透到后面的窗口)。
2293    /// 覆盖层窗口需要此选项让鼠标事件穿透到底层窗口。
2294    pub mouse_passthrough: bool,
2295}
2296
2297/// 表示窗口打开时应处于的状态
2298#[derive(Debug, Copy, Clone, PartialEq)]
2299pub enum WindowBounds {
2300    /// 表示窗口应以窗口化状态打开,使用给定的边界。
2301    Windowed(Bounds<Pixels>),
2302    /// 表示窗口应以最大化状态打开。
2303    /// 此处提供的边界表示窗口的恢复尺寸。
2304    Maximized(Bounds<Pixels>),
2305    /// 表示窗口应以全屏模式打开。
2306    /// 此处提供的边界表示窗口的恢复尺寸。
2307    Fullscreen(Bounds<Pixels>),
2308}
2309
2310impl Default for WindowBounds {
2311    fn default() -> Self {
2312        WindowBounds::Windowed(Bounds::default())
2313    }
2314}
2315
2316impl WindowBounds {
2317    /// 获取内部边界
2318    pub fn get_bounds(&self) -> Bounds<Pixels> {
2319        match self {
2320            WindowBounds::Windowed(bounds) => *bounds,
2321            WindowBounds::Maximized(bounds) => *bounds,
2322            WindowBounds::Fullscreen(bounds) => *bounds,
2323        }
2324    }
2325
2326    /// 创建一个新的窗口边界,使窗口在屏幕上居中。
2327    pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
2328        WindowBounds::Windowed(Bounds::centered(None, size, cx))
2329    }
2330}
2331
2332impl Default for WindowOptions {
2333    fn default() -> Self {
2334        Self {
2335            window_bounds: None,
2336            titlebar: Some(TitlebarOptions {
2337                title: Default::default(),
2338                appears_transparent: Default::default(),
2339                traffic_light_position: Default::default(),
2340            }),
2341            focus: true,
2342            show: true,
2343            kind: WindowKind::Normal,
2344            is_movable: true,
2345            is_resizable: true,
2346            is_minimizable: true,
2347            display_id: None,
2348            window_background: WindowBackgroundAppearance::default(),
2349            icon: None,
2350            app_id: None,
2351            window_min_size: None,
2352            window_decorations: None,
2353            tabbing_identifier: None,
2354            app_owns_titlebar_drag: false,
2355            mouse_passthrough: false,
2356        }
2357    }
2358}
2359
2360/// 窗口标题栏可配置的选项
2361#[derive(Debug, Default)]
2362pub struct TitlebarOptions {
2363    /// 窗口的初始标题
2364    pub title: Option<SharedString>,
2365
2366    /// 是否隐藏默认系统标题栏以使用自定义绘制的标题栏?(仅 macOS 和 Windows)
2367    /// Linux 上请参见 [`WindowOptions::window_decorations`]
2368    pub appears_transparent: bool,
2369
2370    /// macOS 红绿灯按钮的位置
2371    pub traffic_light_position: Option<Point<Pixels>>,
2372}
2373
2374/// 要创建的窗口类型
2375#[derive(Clone, Debug, PartialEq, Eq)]
2376pub enum WindowKind {
2377    /// 普通应用窗口
2378    Normal,
2379
2380    /// 出现在所有其他窗口上方的窗口,通常用于警告或弹出窗口。
2381    /// 应谨慎使用!
2382    PopUp,
2383
2384    /// 父窗口锚定的原生弹出窗口,用于菜单、组合框、上下文菜单和工具提示。
2385    /// 与 [`WindowKind::PopUp`] 不同,它相对于父窗口定位。
2386    ///
2387    /// 弹出窗口的大小来自 [`WindowOptions::window_bounds`],其原点被忽略。
2388    /// 参见 [`popup::PopupOptions`] 了解放置选项。没有原生实现的平台
2389    /// 会以 [`popup::PopupNotSupportedError`] 拒绝。
2390    AnchoredPopup(popup::PopupOptions),
2391
2392    /// 出现在父窗口上方的浮动窗口
2393    Floating,
2394
2395    /// Wayland LayerShell 窗口,用于为应用绘制覆盖层或背景,
2396    /// 如 Dock、通知或壁纸。
2397    #[cfg(all(target_os = "linux", feature = "wayland"))]
2398    LayerShell(layer_shell::LayerShellOptions),
2399
2400    /// 出现在父窗口上方的模态窗口,阻止与父窗口的交互,
2401    /// 直到模态窗口关闭
2402    Dialog,
2403
2404    /// 覆盖层窗口:始终置顶、无边框、支持透明度
2405    Overlay,
2406}
2407
2408/// 窗口的外观,由操作系统定义。
2409///
2410/// 在 macOS 上,这对应于命名的 [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
2411/// 值。
2412#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2413pub enum WindowAppearance {
2414    /// 亮色外观。
2415    ///
2416    /// 在 macOS 上,这对应于 `aqua` 外观。
2417    #[default]
2418    Light,
2419
2420    /// 带有鲜艳颜色的亮色外观。
2421    ///
2422    /// 在 macOS 上,这对应于 `NSAppearanceNameVibrantLight` 外观。
2423    VibrantLight,
2424
2425    /// 暗色外观。
2426    ///
2427    /// 在 macOS 上,这对应于 `darkAqua` 外观。
2428    Dark,
2429
2430    /// 带有鲜艳颜色的暗色外观。
2431    ///
2432    /// 在 macOS 上,这对应于 `NSAppearanceNameVibrantDark` 外观。
2433    VibrantDark,
2434}
2435
2436/// 窗口本身的背景外观,在没有内容或内容透明时显示。
2437#[derive(Copy, Clone, Debug, Default, PartialEq)]
2438pub enum WindowBackgroundAppearance {
2439    /// 不透明。
2440    ///
2441    /// 告诉窗口管理器此窗口背后的内容不需要绘制。
2442    ///
2443    /// 实际颜色取决于系统,主题应定义完全不透明的背景色。
2444    #[default]
2445    Opaque,
2446    /// 纯 Alpha 透明。
2447    Transparent,
2448    /// 透明,但窗口背后的内容会被模糊。
2449    ///
2450    /// 并非总是支持。
2451    Blurred,
2452    /// Mica 背景材质,Windows 11 支持。
2453    MicaBackdrop,
2454    /// Mica Alt 背景材质,Windows 11 支持。
2455    MicaAltBackdrop,
2456}
2457
2458/// 绘制字形时使用的文本渲染模式。
2459#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2460pub enum TextRenderingMode {
2461    /// 使用平台默认的文本渲染模式。
2462    #[default]
2463    PlatformDefault,
2464    /// 使用亚像素(ClearType 风格)文本渲染。
2465    Subpixel,
2466    /// 使用灰度文本渲染。
2467    Grayscale,
2468}
2469
2470/// 文件对话框提示可配置的选项
2471#[derive(Clone, Debug)]
2472pub struct PathPromptOptions {
2473    /// 提示是否允许选择文件?
2474    pub files: bool,
2475    /// 提示是否允许选择目录?
2476    pub directories: bool,
2477    /// 提示是否允许多选文件?
2478    pub multiple: bool,
2479    /// 选择路径时显示给用户的提示文本
2480    pub prompt: Option<SharedString>,
2481}
2482
2483/// 提示样式类型
2484#[derive(Copy, Clone, Debug, PartialEq)]
2485pub enum PromptLevel {
2486    /// 通知用户的提示
2487    Info,
2488
2489    /// 警告用户潜在问题的提示
2490    Warning,
2491
2492    /// 发生严重问题时的提示
2493    Critical,
2494}
2495
2496/// 提示对话框按钮
2497#[derive(Clone, Debug, PartialEq)]
2498pub enum PromptButton {
2499    /// 确认按钮
2500    Ok(SharedString),
2501    /// 取消按钮
2502    Cancel(SharedString),
2503    /// 其他按钮
2504    Other(SharedString),
2505}
2506
2507impl PromptButton {
2508    /// 创建带标签的按钮
2509    pub fn new(label: impl Into<SharedString>) -> Self {
2510        PromptButton::Other(label.into())
2511    }
2512
2513    /// 创建确认按钮
2514    pub fn ok(label: impl Into<SharedString>) -> Self {
2515        PromptButton::Ok(label.into())
2516    }
2517
2518    /// 创建取消按钮
2519    pub fn cancel(label: impl Into<SharedString>) -> Self {
2520        PromptButton::Cancel(label.into())
2521    }
2522
2523    /// 返回此按钮是否为取消按钮。
2524    pub fn is_cancel(&self) -> bool {
2525        matches!(self, PromptButton::Cancel(_))
2526    }
2527
2528    /// 返回按钮的标签文本
2529    pub fn label(&self) -> &SharedString {
2530        match self {
2531            PromptButton::Ok(label) => label,
2532            PromptButton::Cancel(label) => label,
2533            PromptButton::Other(label) => label,
2534        }
2535    }
2536}
2537
2538impl From<&str> for PromptButton {
2539    fn from(value: &str) -> Self {
2540        match value.to_lowercase().as_str() {
2541            "ok" => PromptButton::Ok("OK".into()),
2542            "cancel" => PromptButton::Cancel("Cancel".into()),
2543            _ => PromptButton::Other(SharedString::from(value.to_owned())),
2544        }
2545    }
2546}
2547
2548/// 光标(指针)样式
2549#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2550pub enum CursorStyle {
2551    /// 默认光标
2552    #[default]
2553    Arrow,
2554
2555    /// 文本输入光标
2556    /// 对应 CSS cursor 值 `text`
2557    IBeam,
2558
2559    /// 十字光标
2560    /// 对应 CSS cursor 值 `crosshair`
2561    Crosshair,
2562
2563    /// 闭合手型光标
2564    /// 对应 CSS cursor 值 `grabbing`
2565    ClosedHand,
2566
2567    /// 张开手型光标
2568    /// 对应 CSS cursor 值 `grab`
2569    OpenHand,
2570
2571    /// 指向手型光标
2572    /// 对应 CSS cursor 值 `pointer`
2573    PointingHand,
2574
2575    /// 向左调整大小光标
2576    /// 对应 CSS cursor 值 `w-resize`
2577    ResizeLeft,
2578
2579    /// 向右调整大小光标
2580    /// 对应 CSS cursor 值 `e-resize`
2581    ResizeRight,
2582
2583    /// 左右调整大小光标
2584    /// 对应 CSS cursor 值 `ew-resize`
2585    ResizeLeftRight,
2586
2587    /// 向上调整大小光标
2588    /// 对应 CSS cursor 值 `n-resize`
2589    ResizeUp,
2590
2591    /// 向下调整大小光标
2592    /// 对应 CSS cursor 值 `s-resize`
2593    ResizeDown,
2594
2595    /// 上下调整大小光标
2596    /// 对应 CSS cursor 值 `ns-resize`
2597    ResizeUpDown,
2598
2599    /// 向左上和右下调整大小光标
2600    /// 对应 CSS cursor 值 `nesw-resize`
2601    ResizeUpLeftDownRight,
2602
2603    /// 向右上和左下调整大小光标
2604    /// 对应 CSS cursor 值 `nwse-resize`
2605    ResizeUpRightDownLeft,
2606
2607    /// 表示可以水平调整大小的光标
2608    /// 对应 CSS cursor 值 `col-resize`
2609    ResizeColumn,
2610
2611    /// 表示可以垂直调整大小的光标
2612    /// 对应 CSS cursor 值 `row-resize`
2613    ResizeRow,
2614
2615    /// 垂直布局的文本输入光标
2616    /// 对应 CSS cursor 值 `vertical-text`
2617    IBeamCursorForVerticalLayout,
2618
2619    /// 表示操作不允许的光标
2620    /// 对应 CSS cursor 值 `not-allowed`
2621    OperationNotAllowed,
2622
2623    /// 表示操作将产生链接的光标
2624    /// 对应 CSS cursor 值 `alias`
2625    DragLink,
2626
2627    /// 表示操作将产生副本的光标
2628    /// 对应 CSS cursor 值 `copy`
2629    DragCopy,
2630
2631    /// 表示操作将产生上下文菜单的光标
2632    /// 对应 CSS cursor 值 `context-menu`
2633    ContextualMenu,
2634}
2635
2636/// 应复制到剪贴板的剪贴板项目
2637#[derive(Clone, Debug, Eq, PartialEq)]
2638pub struct ClipboardItem {
2639    /// 此剪贴板项目的条目。
2640    pub entries: Vec<ClipboardEntry>,
2641}
2642
2643/// 剪贴板字符串或剪贴板图像
2644#[derive(Clone, Debug, Eq, PartialEq)]
2645pub enum ClipboardEntry {
2646    /// 字符串条目
2647    String(ClipboardString),
2648    /// 图像条目
2649    Image(Image),
2650    /// 文件条目
2651    ExternalPaths(crate::ExternalPaths),
2652}
2653
2654impl ClipboardItem {
2655    /// 创建一个不带关联元数据的新 ClipboardItem::String
2656    pub fn new_string(text: String) -> Self {
2657        Self {
2658            entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2659        }
2660    }
2661
2662    /// 创建一个带有关联元数据的新 ClipboardItem::String
2663    pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2664        Self {
2665            entries: vec![ClipboardEntry::String(ClipboardString {
2666                text,
2667                metadata: Some(metadata),
2668            })],
2669        }
2670    }
2671
2672    /// 创建一个带有关联元数据(JSON 序列化)的新 ClipboardItem::String
2673    pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2674        Self {
2675            entries: vec![ClipboardEntry::String(
2676                ClipboardString::new(text).with_json_metadata(metadata),
2677            )],
2678        }
2679    }
2680
2681    /// 创建一个不带关联元数据的新 ClipboardItem::Image
2682    pub fn new_image(image: &Image) -> Self {
2683        Self {
2684            entries: vec![ClipboardEntry::Image(image.clone())],
2685        }
2686    }
2687
2688    /// 连接项目中所有 ClipboardString 条目的文本。
2689    /// 如果没有 ClipboardString 条目则返回 None。
2690    pub fn text(&self) -> Option<String> {
2691        let mut answer = String::new();
2692
2693        for entry in self.entries.iter() {
2694            if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2695                answer.push_str(text);
2696            }
2697        }
2698
2699        if answer.is_empty() {
2700            for entry in self.entries.iter() {
2701                if let ClipboardEntry::ExternalPaths(paths) = entry {
2702                    for path in &paths.0 {
2703                        use std::fmt::Write as _;
2704                        _ = write!(answer, "{}", path.display());
2705                    }
2706                }
2707            }
2708        }
2709
2710        if !answer.is_empty() {
2711            Some(answer)
2712        } else {
2713            None
2714        }
2715    }
2716
2717    /// 如果此项目是单个 ClipboardEntry::String,返回其元数据。
2718    pub fn metadata(&self) -> Option<&String> {
2719        match self.entries().first() {
2720            Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2721                clipboard_string.metadata.as_ref()
2722            }
2723            _ => None,
2724        }
2725    }
2726
2727    /// 获取项目的条目
2728    pub fn entries(&self) -> &[ClipboardEntry] {
2729        &self.entries
2730    }
2731
2732    /// 获取项目条目的所有权版本
2733    pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2734        self.entries.into_iter()
2735    }
2736}
2737
2738impl From<ClipboardString> for ClipboardEntry {
2739    fn from(value: ClipboardString) -> Self {
2740        Self::String(value)
2741    }
2742}
2743
2744impl From<String> for ClipboardEntry {
2745    fn from(value: String) -> Self {
2746        Self::from(ClipboardString::from(value))
2747    }
2748}
2749
2750impl From<Image> for ClipboardEntry {
2751    fn from(value: Image) -> Self {
2752        Self::Image(value)
2753    }
2754}
2755
2756impl From<ClipboardEntry> for ClipboardItem {
2757    fn from(value: ClipboardEntry) -> Self {
2758        Self {
2759            entries: vec![value],
2760        }
2761    }
2762}
2763
2764impl From<String> for ClipboardItem {
2765    fn from(value: String) -> Self {
2766        Self::from(ClipboardEntry::from(value))
2767    }
2768}
2769
2770impl From<Image> for ClipboardItem {
2771    fn from(value: Image) -> Self {
2772        Self::from(ClipboardEntry::from(value))
2773    }
2774}
2775
2776/// 编辑器支持的图像格式之一(如 PNG、JPEG)- 用于处理剪贴板中的图像
2777#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2778pub enum ImageFormat {
2779    // 按粘贴到编辑器的可能性从高到低排序,
2780    // 在遍历检查剪贴板内容是否匹配时这很重要。
2781    /// .png
2782    Png,
2783    /// .jpeg 或 .jpg
2784    Jpeg,
2785    /// .webp
2786    Webp,
2787    /// .gif
2788    Gif,
2789    /// .svg
2790    Svg,
2791    /// .bmp
2792    Bmp,
2793    /// .tif 或 .tiff
2794    Tiff,
2795    /// .ico
2796    Ico,
2797    /// Netpbm 图像格式(.pbm、.ppm、.pgm)。
2798    Pnm,
2799}
2800
2801impl ImageFormat {
2802    /// 返回 ImageFormat 的 MIME 类型
2803    pub const fn mime_type(self) -> &'static str {
2804        match self {
2805            ImageFormat::Png => "image/png",
2806            ImageFormat::Jpeg => "image/jpeg",
2807            ImageFormat::Webp => "image/webp",
2808            ImageFormat::Gif => "image/gif",
2809            ImageFormat::Svg => "image/svg+xml",
2810            ImageFormat::Bmp => "image/bmp",
2811            ImageFormat::Tiff => "image/tiff",
2812            ImageFormat::Ico => "image/ico",
2813            ImageFormat::Pnm => "image/x-portable-anymap",
2814        }
2815    }
2816
2817    /// 根据 MIME 类型返回对应的 ImageFormat,包括已知别名。
2818    pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2819        use strum::IntoEnumIterator;
2820        Self::iter()
2821            .find(|format| format.mime_type() == mime_type)
2822            .or_else(|| Self::from_mime_type_alias(mime_type))
2823    }
2824
2825    /// 非规范的 MIME 类型,一些生产者在实际使用中使用。
2826    /// 不同于返回单一规范形式的 `mime_type()`,
2827    /// 这些是我们仍需识别的遗留或缩写变体。
2828    fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
2829        match mime_type {
2830            "image/jpg" => Some(Self::Jpeg),
2831            "image/tif" => Some(Self::Tiff),
2832            _ => None,
2833        }
2834    }
2835}
2836
2837/// 图像,包含格式和字节数据
2838#[derive(Clone, Debug, PartialEq, Eq)]
2839pub struct Image {
2840    /// 字节数据表示的图像格式(如 PNG)
2841    pub format: ImageFormat,
2842    /// 原始图像字节
2843    pub bytes: Vec<u8>,
2844    /// 图像的唯一 ID
2845    pub id: u64,
2846}
2847
2848impl Hash for Image {
2849    fn hash<H: Hasher>(&self, state: &mut H) {
2850        state.write_u64(self.id);
2851    }
2852}
2853
2854impl Image {
2855    /// 一个不包含数据的空图像
2856    pub fn empty() -> Self {
2857        Self::from_bytes(ImageFormat::Png, Vec::new())
2858    }
2859
2860    /// 从格式和字节数据创建图像
2861    pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2862        Self {
2863            id: hash(&bytes),
2864            format,
2865            bytes,
2866        }
2867    }
2868
2869    /// 获取图像的 ID
2870    pub fn id(&self) -> u64 {
2871        self.id
2872    }
2873
2874    /// 使用 RGPUI `use_asset` API 使此图像可渲染
2875    pub fn use_render_image(
2876        self: Arc<Self>,
2877        window: &mut Window,
2878        cx: &mut App,
2879    ) -> Option<Arc<RenderImage>> {
2880        ImageSource::Image(self)
2881            .use_data(None, window, cx)
2882            .and_then(|result| result.ok())
2883    }
2884
2885    /// 使用 RGPUI `get_asset` API 使此图像可渲染
2886    pub fn get_render_image(
2887        self: Arc<Self>,
2888        window: &mut Window,
2889        cx: &mut App,
2890    ) -> Option<Arc<RenderImage>> {
2891        ImageSource::Image(self)
2892            .get_data(None, window, cx)
2893            .and_then(|result| result.ok())
2894    }
2895
2896    /// 使用 RGPUI `remove_asset` API 移除此图像(如果可能)。
2897    pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2898        ImageSource::Image(self).remove_asset(cx);
2899    }
2900
2901    /// 将剪贴板图像转换为 `ImageData` 对象。
2902    pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2903        fn frames_for_image(
2904            bytes: &[u8],
2905            format: image::ImageFormat,
2906        ) -> Result<SmallVec<[Frame; 1]>> {
2907            let mut data = image::load_from_memory_with_format(bytes, format)?.into_rgba8();
2908
2909            // Convert from RGBA to BGRA.
2910            for pixel in data.chunks_exact_mut(4) {
2911                pixel.swap(0, 2);
2912            }
2913
2914            Ok(SmallVec::from_elem(Frame::new(data), 1))
2915        }
2916
2917        let frames = match self.format {
2918            ImageFormat::Gif => {
2919                let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2920                let mut frames = SmallVec::new();
2921
2922                for frame in decoder.into_frames() {
2923                    match frame {
2924                        Ok(mut frame) => {
2925                            // Convert from RGBA to BGRA.
2926                            for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2927                                pixel.swap(0, 2);
2928                            }
2929                            frames.push(frame);
2930                        }
2931                        Err(err) => {
2932                            log::debug!("Skipping GIF frame due to decode error: {err}");
2933                        }
2934                    }
2935                }
2936
2937                if frames.is_empty() {
2938                    anyhow::bail!("GIF could not be decoded: all frames failed");
2939                }
2940
2941                frames
2942            }
2943            ImageFormat::Png => frames_for_image(&self.bytes, image::ImageFormat::Png)?,
2944            ImageFormat::Jpeg => frames_for_image(&self.bytes, image::ImageFormat::Jpeg)?,
2945            ImageFormat::Webp => frames_for_image(&self.bytes, image::ImageFormat::WebP)?,
2946            ImageFormat::Bmp => frames_for_image(&self.bytes, image::ImageFormat::Bmp)?,
2947            ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?,
2948            ImageFormat::Ico => frames_for_image(&self.bytes, image::ImageFormat::Ico)?,
2949            ImageFormat::Svg => {
2950                return svg_renderer
2951                    .render_single_frame(&self.bytes, 1.0)
2952                    .map_err(Into::into);
2953            }
2954            ImageFormat::Pnm => frames_for_image(&self.bytes, image::ImageFormat::Pnm)?,
2955        };
2956
2957        Ok(Arc::new(RenderImage::new(frames)))
2958    }
2959
2960    /// 获取剪贴板图像的格式
2961    pub fn format(&self) -> ImageFormat {
2962        self.format
2963    }
2964
2965    /// 获取剪贴板图像的原始字节
2966    pub fn bytes(&self) -> &[u8] {
2967        self.bytes.as_slice()
2968    }
2969}
2970
2971/// 应复制到剪贴板的剪贴板字符串项目
2972#[derive(Clone, Debug, Eq, PartialEq)]
2973pub struct ClipboardString {
2974    /// 文本内容。
2975    pub text: String,
2976    /// 关联的可选元数据。
2977    pub metadata: Option<String>,
2978}
2979
2980impl ClipboardString {
2981    /// 创建一个新的剪贴板字符串
2982    pub fn new(text: String) -> Self {
2983        Self {
2984            text,
2985            metadata: None,
2986        }
2987    }
2988
2989    /// 返回一个新的剪贴板项目,其元数据通过 JSON 序列化后替换为给定值。
2990    pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2991        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2992        self
2993    }
2994
2995    /// 获取剪贴板字符串的文本
2996    pub fn text(&self) -> &String {
2997        &self.text
2998    }
2999
3000    /// 获取剪贴板字符串的所有权文本
3001    pub fn into_text(self) -> String {
3002        self.text
3003    }
3004
3005    /// 获取剪贴板字符串的元数据(JSON 格式)
3006    pub fn metadata_json<T>(&self) -> Option<T>
3007    where
3008        T: for<'a> Deserialize<'a>,
3009    {
3010        self.metadata
3011            .as_ref()
3012            .and_then(|m| serde_json::from_str(m).ok())
3013    }
3014
3015    /// 计算给定文本的哈希值,用于剪贴板变化检测。
3016    pub fn text_hash(text: &str) -> u64 {
3017        let mut hasher = SeaHasher::new();
3018        text.hash(&mut hasher);
3019        hasher.finish()
3020    }
3021}
3022
3023impl From<String> for ClipboardString {
3024    fn from(value: String) -> Self {
3025        Self {
3026            text: value,
3027            metadata: None,
3028        }
3029    }
3030}
3031
3032#[cfg(test)]
3033mod image_tests {
3034    use super::*;
3035    use std::sync::Arc;
3036
3037    #[test]
3038    fn test_svg_image_to_image_data_converts_to_bgra() {
3039        let image = Image::from_bytes(
3040            ImageFormat::Svg,
3041            br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
3042<rect width="1" height="1" fill="#38BDF8"/>
3043</svg>"##
3044                .to_vec(),
3045        );
3046
3047        let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3048        let bytes = render_image.as_bytes(0).unwrap();
3049
3050        for pixel in bytes.chunks_exact(4) {
3051            assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
3052        }
3053    }
3054}
3055
3056#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
3057mod tests {
3058    use super::*;
3059    use rgpui::collections::HashSet;
3060
3061    #[test]
3062    fn test_window_button_layout_parse_standard() {
3063        let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
3064        assert_eq!(
3065            layout.left,
3066            [
3067                Some(WindowButton::Close),
3068                Some(WindowButton::Minimize),
3069                None
3070            ]
3071        );
3072        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3073    }
3074
3075    #[test]
3076    fn test_window_button_layout_parse_right_only() {
3077        let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
3078        assert_eq!(layout.left, [None, None, None]);
3079        assert_eq!(
3080            layout.right,
3081            [
3082                Some(WindowButton::Minimize),
3083                Some(WindowButton::Maximize),
3084                Some(WindowButton::Close)
3085            ]
3086        );
3087    }
3088
3089    #[test]
3090    fn test_window_button_layout_parse_left_only() {
3091        let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
3092        assert_eq!(
3093            layout.left,
3094            [
3095                Some(WindowButton::Close),
3096                Some(WindowButton::Minimize),
3097                Some(WindowButton::Maximize)
3098            ]
3099        );
3100        assert_eq!(layout.right, [None, None, None]);
3101    }
3102
3103    #[test]
3104    fn test_window_button_layout_parse_with_whitespace() {
3105        let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
3106        assert_eq!(
3107            layout.left,
3108            [
3109                Some(WindowButton::Close),
3110                Some(WindowButton::Minimize),
3111                None
3112            ]
3113        );
3114        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3115    }
3116
3117    #[test]
3118    fn test_window_button_layout_parse_empty() {
3119        let layout = WindowButtonLayout::parse("").unwrap();
3120        assert_eq!(layout.left, [None, None, None]);
3121        assert_eq!(layout.right, [None, None, None]);
3122    }
3123
3124    #[test]
3125    fn test_window_button_layout_parse_intentionally_empty() {
3126        let layout = WindowButtonLayout::parse(":").unwrap();
3127        assert_eq!(layout.left, [None, None, None]);
3128        assert_eq!(layout.right, [None, None, None]);
3129    }
3130
3131    #[test]
3132    fn test_window_button_layout_parse_invalid_buttons() {
3133        let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
3134        assert_eq!(
3135            layout.left,
3136            [
3137                Some(WindowButton::Close),
3138                Some(WindowButton::Minimize),
3139                None
3140            ]
3141        );
3142        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3143    }
3144
3145    #[test]
3146    fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
3147        let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
3148        assert_eq!(
3149            layout.right,
3150            [
3151                Some(WindowButton::Close),
3152                Some(WindowButton::Minimize),
3153                None
3154            ]
3155        );
3156        assert_eq!(layout.format(), ":close,minimize");
3157    }
3158
3159    #[test]
3160    fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
3161        let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
3162        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3163        assert_eq!(
3164            layout.right,
3165            [
3166                Some(WindowButton::Maximize),
3167                Some(WindowButton::Minimize),
3168                None
3169            ]
3170        );
3171
3172        let button_ids: Vec<_> = layout
3173            .left
3174            .iter()
3175            .chain(layout.right.iter())
3176            .flatten()
3177            .map(WindowButton::id)
3178            .collect();
3179        let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
3180        assert_eq!(unique_button_ids.len(), button_ids.len());
3181        assert_eq!(layout.format(), "close:maximize,minimize");
3182    }
3183
3184    #[test]
3185    fn test_window_button_layout_parse_gnome_style() {
3186        let layout = WindowButtonLayout::parse("close").unwrap();
3187        assert_eq!(layout.left, [None, None, None]);
3188        assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
3189    }
3190
3191    #[test]
3192    fn test_window_button_layout_parse_elementary_style() {
3193        let layout = WindowButtonLayout::parse("close:maximize").unwrap();
3194        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3195        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3196    }
3197
3198    #[test]
3199    fn test_window_button_layout_round_trip() {
3200        let cases = [
3201            "close:minimize,maximize",
3202            "minimize,maximize,close:",
3203            ":close",
3204            "close:",
3205            "close:maximize",
3206            ":",
3207        ];
3208
3209        for case in cases {
3210            let layout = WindowButtonLayout::parse(case).unwrap();
3211            assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
3212        }
3213    }
3214
3215    #[test]
3216    fn test_window_button_layout_linux_default() {
3217        let layout = WindowButtonLayout::linux_default();
3218        assert_eq!(layout.left, [None, None, None]);
3219        assert_eq!(
3220            layout.right,
3221            [
3222                Some(WindowButton::Minimize),
3223                Some(WindowButton::Maximize),
3224                Some(WindowButton::Close)
3225            ]
3226        );
3227
3228        let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
3229        assert_eq!(round_tripped, layout);
3230    }
3231
3232    #[test]
3233    fn test_window_button_layout_parse_all_invalid() {
3234        assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
3235    }
3236}