1#![doc = include_str!("../README.md")]
2#![cfg(target_os = "macos")]
3#![allow(unused_mut)] mod mac;
6
7pub(crate) use gpui::*;
8pub(crate) use mac::*;
9
10use std::rc::Rc;
11
12pub fn current_platform(headless: bool) -> Rc<dyn gpui::Platform> {
14 Rc::new(mac::MacPlatform::new(headless))
15}
16
17#[cfg(all(test, feature = "font-kit"))]
18mod tests {
19 use super::{MacTextSystem, current_platform};
20 use gpui::{TestApp, TextRun, VisualTestCapabilities, WindowTextSystem, black, font, px};
21 use std::sync::{
22 Arc,
23 atomic::{AtomicBool, Ordering},
24 };
25 use std::time::Duration;
26
27 #[test]
28 fn headless_mac_text_layout_produces_nonzero_metrics() {
29 let app = TestApp::with_platform_text_system(Arc::new(MacTextSystem::new()));
30 let text_system = app.text_system();
31 let family = text_system
32 .all_font_names()
33 .into_iter()
34 .find(|name| !name.starts_with('.'))
35 .unwrap_or_else(|| ".SystemUIFont".to_string());
36 let window_text_system = WindowTextSystem::new(text_system.clone());
37 let text = "Headless text";
38 let layout = window_text_system.layout_line(
39 text,
40 px(16.),
41 &[TextRun {
42 len: text.len(),
43 font: font(family),
44 color: black(),
45 background_color: None,
46 underline: None,
47 strikethrough: None,
48 }],
49 None,
50 );
51
52 assert!(layout.width > px(0.));
53 assert!(layout.ascent > px(0.));
54 assert!(layout.runs.iter().any(|run| !run.glyphs.is_empty()));
55 }
56
57 #[test]
58 fn background_color_boundaries_shape_consistently() {
59 use gpui::{FontRun, LineLayout, PlatformTextSystem};
60
61 let platform = Arc::new(MacTextSystem::new());
62 let line_app = TestApp::with_platform_text_system(platform.clone());
65 let text_app = TestApp::with_platform_text_system(platform.clone());
66 let line_shaper = WindowTextSystem::new(line_app.text_system().clone());
67 let text_shaper = WindowTextSystem::new(text_app.text_system().clone());
68 let (font, font_id) = platform
73 .all_font_names()
74 .into_iter()
75 .filter(|name| !name.starts_with('.'))
76 .chain([".SystemUIFont".to_string()])
77 .find_map(|family| {
78 let font = font(family);
79 platform.font_id(&font).ok().map(|id| (font, id))
80 })
81 .expect("no loadable system font");
82
83 let glyphs = |layout: &LineLayout| {
84 layout
85 .runs
86 .iter()
87 .flat_map(|run| {
88 run.glyphs.iter().map(move |glyph| {
89 (
90 run.font_id,
91 glyph.id,
92 glyph.index,
93 glyph.position,
94 glyph.is_emoji,
95 )
96 })
97 })
98 .collect::<Vec<_>>()
99 };
100
101 for (text, split) in [
102 ("fi", 1),
103 ("سلام", "س".len()),
104 ("e\u{301}x", "e\u{301}".len()),
105 ("e\u{301}x", 1),
106 ("中文🙂", "中".len()),
107 ] {
108 let plain = TextRun {
109 len: split,
110 font: font.clone(),
111 color: black(),
112 background_color: None,
113 underline: None,
114 strikethrough: None,
115 };
116 let mut highlighted = plain.clone();
117 highlighted.len = text.len() - split;
118 highlighted.background_color = Some(black());
119 let runs = [plain, highlighted];
120
121 let expected = platform.layout_line(
124 text,
125 px(16.),
126 &[
127 FontRun {
128 font_id,
129 len: split,
130 },
131 FontRun {
132 font_id,
133 len: text.len() - split,
134 },
135 ],
136 );
137 let line = line_shaper.shape_line(text.into(), px(16.), &runs, None);
138 let wrapped = text_shaper
139 .shape_text(text.into(), px(16.), &runs, None, None)
140 .unwrap();
141 assert_eq!(wrapped.len(), 1);
142 for layout in [&**line, wrapped[0].unwrapped_layout.as_ref()] {
143 assert_eq!(layout.len, text.len());
144 assert_eq!(layout.width, expected.width, "{text:?}");
145 assert_eq!(layout.ascent, expected.ascent);
146 assert_eq!(layout.descent, expected.descent);
147 assert!(layout.width > px(0.));
148 let actual = glyphs(layout);
149 assert!(!actual.is_empty());
150 assert_eq!(actual, glyphs(&expected), "{text:?} split at {split}");
151 assert!(actual.iter().all(|(_, _, index, _, _)| {
152 *index < text.len() && text.is_char_boundary(*index)
153 }));
154 }
155 }
156 }
157
158 #[test]
159 #[ignore]
160 fn real_visual_context_advance_clock_works() {
161 let capabilities = VisualTestCapabilities::detect();
162 if !capabilities.real_renderer || !capabilities.screenshot_capture {
163 eprintln!("skipping: real visual renderer is not available");
164 return;
165 }
166 let cx = gpui::RealVisualTestContext::with_platform(current_platform(false));
167 let completed = Arc::new(AtomicBool::new(false));
168 let timer = cx.executor().timer(Duration::from_secs(1));
169 let completed_task = completed.clone();
170 cx.spawn(async move {
171 timer.await;
172 completed_task.store(true, Ordering::SeqCst);
173 })
174 .detach();
175
176 cx.run_until_parked();
177 assert!(!completed.load(Ordering::SeqCst));
178
179 cx.advance_clock(Duration::from_secs(1));
180 cx.run_until_parked();
181 assert!(completed.load(Ordering::SeqCst));
182 }
183}