Compare commits

...
Sign in to create a new pull request.

20 commits

Author SHA1 Message Date
Piotr Osiewicz
62949d20a7 chore: fmt 2023-08-31 15:43:50 +02:00
Piotr Osiewicz
110bb84261 Merge branch 'main' into gpui_extract_platform 2023-08-31 15:43:32 +02:00
Piotr Osiewicz
576849c98a WIP. Fix up some more test scenarios 2023-08-26 23:16:58 +02:00
Piotr Osiewicz
f16105f391 fix up platform namespace in test macro 2023-08-26 23:00:13 +02:00
Piotr Osiewicz
ac329e40dc Allow dead code for is_empty fn 2023-08-26 22:42:55 +02:00
Piotr Osiewicz
a9db466e67 chore: fmt 2023-08-26 22:40:11 +02:00
Piotr Osiewicz
6f32431d33 Merge branch 'main' into gpui_extract_platform 2023-08-26 22:39:57 +02:00
Piotr Osiewicz
8342803ba5 WIP test migration 2023-08-26 00:13:57 +02:00
Piotr Osiewicz
d033475565 Fix playground build 2023-08-25 23:43:45 +02:00
Piotr Osiewicz
2a350e91b2 chore: fmt 2023-08-25 23:29:28 +02:00
Piotr Osiewicz
fe17505100 Use gpui_platform in Zed 2023-08-25 23:29:12 +02:00
Piotr Osiewicz
79179e8fff Merge branch 'main' into gpui_extract_platform 2023-08-25 23:28:27 +02:00
Piotr Osiewicz
95b4bd467b chore: fmt 2023-08-25 23:24:23 +02:00
Piotr Osiewicz
978d074b1d WIP 2023-08-23 01:33:19 +02:00
Piotr Osiewicz
d0f287772b chore: fmt 2023-08-23 01:13:04 +02:00
Piotr Osiewicz
cfecbc5522 Start moving tests 2023-08-22 01:06:06 +02:00
Piotr Osiewicz
94cf1c3336 Use gpui_platform in test_support 2023-08-22 00:50:05 +02:00
Piotr Osiewicz
f3b76e0571 Add gpui_platform crate for a simple dispatch to implementation for current platform 2023-08-22 00:37:19 +02:00
Piotr Osiewicz
b4cba64fd6 Start fixing up platform tests 2023-08-22 00:30:52 +02:00
Piotr Osiewicz
7516e91a56 Extract gpui_mac out of gpui 2023-08-21 22:29:49 +02:00
61 changed files with 3108 additions and 2966 deletions

73
Cargo.lock generated
View file

@ -1075,6 +1075,7 @@ dependencies = [
"fs", "fs",
"futures 0.3.28", "futures 0.3.28",
"gpui", "gpui",
"gpui_platform",
"language", "language",
"live_kit_client", "live_kit_client",
"log", "log",
@ -1383,6 +1384,7 @@ dependencies = [
"feature_flags", "feature_flags",
"futures 0.3.28", "futures 0.3.28",
"gpui", "gpui",
"gpui_platform",
"image", "image",
"lazy_static", "lazy_static",
"log", "log",
@ -1539,6 +1541,7 @@ dependencies = [
"futures 0.3.28", "futures 0.3.28",
"fuzzy", "fuzzy",
"gpui", "gpui",
"gpui_platform",
"language", "language",
"log", "log",
"menu", "menu",
@ -1674,6 +1677,7 @@ dependencies = [
"fs", "fs",
"futures 0.3.28", "futures 0.3.28",
"gpui", "gpui",
"gpui_platform",
"language", "language",
"log", "log",
"lsp", "lsp",
@ -2343,6 +2347,7 @@ dependencies = [
"fuzzy", "fuzzy",
"git", "git",
"gpui", "gpui",
"gpui_platform",
"indoc", "indoc",
"itertools", "itertools",
"language", "language",
@ -3116,21 +3121,13 @@ dependencies = [
"anyhow", "anyhow",
"async-task", "async-task",
"backtrace", "backtrace",
"bindgen 0.65.1",
"block",
"cc",
"cocoa",
"collections", "collections",
"core-foundation",
"core-graphics",
"core-text",
"ctor", "ctor",
"derive_more", "derive_more",
"dhat", "dhat",
"env_logger 0.9.3", "env_logger 0.9.3",
"etagere", "etagere",
"font-kit", "font-kit",
"foreign-types",
"futures 0.3.28", "futures 0.3.28",
"gpui_macros", "gpui_macros",
"image", "image",
@ -3138,9 +3135,7 @@ dependencies = [
"lazy_static", "lazy_static",
"log", "log",
"media", "media",
"metal",
"num_cpus", "num_cpus",
"objc",
"ordered-float", "ordered-float",
"parking", "parking",
"parking_lot 0.11.2", "parking_lot 0.11.2",
@ -3170,6 +3165,41 @@ dependencies = [
"waker-fn", "waker-fn",
] ]
[[package]]
name = "gpui_mac"
version = "0.1.0"
dependencies = [
"anyhow",
"async-task",
"bindgen 0.65.1",
"block",
"cc",
"cocoa",
"collections",
"core-foundation",
"core-graphics",
"core-text",
"ctor",
"etagere",
"font-kit",
"foreign-types",
"gpui",
"log",
"media",
"metal",
"objc",
"ordered-float",
"parking_lot 0.11.2",
"pathfinder_geometry",
"postage",
"resvg",
"smol",
"time 0.3.27",
"tiny-skia",
"usvg",
"uuid 1.4.1",
]
[[package]] [[package]]
name = "gpui_macros" name = "gpui_macros"
version = "0.1.0" version = "0.1.0"
@ -3180,6 +3210,21 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "gpui_platform"
version = "0.1.0"
dependencies = [
"gpui",
"gpui_mac",
"gpui_macros",
"itertools",
"log",
"postage",
"rand 0.8.5",
"serde",
"smol",
]
[[package]] [[package]]
name = "grid" name = "grid"
version = "0.10.0" version = "0.10.0"
@ -4096,6 +4141,7 @@ dependencies = [
"foreign-types", "foreign-types",
"futures 0.3.28", "futures 0.3.28",
"gpui", "gpui",
"gpui_platform",
"hmac 0.12.1", "hmac 0.12.1",
"jwt", "jwt",
"live_kit_server", "live_kit_server",
@ -5179,6 +5225,7 @@ dependencies = [
"anyhow", "anyhow",
"derive_more", "derive_more",
"gpui", "gpui",
"gpui_platform",
"log", "log",
"parking_lot 0.11.2", "parking_lot 0.11.2",
"playground_macros", "playground_macros",
@ -6116,6 +6163,7 @@ dependencies = [
"arrayvec 0.7.4", "arrayvec 0.7.4",
"bromberg_sl2", "bromberg_sl2",
"gpui", "gpui",
"gpui_platform",
"log", "log",
"rand 0.8.5", "rand 0.8.5",
"smallvec", "smallvec",
@ -6146,6 +6194,7 @@ dependencies = [
"env_logger 0.9.3", "env_logger 0.9.3",
"futures 0.3.28", "futures 0.3.28",
"gpui", "gpui",
"gpui_platform",
"parking_lot 0.11.2", "parking_lot 0.11.2",
"prost 0.8.0", "prost 0.8.0",
"prost-build", "prost-build",
@ -6854,6 +6903,7 @@ dependencies = [
"fs", "fs",
"futures 0.3.28", "futures 0.3.28",
"gpui", "gpui",
"gpui_platform",
"indoc", "indoc",
"lazy_static", "lazy_static",
"postage", "postage",
@ -7663,6 +7713,7 @@ dependencies = [
"anyhow", "anyhow",
"fs", "fs",
"gpui", "gpui",
"gpui_platform",
"indexmap 1.9.3", "indexmap 1.9.3",
"parking_lot 0.11.2", "parking_lot 0.11.2",
"schemars", "schemars",
@ -9600,6 +9651,7 @@ dependencies = [
"fs", "fs",
"futures 0.3.28", "futures 0.3.28",
"gpui", "gpui",
"gpui_platform",
"indoc", "indoc",
"install_cli", "install_cli",
"itertools", "itertools",
@ -9742,6 +9794,7 @@ dependencies = [
"fuzzy", "fuzzy",
"go_to_line", "go_to_line",
"gpui", "gpui",
"gpui_platform",
"ignore", "ignore",
"image", "image",
"indexmap 1.9.3", "indexmap 1.9.3",

View file

@ -32,6 +32,8 @@ members = [
"crates/git", "crates/git",
"crates/go_to_line", "crates/go_to_line",
"crates/gpui", "crates/gpui",
"crates/gpui_mac",
"crates/gpui_platform",
"crates/gpui/playground", "crates/gpui/playground",
"crates/gpui/playground_macros", "crates/gpui/playground_macros",
"crates/gpui_macros", "crates/gpui_macros",

View file

@ -13,6 +13,7 @@ test-support = [
"client/test-support", "client/test-support",
"collections/test-support", "collections/test-support",
"gpui/test-support", "gpui/test-support",
"gpui_platform",
"live_kit_client/test-support", "live_kit_client/test-support",
"project/test-support", "project/test-support",
"util/test-support" "util/test-support"
@ -24,6 +25,7 @@ channel = { path = "../channel" }
client = { path = "../client" } client = { path = "../client" }
collections = { path = "../collections" } collections = { path = "../collections" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
gpui_platform = {path = "../gpui_platform", optional = true}
log.workspace = true log.workspace = true
live_kit_client = { path = "../live_kit_client" } live_kit_client = { path = "../live_kit_client" }
fs = { path = "../fs" } fs = { path = "../fs" }

View file

@ -9,12 +9,13 @@ path = "src/client.rs"
doctest = false doctest = false
[features] [features]
test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"] test-support = ["collections/test-support", "gpui/test-support", "gpui_platform", "rpc/test-support"]
[dependencies] [dependencies]
collections = { path = "../collections" } collections = { path = "../collections" }
db = { path = "../db" } db = { path = "../db" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
gpui_platform = {path = "../gpui_platform", optional = true}
util = { path = "../util" } util = { path = "../util" }
rpc = { path = "../rpc" } rpc = { path = "../rpc" }
text = { path = "../text" } text = { path = "../text" }

View file

@ -15,6 +15,7 @@ test-support = [
"collections/test-support", "collections/test-support",
"editor/test-support", "editor/test-support",
"gpui/test-support", "gpui/test-support",
"gpui_platform",
"project/test-support", "project/test-support",
"settings/test-support", "settings/test-support",
"util/test-support", "util/test-support",
@ -34,6 +35,7 @@ editor = { path = "../editor" }
feedback = { path = "../feedback" } feedback = { path = "../feedback" }
fuzzy = { path = "../fuzzy" } fuzzy = { path = "../fuzzy" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
gpui_platform = {path = "../gpui_platform", optional = true}
language = { path = "../language" } language = { path = "../language" }
menu = { path = "../menu" } menu = { path = "../menu" }
picker = { path = "../picker" } picker = { path = "../picker" }

View file

@ -12,6 +12,7 @@ doctest = false
test-support = [ test-support = [
"collections/test-support", "collections/test-support",
"gpui/test-support", "gpui/test-support",
"gpui_platform",
"language/test-support", "language/test-support",
"lsp/test-support", "lsp/test-support",
"settings/test-support", "settings/test-support",
@ -22,6 +23,7 @@ test-support = [
collections = { path = "../collections" } collections = { path = "../collections" }
context_menu = { path = "../context_menu" } context_menu = { path = "../context_menu" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
gpui_platform = {path = "../gpui_platform", optional = true}
language = { path = "../language" } language = { path = "../language" }
settings = { path = "../settings" } settings = { path = "../settings" }
theme = { path = "../theme" } theme = { path = "../theme" }

View file

@ -14,11 +14,13 @@ test-support = [
"text/test-support", "text/test-support",
"language/test-support", "language/test-support",
"gpui/test-support", "gpui/test-support",
"gpui_platform",
"project/test-support", "project/test-support",
"util/test-support", "util/test-support",
"workspace/test-support", "workspace/test-support",
"tree-sitter-rust", "tree-sitter-rust",
"tree-sitter-typescript" "tree-sitter-typescript",
] ]
[dependencies] [dependencies]
@ -32,6 +34,7 @@ context_menu = { path = "../context_menu" }
fuzzy = { path = "../fuzzy" } fuzzy = { path = "../fuzzy" }
git = { path = "../git" } git = { path = "../git" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
gpui_platform = { path = "../gpui_platform", optional = true}
language = { path = "../language" } language = { path = "../language" }
lsp = { path = "../lsp" } lsp = { path = "../lsp" }
project = { path = "../project" } project = { path = "../project" }

View file

@ -14,6 +14,7 @@ doctest = false
test-support = ["backtrace", "dhat", "env_logger", "collections/test-support"] test-support = ["backtrace", "dhat", "env_logger", "collections/test-support"]
[dependencies] [dependencies]
anyhow.workspace = true
collections = { path = "../collections" } collections = { path = "../collections" }
gpui_macros = { path = "../gpui_macros" } gpui_macros = { path = "../gpui_macros" }
util = { path = "../util" } util = { path = "../util" }
@ -55,10 +56,6 @@ usvg = { version = "0.14", features = [] }
uuid = { version = "1.1.2", features = ["v4"] } uuid = { version = "1.1.2", features = ["v4"] }
waker-fn = "1.1.0" waker-fn = "1.1.0"
[build-dependencies]
bindgen = "0.65.1"
cc = "1.0.67"
[dev-dependencies] [dev-dependencies]
backtrace = "0.3" backtrace = "0.3"
collections = { path = "../collections", features = ["test-support"] } collections = { path = "../collections", features = ["test-support"] }
@ -69,14 +66,4 @@ simplelog = "0.9"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
media = { path = "../media" } media = { path = "../media" }
anyhow.workspace = true
block = "0.1"
cocoa = "0.24"
core-foundation = { version = "0.9.3", features = ["with-uuid"] }
core-graphics = "0.22.3"
core-text = "19.2"
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" } font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" }
foreign-types = "0.3"
log.workspace = true
metal = "0.21.0"
objc = "0.2"

View file

@ -12,6 +12,7 @@ path = "src/playground.rs"
anyhow.workspace = true anyhow.workspace = true
derive_more.workspace = true derive_more.workspace = true
gpui = { path = ".." } gpui = { path = ".." }
gpui_platform = { path = "../../gpui_platform" }
log.workspace = true log.workspace = true
playground_macros = { path = "../playground_macros" } playground_macros = { path = "../playground_macros" }
parking_lot.workspace = true parking_lot.workspace = true

View file

@ -26,8 +26,11 @@ mod view;
fn main() { fn main() {
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
let platform = gpui_platform::platform();
gpui::App::new(()).unwrap().run(|cx| { let foreground =
std::rc::Rc::new(gpui::executor::Foreground::platform(platform.dispatcher()).unwrap());
let foreground = gpui_platform::foreground_platform(foreground);
gpui::App::new((), platform, foreground).unwrap().run(|cx| {
cx.add_window( cx.add_window(
WindowOptions { WindowOptions {
bounds: gpui::platform::WindowBounds::Fixed(RectF::new( bounds: gpui::platform::WindowBounds::Fixed(RectF::new(

File diff suppressed because it is too large Load diff

View file

@ -54,6 +54,7 @@ impl<K: Clone + Hash + Eq + Copy, F> Default for CallbackCollection<K, F> {
} }
impl<K: Clone + Hash + Eq + Copy, F> CallbackCollection<K, F> { impl<K: Clone + Hash + Eq + Copy, F> CallbackCollection<K, F> {
#[allow(dead_code)]
#[cfg(test)] #[cfg(test)]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.internal.lock().callbacks.is_empty() self.internal.lock().callbacks.is_empty()

View file

@ -33,8 +33,11 @@ impl ClipboardItem {
.as_ref() .as_ref()
.and_then(|m| serde_json::from_str(m).ok()) .and_then(|m| serde_json::from_str(m).ok())
} }
pub fn raw_metadata(&self) -> Option<&str> {
self.metadata.as_deref()
}
pub(crate) fn text_hash(text: &str) -> u64 { pub fn text_hash(text: &str) -> u64 {
let mut hasher = SeaHasher::new(); let mut hasher = SeaHasher::new();
text.hash(&mut hasher); text.hash(&mut hasher);
hasher.finish() hasher.finish()

View file

@ -216,72 +216,3 @@ impl ToJson for LabelStyle {
}) })
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::color::Color;
use crate::fonts::{Properties as FontProperties, Weight};
#[crate::test(self)]
fn test_layout_label_with_highlights(cx: &mut crate::AppContext) {
let default_style = TextStyle::new(
"Menlo",
12.,
Default::default(),
Default::default(),
Default::default(),
Color::black(),
cx.font_cache(),
)
.unwrap();
let highlight_style = TextStyle::new(
"Menlo",
12.,
*FontProperties::new().weight(Weight::BOLD),
Default::default(),
Default::default(),
Color::new(255, 0, 0, 255),
cx.font_cache(),
)
.unwrap();
let label = Label::new(
".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(),
LabelStyle {
text: default_style.clone(),
highlight_text: Some(highlight_style.clone()),
},
)
.with_highlights(vec![
".α".len(),
".αβ".len(),
".αβγδ".len(),
".αβγδε.ⓐ".len(),
".αβγδε.ⓐⓑ".len(),
]);
let default_run_style = RunStyle {
font_id: default_style.font_id,
color: default_style.color,
underline: default_style.underline,
};
let highlight_run_style = RunStyle {
font_id: highlight_style.font_id,
color: highlight_style.color,
underline: highlight_style.underline,
};
let runs = label.compute_runs();
assert_eq!(
runs.as_slice(),
&[
(".α".len(), default_run_style),
("βγ".len(), highlight_run_style),
("δ".len(), default_run_style),
("ε".len(), highlight_run_style),
(".ⓐ".len(), default_run_style),
("ⓑⓒ".len(), highlight_run_style),
("ⓓⓔ.abcde.".len(), default_run_style),
]
);
}
}

View file

@ -643,375 +643,3 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
self.0.partial_cmp(&other.height).unwrap() self.0.partial_cmp(&other.height).unwrap()
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::{elements::Empty, geometry::vector::vec2f, Entity, PaintContext};
use rand::prelude::*;
use std::env;
#[crate::test(self)]
fn test_layout(cx: &mut crate::AppContext) {
cx.add_window(Default::default(), |cx| {
let mut view = TestView;
let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.));
let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)]));
let state = ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, {
let elements = elements.clone();
move |_, ix, _| {
let (id, height) = elements.borrow()[ix];
TestElement::new(id, height).into_any()
}
});
let mut list = List::new(state.clone());
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (size, _) = list.layout(constraint, &mut view, &mut layout_cx);
assert_eq!(size, vec2f(100., 40.));
assert_eq!(
state.0.borrow().items.summary().clone(),
ListItemSummary {
count: 3,
rendered_count: 3,
unrendered_count: 0,
height: 150.
}
);
state.0.borrow_mut().scroll(
&ListOffset {
item_ix: 0,
offset_in_item: 0.,
},
40.,
vec2f(0., -54.),
true,
&mut view,
cx,
);
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (_, logical_scroll_top) = list.layout(constraint, &mut view, &mut layout_cx);
assert_eq!(
logical_scroll_top,
ListOffset {
item_ix: 2,
offset_in_item: 4.
}
);
assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 54.);
elements.borrow_mut().splice(1..2, vec![(3, 40.), (4, 50.)]);
elements.borrow_mut().push((5, 60.));
state.splice(1..2, 2);
state.splice(4..4, 1);
assert_eq!(
state.0.borrow().items.summary().clone(),
ListItemSummary {
count: 5,
rendered_count: 2,
unrendered_count: 3,
height: 120.
}
);
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (size, logical_scroll_top) = list.layout(constraint, &mut view, &mut layout_cx);
assert_eq!(size, vec2f(100., 40.));
assert_eq!(
state.0.borrow().items.summary().clone(),
ListItemSummary {
count: 5,
rendered_count: 5,
unrendered_count: 0,
height: 270.
}
);
assert_eq!(
logical_scroll_top,
ListOffset {
item_ix: 3,
offset_in_item: 4.
}
);
assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 114.);
view
});
}
#[crate::test(self, iterations = 10)]
fn test_random(cx: &mut crate::AppContext, mut rng: StdRng) {
let operations = env::var("OPERATIONS")
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
.unwrap_or(10);
cx.add_window(Default::default(), |cx| {
let mut view = TestView;
let mut next_id = 0;
let elements = Rc::new(RefCell::new(
(0..rng.gen_range(0..=20))
.map(|_| {
let id = next_id;
next_id += 1;
(id, rng.gen_range(0..=200) as f32 / 2.0)
})
.collect::<Vec<_>>(),
));
let orientation = *[Orientation::Top, Orientation::Bottom]
.choose(&mut rng)
.unwrap();
let overdraw = rng.gen_range(1..=100) as f32;
let state = ListState::new(elements.borrow().len(), orientation, overdraw, {
let elements = elements.clone();
move |_, ix, _| {
let (id, height) = elements.borrow()[ix];
TestElement::new(id, height).into_any()
}
});
let mut width = rng.gen_range(0..=2000) as f32 / 2.;
let mut height = rng.gen_range(0..=2000) as f32 / 2.;
log::info!("orientation: {:?}", orientation);
log::info!("overdraw: {}", overdraw);
log::info!("elements: {:?}", elements.borrow());
log::info!("size: ({:?}, {:?})", width, height);
log::info!("==================");
let mut last_logical_scroll_top = None;
for _ in 0..operations {
match rng.gen_range(0..=100) {
0..=29 if last_logical_scroll_top.is_some() => {
let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw));
log::info!(
"Scrolling by {:?}, previous scroll top: {:?}",
delta,
last_logical_scroll_top.unwrap()
);
state.0.borrow_mut().scroll(
last_logical_scroll_top.as_ref().unwrap(),
height,
delta,
true,
&mut view,
cx,
);
}
30..=34 => {
width = rng.gen_range(0..=2000) as f32 / 2.;
log::info!("changing width: {:?}", width);
}
35..=54 => {
height = rng.gen_range(0..=1000) as f32 / 2.;
log::info!("changing height: {:?}", height);
}
_ => {
let mut elements = elements.borrow_mut();
let end_ix = rng.gen_range(0..=elements.len());
let start_ix = rng.gen_range(0..=end_ix);
let new_elements = (0..rng.gen_range(0..10))
.map(|_| {
let id = next_id;
next_id += 1;
(id, rng.gen_range(0..=200) as f32 / 2.)
})
.collect::<Vec<_>>();
log::info!("splice({:?}, {:?})", start_ix..end_ix, new_elements);
state.splice(start_ix..end_ix, new_elements.len());
elements.splice(start_ix..end_ix, new_elements);
for (ix, item) in state.0.borrow().items.cursor::<()>().enumerate() {
if let ListItem::Rendered(element) = item {
let (expected_id, _) = elements[ix];
element.borrow().with_metadata(|metadata: Option<&usize>| {
assert_eq!(*metadata.unwrap(), expected_id);
});
}
}
}
}
let mut list = List::new(state.clone());
let window_size = vec2f(width, height);
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (size, logical_scroll_top) = list.layout(
SizeConstraint::new(vec2f(0., 0.), window_size),
&mut view,
&mut layout_cx,
);
assert_eq!(size, window_size);
last_logical_scroll_top = Some(logical_scroll_top);
let state = state.0.borrow();
log::info!("items {:?}", state.items.items(&()));
let scroll_top = state.scroll_top(&logical_scroll_top);
let rendered_top = (scroll_top - overdraw).max(0.);
let rendered_bottom = scroll_top + height + overdraw;
let mut item_top = 0.;
log::info!(
"rendered top {:?}, rendered bottom {:?}, scroll top {:?}",
rendered_top,
rendered_bottom,
scroll_top,
);
let mut first_rendered_element_top = None;
let mut last_rendered_element_bottom = None;
assert_eq!(state.items.summary().count, elements.borrow().len());
for (ix, item) in state.items.cursor::<()>().enumerate() {
match item {
ListItem::Unrendered => {
let item_bottom = item_top;
assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
item_top = item_bottom;
}
ListItem::Removed(height) => {
let (id, expected_height) = elements.borrow()[ix];
assert_eq!(
*height, expected_height,
"element {} height didn't match",
id
);
let item_bottom = item_top + height;
assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
item_top = item_bottom;
}
ListItem::Rendered(element) => {
let (expected_id, expected_height) = elements.borrow()[ix];
let element = element.borrow();
element.with_metadata(|metadata: Option<&usize>| {
assert_eq!(*metadata.unwrap(), expected_id);
});
assert_eq!(element.size().y(), expected_height);
let item_bottom = item_top + element.size().y();
first_rendered_element_top.get_or_insert(item_top);
last_rendered_element_bottom = Some(item_bottom);
assert!(item_bottom > rendered_top || item_top < rendered_bottom);
item_top = item_bottom;
}
}
}
match orientation {
Orientation::Top => {
if let Some(first_rendered_element_top) = first_rendered_element_top {
assert!(first_rendered_element_top <= scroll_top);
}
}
Orientation::Bottom => {
if let Some(last_rendered_element_bottom) = last_rendered_element_bottom {
assert!(last_rendered_element_bottom >= scroll_top + height);
}
}
}
}
view
});
}
struct TestView;
impl Entity for TestView {
type Event = ();
}
impl crate::View for TestView {
fn ui_name() -> &'static str {
"TestView"
}
fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
Empty::new().into_any()
}
}
struct TestElement {
id: usize,
size: Vector2F,
}
impl TestElement {
fn new(id: usize, height: f32) -> Self {
Self {
id,
size: vec2f(100., height),
}
}
}
impl<V: 'static> Element<V> for TestElement {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
_: SizeConstraint,
_: &mut V,
_: &mut LayoutContext<V>,
) -> (Vector2F, ()) {
(self.size, ())
}
fn paint(
&mut self,
_: &mut SceneBuilder,
_: RectF,
_: RectF,
_: &mut (),
_: &mut V,
_: &mut PaintContext<V>,
) {
unimplemented!()
}
fn rect_for_text_range(
&self,
_: Range<usize>,
_: RectF,
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
_: &V,
_: &ViewContext<V>,
) -> Option<RectF> {
unimplemented!()
}
fn debug(&self, _: RectF, _: &(), _: &(), _: &V, _: &ViewContext<V>) -> serde_json::Value {
self.id.into()
}
fn metadata(&self) -> Option<&dyn std::any::Any> {
Some(&self.id)
}
}
}

View file

@ -399,51 +399,3 @@ pub fn layout_highlighted_chunks<'a>(
layouts layouts
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::{elements::Empty, fonts, AnyElement, AppContext, Entity, View, ViewContext};
#[crate::test(self)]
fn test_soft_wrapping_with_carriage_returns(cx: &mut AppContext) {
cx.add_window(Default::default(), |cx| {
let mut view = TestView;
fonts::with_font_cache(cx.font_cache().clone(), || {
let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true);
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (_, state) = text.layout(
SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)),
&mut view,
&mut layout_cx,
);
assert_eq!(state.shaped_lines.len(), 2);
assert_eq!(state.wrap_boundaries.len(), 2);
});
view
});
}
struct TestView;
impl Entity for TestView {
type Event = ();
}
impl View for TestView {
fn ui_name() -> &'static str {
"TestView"
}
fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
Empty::new().into_any()
}
}
}

View file

@ -278,48 +278,3 @@ impl DerefMut for LineWrapperHandle {
self.wrapper.as_mut().unwrap() self.wrapper.as_mut().unwrap()
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::{
fonts::{Style, Weight},
platform::{test, Platform as _},
};
#[test]
fn test_select_font() {
let platform = test::platform();
let fonts = FontCache::new(platform.fonts());
let arial = fonts
.load_family(
&["Arial"],
&Features {
calt: Some(false),
..Default::default()
},
)
.unwrap();
let arial_regular = fonts.select_font(arial, &Properties::new()).unwrap();
let arial_italic = fonts
.select_font(arial, Properties::new().style(Style::Italic))
.unwrap();
let arial_bold = fonts
.select_font(arial, Properties::new().weight(Weight::BOLD))
.unwrap();
assert_ne!(arial_regular, arial_italic);
assert_ne!(arial_regular, arial_bold);
assert_ne!(arial_italic, arial_bold);
let arial_with_calt = fonts
.load_family(
&["Arial"],
&Features {
calt: Some(true),
..Default::default()
},
)
.unwrap();
assert_ne!(arial_with_calt, arial);
}
}

View file

@ -1,8 +1,6 @@
mod app; mod app;
pub use app::*; pub use app::*;
mod assets; mod assets;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
pub use assets::*; pub use assets::*;
pub mod elements; pub mod elements;
pub mod font_cache; pub mod font_cache;
@ -32,6 +30,8 @@ pub use window::{
WindowContext, WindowContext,
}; };
#[cfg(any(test, feature = "test-support"))]
pub mod test;
pub use anyhow; pub use anyhow;
pub use serde_json; pub use serde_json;

View file

@ -1,11 +1,6 @@
mod event; mod event;
#[cfg(target_os = "macos")] #[cfg(any(test, feature = "test-support"))]
pub mod mac; pub(super) mod test;
pub mod test;
pub mod current {
#[cfg(target_os = "macos")]
pub use super::mac::*;
}
use crate::{ use crate::{
executor, executor,
@ -88,7 +83,7 @@ pub trait Platform: Send + Sync {
fn restart(&self); fn restart(&self);
} }
pub(crate) trait ForegroundPlatform { pub trait ForegroundPlatform {
fn on_become_active(&self, callback: Box<dyn FnMut()>); fn on_become_active(&self, callback: Box<dyn FnMut()>);
fn on_resign_active(&self, callback: Box<dyn FnMut()>); fn on_resign_active(&self, callback: Box<dyn FnMut()>);
fn on_quit(&self, callback: Box<dyn FnMut()>); fn on_quit(&self, callback: Box<dyn FnMut()>);
@ -312,9 +307,9 @@ impl Default for CursorStyle {
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct AppVersion { pub struct AppVersion {
major: usize, pub major: usize,
minor: usize, pub minor: usize,
patch: usize, pub patch: usize,
} }
impl FromStr for AppVersion { impl FromStr for AppVersion {

View file

@ -64,7 +64,7 @@ impl super::ForegroundPlatform for ForegroundPlatform {
fn on_resign_active(&self, _: Box<dyn FnMut()>) {} fn on_resign_active(&self, _: Box<dyn FnMut()>) {}
fn on_quit(&self, _: Box<dyn FnMut()>) {} fn on_quit(&self, _: Box<dyn FnMut()>) {}
fn on_reopen(&self, _: Box<dyn FnMut()>) {} fn on_reopen(&self, _: Box<dyn FnMut()>) {}
fn on_event(&self, _: Box<dyn FnMut(crate::platform::Event) -> bool>) {} fn on_event(&self, _: Box<dyn FnMut(super::Event) -> bool>) {}
fn on_open_urls(&self, _: Box<dyn FnMut(Vec<String>)>) {} fn on_open_urls(&self, _: Box<dyn FnMut(Vec<String>)>) {}
fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) { fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {
@ -93,10 +93,6 @@ impl super::ForegroundPlatform for ForegroundPlatform {
fn reveal_path(&self, _: &Path) {} fn reveal_path(&self, _: &Path) {}
} }
pub fn platform() -> Platform {
Platform::new()
}
pub struct Platform { pub struct Platform {
dispatcher: Arc<dyn super::Dispatcher>, dispatcher: Arc<dyn super::Dispatcher>,
fonts: Arc<dyn super::FontSystem>, fonts: Arc<dyn super::FontSystem>,
@ -106,10 +102,10 @@ pub struct Platform {
} }
impl Platform { impl Platform {
fn new() -> Self { pub fn new(fonts: Arc<dyn super::FontSystem>) -> Self {
Self { Self {
dispatcher: Arc::new(Dispatcher), dispatcher: Arc::new(Dispatcher),
fonts: Arc::new(super::current::FontSystem::new()), fonts,
current_clipboard_item: Default::default(), current_clipboard_item: Default::default(),
cursor: Mutex::new(CursorStyle::Arrow), cursor: Mutex::new(CursorStyle::Arrow),
active_window: Default::default(), active_window: Default::default(),
@ -136,11 +132,11 @@ impl super::Platform for Platform {
fn quit(&self) {} fn quit(&self) {}
fn screen_by_id(&self, _id: uuid::Uuid) -> Option<Rc<dyn crate::platform::Screen>> { fn screen_by_id(&self, _id: uuid::Uuid) -> Option<Rc<dyn super::Screen>> {
None None
} }
fn screens(&self) -> Vec<Rc<dyn crate::platform::Screen>> { fn screens(&self) -> Vec<Rc<dyn super::Screen>> {
Default::default() Default::default()
} }
@ -165,7 +161,7 @@ impl super::Platform for Platform {
self.active_window.lock().clone() self.active_window.lock().clone()
} }
fn add_status_item(&self, handle: AnyWindowHandle) -> Box<dyn crate::platform::Window> { fn add_status_item(&self, handle: AnyWindowHandle) -> Box<dyn super::Window> {
Box::new(Window::new( Box::new(Window::new(
handle, handle,
vec2f(24., 24.), vec2f(24., 24.),
@ -301,10 +297,6 @@ impl Window {
active_window, active_window,
} }
} }
pub fn title(&self) -> Option<String> {
self.title.clone()
}
} }
impl super::Window for Window { impl super::Window for Window {
@ -324,11 +316,11 @@ impl super::Window for Window {
24. 24.
} }
fn appearance(&self) -> crate::platform::Appearance { fn appearance(&self) -> super::Appearance {
crate::platform::Appearance::Light super::Appearance::Light
} }
fn screen(&self) -> Rc<dyn crate::platform::Screen> { fn screen(&self) -> Rc<dyn super::Screen> {
Rc::new(Screen) Rc::new(Screen)
} }
@ -336,14 +328,9 @@ impl super::Window for Window {
self self
} }
fn set_input_handler(&mut self, _: Box<dyn crate::platform::InputHandler>) {} fn set_input_handler(&mut self, _: Box<dyn super::InputHandler>) {}
fn prompt( fn prompt(&self, _: super::PromptLevel, _: &str, _: &[&str]) -> oneshot::Receiver<usize> {
&self,
_: crate::platform::PromptLevel,
_: &str,
_: &[&str],
) -> oneshot::Receiver<usize> {
let (done_tx, done_rx) = oneshot::channel(); let (done_tx, done_rx) = oneshot::channel();
self.pending_prompts.borrow_mut().push_back(done_tx); self.pending_prompts.borrow_mut().push_back(done_tx);
done_rx done_rx
@ -373,7 +360,7 @@ impl super::Window for Window {
fn toggle_full_screen(&self) {} fn toggle_full_screen(&self) {}
fn on_event(&mut self, callback: Box<dyn FnMut(crate::platform::Event) -> bool>) { fn on_event(&mut self, callback: Box<dyn FnMut(super::Event) -> bool>) {
self.event_handlers.push(callback); self.event_handlers.push(callback);
} }

View file

@ -21,7 +21,7 @@ use crate::{
fonts::{FontId, GlyphId}, fonts::{FontId, GlyphId},
geometry::{rect::RectF, vector::Vector2F}, geometry::{rect::RectF, vector::Vector2F},
json::ToJson, json::ToJson,
platform::{current::Surface, CursorStyle}, platform::CursorStyle,
ImageData, WindowContext, ImageData, WindowContext,
}; };
pub use mouse_event::*; pub use mouse_event::*;
@ -171,6 +171,11 @@ pub struct Icon {
pub color: Color, pub color: Color,
} }
pub struct Surface {
pub bounds: RectF,
pub image_buffer: media::core_video::CVImageBuffer,
}
#[derive(Clone, Copy, Default, Debug, JsonSchema)] #[derive(Clone, Copy, Default, Debug, JsonSchema)]
pub struct Border { pub struct Border {
pub width: f32, pub width: f32,

View file

@ -17,7 +17,6 @@ use crate::{
elements::Empty, elements::Empty,
executor::{self, ExecutorEvent}, executor::{self, ExecutorEvent},
platform, platform,
platform::Platform,
util::CwdBacktrace, util::CwdBacktrace,
AnyElement, AppContext, Element, Entity, FontCache, Handle, Subscription, TestAppContext, View, AnyElement, AppContext, Element, Entity, FontCache, Handle, Subscription, TestAppContext, View,
ViewContext, ViewContext,
@ -35,6 +34,7 @@ fn init_logger() {
// static ALLOC: dhat::Alloc = dhat::Alloc; // static ALLOC: dhat::Alloc = dhat::Alloc;
pub fn run_test( pub fn run_test(
fonts: fn() -> std::sync::Arc<dyn crate::platform::FontSystem>,
mut num_iterations: u64, mut num_iterations: u64,
mut starting_seed: u64, mut starting_seed: u64,
max_retries: usize, max_retries: usize,
@ -66,10 +66,10 @@ pub fn run_test(
loop { loop {
let result = panic::catch_unwind(|| { let result = panic::catch_unwind(|| {
let fonts = fonts();
let foreground_platform = Rc::new(platform::test::foreground_platform()); let foreground_platform = Rc::new(platform::test::foreground_platform());
let platform = Arc::new(platform::test::platform()); let platform = Arc::new(platform::test::Platform::new(fonts.clone()));
let font_system = platform.fonts(); let font_cache = Arc::new(FontCache::new(fonts.clone()));
let font_cache = Arc::new(FontCache::new(font_system));
let mut prev_runnable_history: Option<Vec<ExecutorEvent>> = None; let mut prev_runnable_history: Option<Vec<ExecutorEvent>> = None;
for _ in 0..num_iterations { for _ in 0..num_iterations {

View file

@ -528,7 +528,7 @@ pub struct ShapedBoundary {
} }
impl Boundary { impl Boundary {
fn new(ix: usize, next_indent: u32) -> Self { pub fn new(ix: usize, next_indent: u32) -> Self {
Self { ix, next_indent } Self { ix, next_indent }
} }
} }
@ -727,129 +727,3 @@ impl LineWrapper {
.width .width
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::fonts::{Properties, Weight};
#[crate::test(self)]
fn test_wrap_line(cx: &mut crate::AppContext) {
let font_cache = cx.font_cache().clone();
let font_system = cx.platform().fonts();
let family = font_cache
.load_family(&["Courier"], &Default::default())
.unwrap();
let font_id = font_cache.select_font(family, &Default::default()).unwrap();
let mut wrapper = LineWrapper::new(font_id, 16., font_system);
assert_eq!(
wrapper
.wrap_line("aa bbb cccc ddddd eeee", 72.0)
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(12, 0),
Boundary::new(18, 0)
],
);
assert_eq!(
wrapper
.wrap_line("aaa aaaaaaaaaaaaaaaaaa", 72.0)
.collect::<Vec<_>>(),
&[
Boundary::new(4, 0),
Boundary::new(11, 0),
Boundary::new(18, 0)
],
);
assert_eq!(
wrapper.wrap_line(" aaaaaaa", 72.).collect::<Vec<_>>(),
&[
Boundary::new(7, 5),
Boundary::new(9, 5),
Boundary::new(11, 5),
]
);
assert_eq!(
wrapper
.wrap_line(" ", 72.)
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(14, 0),
Boundary::new(21, 0)
]
);
assert_eq!(
wrapper
.wrap_line(" aaaaaaaaaaaaaa", 72.)
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(14, 3),
Boundary::new(18, 3),
Boundary::new(22, 3),
]
);
}
#[crate::test(self, retries = 5)]
fn test_wrap_shaped_line(cx: &mut crate::AppContext) {
// This is failing intermittently on CI and we don't have time to figure it out
let font_cache = cx.font_cache().clone();
let font_system = cx.platform().fonts();
let text_layout_cache = TextLayoutCache::new(font_system.clone());
let family = font_cache
.load_family(&["Helvetica"], &Default::default())
.unwrap();
let font_id = font_cache.select_font(family, &Default::default()).unwrap();
let normal = RunStyle {
font_id,
color: Default::default(),
underline: Default::default(),
};
let bold = RunStyle {
font_id: font_cache
.select_font(
family,
&Properties {
weight: Weight::BOLD,
..Default::default()
},
)
.unwrap(),
color: Default::default(),
underline: Default::default(),
};
let text = "aa bbb cccc ddddd eeee";
let line = text_layout_cache.layout_str(
text,
16.0,
&[(4, normal), (5, bold), (6, normal), (1, bold), (7, normal)],
);
let mut wrapper = LineWrapper::new(font_id, 16., font_system);
assert_eq!(
wrapper
.wrap_shaped_line(text, &line, 72.0)
.collect::<Vec<_>>(),
&[
ShapedBoundary {
run_ix: 1,
glyph_ix: 3
},
ShapedBoundary {
run_ix: 2,
glyph_ix: 3
},
ShapedBoundary {
run_ix: 4,
glyph_ix: 2
}
],
);
}
}

View file

@ -0,0 +1,41 @@
[package]
name = "gpui_mac"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
resvg = "0.14"
gpui = {path = "../gpui"}
usvg = { version = "0.14", features = [] }
tiny-skia = "0.5"
uuid = { version = "1.1.2", features = ["v4"] }
time.workspace = true
etagere = "0.2"
ordered-float.workspace = true
postage.workspace = true
smol.workspace = true
ctor.workspace = true
parking_lot.workspace = true
collections = { path = "../collections" }
async-task = "4.0.3"
pathfinder_geometry = "0.5"
[target.'cfg(target_os = "macos")'.dependencies]
media = { path = "../media" }
anyhow.workspace = true
block = "0.1"
cocoa = "0.24"
core-foundation = { version = "0.9.3", features = ["with-uuid"] }
core-graphics = "0.22.3"
core-text = "19.2"
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" }
foreign-types = "0.3"
log.workspace = true
metal = "0.21.0"
objc = "0.2"
[build-dependencies]
bindgen = "0.65.1"
cc = "1.0.67"

View file

@ -12,10 +12,10 @@ fn main() {
fn generate_dispatch_bindings() { fn generate_dispatch_bindings() {
println!("cargo:rustc-link-lib=framework=System"); println!("cargo:rustc-link-lib=framework=System");
println!("cargo:rerun-if-changed=src/platform/mac/dispatch.h"); println!("cargo:rerun-if-changed=src/dispatch.h");
let bindings = bindgen::Builder::default() let bindings = bindgen::Builder::default()
.header("src/platform/mac/dispatch.h") .header("src/dispatch.h")
.allowlist_var("_dispatch_main_q") .allowlist_var("_dispatch_main_q")
.allowlist_function("dispatch_async_f") .allowlist_function("dispatch_async_f")
.parse_callbacks(Box::new(bindgen::CargoCallbacks)) .parse_callbacks(Box::new(bindgen::CargoCallbacks))
@ -29,10 +29,10 @@ fn generate_dispatch_bindings() {
.expect("couldn't write dispatch bindings"); .expect("couldn't write dispatch bindings");
} }
const SHADER_HEADER_PATH: &str = "./src/platform/mac/shaders/shaders.h"; const SHADER_HEADER_PATH: &str = "./src/shaders/shaders.h";
fn compile_metal_shaders() { fn compile_metal_shaders() {
let shader_path = "./src/platform/mac/shaders/shaders.metal"; let shader_path = "./src/shaders/shaders.metal";
let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air"); let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air");
let metallib_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib"); let metallib_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");

View file

@ -1,30 +1,33 @@
use std::ffi::CStr; use std::ffi::CStr;
use crate::platform::Appearance;
use cocoa::{ use cocoa::{
appkit::{NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight}, appkit::{NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight},
base::id, base::id,
foundation::NSString, foundation::NSString,
}; };
use gpui::platform::Appearance;
use objc::{msg_send, sel, sel_impl}; use objc::{msg_send, sel, sel_impl};
impl Appearance { pub trait AppearanceFromNative {
pub unsafe fn from_native(appearance: id) -> Self { unsafe fn from_native(appearance: id) -> Self;
}
impl AppearanceFromNative for Appearance {
unsafe fn from_native(appearance: id) -> Appearance {
let name: id = msg_send![appearance, name]; let name: id = msg_send![appearance, name];
if name == NSAppearanceNameVibrantLight { if name == NSAppearanceNameVibrantLight {
Self::VibrantLight Appearance::VibrantLight
} else if name == NSAppearanceNameVibrantDark { } else if name == NSAppearanceNameVibrantDark {
Self::VibrantDark Appearance::VibrantDark
} else if name == NSAppearanceNameAqua { } else if name == NSAppearanceNameAqua {
Self::Light Appearance::Light
} else if name == NSAppearanceNameDarkAqua { } else if name == NSAppearanceNameDarkAqua {
Self::Dark Appearance::Dark
} else { } else {
println!( println!(
"unknown appearance: {:?}", "unknown appearance: {:?}",
CStr::from_ptr(name.UTF8String()) CStr::from_ptr(name.UTF8String())
); );
Self::Light Appearance::Light
} }
} }
} }

View file

@ -1,9 +1,9 @@
use crate::geometry::{ use etagere::BucketedAtlasAllocator;
use foreign_types::ForeignType;
use gpui::geometry::{
rect::RectI, rect::RectI,
vector::{vec2i, Vector2I}, vector::{vec2i, Vector2I},
}; };
use etagere::BucketedAtlasAllocator;
use foreign_types::ForeignType;
use log::warn; use log::warn;
use metal::{Device, TextureDescriptor}; use metal::{Device, TextureDescriptor};
use objc::{msg_send, sel, sel_impl}; use objc::{msg_send, sel, sel_impl};

View file

@ -10,7 +10,7 @@ use objc::{
}; };
use std::ffi::c_void; use std::ffi::c_void;
use crate::platform; use gpui::platform;
include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs")); include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));

View file

@ -1,12 +1,3 @@
use crate::{
geometry::vector::vec2f,
keymap_matcher::Keystroke,
platform::{
Event, KeyDownEvent, KeyUpEvent, Modifiers, ModifiersChangedEvent, MouseButton,
MouseButtonEvent, MouseExitedEvent, MouseMovedEvent, NavigationDirection, ScrollDelta,
ScrollWheelEvent, TouchPhase,
},
};
use cocoa::{ use cocoa::{
appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType}, appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
base::{id, YES}, base::{id, YES},
@ -18,6 +9,15 @@ use core_graphics::{
}; };
use ctor::ctor; use ctor::ctor;
use foreign_types::ForeignType; use foreign_types::ForeignType;
use gpui::{
geometry::vector::vec2f,
keymap_matcher::Keystroke,
platform::{
Event, KeyDownEvent, KeyUpEvent, Modifiers, ModifiersChangedEvent, MouseButton,
MouseButtonEvent, MouseExitedEvent, MouseMovedEvent, NavigationDirection, ScrollDelta,
ScrollWheelEvent, TouchPhase,
},
};
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use std::{borrow::Cow, ffi::CStr, mem, os::raw::c_char, ptr}; use std::{borrow::Cow, ffi::CStr, mem, os::raw::c_char, ptr};
@ -86,8 +86,11 @@ unsafe fn read_modifiers(native_event: id) -> Modifiers {
} }
} }
impl Event { pub trait EventFromNative {
pub unsafe fn from_native(native_event: id, window_height: Option<f32>) -> Option<Self> { unsafe fn from_native(native_event: id, window_height: Option<f32>) -> Option<Event>;
}
impl EventFromNative for Event {
unsafe fn from_native(native_event: id, window_height: Option<f32>) -> Option<Event> {
let event_type = native_event.eventType(); let event_type = native_event.eventType();
// Filter out event types that aren't in the NSEventType enum. // Filter out event types that aren't in the NSEventType enum.

View file

@ -1,15 +1,5 @@
mod open_type; mod open_type;
use crate::{
fonts::{Features, FontId, GlyphId, Metrics, Properties},
geometry::{
rect::{RectF, RectI},
transform2d::Transform2F,
vector::{vec2f, Vector2F},
},
platform::{self, RasterizationOptions},
text_layout::{Glyph, LineLayout, Run, RunStyle},
};
use cocoa::appkit::{CGFloat, CGPoint}; use cocoa::appkit::{CGFloat, CGPoint};
use collections::HashMap; use collections::HashMap;
use core_foundation::{ use core_foundation::{
@ -27,6 +17,16 @@ use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeN
use font_kit::{ use font_kit::{
handle::Handle, hinting::HintingOptions, source::SystemSource, sources::mem::MemSource, handle::Handle, hinting::HintingOptions, source::SystemSource, sources::mem::MemSource,
}; };
use gpui::{
fonts::{Features, FontId, GlyphId, Metrics, Properties},
geometry::{
rect::{RectF, RectI},
transform2d::Transform2F,
vector::{vec2f, Vector2F},
},
platform::{self, RasterizationOptions},
text_layout::{Glyph, LineLayout, Run, RunStyle},
};
use parking_lot::RwLock; use parking_lot::RwLock;
use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc}; use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
@ -506,8 +506,8 @@ extern "C" {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::AppContext;
use font_kit::properties::{Style, Weight}; use font_kit::properties::{Style, Weight};
use gpui::AppContext;
use platform::FontSystem as _; use platform::FontSystem as _;
#[crate::test(self, retries = 5)] #[crate::test(self, retries = 5)]
@ -668,4 +668,39 @@ mod tests {
// There's no glyph for \u{feff} // There's no glyph for \u{feff}
assert_eq!(layout.runs[0].glyphs[1].id, 69); // b assert_eq!(layout.runs[0].glyphs[1].id, 69); // b
} }
#[test]
fn test_select_font() {
let platform = test::platform(FontSystem::new());
let fonts = FontCache::new(platform.fonts());
let arial = fonts
.load_family(
&["Arial"],
&Features {
calt: Some(false),
..Default::default()
},
)
.unwrap();
let arial_regular = fonts.select_font(arial, &Properties::new()).unwrap();
let arial_italic = fonts
.select_font(arial, Properties::new().style(Style::Italic))
.unwrap();
let arial_bold = fonts
.select_font(arial, Properties::new().weight(Weight::BOLD))
.unwrap();
assert_ne!(arial_regular, arial_italic);
assert_ne!(arial_regular, arial_bold);
assert_ne!(arial_italic, arial_bold);
let arial_with_calt = fonts
.load_family(
&["Arial"],
&Features {
calt: Some(true),
..Default::default()
},
)
.unwrap();
assert_ne!(arial_with_calt, arial);
}
} }

View file

@ -2,7 +2,6 @@
use std::ptr; use std::ptr;
use crate::fonts::Features;
use cocoa::appkit::CGFloat; use cocoa::appkit::CGFloat;
use core_foundation::{base::TCFType, number::CFNumber}; use core_foundation::{base::TCFType, number::CFNumber};
use core_graphics::geometry::CGAffineTransform; use core_graphics::geometry::CGAffineTransform;
@ -13,6 +12,7 @@ use core_text::{
}, },
}; };
use font_kit::font::Font; use font_kit::font::Font;
use gpui::fonts::Features;
const kCaseSensitiveLayoutOffSelector: i32 = 1; const kCaseSensitiveLayoutOffSelector: i32 = 1;
const kCaseSensitiveLayoutOnSelector: i32 = 0; const kCaseSensitiveLayoutOnSelector: i32 = 0;

View file

@ -1,12 +1,12 @@
use super::atlas::{AllocId, AtlasAllocator}; use super::atlas::{AllocId, AtlasAllocator};
use crate::{ use anyhow::anyhow;
use gpui::{
fonts::{FontId, GlyphId}, fonts::{FontId, GlyphId},
geometry::{rect::RectI, vector::Vector2I}, geometry::{rect::RectI, vector::Vector2I},
platform::{FontSystem, RasterizationOptions}, platform::{FontSystem, RasterizationOptions},
scene::ImageGlyph, scene::ImageGlyph,
ImageData, ImageData,
}; };
use anyhow::anyhow;
use metal::{MTLPixelFormat, TextureDescriptor, TextureRef}; use metal::{MTLPixelFormat, TextureDescriptor, TextureRef};
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use std::{collections::HashMap, mem, sync::Arc}; use std::{collections::HashMap, mem, sync::Arc};

View file

@ -10,6 +10,7 @@ mod renderer;
mod screen; mod screen;
mod sprite_cache; mod sprite_cache;
mod status_item; mod status_item;
mod window; mod window;
use cocoa::{ use cocoa::{
@ -23,15 +24,15 @@ pub use renderer::Surface;
use std::{ops::Range, rc::Rc, sync::Arc}; use std::{ops::Range, rc::Rc, sync::Arc};
use window::MacWindow; use window::MacWindow;
use crate::executor; use gpui::executor;
pub(crate) fn platform() -> Arc<dyn super::Platform> { pub fn platform() -> Arc<dyn gpui::platform::Platform> {
Arc::new(MacPlatform::new()) Arc::new(MacPlatform::new())
} }
pub(crate) fn foreground_platform( pub fn foreground_platform(
foreground: Rc<executor::Foreground>, foreground: Rc<executor::Foreground>,
) -> Rc<dyn super::ForegroundPlatform> { ) -> Rc<dyn gpui::platform::ForegroundPlatform> {
Rc::new(MacForegroundPlatform::new(foreground)) Rc::new(MacForegroundPlatform::new(foreground))
} }
@ -102,3 +103,9 @@ unsafe impl objc::Encode for NSRange {
unsafe fn ns_string(string: &str) -> id { unsafe fn ns_string(string: &str) -> id {
NSString::alloc(nil).init_str(string).autorelease() NSString::alloc(nil).init_str(string).autorelease()
} }
#[doc(hidden)]
#[allow(dead_code)]
pub fn font_system() -> Arc<dyn gpui::platform::FontSystem> {
Arc::new(FontSystem::new())
}

View file

@ -1,13 +1,9 @@
use crate::event::EventFromNative;
use super::{ use super::{
event::key_to_native, screen::Screen, status_item::StatusItem, BoolExt as _, Dispatcher, event::key_to_native, screen::Screen, status_item::StatusItem, BoolExt as _, Dispatcher,
FontSystem, MacWindow, FontSystem, MacWindow,
}; };
use crate::{
executor,
keymap_matcher::KeymapMatcher,
platform::{self, AppVersion, CursorStyle, Event},
Action, AnyWindowHandle, ClipboardItem, Menu, MenuItem,
};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use block::ConcreteBlock; use block::ConcreteBlock;
use cocoa::{ use cocoa::{
@ -30,6 +26,12 @@ use core_foundation::{
string::{CFString, CFStringRef}, string::{CFString, CFStringRef},
}; };
use ctor::ctor; use ctor::ctor;
use gpui::{
executor,
keymap_matcher::KeymapMatcher,
platform::{self, AppVersion, CursorStyle, Event},
Action, AnyWindowHandle, ClipboardItem, Menu, MenuItem,
};
use objc::{ use objc::{
class, class,
declare::ClassDecl, declare::ClassDecl,
@ -235,12 +237,12 @@ impl MacForegroundPlatform {
.find(|binding| binding.action().eq(action.as_ref())) .find(|binding| binding.action().eq(action.as_ref()))
.map(|binding| binding.keystrokes()); .map(|binding| binding.keystrokes());
let selector = match os_action { let selector = match os_action {
Some(crate::OsAction::Cut) => selector("cut:"), Some(gpui::OsAction::Cut) => selector("cut:"),
Some(crate::OsAction::Copy) => selector("copy:"), Some(gpui::OsAction::Copy) => selector("copy:"),
Some(crate::OsAction::Paste) => selector("paste:"), Some(gpui::OsAction::Paste) => selector("paste:"),
Some(crate::OsAction::SelectAll) => selector("selectAll:"), Some(gpui::OsAction::SelectAll) => selector("selectAll:"),
Some(crate::OsAction::Undo) => selector("undo:"), Some(gpui::OsAction::Undo) => selector("undo:"),
Some(crate::OsAction::Redo) => selector("redo:"), Some(gpui::OsAction::Redo) => selector("redo:"),
None => selector("handleGPUIMenuItem:"), None => selector("handleGPUIMenuItem:"),
}; };
@ -611,14 +613,14 @@ impl platform::Platform for MacPlatform {
let text_bytes = NSData::dataWithBytes_length_( let text_bytes = NSData::dataWithBytes_length_(
nil, nil,
item.text.as_ptr() as *const c_void, item.text().as_ptr() as *const c_void,
item.text.len() as u64, item.text().len() as u64,
); );
self.pasteboard self.pasteboard
.setData_forType(text_bytes, NSPasteboardTypeString); .setData_forType(text_bytes, NSPasteboardTypeString);
if let Some(metadata) = item.metadata.as_ref() { if let Some(metadata) = item.raw_metadata() {
let hash_bytes = ClipboardItem::text_hash(&item.text).to_be_bytes(); let hash_bytes = ClipboardItem::text_hash(&item.text()).to_be_bytes();
let hash_bytes = NSData::dataWithBytes_length_( let hash_bytes = NSData::dataWithBytes_length_(
nil, nil,
hash_bytes.as_ptr() as *const c_void, hash_bytes.as_ptr() as *const c_void,
@ -652,21 +654,12 @@ impl platform::Platform for MacPlatform {
if let Some((hash, metadata)) = hash_bytes.zip(metadata_bytes) { if let Some((hash, metadata)) = hash_bytes.zip(metadata_bytes) {
if hash == ClipboardItem::text_hash(&text) { if hash == ClipboardItem::text_hash(&text) {
Some(ClipboardItem { Some(ClipboardItem::new(text).with_metadata(metadata))
text,
metadata: Some(metadata),
})
} else { } else {
Some(ClipboardItem { Some(ClipboardItem::new(text))
text,
metadata: None,
})
} }
} else { } else {
Some(ClipboardItem { Some(ClipboardItem::new(text))
text,
metadata: None,
})
} }
} else { } else {
None None
@ -868,7 +861,7 @@ impl platform::Platform for MacPlatform {
"macOS" "macOS"
} }
fn os_version(&self) -> Result<crate::platform::AppVersion> { fn os_version(&self) -> Result<gpui::platform::AppVersion> {
unsafe { unsafe {
let process_info = NSProcessInfo::processInfo(nil); let process_info = NSProcessInfo::processInfo(nil);
let version = process_info.operatingSystemVersion(); let version = process_info.operatingSystemVersion();
@ -1096,7 +1089,7 @@ mod security {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::platform::Platform; use gpui::platform::Platform;
use super::*; use super::*;

View file

@ -1,5 +1,14 @@
use super::{atlas::AtlasAllocator, image_cache::ImageCache, sprite_cache::SpriteCache}; use super::{atlas::AtlasAllocator, image_cache::ImageCache, sprite_cache::SpriteCache};
use crate::{ use crate::renderer::shaders::ColorToUchar4;
use cocoa::{
base::{NO, YES},
foundation::NSUInteger,
quartzcore::AutoresizingMask,
};
use core_foundation::base::TCFType;
use foreign_types::ForeignTypeRef;
pub use gpui::scene::Surface;
use gpui::{
color::Color, color::Color,
geometry::{ geometry::{
rect::RectF, rect::RectF,
@ -8,13 +17,6 @@ use crate::{
platform, platform,
scene::{Glyph, Icon, Image, ImageGlyph, Layer, Quad, Scene, Shadow, Underline}, scene::{Glyph, Icon, Image, ImageGlyph, Layer, Quad, Scene, Shadow, Underline},
}; };
use cocoa::{
base::{NO, YES},
foundation::NSUInteger,
quartzcore::AutoresizingMask,
};
use core_foundation::base::TCFType;
use foreign_types::ForeignTypeRef;
use log::warn; use log::warn;
use media::core_video::{self, CVMetalTextureCache}; use media::core_video::{self, CVMetalTextureCache};
use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange}; use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
@ -49,11 +51,6 @@ struct PathSprite {
shader_data: shaders::GPUISprite, shader_data: shaders::GPUISprite,
} }
pub struct Surface {
pub bounds: RectF,
pub image_buffer: core_video::CVImageBuffer,
}
impl Renderer { impl Renderer {
pub fn new(is_opaque: bool, fonts: Arc<dyn platform::FontSystem>) -> Self { pub fn new(is_opaque: bool, fonts: Arc<dyn platform::FontSystem>) -> Self {
const PIXEL_FORMAT: MTLPixelFormat = MTLPixelFormat::BGRA8Unorm; const PIXEL_FORMAT: MTLPixelFormat = MTLPixelFormat::BGRA8Unorm;
@ -1221,7 +1218,7 @@ mod shaders {
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::{ use gpui::{
color::Color, color::Color,
geometry::vector::{Vector2F, Vector2I}, geometry::vector::{Vector2F, Vector2I},
}; };
@ -1260,9 +1257,11 @@ mod shaders {
self.to_f32().to_float2() self.to_f32().to_float2()
} }
} }
pub trait ColorToUchar4 {
impl Color { fn to_uchar4(&self) -> vector_uchar4;
pub fn to_uchar4(&self) -> vector_uchar4 { }
impl ColorToUchar4 for Color {
fn to_uchar4(&self) -> vector_uchar4 {
let mut vec = self.a as vector_uchar4; let mut vec = self.a as vector_uchar4;
vec <<= 8; vec <<= 8;
vec |= self.b as vector_uchar4; vec |= self.b as vector_uchar4;

View file

@ -1,5 +1,4 @@
use super::ns_string; use super::ns_string;
use crate::platform;
use cocoa::{ use cocoa::{
appkit::NSScreen, appkit::NSScreen,
base::{id, nil}, base::{id, nil},
@ -10,6 +9,7 @@ use core_foundation::{
uuid::{CFUUIDGetUUIDBytes, CFUUIDRef}, uuid::{CFUUIDGetUUIDBytes, CFUUIDRef},
}; };
use core_graphics::display::CGDirectDisplayID; use core_graphics::display::CGDirectDisplayID;
use gpui::platform;
use pathfinder_geometry::{rect::RectF, vector::vec2f}; use pathfinder_geometry::{rect::RectF, vector::vec2f};
use std::{any::Any, ffi::c_void}; use std::{any::Any, ffi::c_void};
use uuid::Uuid; use uuid::Uuid;

View file

@ -1,10 +1,10 @@
use super::atlas::AtlasAllocator; use super::atlas::AtlasAllocator;
use crate::{ use collections::hash_map::Entry;
use gpui::{
fonts::{FontId, GlyphId}, fonts::{FontId, GlyphId},
geometry::vector::{vec2f, Vector2F, Vector2I}, geometry::vector::{vec2f, Vector2F, Vector2I},
platform::{self, RasterizationOptions}, platform::{self, RasterizationOptions},
}; };
use collections::hash_map::Entry;
use metal::{MTLPixelFormat, TextureDescriptor}; use metal::{MTLPixelFormat, TextureDescriptor};
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use std::{borrow::Cow, collections::HashMap, sync::Arc}; use std::{borrow::Cow, collections::HashMap, sync::Arc};

View file

@ -1,15 +1,6 @@
use crate::{ use crate::appearance::AppearanceFromNative;
geometry::{ use crate::event::EventFromNative;
rect::RectF, use crate::{platform::NSViewLayerContentsRedrawDuringViewResize, renderer::Renderer};
vector::{vec2f, Vector2F},
},
platform::{
self,
mac::{platform::NSViewLayerContentsRedrawDuringViewResize, renderer::Renderer},
Event, FontSystem, WindowBounds,
},
Scene,
};
use cocoa::{ use cocoa::{
appkit::{NSScreen, NSSquareStatusItemLength, NSStatusBar, NSStatusItem, NSView, NSWindow}, appkit::{NSScreen, NSSquareStatusItemLength, NSStatusBar, NSStatusItem, NSView, NSWindow},
base::{id, nil, YES}, base::{id, nil, YES},
@ -17,6 +8,14 @@ use cocoa::{
}; };
use ctor::ctor; use ctor::ctor;
use foreign_types::ForeignTypeRef; use foreign_types::ForeignTypeRef;
use gpui::{
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
platform::{self, Event, FontSystem, WindowBounds},
Scene,
};
use objc::{ use objc::{
class, class,
declare::ClassDecl, declare::ClassDecl,
@ -210,7 +209,7 @@ impl platform::Window for StatusItem {
fn prompt( fn prompt(
&self, &self,
_: crate::platform::PromptLevel, _: gpui::platform::PromptLevel,
_: &str, _: &str,
_: &[&str], _: &[&str],
) -> postage::oneshot::Receiver<usize> { ) -> postage::oneshot::Receiver<usize> {

View file

@ -1,19 +1,7 @@
use crate::appearance::AppearanceFromNative;
use crate::event::EventFromNative;
use crate::{ use crate::{
executor, platform::NSViewLayerContentsRedrawDuringViewResize, renderer::Renderer, screen::Screen,
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
keymap_matcher::Keystroke,
platform::{
self,
mac::{
platform::NSViewLayerContentsRedrawDuringViewResize, renderer::Renderer, screen::Screen,
},
Event, InputHandler, KeyDownEvent, Modifiers, ModifiersChangedEvent, MouseButton,
MouseButtonEvent, MouseMovedEvent, Scene, WindowBounds, WindowKind,
},
AnyWindowHandle,
}; };
use block::ConcreteBlock; use block::ConcreteBlock;
use cocoa::{ use cocoa::{
@ -28,6 +16,19 @@ use cocoa::{
use core_graphics::display::CGRect; use core_graphics::display::CGRect;
use ctor::ctor; use ctor::ctor;
use foreign_types::ForeignTypeRef; use foreign_types::ForeignTypeRef;
use gpui::{
executor,
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
keymap_matcher::Keystroke,
platform::{
self, Event, InputHandler, KeyDownEvent, Modifiers, ModifiersChangedEvent, MouseButton,
MouseButtonEvent, MouseMovedEvent, WindowBounds, WindowKind,
},
AnyWindowHandle, Scene,
};
use objc::{ use objc::{
class, class,
declare::ClassDecl, declare::ClassDecl,

View file

@ -9,8 +9,8 @@ use syn::{
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
let mut namespace = format_ident!("gpui"); let namespace = format_ident!("gpui");
let mut platform_namespace = format_ident!("gpui_platform");
let args = syn::parse_macro_input!(args as AttributeArgs); let args = syn::parse_macro_input!(args as AttributeArgs);
let mut max_retries = 0; let mut max_retries = 0;
let mut num_iterations = 1; let mut num_iterations = 1;
@ -23,7 +23,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
NestedMeta::Meta(Meta::Path(name)) NestedMeta::Meta(Meta::Path(name))
if name.get_ident().map_or(false, |n| n == "self") => if name.get_ident().map_or(false, |n| n == "self") =>
{ {
namespace = format_ident!("crate"); platform_namespace = format_ident!("crate");
} }
NestedMeta::Meta(Meta::NameValue(meta)) => { NestedMeta::Meta(Meta::NameValue(meta)) => {
let key_name = meta.path.get_ident().map(|i| i.to_string()); let key_name = meta.path.get_ident().map(|i| i.to_string());
@ -157,8 +157,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
#[test] #[test]
fn #outer_fn_name() { fn #outer_fn_name() {
#inner_fn #inner_fn
#namespace::test::run_test( #namespace::test::run_test(
#platform_namespace::font_system,
#num_iterations as u64, #num_iterations as u64,
#starting_seed as u64, #starting_seed as u64,
#max_retries, #max_retries,
@ -234,8 +234,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
#[test] #[test]
fn #outer_fn_name() { fn #outer_fn_name() {
#inner_fn #inner_fn
#namespace::test::run_test( #namespace::test::run_test(
#platform_namespace::font_system,
#num_iterations as u64, #num_iterations as u64,
#starting_seed as u64, #starting_seed as u64,
#max_retries, #max_retries,

View file

@ -0,0 +1,25 @@
[package]
name = "gpui_platform"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
test-support = ["gpui/test-support"]
[dependencies]
gpui = {path = "../gpui", optional = true}
gpui_macros = { path = "../gpui_macros" }
[target.'cfg(target_os = "macos")'.dependencies]
current_platform = {path = "../gpui_mac", package = "gpui_mac"}
[dev-dependencies]
gpui = {path = "../gpui", features = ["test-support"]}
postage.workspace = true
log.workspace = true
itertools = "0.10"
smol.workspace = true
serde.workspace = true
rand.workspace = true

View file

@ -0,0 +1,5 @@
pub use current_platform::*;
#[cfg(any(test, feature = "test-support"))]
mod tests;
#[cfg(any(test, feature = "test-support"))]
use gpui_macros::test;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,67 @@
use gpui::color::Color;
use gpui::elements::{Label, LabelStyle};
use gpui::fonts::{Properties as FontProperties, TextStyle, Weight};
use gpui::text_layout::RunStyle;
use gpui::AppContext;
#[crate::test(self)]
fn test_layout_label_with_highlights(cx: &mut AppContext) {
let default_style = TextStyle::new(
"Menlo",
12.,
Default::default(),
Default::default(),
Default::default(),
Color::black(),
cx.font_cache(),
)
.unwrap();
let highlight_style = TextStyle::new(
"Menlo",
12.,
*FontProperties::new().weight(Weight::BOLD),
Default::default(),
Default::default(),
Color::new(255, 0, 0, 255),
cx.font_cache(),
)
.unwrap();
let label = Label::new(
".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(),
LabelStyle {
text: default_style.clone(),
highlight_text: Some(highlight_style.clone()),
},
)
.with_highlights(vec![
".α".len(),
".αβ".len(),
".αβγδ".len(),
".αβγδε.ⓐ".len(),
".αβγδε.ⓐⓑ".len(),
]);
let default_run_style = RunStyle {
font_id: default_style.font_id,
color: default_style.color,
underline: default_style.underline,
};
let highlight_run_style = RunStyle {
font_id: highlight_style.font_id,
color: highlight_style.color,
underline: highlight_style.underline,
};
let runs = label.compute_runs();
assert_eq!(
runs.as_slice(),
&[
(".α".len(), default_run_style),
("βγ".len(), highlight_run_style),
("δ".len(), default_run_style),
("ε".len(), highlight_run_style),
(".ⓐ".len(), default_run_style),
("ⓑⓒ".len(), highlight_run_style),
("ⓓⓔ.abcde.".len(), default_run_style),
]
);
}

View file

@ -0,0 +1,380 @@
use gpui::elements::list::ListItem;
use gpui::elements::List;
use gpui::elements::ListOffset;
use gpui::elements::ListState;
use gpui::elements::Orientation;
use gpui::geometry::rect::RectF;
use gpui::geometry::vector::Vector2F;
use gpui::serde_json;
use gpui::AnyElement;
use gpui::AppContext;
use gpui::Element;
use gpui::LayoutContext;
use gpui::SceneBuilder;
use gpui::SizeConstraint;
use gpui::View;
use gpui::ViewContext;
use gpui::{elements::Empty, geometry::vector::vec2f, Entity, PaintContext};
use rand::prelude::*;
use std::cell::RefCell;
use std::env;
use std::ops::Range;
use std::rc::Rc;
#[crate::test(self)]
fn test_layout(cx: &mut AppContext) {
cx.add_window(Default::default(), |cx| {
let mut view = TestView;
let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.));
let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)]));
let state = ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, {
let elements = elements.clone();
move |_, ix, _| {
let (id, height) = elements.borrow()[ix];
TestElement::new(id, height).into_any()
}
});
let mut list = List::new(state.clone());
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (size, _) = list.layout(constraint, &mut view, &mut layout_cx);
assert_eq!(size, vec2f(100., 40.));
assert_eq!(
state.0.borrow().items.summary().clone(),
ListItemSummary {
count: 3,
rendered_count: 3,
unrendered_count: 0,
height: 150.
}
);
state.0.borrow_mut().scroll(
&ListOffset {
item_ix: 0,
offset_in_item: 0.,
},
40.,
vec2f(0., -54.),
true,
&mut view,
cx,
);
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (_, logical_scroll_top) = list.layout(constraint, &mut view, &mut layout_cx);
assert_eq!(
logical_scroll_top,
ListOffset {
item_ix: 2,
offset_in_item: 4.
}
);
assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 54.);
elements.borrow_mut().splice(1..2, vec![(3, 40.), (4, 50.)]);
elements.borrow_mut().push((5, 60.));
state.splice(1..2, 2);
state.splice(4..4, 1);
assert_eq!(
state.0.borrow().items.summary().clone(),
ListItemSummary {
count: 5,
rendered_count: 2,
unrendered_count: 3,
height: 120.
}
);
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (size, logical_scroll_top) = list.layout(constraint, &mut view, &mut layout_cx);
assert_eq!(size, vec2f(100., 40.));
assert_eq!(
state.0.borrow().items.summary().clone(),
ListItemSummary {
count: 5,
rendered_count: 5,
unrendered_count: 0,
height: 270.
}
);
assert_eq!(
logical_scroll_top,
ListOffset {
item_ix: 3,
offset_in_item: 4.
}
);
assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 114.);
view
});
}
#[crate::test(self, iterations = 10)]
fn test_random(cx: &mut AppContext, mut rng: StdRng) {
let operations = env::var("OPERATIONS")
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
.unwrap_or(10);
cx.add_window(Default::default(), |cx| {
let mut view = TestView;
let mut next_id = 0;
let elements = Rc::new(RefCell::new(
(0..rng.gen_range(0..=20))
.map(|_| {
let id = next_id;
next_id += 1;
(id, rng.gen_range(0..=200) as f32 / 2.0)
})
.collect::<Vec<_>>(),
));
let orientation = *[Orientation::Top, Orientation::Bottom]
.choose(&mut rng)
.unwrap();
let overdraw = rng.gen_range(1..=100) as f32;
let state = ListState::new(elements.borrow().len(), orientation, overdraw, {
let elements = elements.clone();
move |_, ix, _| {
let (id, height) = elements.borrow()[ix];
TestElement::new(id, height).into_any()
}
});
let mut width = rng.gen_range(0..=2000) as f32 / 2.;
let mut height = rng.gen_range(0..=2000) as f32 / 2.;
log::info!("orientation: {:?}", orientation);
log::info!("overdraw: {}", overdraw);
log::info!("elements: {:?}", elements.borrow());
log::info!("size: ({:?}, {:?})", width, height);
log::info!("==================");
let mut last_logical_scroll_top = None;
for _ in 0..operations {
match rng.gen_range(0..=100) {
0..=29 if last_logical_scroll_top.is_some() => {
let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw));
log::info!(
"Scrolling by {:?}, previous scroll top: {:?}",
delta,
last_logical_scroll_top.unwrap()
);
state.0.borrow_mut().scroll(
last_logical_scroll_top.as_ref().unwrap(),
height,
delta,
true,
&mut view,
cx,
);
}
30..=34 => {
width = rng.gen_range(0..=2000) as f32 / 2.;
log::info!("changing width: {:?}", width);
}
35..=54 => {
height = rng.gen_range(0..=1000) as f32 / 2.;
log::info!("changing height: {:?}", height);
}
_ => {
let mut elements = elements.borrow_mut();
let end_ix = rng.gen_range(0..=elements.len());
let start_ix = rng.gen_range(0..=end_ix);
let new_elements = (0..rng.gen_range(0..10))
.map(|_| {
let id = next_id;
next_id += 1;
(id, rng.gen_range(0..=200) as f32 / 2.)
})
.collect::<Vec<_>>();
log::info!("splice({:?}, {:?})", start_ix..end_ix, new_elements);
state.splice(start_ix..end_ix, new_elements.len());
elements.splice(start_ix..end_ix, new_elements);
for (ix, item) in state.0.borrow().items.cursor::<()>().enumerate() {
if let ListItem::Rendered(element) = item {
let (expected_id, _) = elements[ix];
element.borrow().with_metadata(|metadata: Option<&usize>| {
assert_eq!(*metadata.unwrap(), expected_id);
});
}
}
}
}
let mut list = List::new(state.clone());
let window_size = vec2f(width, height);
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (size, logical_scroll_top) = list.layout(
SizeConstraint::new(vec2f(0., 0.), window_size),
&mut view,
&mut layout_cx,
);
assert_eq!(size, window_size);
last_logical_scroll_top = Some(logical_scroll_top);
let state = state.0.borrow();
log::info!("items {:?}", state.items.items(&()));
let scroll_top = state.scroll_top(&logical_scroll_top);
let rendered_top = (scroll_top - overdraw).max(0.);
let rendered_bottom = scroll_top + height + overdraw;
let mut item_top = 0.;
log::info!(
"rendered top {:?}, rendered bottom {:?}, scroll top {:?}",
rendered_top,
rendered_bottom,
scroll_top,
);
let mut first_rendered_element_top = None;
let mut last_rendered_element_bottom = None;
assert_eq!(state.items.summary().count, elements.borrow().len());
for (ix, item) in state.items.cursor::<()>().enumerate() {
match item {
ListItem::Unrendered => {
let item_bottom = item_top;
assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
item_top = item_bottom;
}
ListItem::Removed(height) => {
let (id, expected_height) = elements.borrow()[ix];
assert_eq!(
*height, expected_height,
"element {} height didn't match",
id
);
let item_bottom = item_top + height;
assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
item_top = item_bottom;
}
ListItem::Rendered(element) => {
let (expected_id, expected_height) = elements.borrow()[ix];
let element = element.borrow();
element.with_metadata(|metadata: Option<&usize>| {
assert_eq!(*metadata.unwrap(), expected_id);
});
assert_eq!(element.size().y(), expected_height);
let item_bottom = item_top + element.size().y();
first_rendered_element_top.get_or_insert(item_top);
last_rendered_element_bottom = Some(item_bottom);
assert!(item_bottom > rendered_top || item_top < rendered_bottom);
item_top = item_bottom;
}
}
}
match orientation {
Orientation::Top => {
if let Some(first_rendered_element_top) = first_rendered_element_top {
assert!(first_rendered_element_top <= scroll_top);
}
}
Orientation::Bottom => {
if let Some(last_rendered_element_bottom) = last_rendered_element_bottom {
assert!(last_rendered_element_bottom >= scroll_top + height);
}
}
}
}
view
});
}
struct TestView;
impl Entity for TestView {
type Event = ();
}
impl View for TestView {
fn ui_name() -> &'static str {
"TestView"
}
fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
Empty::new().into_any()
}
}
struct TestElement {
id: usize,
size: Vector2F,
}
impl TestElement {
fn new(id: usize, height: f32) -> Self {
Self {
id,
size: vec2f(100., height),
}
}
}
impl<V: 'static> Element<V> for TestElement {
type LayoutState = ();
type PaintState = ();
fn layout(&mut self, _: SizeConstraint, _: &mut V, _: &mut LayoutContext<V>) -> (Vector2F, ()) {
(self.size, ())
}
fn paint(
&mut self,
_: &mut SceneBuilder,
_: RectF,
_: RectF,
_: &mut (),
_: &mut V,
_: &mut PaintContext<V>,
) {
unimplemented!()
}
fn rect_for_text_range(
&self,
_: Range<usize>,
_: RectF,
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
_: &V,
_: &ViewContext<V>,
) -> Option<RectF> {
unimplemented!()
}
fn debug(&self, _: RectF, _: &(), _: &(), _: &V, _: &ViewContext<V>) -> serde_json::Value {
self.id.into()
}
fn metadata(&self) -> Option<&dyn std::any::Any> {
Some(&self.id)
}
}

View file

@ -0,0 +1,3 @@
mod label;
mod list;
mod text;

View file

@ -0,0 +1,48 @@
use gpui::elements::Text;
use gpui::geometry::vector::vec2f;
use gpui::Element;
use gpui::LayoutContext;
use gpui::SizeConstraint;
use gpui::{elements::Empty, fonts, AnyElement, AppContext, Entity, View, ViewContext};
#[crate::test(self)]
fn test_soft_wrapping_with_carriage_returns(cx: &mut AppContext) {
cx.add_window(Default::default(), |cx| {
let mut view = TestView;
fonts::with_font_cache(cx.font_cache().clone(), || {
let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true);
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (_, state) = text.layout(
SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)),
&mut view,
&mut layout_cx,
);
assert_eq!(state.shaped_lines.len(), 2);
assert_eq!(state.wrap_boundaries.len(), 2);
});
view
});
}
struct TestView;
impl Entity for TestView {
type Event = ();
}
impl View for TestView {
fn ui_name() -> &'static str {
"TestView"
}
fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
Empty::new().into_any()
}
}

View file

@ -0,0 +1,125 @@
mod app;
mod elements;
use gpui::fonts::{Properties, Weight};
use gpui::text_layout::*;
#[crate::test]
fn test_wrap_line(cx: &mut gpui::AppContext) {
let font_cache = cx.font_cache().clone();
let font_system = cx.platform().fonts();
let family = font_cache
.load_family(&["Courier"], &Default::default())
.unwrap();
let font_id = font_cache.select_font(family, &Default::default()).unwrap();
let mut wrapper = LineWrapper::new(font_id, 16., font_system);
assert_eq!(
wrapper
.wrap_line("aa bbb cccc ddddd eeee", 72.0)
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(12, 0),
Boundary::new(18, 0)
],
);
assert_eq!(
wrapper
.wrap_line("aaa aaaaaaaaaaaaaaaaaa", 72.0)
.collect::<Vec<_>>(),
&[
Boundary::new(4, 0),
Boundary::new(11, 0),
Boundary::new(18, 0)
],
);
assert_eq!(
wrapper.wrap_line(" aaaaaaa", 72.).collect::<Vec<_>>(),
&[
Boundary::new(7, 5),
Boundary::new(9, 5),
Boundary::new(11, 5),
]
);
assert_eq!(
wrapper
.wrap_line(" ", 72.)
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(14, 0),
Boundary::new(21, 0)
]
);
assert_eq!(
wrapper
.wrap_line(" aaaaaaaaaaaaaa", 72.)
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(14, 3),
Boundary::new(18, 3),
Boundary::new(22, 3),
]
);
}
#[crate::test(retries = 5)]
fn test_wrap_shaped_line(cx: &mut gpui::AppContext) {
// This is failing intermittently on CI and we don't have time to figure it out
let font_cache = cx.font_cache().clone();
let font_system = cx.platform().fonts();
let text_layout_cache = TextLayoutCache::new(font_system.clone());
let family = font_cache
.load_family(&["Helvetica"], &Default::default())
.unwrap();
let font_id = font_cache.select_font(family, &Default::default()).unwrap();
let normal = RunStyle {
font_id,
color: Default::default(),
underline: Default::default(),
};
let bold = RunStyle {
font_id: font_cache
.select_font(
family,
&Properties {
weight: Weight::BOLD,
..Default::default()
},
)
.unwrap(),
color: Default::default(),
underline: Default::default(),
};
let text = "aa bbb cccc ddddd eeee";
let line = text_layout_cache.layout_str(
text,
16.0,
&[(4, normal), (5, bold), (6, normal), (1, bold), (7, normal)],
);
let mut wrapper = LineWrapper::new(font_id, 16., font_system);
assert_eq!(
wrapper
.wrap_shaped_line(text, &line, 72.0)
.collect::<Vec<_>>(),
&[
ShapedBoundary {
run_ix: 1,
glyph_ix: 3
},
ShapedBoundary {
run_ix: 2,
glyph_ix: 3
},
ShapedBoundary {
run_ix: 4,
glyph_ix: 2
}
],
);
}

View file

@ -17,6 +17,7 @@ test-support = [
"async-trait", "async-trait",
"collections/test-support", "collections/test-support",
"gpui/test-support", "gpui/test-support",
"gpui_platform",
"live_kit_server", "live_kit_server",
"nanoid", "nanoid",
] ]
@ -24,6 +25,7 @@ test-support = [
[dependencies] [dependencies]
collections = { path = "../collections", optional = true } collections = { path = "../collections", optional = true }
gpui = { path = "../gpui", optional = true } gpui = { path = "../gpui", optional = true }
gpui_platform = {path = "../gpui_platform", optional = true}
live_kit_server = { path = "../live_kit_server", optional = true } live_kit_server = { path = "../live_kit_server", optional = true }
media = { path = "../media" } media = { path = "../media" }

View file

@ -19,3 +19,4 @@ util = { path = "../util" }
rand.workspace = true rand.workspace = true
util = { path = "../util", features = ["test-support"] } util = { path = "../util", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] }
gpui_platform = {path = "../gpui_platform" }

View file

@ -10,12 +10,13 @@ path = "src/rpc.rs"
doctest = false doctest = false
[features] [features]
test-support = ["collections/test-support", "gpui/test-support"] test-support = ["collections/test-support", "gpui/test-support", "gpui_platform"]
[dependencies] [dependencies]
clock = { path = "../clock" } clock = { path = "../clock" }
collections = { path = "../collections" } collections = { path = "../collections" }
gpui = { path = "../gpui", optional = true } gpui = { path = "../gpui", optional = true }
gpui_platform = {path = "../gpui_platform", optional = true}
util = { path = "../util" } util = { path = "../util" }
anyhow.workspace = true anyhow.workspace = true
async-lock = "2.4" async-lock = "2.4"

View file

@ -9,11 +9,12 @@ path = "src/settings.rs"
doctest = false doctest = false
[features] [features]
test-support = ["gpui/test-support", "fs/test-support"] test-support = ["gpui/test-support", "fs/test-support", "gpui_platform"]
[dependencies] [dependencies]
collections = { path = "../collections" } collections = { path = "../collections" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
gpui_platform = { path = "../gpui_platform", optional = true}
sqlez = { path = "../sqlez" } sqlez = { path = "../sqlez" }
fs = { path = "../fs" } fs = { path = "../fs" }
feature_flags = { path = "../feature_flags" } feature_flags = { path = "../feature_flags" }

View file

@ -7,6 +7,7 @@ publish = false
[features] [features]
test-support = [ test-support = [
"gpui/test-support", "gpui/test-support",
"gpui_platform",
"fs/test-support", "fs/test-support",
"settings/test-support" "settings/test-support"
] ]
@ -17,6 +18,7 @@ doctest = false
[dependencies] [dependencies]
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
gpui_platform = {path = "../gpui_platform", optional = true}
fs = { path = "../fs" } fs = { path = "../fs" }
settings = { path = "../settings" } settings = { path = "../settings" }
util = { path = "../util" } util = { path = "../util" }

View file

@ -28,6 +28,7 @@ context_menu = { path = "../context_menu" }
drag_and_drop = { path = "../drag_and_drop" } drag_and_drop = { path = "../drag_and_drop" }
fs = { path = "../fs" } fs = { path = "../fs" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
gpui_platform = { path = "../gpui_platform", optional = true }
install_cli = { path = "../install_cli" } install_cli = { path = "../install_cli" }
language = { path = "../language" } language = { path = "../language" }
menu = { path = "../menu" } menu = { path = "../menu" }

View file

@ -80,7 +80,7 @@ impl View for SharedScreen {
vec2f(frame.width() as f32, frame.height() as f32), vec2f(frame.width() as f32, frame.height() as f32),
); );
let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.; let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.;
scene.push_surface(gpui::platform::mac::Surface { scene.push_surface(gpui::scene::Surface {
bounds: RectF::new(origin, size), bounds: RectF::new(origin, size),
image_buffer: frame.image(), image_buffer: frame.image(),
}); });

View file

@ -72,6 +72,7 @@ vim = { path = "../vim" }
workspace = { path = "../workspace" } workspace = { path = "../workspace" }
welcome = { path = "../welcome" } welcome = { path = "../welcome" }
zed-actions = {path = "../zed-actions"} zed-actions = {path = "../zed-actions"}
gpui_platform = {path = "../gpui_platform"}
anyhow.workspace = true anyhow.workspace = true
async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] } async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
async-tar = "0.4.2" async-tar = "0.4.2"

View file

@ -53,6 +53,7 @@ use uuid::Uuid;
use welcome::{show_welcome_experience, FIRST_OPEN}; use welcome::{show_welcome_experience, FIRST_OPEN};
use fs::RealFs; use fs::RealFs;
use gpui_platform::{foreground_platform, platform};
use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
use workspace::AppState; use workspace::AppState;
use zed::{ use zed::{
@ -60,7 +61,6 @@ use zed::{
build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus, build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus,
only_instance::{ensure_only_instance, IsOnlyInstance}, only_instance::{ensure_only_instance, IsOnlyInstance},
}; };
fn main() { fn main() {
let http = http::client(); let http = http::client();
init_paths(); init_paths();
@ -71,7 +71,11 @@ fn main() {
} }
log::info!("========== starting zed =========="); log::info!("========== starting zed ==========");
let mut app = gpui::App::new(Assets).unwrap(); let platform = platform();
let foreground =
std::rc::Rc::new(gpui::executor::Foreground::platform(platform.dispatcher()).unwrap());
let fplatform = foreground_platform(foreground);
let mut app = gpui::App::new(Assets, platform, fplatform).unwrap();
let installation_id = app.background().block(installation_id()).ok(); let installation_id = app.background().block(installation_id()).ok();
init_panic_hook(&app, installation_id.clone()); init_panic_hook(&app, installation_id.clone());