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    #[cfg(any(test, feature = "test-support"))]
1760    fn contains(&self, _key: &AtlasKey) -> bool {
1761        false
1762    }
1763}
1764
1765#[doc(hidden)]
1766pub struct AtlasTextureList<T> {
1767    pub textures: Vec<Option<T>>,
1768    pub free_list: Vec<usize>,
1769}
1770
1771impl<T> Default for AtlasTextureList<T> {
1772    fn default() -> Self {
1773        Self {
1774            textures: Vec::default(),
1775            free_list: Vec::default(),
1776        }
1777    }
1778}
1779
1780impl<T> ops::Index<usize> for AtlasTextureList<T> {
1781    type Output = Option<T>;
1782
1783    fn index(&self, index: usize) -> &Self::Output {
1784        &self.textures[index]
1785    }
1786}
1787
1788impl<T> AtlasTextureList<T> {
1789    #[allow(unused)]
1790    pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1791        self.free_list.clear();
1792        self.textures.drain(..)
1793    }
1794
1795    pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1796        self.textures.iter_mut().flatten()
1797    }
1798}
1799
1800/// 精灵图集中的一块瓦片,描述其在纹理中的位置和边距。
1801#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1802#[repr(C)]
1803pub struct AtlasTile {
1804    /// 该瓦片所属的纹理。
1805    pub texture_id: AtlasTextureId,
1806    /// 该瓦片在其纹理内的唯一 ID。
1807    pub tile_id: TileId,
1808    /// 瓦片内容周围的像素边距。
1809    pub padding: u32,
1810    /// 该瓦片在纹理中的边界区域。
1811    pub bounds: Bounds<DevicePixels>,
1812}
1813
1814/// 图集纹理的唯一标识符,包含索引和内容类型。
1815#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1816#[repr(C)]
1817pub struct AtlasTextureId {
1818    // 使用 u32 而非 usize 以兼容 Metal Shader Language
1819    /// 该纹理在图集中的索引。
1820    pub index: u32,
1821    /// 该纹理中存储的内容类型。
1822    pub kind: AtlasTextureKind,
1823}
1824
1825#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1826#[repr(C)]
1827/// 图集纹理的内容类型,决定颜色格式和渲染路径。
1828pub enum AtlasTextureKind {
1829    /// 单色(灰度字形)
1830    Monochrome,
1831    /// 多色(彩色图像、Emoji)
1832    Polychrome,
1833    /// 亚像素渲染(LCD 抗锯齿字形)
1834    Subpixel,
1835}
1836
1837/// 图集瓦片的唯一标识符,封装 etagere 分配器的序列化 ID。
1838#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1839#[repr(C)]
1840pub struct TileId(pub u32);
1841
1842impl From<etagere::AllocId> for TileId {
1843    fn from(id: etagere::AllocId) -> Self {
1844        Self(id.serialize())
1845    }
1846}
1847
1848impl From<TileId> for etagere::AllocId {
1849    fn from(id: TileId) -> Self {
1850        Self::deserialize(id.0)
1851    }
1852}
1853
1854/// 平台输入处理器,封装异步窗口上下文和文本输入回调,处理选区、标记文本等 IME 操作。
1855pub struct PlatformInputHandler {
1856    cx: AsyncWindowContext,
1857    handler: Box<dyn InputHandler>,
1858}
1859
1860impl PlatformInputHandler {
1861    /// 创建新的输入处理器。
1862    pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1863        Self { cx, handler }
1864    }
1865
1866    /// 获取当前选中的文本范围(UTF-16 偏移)。
1867    pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1868        self.cx
1869            .update(|window, cx| {
1870                self.handler
1871                    .selected_text_range(ignore_disabled_input, window, cx)
1872            })
1873            .ok()
1874            .flatten()
1875    }
1876
1877    /// 获取当前标记(未确认)文本的范围。
1878    pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1879        self.cx
1880            .update(|window, cx| self.handler.marked_text_range(window, cx))
1881            .ok()
1882            .flatten()
1883    }
1884
1885    /// 获取指定 UTF-16 范围内的文本内容。
1886    pub fn text_for_range(
1887        &mut self,
1888        range_utf16: Range<usize>,
1889        adjusted: &mut Option<Range<usize>>,
1890    ) -> Option<String> {
1891        self.cx
1892            .update(|window, cx| {
1893                self.handler
1894                    .text_for_range(range_utf16, adjusted, window, cx)
1895            })
1896            .ok()
1897            .flatten()
1898    }
1899
1900    /// 替换指定范围内的文本。
1901    pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1902        self.cx
1903            .update(|window, cx| {
1904                self.handler
1905                    .replace_text_in_range(replacement_range, text, window, cx);
1906            })
1907            .ok();
1908    }
1909
1910    /// 替换指定范围内的文本并设置标记(IME 组合文本)。
1911    pub fn replace_and_mark_text_in_range(
1912        &mut self,
1913        range_utf16: Option<Range<usize>>,
1914        new_text: &str,
1915        new_selected_range: Option<Range<usize>>,
1916    ) {
1917        self.cx
1918            .update(|window, cx| {
1919                self.handler.replace_and_mark_text_in_range(
1920                    range_utf16,
1921                    new_text,
1922                    new_selected_range,
1923                    window,
1924                    cx,
1925                )
1926            })
1927            .ok();
1928    }
1929
1930    /// 清除标记文本(确认输入)。
1931    pub fn unmark_text(&mut self) {
1932        self.cx
1933            .update(|window, cx| self.handler.unmark_text(window, cx))
1934            .ok();
1935    }
1936
1937    /// 获取指定 UTF-16 范围在屏幕上的边界矩形。
1938    pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1939        self.cx
1940            .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1941            .ok()
1942            .flatten()
1943    }
1944
1945    /// macOS: 是否启用长按弹出字符面板功能。
1946    pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1947        self.handler.apple_press_and_hold_enabled()
1948    }
1949
1950    /// 直接分发文本输入(绕过 IME 组合流程)。
1951    pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1952        self.handler.replace_text_in_range(None, input, window, cx);
1953    }
1954
1955    /// 计算 IME 候选框的屏幕位置(基于标记文本范围和选区位置)。
1956    pub fn compute_ime_candidate_bounds(
1957        marked_range: Option<Range<usize>>,
1958        selection: &UTF16Selection,
1959        mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1960    ) -> Option<Bounds<Pixels>> {
1961        if let Some(marked_range) = marked_range {
1962            // Default to the start of the marked (composing) range.
1963            let mut line_start = marked_range.start;
1964
1965            // Walk backward from the caret looking for a line break. A change in
1966            // the Y coordinate means we crossed into the previous visual line, so
1967            // the line start is one position after the break point.
1968            let caret = selection.range.end;
1969            if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1970                for i in (marked_range.start..caret).rev() {
1971                    if let Some(b) = bounds_for_range(i..i) {
1972                        if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
1973                            line_start = i + 1;
1974                            break;
1975                        }
1976                    }
1977                }
1978            }
1979            bounds_for_range(line_start..line_start)
1980        } else {
1981            // No active composition 鈥?use the selection endpoint.
1982            let offset = if selection.reversed {
1983                selection.range.start
1984            } else {
1985                selection.range.end
1986            };
1987            bounds_for_range(offset..offset)
1988        }
1989    }
1990
1991    /// 获取当前选中文本的边界框。
1992    pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1993        let marked_range = self.handler.marked_text_range(window, cx);
1994        let selection = self.handler.selected_text_range(true, window, cx)?;
1995        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1996            self.handler.bounds_for_range(range, window, cx)
1997        })
1998    }
1999
2000    /// 获取 IME 候选区域的边界框。
2001    pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
2002        let marked_range = self.marked_text_range();
2003        let selection = self.selected_text_range(true)?;
2004        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
2005            self.bounds_for_range(range)
2006        })
2007    }
2008
2009    /// 根据屏幕坐标返回最近的字符索引。
2010    #[allow(unused)]
2011    pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
2012        self.cx
2013            .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
2014            .ok()
2015            .flatten()
2016    }
2017
2018    /// 查询当前是否接受文本输入。
2019    pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
2020        self.handler.accepts_text_input(window, cx)
2021    }
2022
2023    /// 查询当前是否接受文本输入(异步版本)。
2024    pub fn query_accepts_text_input(&mut self) -> bool {
2025        self.cx
2026            .update(|window, cx| self.handler.accepts_text_input(window, cx))
2027            .unwrap_or(true)
2028    }
2029
2030    /// 参见 [`InputHandler::prefers_ime_for_printable_keys`]。
2031    ///
2032    /// 这不是对处理器简单的委托:当多按键绑定处于待处理状态时,无论处理器的偏好如何,
2033    /// 该函数都会返回 `false`,因为下一个可打印按键可能完成一个前缀已绕过 IME 的绑定。
2034    pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
2035        self.cx
2036            .update(|window, cx| {
2037                // 下一个可打印按键可能完成一个前缀已绕过 IME 的按键组合。
2038                !window.has_pending_keystrokes()
2039                    && self.handler.prefers_ime_for_printable_keys(window, cx)
2040            })
2041            .unwrap_or(false)
2042    }
2043}
2044
2045/// 表示文本缓冲区中的选区,以 UTF16 字符为单位。
2046/// 与 Range 不同,选区的头部可能在尾部之前。
2047#[derive(Debug)]
2048pub struct UTF16Selection {
2049    /// 该选区对应的文档中文本的范围(以 UTF16 字符为单位)。
2050    pub range: Range<usize>,
2051    /// 选区的头部是否在范围的起始位置(true)或结束位置(false)。
2052    pub reversed: bool,
2053}
2054
2055/// Zed 的平台 IME 系统文本输入处理接口。
2056/// 目前是 NSTextInputClient API 的 1:1 映射:
2057///
2058/// <https://developer.apple.com/documentation/appkit/nstextinputclient>
2059pub trait InputHandler: 'static {
2060    /// 获取用户当前选中文本的范围(如果有)。
2061    /// 对应 [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
2062    ///
2063    /// 返回值以 UTF-16 字符为单位,范围从 0 到文档长度。
2064    fn selected_text_range(
2065        &mut self,
2066        ignore_disabled_input: bool,
2067        window: &mut Window,
2068        cx: &mut App,
2069    ) -> Option<UTF16Selection>;
2070
2071    /// 获取当前标记(未确认)文本的范围(如果有)。
2072    /// 对应 [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
2073    ///
2074    /// 返回值以 UTF-16 字符为单位,范围从 0 到文档长度。
2075    fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
2076
2077    /// 获取给定文档范围内的文本(以 UTF-16 字符为单位)。
2078    /// 对应 [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
2079    ///
2080    /// range_utf16 以 UTF-16 字符为单位。
2081    fn text_for_range(
2082        &mut self,
2083        range_utf16: Range<usize>,
2084        adjusted_range: &mut Option<Range<usize>>,
2085        window: &mut Window,
2086        cx: &mut App,
2087    ) -> Option<String>;
2088
2089    /// 用给定文本替换文档中指定范围的文本。
2090    /// 对应 [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
2091    ///
2092    /// replacement_range 以 UTF-16 字符为单位。
2093    fn replace_text_in_range(
2094        &mut self,
2095        replacement_range: Option<Range<usize>>,
2096        text: &str,
2097        window: &mut Window,
2098        cx: &mut App,
2099    );
2100
2101    /// 用给定文本替换文档中指定范围的文本,
2102    /// 并将给定文本标记为 IME「组合」状态的一部分。
2103    /// 对应 [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
2104    ///
2105    /// range_utf16 以 UTF-16 字符为单位。
2106    /// new_selected_range 以 UTF-16 字符为单位。
2107    fn replace_and_mark_text_in_range(
2108        &mut self,
2109        range_utf16: Option<Range<usize>>,
2110        new_text: &str,
2111        new_selected_range: Option<Range<usize>>,
2112        window: &mut Window,
2113        cx: &mut App,
2114    );
2115
2116    /// 移除文档中的 IME「组合」状态。
2117    /// 对应 [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
2118    fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
2119
2120    /// 获取给定文档范围在屏幕坐标中的边界区域。
2121    /// 对应 [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
2122    ///
2123    /// 用于定位 IME 候选窗口。
2124    fn bounds_for_range(
2125        &mut self,
2126        range_utf16: Range<usize>,
2127        window: &mut Window,
2128        cx: &mut App,
2129    ) -> Option<Bounds<Pixels>>;
2130
2131    /// 获取给定点在 UTF16 字符中的字符偏移量。
2132    ///
2133    /// 对应 [characterIndexForPoint:](https://developer.apple.com/documentation/appkit/nstextinputclient/characterindex(for:))
2134    fn character_index_for_point(
2135        &mut self,
2136        point: Point<Pixels>,
2137        window: &mut Window,
2138        cx: &mut App,
2139    ) -> Option<usize>;
2140
2141    /// 允许输入上下文选择接收原始按键重复,而非将其发送到平台。
2142    /// TODO: 理想情况下应能通过 NSUserDefaults 设置 ApplePressAndHoldEnabled
2143    /// (iTerm 就是这样做的),但目前似乎不生效。
2144    fn apple_press_and_hold_enabled(&mut self) -> bool {
2145        true
2146    }
2147
2148    /// 返回此处理器是否接受要插入的文本输入。
2149    fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
2150        true
2151    }
2152
2153    /// 返回在非 ASCII 输入源(如日语、韩语、中文 IME)激活时,
2154    /// 可打印按键是否应在按键绑定匹配之前先路由到 IME。
2155    /// 这防止了 `jj` 等多击按键绑定拦截 IME 应该组合的按键。
2156    ///
2157    /// 默认为 `false`。编辑器根据是否期望字符输入来覆盖此值
2158    /// (例如 Vim 插入模式返回 `true`,正常模式返回 `false`)。
2159    /// 终端保持默认的 `false`,以便原始按键到达终端进程。
2160    fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
2161        false
2162    }
2163
2164    /// 设置输入中的选中文本范围。
2165    fn set_selected_text_range(
2166        &mut self,
2167        _range_utf16: Range<usize>,
2168        _window: &mut Window,
2169        _cx: &mut App,
2170    ) {
2171    }
2172
2173    /// 获取元素在屏幕坐标中的边界区域。
2174    fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
2175        None
2176    }
2177
2178    /// 获取文本的长度(以 UTF-16 字符为单位)。
2179    fn text_length_utf16(&mut self, _window: &mut Window, _cx: &mut App) -> Option<usize> {
2180        None
2181    }
2182}
2183
2184/// 创建窗口时可配置的变量
2185#[derive(Debug)]
2186pub struct WindowOptions {
2187    /// 指定窗口在屏幕坐标中的状态和边界。
2188    /// - `None`:继承边界。
2189    /// - `Some(WindowBounds)`:以对应的状态和恢复尺寸打开窗口。
2190    pub window_bounds: Option<WindowBounds>,
2191
2192    /// 窗口标题栏配置
2193    pub titlebar: Option<TitlebarOptions>,
2194
2195    /// 窗口创建时是否获取焦点
2196    pub focus: bool,
2197
2198    /// 窗口创建时是否显示
2199    pub show: bool,
2200
2201    /// 要创建的窗口类型
2202    pub kind: WindowKind,
2203
2204    /// 窗口是否可被用户拖拽移动
2205    pub is_movable: bool,
2206
2207    /// 窗口是否可被用户调整大小
2208    pub is_resizable: bool,
2209
2210    /// 窗口是否可被用户最小化
2211    pub is_minimizable: bool,
2212
2213    /// 在哪个显示器上创建窗口,若为 None,
2214    /// 则在主显示器上创建
2215    pub display_id: Option<DisplayId>,
2216
2217    /// 窗口背景外观。
2218    pub window_background: WindowBackgroundAppearance,
2219
2220    /// 窗口的应用标识符,桌面环境可用于将应用分组。
2221    pub app_id: Option<String>,
2222
2223    /// 窗口最小尺寸
2224    pub window_min_size: Option<Size<Pixels>>,
2225
2226    /// 使用客户端还是服务端装饰。仅 Wayland。
2227    /// 注意此设置可能被忽略。
2228    pub window_decorations: Option<WindowDecorations>,
2229
2230    /// 图标图片(仅 X11)
2231    pub icon: Option<Arc<image::RgbaImage>>,
2232
2233    /// 标签页组名称,允许在 macOS 10.12+ 上以原生标签页方式打开窗口。具有相同 tabbing identifier 的窗口将被分组在一起。
2234    pub tabbing_identifier: Option<String>,
2235
2236    /// macOS 专用:应用是否自行处理标题栏拖拽。当使用自定义标题栏时设置为 true,
2237    /// 使 AppKit 不拦截标题栏点击,由应用通过 `Window::start_window_move` 自行处理。
2238    pub app_owns_titlebar_drag: bool,
2239
2240    /// Windows/Linux:是否启用鼠标事件穿透(点击穿透到后面的窗口)。
2241    /// 用于桌面宠物、覆盖层等需要让鼠标点击穿透到下层窗口的场景。
2242    pub mouse_passthrough: bool,
2243}
2244
2245/// 创建窗口时的配置参数。
2246#[derive(Debug)]
2247pub struct WindowParams {
2248    /// 窗口初始位置和尺寸。
2249    pub bounds: Bounds<Pixels>,
2250
2251    /// 标题栏配置。
2252    pub titlebar: Option<TitlebarOptions>,
2253
2254    /// 窗口类型(普通窗口、覆盖层等)。
2255    pub kind: WindowKind,
2256
2257    /// 窗口是否可被用户拖拽移动。
2258    pub is_movable: bool,
2259
2260    /// 窗口是否可被用户调整大小。
2261    pub is_resizable: bool,
2262
2263    /// 窗口是否可被用户最小化。
2264    pub is_minimizable: bool,
2265
2266    /// 窗口打开后是否自动获取焦点。
2267    pub focus: bool,
2268
2269    /// 窗口打开后是否立即显示。
2270    pub show: bool,
2271
2272    /// 窗口图标(仅 X11 有效)。
2273    pub icon: Option<Arc<image::RgbaImage>>,
2274
2275    /// 指定显示在哪个显示器上(None 为默认)。
2276    pub display_id: Option<DisplayId>,
2277
2278    /// 应用标识符(主要用于 Wayland)。
2279    pub app_id: Option<String>,
2280
2281    /// 窗口最小尺寸限制。
2282    pub window_min_size: Option<Size<Pixels>>,
2283    /// macOS 标签页分组标识符,相同标识符的窗口可合并为标签页。
2284    #[cfg(target_os = "macos")]
2285    pub tabbing_identifier: Option<String>,
2286
2287    /// macOS only: 应用是否自行处理标题栏拖拽。
2288    /// 当使用自定义标题栏时设置为 true(macOS 专用,其他平台无效果)。
2289    pub app_owns_titlebar_drag: bool,
2290
2291    /// Windows/Linux: 是否启用鼠标事件穿透(点击穿透到后面的窗口)。
2292    /// 覆盖层窗口需要此选项让鼠标事件穿透到底层窗口。
2293    pub mouse_passthrough: bool,
2294}
2295
2296/// 表示窗口打开时应处于的状态
2297#[derive(Debug, Copy, Clone, PartialEq)]
2298pub enum WindowBounds {
2299    /// 表示窗口应以窗口化状态打开,使用给定的边界。
2300    Windowed(Bounds<Pixels>),
2301    /// 表示窗口应以最大化状态打开。
2302    /// 此处提供的边界表示窗口的恢复尺寸。
2303    Maximized(Bounds<Pixels>),
2304    /// 表示窗口应以全屏模式打开。
2305    /// 此处提供的边界表示窗口的恢复尺寸。
2306    Fullscreen(Bounds<Pixels>),
2307}
2308
2309impl Default for WindowBounds {
2310    fn default() -> Self {
2311        WindowBounds::Windowed(Bounds::default())
2312    }
2313}
2314
2315impl WindowBounds {
2316    /// 获取内部边界
2317    pub fn get_bounds(&self) -> Bounds<Pixels> {
2318        match self {
2319            WindowBounds::Windowed(bounds) => *bounds,
2320            WindowBounds::Maximized(bounds) => *bounds,
2321            WindowBounds::Fullscreen(bounds) => *bounds,
2322        }
2323    }
2324
2325    /// 创建一个新的窗口边界,使窗口在屏幕上居中。
2326    pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
2327        WindowBounds::Windowed(Bounds::centered(None, size, cx))
2328    }
2329}
2330
2331impl Default for WindowOptions {
2332    fn default() -> Self {
2333        Self {
2334            window_bounds: None,
2335            titlebar: Some(TitlebarOptions {
2336                title: Default::default(),
2337                appears_transparent: Default::default(),
2338                traffic_light_position: Default::default(),
2339            }),
2340            focus: true,
2341            show: true,
2342            kind: WindowKind::Normal,
2343            is_movable: true,
2344            is_resizable: true,
2345            is_minimizable: true,
2346            display_id: None,
2347            window_background: WindowBackgroundAppearance::default(),
2348            icon: None,
2349            app_id: None,
2350            window_min_size: None,
2351            window_decorations: None,
2352            tabbing_identifier: None,
2353            app_owns_titlebar_drag: false,
2354            mouse_passthrough: false,
2355        }
2356    }
2357}
2358
2359/// 窗口标题栏可配置的选项
2360#[derive(Debug, Default)]
2361pub struct TitlebarOptions {
2362    /// 窗口的初始标题
2363    pub title: Option<SharedString>,
2364
2365    /// 是否隐藏默认系统标题栏以使用自定义绘制的标题栏?(仅 macOS 和 Windows)
2366    /// Linux 上请参见 [`WindowOptions::window_decorations`]
2367    pub appears_transparent: bool,
2368
2369    /// macOS 红绿灯按钮的位置
2370    pub traffic_light_position: Option<Point<Pixels>>,
2371}
2372
2373/// 要创建的窗口类型
2374#[derive(Clone, Debug, PartialEq, Eq)]
2375pub enum WindowKind {
2376    /// 普通应用窗口
2377    Normal,
2378
2379    /// 出现在所有其他窗口上方的窗口,通常用于警告或弹出窗口。
2380    /// 应谨慎使用!
2381    PopUp,
2382
2383    /// 父窗口锚定的原生弹出窗口,用于菜单、组合框、上下文菜单和工具提示。
2384    /// 与 [`WindowKind::PopUp`] 不同,它相对于父窗口定位。
2385    ///
2386    /// 弹出窗口的大小来自 [`WindowOptions::window_bounds`],其原点被忽略。
2387    /// 参见 [`popup::PopupOptions`] 了解放置选项。没有原生实现的平台
2388    /// 会以 [`popup::PopupNotSupportedError`] 拒绝。
2389    AnchoredPopup(popup::PopupOptions),
2390
2391    /// 出现在父窗口上方的浮动窗口
2392    Floating,
2393
2394    /// Wayland LayerShell 窗口,用于为应用绘制覆盖层或背景,
2395    /// 如 Dock、通知或壁纸。
2396    #[cfg(all(target_os = "linux", feature = "wayland"))]
2397    LayerShell(layer_shell::LayerShellOptions),
2398
2399    /// 出现在父窗口上方的模态窗口,阻止与父窗口的交互,
2400    /// 直到模态窗口关闭
2401    Dialog,
2402
2403    /// 覆盖层窗口:始终置顶、无边框、支持透明度
2404    Overlay,
2405}
2406
2407/// 窗口的外观,由操作系统定义。
2408///
2409/// 在 macOS 上,这对应于命名的 [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
2410/// 值。
2411#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2412pub enum WindowAppearance {
2413    /// 亮色外观。
2414    ///
2415    /// 在 macOS 上,这对应于 `aqua` 外观。
2416    #[default]
2417    Light,
2418
2419    /// 带有鲜艳颜色的亮色外观。
2420    ///
2421    /// 在 macOS 上,这对应于 `NSAppearanceNameVibrantLight` 外观。
2422    VibrantLight,
2423
2424    /// 暗色外观。
2425    ///
2426    /// 在 macOS 上,这对应于 `darkAqua` 外观。
2427    Dark,
2428
2429    /// 带有鲜艳颜色的暗色外观。
2430    ///
2431    /// 在 macOS 上,这对应于 `NSAppearanceNameVibrantDark` 外观。
2432    VibrantDark,
2433}
2434
2435/// 窗口本身的背景外观,在没有内容或内容透明时显示。
2436#[derive(Copy, Clone, Debug, Default, PartialEq)]
2437pub enum WindowBackgroundAppearance {
2438    /// 不透明。
2439    ///
2440    /// 告诉窗口管理器此窗口背后的内容不需要绘制。
2441    ///
2442    /// 实际颜色取决于系统,主题应定义完全不透明的背景色。
2443    #[default]
2444    Opaque,
2445    /// 纯 Alpha 透明。
2446    Transparent,
2447    /// 透明,但窗口背后的内容会被模糊。
2448    ///
2449    /// 并非总是支持。
2450    Blurred,
2451    /// Mica 背景材质,Windows 11 支持。
2452    MicaBackdrop,
2453    /// Mica Alt 背景材质,Windows 11 支持。
2454    MicaAltBackdrop,
2455}
2456
2457/// 绘制字形时使用的文本渲染模式。
2458#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2459pub enum TextRenderingMode {
2460    /// 使用平台默认的文本渲染模式。
2461    #[default]
2462    PlatformDefault,
2463    /// 使用亚像素(ClearType 风格)文本渲染。
2464    Subpixel,
2465    /// 使用灰度文本渲染。
2466    Grayscale,
2467}
2468
2469/// 文件对话框提示可配置的选项
2470#[derive(Clone, Debug)]
2471pub struct PathPromptOptions {
2472    /// 提示是否允许选择文件?
2473    pub files: bool,
2474    /// 提示是否允许选择目录?
2475    pub directories: bool,
2476    /// 提示是否允许多选文件?
2477    pub multiple: bool,
2478    /// 选择路径时显示给用户的提示文本
2479    pub prompt: Option<SharedString>,
2480}
2481
2482/// 提示样式类型
2483#[derive(Copy, Clone, Debug, PartialEq)]
2484pub enum PromptLevel {
2485    /// 通知用户的提示
2486    Info,
2487
2488    /// 警告用户潜在问题的提示
2489    Warning,
2490
2491    /// 发生严重问题时的提示
2492    Critical,
2493}
2494
2495/// 提示对话框按钮
2496#[derive(Clone, Debug, PartialEq)]
2497pub enum PromptButton {
2498    /// 确认按钮
2499    Ok(SharedString),
2500    /// 取消按钮
2501    Cancel(SharedString),
2502    /// 其他按钮
2503    Other(SharedString),
2504}
2505
2506impl PromptButton {
2507    /// 创建带标签的按钮
2508    pub fn new(label: impl Into<SharedString>) -> Self {
2509        PromptButton::Other(label.into())
2510    }
2511
2512    /// 创建确认按钮
2513    pub fn ok(label: impl Into<SharedString>) -> Self {
2514        PromptButton::Ok(label.into())
2515    }
2516
2517    /// 创建取消按钮
2518    pub fn cancel(label: impl Into<SharedString>) -> Self {
2519        PromptButton::Cancel(label.into())
2520    }
2521
2522    /// 返回此按钮是否为取消按钮。
2523    pub fn is_cancel(&self) -> bool {
2524        matches!(self, PromptButton::Cancel(_))
2525    }
2526
2527    /// 返回按钮的标签文本
2528    pub fn label(&self) -> &SharedString {
2529        match self {
2530            PromptButton::Ok(label) => label,
2531            PromptButton::Cancel(label) => label,
2532            PromptButton::Other(label) => label,
2533        }
2534    }
2535}
2536
2537impl From<&str> for PromptButton {
2538    fn from(value: &str) -> Self {
2539        match value.to_lowercase().as_str() {
2540            "ok" => PromptButton::Ok("OK".into()),
2541            "cancel" => PromptButton::Cancel("Cancel".into()),
2542            _ => PromptButton::Other(SharedString::from(value.to_owned())),
2543        }
2544    }
2545}
2546
2547/// 光标(指针)样式
2548#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2549pub enum CursorStyle {
2550    /// 默认光标
2551    #[default]
2552    Arrow,
2553
2554    /// 文本输入光标
2555    /// 对应 CSS cursor 值 `text`
2556    IBeam,
2557
2558    /// 十字光标
2559    /// 对应 CSS cursor 值 `crosshair`
2560    Crosshair,
2561
2562    /// 闭合手型光标
2563    /// 对应 CSS cursor 值 `grabbing`
2564    ClosedHand,
2565
2566    /// 张开手型光标
2567    /// 对应 CSS cursor 值 `grab`
2568    OpenHand,
2569
2570    /// 指向手型光标
2571    /// 对应 CSS cursor 值 `pointer`
2572    PointingHand,
2573
2574    /// 向左调整大小光标
2575    /// 对应 CSS cursor 值 `w-resize`
2576    ResizeLeft,
2577
2578    /// 向右调整大小光标
2579    /// 对应 CSS cursor 值 `e-resize`
2580    ResizeRight,
2581
2582    /// 左右调整大小光标
2583    /// 对应 CSS cursor 值 `ew-resize`
2584    ResizeLeftRight,
2585
2586    /// 向上调整大小光标
2587    /// 对应 CSS cursor 值 `n-resize`
2588    ResizeUp,
2589
2590    /// 向下调整大小光标
2591    /// 对应 CSS cursor 值 `s-resize`
2592    ResizeDown,
2593
2594    /// 上下调整大小光标
2595    /// 对应 CSS cursor 值 `ns-resize`
2596    ResizeUpDown,
2597
2598    /// 向左上和右下调整大小光标
2599    /// 对应 CSS cursor 值 `nesw-resize`
2600    ResizeUpLeftDownRight,
2601
2602    /// 向右上和左下调整大小光标
2603    /// 对应 CSS cursor 值 `nwse-resize`
2604    ResizeUpRightDownLeft,
2605
2606    /// 表示可以水平调整大小的光标
2607    /// 对应 CSS cursor 值 `col-resize`
2608    ResizeColumn,
2609
2610    /// 表示可以垂直调整大小的光标
2611    /// 对应 CSS cursor 值 `row-resize`
2612    ResizeRow,
2613
2614    /// 垂直布局的文本输入光标
2615    /// 对应 CSS cursor 值 `vertical-text`
2616    IBeamCursorForVerticalLayout,
2617
2618    /// 表示操作不允许的光标
2619    /// 对应 CSS cursor 值 `not-allowed`
2620    OperationNotAllowed,
2621
2622    /// 表示操作将产生链接的光标
2623    /// 对应 CSS cursor 值 `alias`
2624    DragLink,
2625
2626    /// 表示操作将产生副本的光标
2627    /// 对应 CSS cursor 值 `copy`
2628    DragCopy,
2629
2630    /// 表示操作将产生上下文菜单的光标
2631    /// 对应 CSS cursor 值 `context-menu`
2632    ContextualMenu,
2633}
2634
2635/// 应复制到剪贴板的剪贴板项目
2636#[derive(Clone, Debug, Eq, PartialEq)]
2637pub struct ClipboardItem {
2638    /// 此剪贴板项目的条目。
2639    pub entries: Vec<ClipboardEntry>,
2640}
2641
2642/// 剪贴板字符串或剪贴板图像
2643#[derive(Clone, Debug, Eq, PartialEq)]
2644pub enum ClipboardEntry {
2645    /// 字符串条目
2646    String(ClipboardString),
2647    /// 图像条目
2648    Image(Image),
2649    /// 文件条目
2650    ExternalPaths(crate::ExternalPaths),
2651}
2652
2653impl ClipboardItem {
2654    /// 创建一个不带关联元数据的新 ClipboardItem::String
2655    pub fn new_string(text: String) -> Self {
2656        Self {
2657            entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2658        }
2659    }
2660
2661    /// 创建一个带有关联元数据的新 ClipboardItem::String
2662    pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2663        Self {
2664            entries: vec![ClipboardEntry::String(ClipboardString {
2665                text,
2666                metadata: Some(metadata),
2667            })],
2668        }
2669    }
2670
2671    /// 创建一个带有关联元数据(JSON 序列化)的新 ClipboardItem::String
2672    pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2673        Self {
2674            entries: vec![ClipboardEntry::String(
2675                ClipboardString::new(text).with_json_metadata(metadata),
2676            )],
2677        }
2678    }
2679
2680    /// 创建一个不带关联元数据的新 ClipboardItem::Image
2681    pub fn new_image(image: &Image) -> Self {
2682        Self {
2683            entries: vec![ClipboardEntry::Image(image.clone())],
2684        }
2685    }
2686
2687    /// 连接项目中所有 ClipboardString 条目的文本。
2688    /// 如果没有 ClipboardString 条目则返回 None。
2689    pub fn text(&self) -> Option<String> {
2690        let mut answer = String::new();
2691
2692        for entry in self.entries.iter() {
2693            if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2694                answer.push_str(text);
2695            }
2696        }
2697
2698        if answer.is_empty() {
2699            for entry in self.entries.iter() {
2700                if let ClipboardEntry::ExternalPaths(paths) = entry {
2701                    for path in &paths.0 {
2702                        use std::fmt::Write as _;
2703                        _ = write!(answer, "{}", path.display());
2704                    }
2705                }
2706            }
2707        }
2708
2709        if !answer.is_empty() {
2710            Some(answer)
2711        } else {
2712            None
2713        }
2714    }
2715
2716    /// 如果此项目是单个 ClipboardEntry::String,返回其元数据。
2717    pub fn metadata(&self) -> Option<&String> {
2718        match self.entries().first() {
2719            Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2720                clipboard_string.metadata.as_ref()
2721            }
2722            _ => None,
2723        }
2724    }
2725
2726    /// 获取项目的条目
2727    pub fn entries(&self) -> &[ClipboardEntry] {
2728        &self.entries
2729    }
2730
2731    /// 获取项目条目的所有权版本
2732    pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2733        self.entries.into_iter()
2734    }
2735}
2736
2737impl From<ClipboardString> for ClipboardEntry {
2738    fn from(value: ClipboardString) -> Self {
2739        Self::String(value)
2740    }
2741}
2742
2743impl From<String> for ClipboardEntry {
2744    fn from(value: String) -> Self {
2745        Self::from(ClipboardString::from(value))
2746    }
2747}
2748
2749impl From<Image> for ClipboardEntry {
2750    fn from(value: Image) -> Self {
2751        Self::Image(value)
2752    }
2753}
2754
2755impl From<ClipboardEntry> for ClipboardItem {
2756    fn from(value: ClipboardEntry) -> Self {
2757        Self {
2758            entries: vec![value],
2759        }
2760    }
2761}
2762
2763impl From<String> for ClipboardItem {
2764    fn from(value: String) -> Self {
2765        Self::from(ClipboardEntry::from(value))
2766    }
2767}
2768
2769impl From<Image> for ClipboardItem {
2770    fn from(value: Image) -> Self {
2771        Self::from(ClipboardEntry::from(value))
2772    }
2773}
2774
2775/// 编辑器支持的图像格式之一(如 PNG、JPEG)- 用于处理剪贴板中的图像
2776#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2777pub enum ImageFormat {
2778    // 按粘贴到编辑器的可能性从高到低排序,
2779    // 在遍历检查剪贴板内容是否匹配时这很重要。
2780    /// .png
2781    Png,
2782    /// .jpeg 或 .jpg
2783    Jpeg,
2784    /// .webp
2785    Webp,
2786    /// .gif
2787    Gif,
2788    /// .svg
2789    Svg,
2790    /// .bmp
2791    Bmp,
2792    /// .tif 或 .tiff
2793    Tiff,
2794    /// .ico
2795    Ico,
2796    /// Netpbm 图像格式(.pbm、.ppm、.pgm)。
2797    Pnm,
2798}
2799
2800impl ImageFormat {
2801    /// 返回 ImageFormat 的 MIME 类型
2802    pub const fn mime_type(self) -> &'static str {
2803        match self {
2804            ImageFormat::Png => "image/png",
2805            ImageFormat::Jpeg => "image/jpeg",
2806            ImageFormat::Webp => "image/webp",
2807            ImageFormat::Gif => "image/gif",
2808            ImageFormat::Svg => "image/svg+xml",
2809            ImageFormat::Bmp => "image/bmp",
2810            ImageFormat::Tiff => "image/tiff",
2811            ImageFormat::Ico => "image/ico",
2812            ImageFormat::Pnm => "image/x-portable-anymap",
2813        }
2814    }
2815
2816    /// 根据 MIME 类型返回对应的 ImageFormat,包括已知别名。
2817    pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2818        use strum::IntoEnumIterator;
2819        Self::iter()
2820            .find(|format| format.mime_type() == mime_type)
2821            .or_else(|| Self::from_mime_type_alias(mime_type))
2822    }
2823
2824    /// 非规范的 MIME 类型,一些生产者在实际使用中使用。
2825    /// 不同于返回单一规范形式的 `mime_type()`,
2826    /// 这些是我们仍需识别的遗留或缩写变体。
2827    fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
2828        match mime_type {
2829            "image/jpg" => Some(Self::Jpeg),
2830            "image/tif" => Some(Self::Tiff),
2831            _ => None,
2832        }
2833    }
2834}
2835
2836/// 图像,包含格式和字节数据
2837#[derive(Clone, Debug, PartialEq, Eq)]
2838pub struct Image {
2839    /// 字节数据表示的图像格式(如 PNG)
2840    pub format: ImageFormat,
2841    /// 原始图像字节
2842    pub bytes: Vec<u8>,
2843    /// 图像的唯一 ID
2844    pub id: u64,
2845}
2846
2847impl Hash for Image {
2848    fn hash<H: Hasher>(&self, state: &mut H) {
2849        state.write_u64(self.id);
2850    }
2851}
2852
2853impl Image {
2854    /// 一个不包含数据的空图像
2855    pub fn empty() -> Self {
2856        Self::from_bytes(ImageFormat::Png, Vec::new())
2857    }
2858
2859    /// 从格式和字节数据创建图像
2860    pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2861        Self {
2862            id: hash(&bytes),
2863            format,
2864            bytes,
2865        }
2866    }
2867
2868    /// 获取图像的 ID
2869    pub fn id(&self) -> u64 {
2870        self.id
2871    }
2872
2873    /// 使用 RGPUI `use_asset` API 使此图像可渲染
2874    pub fn use_render_image(
2875        self: Arc<Self>,
2876        window: &mut Window,
2877        cx: &mut App,
2878    ) -> Option<Arc<RenderImage>> {
2879        ImageSource::Image(self)
2880            .use_data(None, window, cx)
2881            .and_then(|result| result.ok())
2882    }
2883
2884    /// 使用 RGPUI `get_asset` API 使此图像可渲染
2885    pub fn get_render_image(
2886        self: Arc<Self>,
2887        window: &mut Window,
2888        cx: &mut App,
2889    ) -> Option<Arc<RenderImage>> {
2890        ImageSource::Image(self)
2891            .get_data(None, window, cx)
2892            .and_then(|result| result.ok())
2893    }
2894
2895    /// 使用 RGPUI `remove_asset` API 移除此图像(如果可能)。
2896    pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2897        ImageSource::Image(self).remove_asset(cx);
2898    }
2899
2900    /// 将剪贴板图像转换为 `ImageData` 对象。
2901    pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2902        fn frames_for_image(
2903            bytes: &[u8],
2904            format: image::ImageFormat,
2905        ) -> Result<SmallVec<[Frame; 1]>> {
2906            let mut data = image::load_from_memory_with_format(bytes, format)?.into_rgba8();
2907
2908            // Convert from RGBA to BGRA.
2909            for pixel in data.chunks_exact_mut(4) {
2910                pixel.swap(0, 2);
2911            }
2912
2913            Ok(SmallVec::from_elem(Frame::new(data), 1))
2914        }
2915
2916        let frames = match self.format {
2917            ImageFormat::Gif => {
2918                let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2919                let mut frames = SmallVec::new();
2920
2921                for frame in decoder.into_frames() {
2922                    match frame {
2923                        Ok(mut frame) => {
2924                            // Convert from RGBA to BGRA.
2925                            for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2926                                pixel.swap(0, 2);
2927                            }
2928                            frames.push(frame);
2929                        }
2930                        Err(err) => {
2931                            log::debug!("Skipping GIF frame due to decode error: {err}");
2932                        }
2933                    }
2934                }
2935
2936                if frames.is_empty() {
2937                    anyhow::bail!("GIF could not be decoded: all frames failed");
2938                }
2939
2940                frames
2941            }
2942            ImageFormat::Png => frames_for_image(&self.bytes, image::ImageFormat::Png)?,
2943            ImageFormat::Jpeg => frames_for_image(&self.bytes, image::ImageFormat::Jpeg)?,
2944            ImageFormat::Webp => frames_for_image(&self.bytes, image::ImageFormat::WebP)?,
2945            ImageFormat::Bmp => frames_for_image(&self.bytes, image::ImageFormat::Bmp)?,
2946            ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?,
2947            ImageFormat::Ico => frames_for_image(&self.bytes, image::ImageFormat::Ico)?,
2948            ImageFormat::Svg => {
2949                return svg_renderer
2950                    .render_single_frame(&self.bytes, 1.0)
2951                    .map_err(Into::into);
2952            }
2953            ImageFormat::Pnm => frames_for_image(&self.bytes, image::ImageFormat::Pnm)?,
2954        };
2955
2956        Ok(Arc::new(RenderImage::new(frames)))
2957    }
2958
2959    /// 获取剪贴板图像的格式
2960    pub fn format(&self) -> ImageFormat {
2961        self.format
2962    }
2963
2964    /// 获取剪贴板图像的原始字节
2965    pub fn bytes(&self) -> &[u8] {
2966        self.bytes.as_slice()
2967    }
2968}
2969
2970/// 应复制到剪贴板的剪贴板字符串项目
2971#[derive(Clone, Debug, Eq, PartialEq)]
2972pub struct ClipboardString {
2973    /// 文本内容。
2974    pub text: String,
2975    /// 关联的可选元数据。
2976    pub metadata: Option<String>,
2977}
2978
2979impl ClipboardString {
2980    /// 创建一个新的剪贴板字符串
2981    pub fn new(text: String) -> Self {
2982        Self {
2983            text,
2984            metadata: None,
2985        }
2986    }
2987
2988    /// 返回一个新的剪贴板项目,其元数据通过 JSON 序列化后替换为给定值。
2989    pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2990        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2991        self
2992    }
2993
2994    /// 获取剪贴板字符串的文本
2995    pub fn text(&self) -> &String {
2996        &self.text
2997    }
2998
2999    /// 获取剪贴板字符串的所有权文本
3000    pub fn into_text(self) -> String {
3001        self.text
3002    }
3003
3004    /// 获取剪贴板字符串的元数据(JSON 格式)
3005    pub fn metadata_json<T>(&self) -> Option<T>
3006    where
3007        T: for<'a> Deserialize<'a>,
3008    {
3009        self.metadata
3010            .as_ref()
3011            .and_then(|m| serde_json::from_str(m).ok())
3012    }
3013
3014    /// 计算给定文本的哈希值,用于剪贴板变化检测。
3015    pub fn text_hash(text: &str) -> u64 {
3016        let mut hasher = SeaHasher::new();
3017        text.hash(&mut hasher);
3018        hasher.finish()
3019    }
3020}
3021
3022impl From<String> for ClipboardString {
3023    fn from(value: String) -> Self {
3024        Self {
3025            text: value,
3026            metadata: None,
3027        }
3028    }
3029}
3030
3031#[cfg(test)]
3032mod image_tests {
3033    use super::*;
3034    use std::sync::Arc;
3035
3036    #[test]
3037    fn test_svg_image_to_image_data_converts_to_bgra() {
3038        let image = Image::from_bytes(
3039            ImageFormat::Svg,
3040            br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
3041<rect width="1" height="1" fill="#38BDF8"/>
3042</svg>"##
3043                .to_vec(),
3044        );
3045
3046        let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3047        let bytes = render_image.as_bytes(0).unwrap();
3048
3049        for pixel in bytes.chunks_exact(4) {
3050            assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
3051        }
3052    }
3053}
3054
3055#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
3056mod tests {
3057    use super::*;
3058    use rgpui::collections::HashSet;
3059
3060    #[test]
3061    fn test_window_button_layout_parse_standard() {
3062        let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
3063        assert_eq!(
3064            layout.left,
3065            [
3066                Some(WindowButton::Close),
3067                Some(WindowButton::Minimize),
3068                None
3069            ]
3070        );
3071        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3072    }
3073
3074    #[test]
3075    fn test_window_button_layout_parse_right_only() {
3076        let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
3077        assert_eq!(layout.left, [None, None, None]);
3078        assert_eq!(
3079            layout.right,
3080            [
3081                Some(WindowButton::Minimize),
3082                Some(WindowButton::Maximize),
3083                Some(WindowButton::Close)
3084            ]
3085        );
3086    }
3087
3088    #[test]
3089    fn test_window_button_layout_parse_left_only() {
3090        let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
3091        assert_eq!(
3092            layout.left,
3093            [
3094                Some(WindowButton::Close),
3095                Some(WindowButton::Minimize),
3096                Some(WindowButton::Maximize)
3097            ]
3098        );
3099        assert_eq!(layout.right, [None, None, None]);
3100    }
3101
3102    #[test]
3103    fn test_window_button_layout_parse_with_whitespace() {
3104        let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
3105        assert_eq!(
3106            layout.left,
3107            [
3108                Some(WindowButton::Close),
3109                Some(WindowButton::Minimize),
3110                None
3111            ]
3112        );
3113        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3114    }
3115
3116    #[test]
3117    fn test_window_button_layout_parse_empty() {
3118        let layout = WindowButtonLayout::parse("").unwrap();
3119        assert_eq!(layout.left, [None, None, None]);
3120        assert_eq!(layout.right, [None, None, None]);
3121    }
3122
3123    #[test]
3124    fn test_window_button_layout_parse_intentionally_empty() {
3125        let layout = WindowButtonLayout::parse(":").unwrap();
3126        assert_eq!(layout.left, [None, None, None]);
3127        assert_eq!(layout.right, [None, None, None]);
3128    }
3129
3130    #[test]
3131    fn test_window_button_layout_parse_invalid_buttons() {
3132        let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
3133        assert_eq!(
3134            layout.left,
3135            [
3136                Some(WindowButton::Close),
3137                Some(WindowButton::Minimize),
3138                None
3139            ]
3140        );
3141        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3142    }
3143
3144    #[test]
3145    fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
3146        let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
3147        assert_eq!(
3148            layout.right,
3149            [
3150                Some(WindowButton::Close),
3151                Some(WindowButton::Minimize),
3152                None
3153            ]
3154        );
3155        assert_eq!(layout.format(), ":close,minimize");
3156    }
3157
3158    #[test]
3159    fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
3160        let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
3161        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3162        assert_eq!(
3163            layout.right,
3164            [
3165                Some(WindowButton::Maximize),
3166                Some(WindowButton::Minimize),
3167                None
3168            ]
3169        );
3170
3171        let button_ids: Vec<_> = layout
3172            .left
3173            .iter()
3174            .chain(layout.right.iter())
3175            .flatten()
3176            .map(WindowButton::id)
3177            .collect();
3178        let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
3179        assert_eq!(unique_button_ids.len(), button_ids.len());
3180        assert_eq!(layout.format(), "close:maximize,minimize");
3181    }
3182
3183    #[test]
3184    fn test_window_button_layout_parse_gnome_style() {
3185        let layout = WindowButtonLayout::parse("close").unwrap();
3186        assert_eq!(layout.left, [None, None, None]);
3187        assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
3188    }
3189
3190    #[test]
3191    fn test_window_button_layout_parse_elementary_style() {
3192        let layout = WindowButtonLayout::parse("close:maximize").unwrap();
3193        assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3194        assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3195    }
3196
3197    #[test]
3198    fn test_window_button_layout_round_trip() {
3199        let cases = [
3200            "close:minimize,maximize",
3201            "minimize,maximize,close:",
3202            ":close",
3203            "close:",
3204            "close:maximize",
3205            ":",
3206        ];
3207
3208        for case in cases {
3209            let layout = WindowButtonLayout::parse(case).unwrap();
3210            assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
3211        }
3212    }
3213
3214    #[test]
3215    fn test_window_button_layout_linux_default() {
3216        let layout = WindowButtonLayout::linux_default();
3217        assert_eq!(layout.left, [None, None, None]);
3218        assert_eq!(
3219            layout.right,
3220            [
3221                Some(WindowButton::Minimize),
3222                Some(WindowButton::Maximize),
3223                Some(WindowButton::Close)
3224            ]
3225        );
3226
3227        let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
3228        assert_eq!(round_tripped, layout);
3229    }
3230
3231    #[test]
3232    fn test_window_button_layout_parse_all_invalid() {
3233        assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
3234    }
3235}