Detailed changes
@@ -10,7 +10,7 @@ dependencies = [
"auto_update",
"editor",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"language",
"project",
"settings",
@@ -82,7 +82,7 @@ dependencies = [
"async-trait",
"bincode",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"isahc",
"language",
"lazy_static",
@@ -312,7 +312,7 @@ dependencies = [
"env_logger",
"fs",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"indoc",
"isahc",
"language",
@@ -662,7 +662,7 @@ dependencies = [
"anyhow",
"collections",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"log",
"parking_lot 0.11.2",
"rodio",
@@ -676,7 +676,7 @@ dependencies = [
"anyhow",
"client",
"db",
- "gpui2",
+ "gpui",
"isahc",
"lazy_static",
"log",
@@ -1010,7 +1010,7 @@ version = "0.1.0"
dependencies = [
"collections",
"editor",
- "gpui2",
+ "gpui",
"itertools 0.10.5",
"language",
"outline",
@@ -1111,7 +1111,7 @@ dependencies = [
"collections",
"fs",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"image",
"language",
"live_kit_client",
@@ -1200,7 +1200,7 @@ dependencies = [
"db",
"feature_flags",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"image",
"language",
"lazy_static",
@@ -1374,7 +1374,7 @@ dependencies = [
"db",
"feature_flags",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"image",
"lazy_static",
"log",
@@ -1470,7 +1470,7 @@ dependencies = [
"fs",
"futures 0.3.28",
"git",
- "gpui2",
+ "gpui",
"hyper",
"indoc",
"language",
@@ -1534,7 +1534,7 @@ dependencies = [
"feedback",
"futures 0.3.28",
"fuzzy",
- "gpui2",
+ "gpui",
"language",
"lazy_static",
"log",
@@ -1603,7 +1603,7 @@ dependencies = [
"env_logger",
"fuzzy",
"go_to_line",
- "gpui2",
+ "gpui",
"language",
"menu",
"picker",
@@ -1702,7 +1702,7 @@ dependencies = [
"collections",
"fs",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"language",
"log",
"lsp",
@@ -1727,7 +1727,7 @@ dependencies = [
"editor",
"fs",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"language",
"settings",
"smol",
@@ -2127,7 +2127,7 @@ dependencies = [
"async-trait",
"collections",
"env_logger",
- "gpui2",
+ "gpui",
"indoc",
"lazy_static",
"log",
@@ -2229,7 +2229,7 @@ dependencies = [
"collections",
"editor",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"language",
"log",
"lsp",
@@ -2397,7 +2397,7 @@ dependencies = [
"futures 0.3.28",
"fuzzy",
"git",
- "gpui2",
+ "gpui",
"indoc",
"itertools 0.10.5",
"language",
@@ -2606,7 +2606,7 @@ name = "feature_flags"
version = "0.1.0"
dependencies = [
"anyhow",
- "gpui2",
+ "gpui",
]
[[package]]
@@ -2619,7 +2619,7 @@ dependencies = [
"db",
"editor",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"human_bytes",
"isahc",
"language",
@@ -2653,7 +2653,7 @@ dependencies = [
"editor",
"env_logger",
"fuzzy",
- "gpui2",
+ "gpui",
"language",
"menu",
"picker",
@@ -2822,7 +2822,7 @@ dependencies = [
"fsevent",
"futures 0.3.28",
"git2",
- "gpui2",
+ "gpui",
"lazy_static",
"libc",
"log",
@@ -3014,7 +3014,7 @@ dependencies = [
name = "fuzzy"
version = "0.1.0"
dependencies = [
- "gpui2",
+ "gpui",
"util",
]
@@ -3149,7 +3149,7 @@ name = "go_to_line"
version = "0.1.0"
dependencies = [
"editor",
- "gpui2",
+ "gpui",
"menu",
"postage",
"serde",
@@ -3164,68 +3164,6 @@ dependencies = [
[[package]]
name = "gpui"
version = "0.1.0"
-dependencies = [
- "anyhow",
- "async-task",
- "backtrace",
- "bindgen 0.65.1",
- "block",
- "cc",
- "cocoa",
- "collections",
- "core-foundation",
- "core-graphics",
- "core-text",
- "ctor",
- "derive_more",
- "dhat",
- "env_logger",
- "etagere",
- "font-kit",
- "foreign-types",
- "futures 0.3.28",
- "gpui_macros",
- "image",
- "itertools 0.10.5",
- "lazy_static",
- "log",
- "media",
- "metal",
- "num_cpus",
- "objc",
- "ordered-float 2.10.0",
- "parking",
- "parking_lot 0.11.2",
- "pathfinder_color",
- "pathfinder_geometry",
- "png",
- "postage",
- "rand 0.8.5",
- "refineable",
- "resvg",
- "schemars",
- "seahash",
- "serde",
- "serde_derive",
- "serde_json",
- "simplelog",
- "smallvec",
- "smol",
- "sqlez",
- "sum_tree",
- "taffy 0.3.11 (git+https://github.com/DioxusLabs/taffy?rev=4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e)",
- "thiserror",
- "time",
- "tiny-skia",
- "usvg",
- "util",
- "uuid 1.4.1",
- "waker-fn",
-]
-
-[[package]]
-name = "gpui2"
-version = "0.1.0"
dependencies = [
"anyhow",
"async-task",
@@ -3277,7 +3215,7 @@ dependencies = [
"smol",
"sqlez",
"sum_tree",
- "taffy 0.3.11 (git+https://github.com/DioxusLabs/taffy?rev=1876f72bee5e376023eaa518aa7b8a34c769bd1b)",
+ "taffy",
"thiserror",
"time",
"tiny-skia",
@@ -3306,12 +3244,6 @@ dependencies = [
"syn 1.0.109",
]
-[[package]]
-name = "grid"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eec1c01eb1de97451ee0d60de7d81cf1e72aabefb021616027f3d1c3ec1c723c"
-
[[package]]
name = "grid"
version = "0.11.0"
@@ -3694,7 +3626,7 @@ name = "install_cli"
version = "0.1.0"
dependencies = [
"anyhow",
- "gpui2",
+ "gpui",
"log",
"smol",
"util",
@@ -3847,7 +3779,7 @@ dependencies = [
"chrono",
"dirs 4.0.0",
"editor",
- "gpui2",
+ "gpui",
"log",
"schemars",
"serde",
@@ -3940,7 +3872,7 @@ dependencies = [
"fuzzy",
"git",
"globset",
- "gpui2",
+ "gpui",
"indoc",
"lazy_static",
"log",
@@ -3985,7 +3917,7 @@ dependencies = [
"anyhow",
"editor",
"fuzzy",
- "gpui2",
+ "gpui",
"language",
"picker",
"project",
@@ -4006,7 +3938,7 @@ dependencies = [
"editor",
"env_logger",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"language",
"lsp",
"project",
@@ -4181,7 +4113,7 @@ dependencies = [
"core-graphics",
"foreign-types",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"hmac 0.12.1",
"jwt",
"live_kit_server",
@@ -4247,7 +4179,7 @@ dependencies = [
"ctor",
"env_logger",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"log",
"lsp-types",
"parking_lot 0.11.2",
@@ -4399,7 +4331,7 @@ dependencies = [
name = "menu"
version = "0.1.0"
dependencies = [
- "gpui2",
+ "gpui",
"serde",
]
@@ -4569,7 +4501,7 @@ dependencies = [
"env_logger",
"futures 0.3.28",
"git",
- "gpui2",
+ "gpui",
"indoc",
"itertools 0.10.5",
"language",
@@ -4761,7 +4693,7 @@ dependencies = [
"collections",
"db",
"feature_flags",
- "gpui2",
+ "gpui",
"rpc",
"settings",
"sum_tree",
@@ -5162,7 +5094,7 @@ version = "0.1.0"
dependencies = [
"editor",
"fuzzy",
- "gpui2",
+ "gpui",
"language",
"ordered-float 2.10.0",
"picker",
@@ -5386,7 +5318,7 @@ dependencies = [
"ctor",
"editor",
"env_logger",
- "gpui2",
+ "gpui",
"menu",
"parking_lot 0.11.2",
"serde_json",
@@ -5570,7 +5502,7 @@ dependencies = [
"collections",
"fs",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"language",
"log",
"lsp",
@@ -5687,7 +5619,7 @@ dependencies = [
"git",
"git2",
"globset",
- "gpui2",
+ "gpui",
"ignore",
"itertools 0.10.5",
"language",
@@ -5730,7 +5662,7 @@ dependencies = [
"db",
"editor",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"language",
"menu",
"postage",
@@ -5758,7 +5690,7 @@ dependencies = [
"editor",
"futures 0.3.28",
"fuzzy",
- "gpui2",
+ "gpui",
"language",
"lsp",
"ordered-float 2.10.0",
@@ -5935,7 +5867,7 @@ version = "0.1.0"
dependencies = [
"assistant",
"editor",
- "gpui2",
+ "gpui",
"search",
"ui",
"workspace",
@@ -6109,7 +6041,7 @@ dependencies = [
"editor",
"futures 0.3.28",
"fuzzy",
- "gpui2",
+ "gpui",
"language",
"ordered-float 2.10.0",
"picker",
@@ -6306,7 +6238,7 @@ dependencies = [
"anyhow",
"collections",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"language",
"lazy_static",
"pulldown-cmark",
@@ -6397,7 +6329,7 @@ version = "0.1.0"
dependencies = [
"arrayvec 0.7.4",
"bromberg_sl2",
- "gpui2",
+ "gpui",
"log",
"rand 0.8.5",
"smallvec",
@@ -6427,7 +6359,7 @@ dependencies = [
"ctor",
"env_logger",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"parking_lot 0.11.2",
"prost 0.8.0",
"prost-build",
@@ -6887,7 +6819,7 @@ dependencies = [
"collections",
"editor",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"language",
"log",
"menu",
@@ -6943,7 +6875,7 @@ dependencies = [
"env_logger",
"futures 0.3.28",
"globset",
- "gpui2",
+ "gpui",
"language",
"lazy_static",
"log",
@@ -7110,7 +7042,7 @@ dependencies = [
"feature_flags",
"fs",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"indoc",
"lazy_static",
"postage",
@@ -7714,7 +7646,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
name = "story"
version = "0.1.0"
dependencies = [
- "gpui2",
+ "gpui",
"itertools 0.10.5",
"smallvec",
]
@@ -7730,7 +7662,7 @@ dependencies = [
"dialoguer",
"editor",
"fuzzy",
- "gpui2",
+ "gpui",
"indoc",
"itertools 0.11.0",
"language",
@@ -7957,18 +7889,7 @@ version = "0.3.11"
source = "git+https://github.com/DioxusLabs/taffy?rev=1876f72bee5e376023eaa518aa7b8a34c769bd1b#1876f72bee5e376023eaa518aa7b8a34c769bd1b"
dependencies = [
"arrayvec 0.7.4",
- "grid 0.11.0",
- "num-traits",
- "slotmap",
-]
-
-[[package]]
-name = "taffy"
-version = "0.3.11"
-source = "git+https://github.com/DioxusLabs/taffy?rev=4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e#4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e"
-dependencies = [
- "arrayvec 0.7.4",
- "grid 0.10.0",
+ "grid",
"num-traits",
"slotmap",
]
@@ -8032,7 +7953,7 @@ dependencies = [
"db",
"dirs 4.0.0",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"itertools 0.10.5",
"lazy_static",
"libc",
@@ -8062,7 +7983,7 @@ dependencies = [
"dirs 4.0.0",
"editor",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"itertools 0.10.5",
"language",
"lazy_static",
@@ -8096,7 +8017,7 @@ dependencies = [
"ctor",
"digest 0.9.0",
"env_logger",
- "gpui2",
+ "gpui",
"lazy_static",
"log",
"parking_lot 0.11.2",
@@ -8121,7 +8042,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"fs",
- "gpui2",
+ "gpui",
"indexmap 1.9.3",
"itertools 0.11.0",
"parking_lot 0.11.2",
@@ -8145,7 +8066,7 @@ dependencies = [
"anyhow",
"clap 4.4.4",
"convert_case 0.6.0",
- "gpui2",
+ "gpui",
"indexmap 1.9.3",
"json_comments",
"log",
@@ -8168,7 +8089,7 @@ dependencies = [
"feature_flags",
"fs",
"fuzzy",
- "gpui2",
+ "gpui",
"log",
"parking_lot 0.11.2",
"picker",
@@ -8985,7 +8906,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
- "gpui2",
+ "gpui",
"itertools 0.11.0",
"menu",
"rand 0.8.5",
@@ -9238,7 +9159,7 @@ dependencies = [
"anyhow",
"fs",
"fuzzy",
- "gpui2",
+ "gpui",
"picker",
"ui",
"util",
@@ -9263,7 +9184,7 @@ dependencies = [
"diagnostics",
"editor",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"indoc",
"itertools 0.10.5",
"language",
@@ -9682,7 +9603,7 @@ dependencies = [
"editor",
"fs",
"fuzzy",
- "gpui2",
+ "gpui",
"install_cli",
"log",
"picker",
@@ -9951,7 +9872,7 @@ dependencies = [
"env_logger",
"fs",
"futures 0.3.28",
- "gpui2",
+ "gpui",
"indoc",
"install_cli",
"itertools 0.10.5",
@@ -10091,7 +10012,7 @@ dependencies = [
"fsevent",
"futures 0.3.28",
"go_to_line",
- "gpui2",
+ "gpui",
"ignore",
"image",
"indexmap 1.9.3",
@@ -10187,7 +10108,7 @@ dependencies = [
name = "zed_actions"
version = "0.1.0"
dependencies = [
- "gpui2",
+ "gpui",
"serde",
]
@@ -35,7 +35,7 @@ members = [
"crates/go_to_line",
"crates/gpui",
"crates/gpui_macros",
- "crates/gpui2",
+ "crates/gpui",
"crates/gpui2_macros",
"crates/install_cli",
"crates/journal",
@@ -12,7 +12,7 @@ doctest = false
auto_update = { path = "../auto_update" }
editor = { path = "../editor" }
language = { path = "../language" }
-gpui = { path = "../gpui2", package = "gpui2" }
+gpui = { path = "../gpui" }
project = { path = "../project" }
settings = { path = "../settings" }
ui = { path = "../ui" }
@@ -12,7 +12,7 @@ doctest = false
test-support = []
[dependencies]
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
util = { path = "../util" }
language = { path = "../language" }
async-trait.workspace = true
@@ -35,4 +35,4 @@ rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
bincode = "1.3.3"
[dev-dependencies]
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
@@ -14,7 +14,7 @@ client = { path = "../client" }
collections = { path = "../collections"}
editor = { path = "../editor" }
fs = { path = "../fs" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
language = { path = "../language" }
menu = { path = "../menu" }
multi_buffer = { path = "../multi_buffer" }
@@ -9,7 +9,7 @@ path = "src/audio.rs"
doctest = false
[dependencies]
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
collections = { path = "../collections" }
util = { path = "../util" }
@@ -11,7 +11,7 @@ doctest = false
[dependencies]
db = { path = "../db" }
client = { path = "../client" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
menu = { path = "../menu" }
project = { path = "../project" }
settings = { path = "../settings" }
@@ -11,7 +11,7 @@ doctest = false
[dependencies]
collections = { path = "../collections" }
editor = { path = "../editor" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
ui = { path = "../ui" }
language = { path = "../language" }
project = { path = "../project" }
@@ -24,5 +24,5 @@ itertools = "0.10"
[dev-dependencies]
editor = { path = "../editor", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
workspace = { path = "../workspace", features = ["test-support"] }
@@ -22,7 +22,7 @@ test-support = [
audio = { path = "../audio" }
client = { path = "../client" }
collections = { path = "../collections" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
log.workspace = true
live_kit_client = { path = "../live_kit_client" }
fs = { path = "../fs" }
@@ -48,7 +48,7 @@ client = { path = "../client", features = ["test-support"] }
fs = { path = "../fs", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
live_kit_client = { path = "../live_kit_client", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
@@ -15,7 +15,7 @@ test-support = ["collections/test-support", "gpui/test-support", "rpc/test-suppo
client = { path = "../client" }
collections = { path = "../collections" }
db = { path = "../db" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
util = { path = "../util" }
rpc = { path = "../rpc" }
text = { path = "../text" }
@@ -47,7 +47,7 @@ tempfile = "3"
[dev-dependencies]
collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
rpc = { path = "../rpc", features = ["test-support"] }
client = { path = "../client", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
@@ -15,7 +15,7 @@ test-support = ["collections/test-support", "gpui/test-support", "rpc/test-suppo
chrono = { version = "0.4", features = ["serde"] }
collections = { path = "../collections" }
db = { path = "../db" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
util = { path = "../util" }
rpc = { path = "../rpc" }
text = { path = "../text" }
@@ -47,7 +47,7 @@ url = "2.2"
[dev-dependencies]
collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
rpc = { path = "../rpc", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
@@ -62,7 +62,7 @@ uuid.workspace = true
[dev-dependencies]
audio = { path = "../audio" }
collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
call = { path = "../call", features = ["test-support"] }
client = { path = "../client", features = ["test-support"] }
channel = { path = "../channel" }
@@ -0,0 +1,99 @@
+[package]
+authors = ["Nathan Sobo <nathan@zed.dev>"]
+default-run = "collab"
+edition = "2021"
+name = "collab"
+version = "0.28.0"
+publish = false
+
+[[bin]]
+name = "collab"
+
+[[bin]]
+name = "seed"
+required-features = ["seed-support"]
+
+[dependencies]
+clock = { path = "../clock" }
+collections = { path = "../collections" }
+live_kit_server = { path = "../live_kit_server" }
+text = { path = "../text" }
+rpc = { path = "../rpc" }
+util = { path = "../util" }
+
+anyhow.workspace = true
+async-tungstenite = "0.16"
+axum = { version = "0.5", features = ["json", "headers", "ws"] }
+axum-extra = { version = "0.3", features = ["erased-json"] }
+base64 = "0.13"
+clap = { version = "3.1", features = ["derive"], optional = true }
+dashmap = "5.4"
+envy = "0.4.2"
+futures.workspace = true
+hyper = "0.14"
+lazy_static.workspace = true
+lipsum = { version = "0.8", optional = true }
+log.workspace = true
+nanoid = "0.4"
+parking_lot.workspace = true
+prometheus = "0.13"
+prost.workspace = true
+rand.workspace = true
+reqwest = { version = "0.11", features = ["json"], optional = true }
+scrypt = "0.7"
+smallvec.workspace = true
+sea-orm = { version = "0.12.x", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] }
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
+sha-1 = "0.9"
+sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "json", "time", "uuid", "any"] }
+time.workspace = true
+tokio = { version = "1", features = ["full"] }
+tokio-tungstenite = "0.17"
+tonic = "0.6"
+tower = "0.4"
+toml.workspace = true
+tracing = "0.1.34"
+tracing-log = "0.1.3"
+tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] }
+uuid.workspace = true
+
+[dev-dependencies]
+audio = { path = "../audio" }
+collections = { path = "../collections", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
+call = { path = "../call", features = ["test-support"] }
+client = { path = "../client", features = ["test-support"] }
+channel = { path = "../channel" }
+editor = { path = "../editor", features = ["test-support"] }
+language = { path = "../language", features = ["test-support"] }
+fs = { path = "../fs", features = ["test-support"] }
+git = { path = "../git", features = ["test-support"] }
+live_kit_client = { path = "../live_kit_client", features = ["test-support"] }
+lsp = { path = "../lsp", features = ["test-support"] }
+node_runtime = { path = "../node_runtime" }
+notifications = { path = "../notifications", features = ["test-support"] }
+
+project = { path = "../project", features = ["test-support"] }
+rpc = { path = "../rpc", features = ["test-support"] }
+settings = { path = "../settings", features = ["test-support"] }
+theme = { path = "../theme" }
+workspace = { path = "../workspace", features = ["test-support"] }
+
+collab_ui = { path = "../collab_ui", features = ["test-support"] }
+
+async-trait.workspace = true
+pretty_assertions.workspace = true
+ctor.workspace = true
+env_logger.workspace = true
+indoc.workspace = true
+util = { path = "../util" }
+lazy_static.workspace = true
+sea-orm = { version = "0.12.x", features = ["sqlx-sqlite"] }
+serde_json.workspace = true
+sqlx = { version = "0.7", features = ["sqlite"] }
+unindent.workspace = true
+
+[features]
+seed-support = ["clap", "lipsum", "reqwest"]
@@ -34,7 +34,7 @@ collections = { path = "../collections" }
editor = { path = "../editor" }
feedback = { path = "../feedback" }
fuzzy = { path = "../fuzzy" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
language = { path = "../language" }
menu = { path = "../menu" }
notifications = { path = "../notifications" }
@@ -69,7 +69,7 @@ call = { path = "../call", features = ["test-support"] }
client = { path = "../client", features = ["test-support"] }
collections = { path = "../collections", features = ["test-support"] }
editor = { path = "../editor", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
notifications = { path = "../notifications", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
rpc = { path = "../rpc", features = ["test-support"] }
@@ -12,7 +12,7 @@ doctest = false
collections = { path = "../collections" }
editor = { path = "../editor" }
fuzzy = { path = "../fuzzy" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
picker = { path = "../picker" }
project = { path = "../project" }
settings = { path = "../settings" }
@@ -25,7 +25,7 @@ anyhow.workspace = true
serde.workspace = true
[dev-dependencies]
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
editor = { path = "../editor", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
@@ -21,7 +21,7 @@ test-support = [
[dependencies]
collections = { path = "../collections" }
# context_menu = { path = "../context_menu" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
language = { path = "../language" }
settings = { path = "../settings" }
theme = { path = "../theme" }
@@ -43,7 +43,7 @@ parking_lot.workspace = true
clock = { path = "../clock" }
collections = { path = "../collections", features = ["test-support"] }
fs = { path = "../fs", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
rpc = { path = "../rpc", features = ["test-support"] }
@@ -13,7 +13,7 @@ copilot = { path = "../copilot" }
editor = { path = "../editor" }
fs = { path = "../fs" }
zed_actions = { path = "../zed_actions"}
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
language = { path = "../language" }
settings = { path = "../settings" }
theme = { path = "../theme" }
@@ -13,7 +13,7 @@ test-support = []
[dependencies]
collections = { path = "../collections" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
sqlez = { path = "../sqlez" }
sqlez_macros = { path = "../sqlez_macros" }
util = { path = "../util" }
@@ -28,6 +28,6 @@ serde_derive.workspace = true
smol.workspace = true
[dev-dependencies]
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
env_logger.workspace = true
tempdir.workspace = true
@@ -11,7 +11,7 @@ doctest = false
[dependencies]
collections = { path = "../collections" }
editor = { path = "../editor" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
ui = { path = "../ui" }
language = { path = "../language" }
lsp = { path = "../lsp" }
@@ -35,7 +35,7 @@ client = { path = "../client", features = ["test-support"] }
editor = { path = "../editor", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
workspace = {path = "../workspace", features = ["test-support"] }
theme = { path = "../theme", features = ["test-support"] }
@@ -31,7 +31,7 @@ collections = { path = "../collections" }
# context_menu = { path = "../context_menu" }
fuzzy = { path = "../fuzzy" }
git = { path = "../git" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
language = { path = "../language" }
lsp = { path = "../lsp" }
multi_buffer = { path = "../multi_buffer" }
@@ -76,7 +76,7 @@ copilot = { path = "../copilot", features = ["test-support"] }
text = { path = "../text", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
@@ -8,5 +8,5 @@ publish = false
path = "src/feature_flags.rs"
[dependencies]
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
anyhow.workspace = true
@@ -14,7 +14,7 @@ test-support = []
client = { path = "../client" }
db = { path = "../db" }
editor = { path = "../editor" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
language = { path = "../language" }
menu = { path = "../menu" }
project = { path = "../project" }
@@ -12,7 +12,7 @@ doctest = false
editor = { path = "../editor" }
collections = { path = "../collections" }
fuzzy = { path = "../fuzzy" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
menu = { path = "../menu" }
picker = { path = "../picker" }
project = { path = "../project" }
@@ -27,7 +27,7 @@ serde.workspace = true
[dev-dependencies]
editor = { path = "../editor", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
workspace = { path = "../workspace", features = ["test-support"] }
theme = { path = "../theme", features = ["test-support"] }
@@ -31,10 +31,10 @@ log.workspace = true
libc = "0.2"
time.workspace = true
-gpui = { package = "gpui2", path = "../gpui2", optional = true}
+gpui = { path = "../gpui", optional = true}
[dev-dependencies]
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
[features]
test-support = ["gpui/test-support"]
@@ -9,5 +9,5 @@ path = "src/fuzzy.rs"
doctest = false
[dependencies]
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
util = { path = "../util" }
@@ -10,7 +10,7 @@ doctest = false
[dependencies]
editor = { path = "../editor" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
menu = { path = "../menu" }
serde.workspace = true
settings = { path = "../settings" }
@@ -1,27 +1,28 @@
[package]
-authors = ["Nathan Sobo <nathansobo@gmail.com>"]
-edition = "2021"
name = "gpui"
version = "0.1.0"
-description = "A GPU-accelerated UI framework"
+edition = "2021"
+authors = ["Nathan Sobo <nathan@zed.dev>"]
+description = "The next version of Zed's GPU-accelerated UI framework"
publish = false
+[features]
+test-support = ["backtrace", "dhat", "env_logger", "collections/test-support", "util/test-support"]
+
[lib]
path = "src/gpui.rs"
doctest = false
-[features]
-test-support = ["backtrace", "dhat", "env_logger", "collections/test-support", "util/test-support"]
-
[dependencies]
collections = { path = "../collections" }
-gpui_macros = { path = "../gpui_macros" }
+gpui2_macros = { path = "../gpui2_macros" }
util = { path = "../util" }
sum_tree = { path = "../sum_tree" }
sqlez = { path = "../sqlez" }
async-task = "4.0.3"
backtrace = { version = "0.3", optional = true }
ctor.workspace = true
+linkme = "0.3"
derive_more.workspace = true
dhat = { version = "0.3", optional = true }
env_logger = { version = "0.9", optional = true }
@@ -35,30 +36,27 @@ num_cpus = "1.13"
ordered-float.workspace = true
parking = "2.0.0"
parking_lot.workspace = true
-pathfinder_color = "0.5"
pathfinder_geometry = "0.5"
postage.workspace = true
rand.workspace = true
refineable.workspace = true
resvg = "0.14"
-schemars = "0.8"
seahash = "4.1"
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
smallvec.workspace = true
smol.workspace = true
-taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e" }
+taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "1876f72bee5e376023eaa518aa7b8a34c769bd1b" }
thiserror.workspace = true
time.workspace = true
tiny-skia = "0.5"
usvg = { version = "0.14", features = [] }
-uuid.workspace = true
+uuid = { version = "1.1.2", features = ["v4"] }
waker-fn = "1.1.0"
-
-[build-dependencies]
-bindgen = "0.65.1"
-cc = "1.0.67"
+slotmap = "1.0.6"
+schemars.workspace = true
+bitflags = "2.4.0"
[dev-dependencies]
backtrace = "0.3"
@@ -69,6 +67,10 @@ png = "0.16"
simplelog = "0.9"
util = { path = "../util", features = ["test-support"] }
+[build-dependencies]
+bindgen = "0.65.1"
+cbindgen = "0.26.0"
+
[target.'cfg(target_os = "macos")'.dependencies]
media = { path = "../media" }
anyhow.workspace = true
@@ -1,13 +1,15 @@
use std::{
env,
- path::PathBuf,
+ path::{Path, PathBuf},
process::{self, Command},
};
+use cbindgen::Config;
+
fn main() {
generate_dispatch_bindings();
- compile_metal_shaders();
- generate_shader_bindings();
+ let header_path = generate_shader_bindings();
+ compile_metal_shaders(&header_path);
}
fn generate_dispatch_bindings() {
@@ -17,7 +19,12 @@ fn generate_dispatch_bindings() {
let bindings = bindgen::Builder::default()
.header("src/platform/mac/dispatch.h")
.allowlist_var("_dispatch_main_q")
+ .allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
+ .allowlist_var("DISPATCH_TIME_NOW")
+ .allowlist_function("dispatch_get_global_queue")
.allowlist_function("dispatch_async_f")
+ .allowlist_function("dispatch_after_f")
+ .allowlist_function("dispatch_time")
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.layout_tests(false)
.generate()
@@ -29,14 +36,61 @@ fn generate_dispatch_bindings() {
.expect("couldn't write dispatch bindings");
}
-const SHADER_HEADER_PATH: &str = "./src/platform/mac/shaders/shaders.h";
+fn generate_shader_bindings() -> PathBuf {
+ let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
+ let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
+ let mut config = Config::default();
+ config.include_guard = Some("SCENE_H".into());
+ config.language = cbindgen::Language::C;
+ config.export.include.extend([
+ "Bounds".into(),
+ "Corners".into(),
+ "Edges".into(),
+ "Size".into(),
+ "Pixels".into(),
+ "PointF".into(),
+ "Hsla".into(),
+ "ContentMask".into(),
+ "Uniforms".into(),
+ "AtlasTile".into(),
+ "PathRasterizationInputIndex".into(),
+ "PathVertex_ScaledPixels".into(),
+ "ShadowInputIndex".into(),
+ "Shadow".into(),
+ "QuadInputIndex".into(),
+ "Underline".into(),
+ "UnderlineInputIndex".into(),
+ "Quad".into(),
+ "SpriteInputIndex".into(),
+ "MonochromeSprite".into(),
+ "PolychromeSprite".into(),
+ "PathSprite".into(),
+ "SurfaceInputIndex".into(),
+ "SurfaceBounds".into(),
+ ]);
+ config.no_includes = true;
+ config.enumeration.prefix_with_name = true;
+ cbindgen::Builder::new()
+ .with_src(crate_dir.join("src/scene.rs"))
+ .with_src(crate_dir.join("src/geometry.rs"))
+ .with_src(crate_dir.join("src/color.rs"))
+ .with_src(crate_dir.join("src/window.rs"))
+ .with_src(crate_dir.join("src/platform.rs"))
+ .with_src(crate_dir.join("src/platform/mac/metal_renderer.rs"))
+ .with_config(config)
+ .generate()
+ .expect("Unable to generate bindings")
+ .write_to_file(&output_path);
+
+ output_path
+}
-fn compile_metal_shaders() {
- let shader_path = "./src/platform/mac/shaders/shaders.metal";
+fn compile_metal_shaders(header_path: &Path) {
+ let shader_path = "./src/platform/mac/shaders.metal";
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");
- println!("cargo:rerun-if-changed={}", SHADER_HEADER_PATH);
+ println!("cargo:rerun-if-changed={}", header_path.display());
println!("cargo:rerun-if-changed={}", shader_path);
let output = Command::new("xcrun")
@@ -49,6 +103,8 @@ fn compile_metal_shaders() {
"-MO",
"-c",
shader_path,
+ "-include",
+ &header_path.to_str().unwrap(),
"-o",
])
.arg(&air_output_path)
@@ -79,18 +135,3 @@ fn compile_metal_shaders() {
process::exit(1);
}
}
-
-fn generate_shader_bindings() {
- let bindings = bindgen::Builder::default()
- .header(SHADER_HEADER_PATH)
- .allowlist_type("GPUI.*")
- .parse_callbacks(Box::new(bindgen::CargoCallbacks))
- .layout_tests(false)
- .generate()
- .expect("unable to generate bindings");
-
- let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
- bindings
- .write_to_file(out_path.join("shaders.rs"))
- .expect("couldn't write shader bindings");
-}
@@ -1,237 +0,0 @@
-use button_component::Button;
-
-use gpui::{
- color::Color,
- elements::{ContainerStyle, Flex, Label, ParentElement, StatefulComponent},
- fonts::{self, TextStyle},
- platform::WindowOptions,
- AnyElement, App, Element, Entity, View, ViewContext,
-};
-use log::LevelFilter;
-use pathfinder_geometry::vector::vec2f;
-use simplelog::SimpleLogger;
-use theme::Toggleable;
-use toggleable_button::ToggleableButton;
-
-// cargo run -p gpui --example components
-
-fn main() {
- SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
-
- App::new(()).unwrap().run(|cx| {
- cx.platform().activate(true);
- cx.add_window(WindowOptions::with_bounds(vec2f(300., 200.)), |_| {
- TestView {
- count: 0,
- is_doubling: false,
- }
- });
- });
-}
-
-pub struct TestView {
- count: usize,
- is_doubling: bool,
-}
-
-impl TestView {
- fn increase_count(&mut self) {
- if self.is_doubling {
- self.count *= 2;
- } else {
- self.count += 1;
- }
- }
-}
-
-impl Entity for TestView {
- type Event = ();
-}
-
-type ButtonStyle = ContainerStyle;
-
-impl View for TestView {
- fn ui_name() -> &'static str {
- "TestView"
- }
-
- fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self> {
- fonts::with_font_cache(cx.font_cache.to_owned(), || {
- Flex::column()
- .with_child(Label::new(
- format!("Count: {}", self.count),
- TextStyle::for_color(Color::red()),
- ))
- .with_child(
- Button::new(move |_, v: &mut Self, cx| {
- v.increase_count();
- cx.notify();
- })
- .with_text(
- "Hello from a counting BUTTON",
- TextStyle::for_color(Color::blue()),
- )
- .with_style(ButtonStyle::fill(Color::yellow()))
- .element(),
- )
- .with_child(
- ToggleableButton::new(self.is_doubling, move |_, v: &mut Self, cx| {
- v.is_doubling = !v.is_doubling;
- cx.notify();
- })
- .with_text("Double the count?", TextStyle::for_color(Color::black()))
- .with_style(Toggleable {
- inactive: ButtonStyle::fill(Color::red()),
- active: ButtonStyle::fill(Color::green()),
- })
- .element(),
- )
- .expanded()
- .contained()
- .with_background_color(Color::white())
- .into_any()
- })
- }
-}
-
-mod theme {
- pub struct Toggleable<T> {
- pub inactive: T,
- pub active: T,
- }
-
- impl<T> Toggleable<T> {
- pub fn style_for(&self, active: bool) -> &T {
- if active {
- &self.active
- } else {
- &self.inactive
- }
- }
- }
-}
-
-// Component creation:
-mod toggleable_button {
- use gpui::{
- elements::{ContainerStyle, LabelStyle, StatefulComponent},
- scene::MouseClick,
- EventContext, View,
- };
-
- use crate::{button_component::Button, theme::Toggleable};
-
- pub struct ToggleableButton<V: View> {
- active: bool,
- style: Option<Toggleable<ContainerStyle>>,
- button: Button<V>,
- }
-
- impl<V: View> ToggleableButton<V> {
- pub fn new<F>(active: bool, on_click: F) -> Self
- where
- F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
- {
- Self {
- active,
- button: Button::new(on_click),
- style: None,
- }
- }
-
- pub fn with_text(self, text: &str, style: impl Into<LabelStyle>) -> ToggleableButton<V> {
- ToggleableButton {
- active: self.active,
- style: self.style,
- button: self.button.with_text(text, style),
- }
- }
-
- pub fn with_style(self, style: Toggleable<ContainerStyle>) -> ToggleableButton<V> {
- ToggleableButton {
- active: self.active,
- style: Some(style),
- button: self.button,
- }
- }
- }
-
- impl<V: View> StatefulComponent<V> for ToggleableButton<V> {
- fn render(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
- let button = if let Some(style) = self.style {
- self.button.with_style(*style.style_for(self.active))
- } else {
- self.button
- };
- button.render(v, cx)
- }
- }
-}
-
-mod button_component {
-
- use gpui::{
- elements::{ContainerStyle, Label, LabelStyle, MouseEventHandler, StatefulComponent},
- platform::MouseButton,
- scene::MouseClick,
- AnyElement, Element, EventContext, TypeTag, View, ViewContext,
- };
-
- type ClickHandler<V> = Box<dyn Fn(MouseClick, &mut V, &mut EventContext<V>)>;
-
- pub struct Button<V: View> {
- click_handler: ClickHandler<V>,
- tag: TypeTag,
- contents: Option<AnyElement<V>>,
- style: Option<ContainerStyle>,
- }
-
- impl<V: View> Button<V> {
- pub fn new<F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static>(handler: F) -> Self {
- Self {
- click_handler: Box::new(handler),
- tag: TypeTag::new::<F>(),
- style: None,
- contents: None,
- }
- }
-
- pub fn with_text(mut self, text: &str, style: impl Into<LabelStyle>) -> Self {
- self.contents = Some(Label::new(text.to_string(), style).into_any());
- self
- }
-
- pub fn _with_contents<E: Element<V>>(mut self, contents: E) -> Self {
- self.contents = Some(contents.into_any());
- self
- }
-
- pub fn with_style(mut self, style: ContainerStyle) -> Self {
- self.style = Some(style);
- self
- }
- }
-
- impl<V: View> StatefulComponent<V> for Button<V> {
- fn render(self, _: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
- let click_handler = self.click_handler;
-
- let result = MouseEventHandler::new_dynamic(self.tag, 0, cx, |_, _| {
- self.contents
- .unwrap_or_else(|| gpui::elements::Empty::new().into_any())
- })
- .on_click(MouseButton::Left, move |click, v, cx| {
- click_handler(click, v, cx);
- })
- .contained();
-
- let result = if let Some(style) = self.style {
- result.with_style(style)
- } else {
- result
- };
-
- result.into_any()
- }
- }
-}
@@ -1,154 +0,0 @@
-use gpui::{
- color::Color, geometry::rect::RectF, scene::Shadow, AnyElement, App, Element, Entity, Quad,
- View,
-};
-use log::LevelFilter;
-use pathfinder_geometry::vector::vec2f;
-use simplelog::SimpleLogger;
-
-fn main() {
- SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
-
- App::new(()).unwrap().run(|cx| {
- cx.platform().activate(true);
- cx.add_window(Default::default(), |_| CornersView);
- });
-}
-
-struct CornersView;
-
-impl Entity for CornersView {
- type Event = ();
-}
-
-impl View for CornersView {
- fn ui_name() -> &'static str {
- "CornersView"
- }
-
- fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> AnyElement<CornersView> {
- CornersElement.into_any()
- }
-}
-
-struct CornersElement;
-
-impl<V: View> gpui::Element<V> for CornersElement {
- type LayoutState = ();
-
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: gpui::SizeConstraint,
- _: &mut V,
- _: &mut gpui::ViewContext<V>,
- ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
- (constraint.max, ())
- }
-
- fn paint(
- &mut self,
- bounds: pathfinder_geometry::rect::RectF,
- _: pathfinder_geometry::rect::RectF,
- _: &mut Self::LayoutState,
- _: &mut V,
- cx: &mut gpui::ViewContext<V>,
- ) -> Self::PaintState {
- cx.scene().push_quad(Quad {
- bounds,
- background: Some(Color::white()),
- ..Default::default()
- });
-
- cx.scene().push_layer(None);
-
- cx.scene().push_quad(Quad {
- bounds: RectF::new(vec2f(100., 100.), vec2f(100., 100.)),
- background: Some(Color::red()),
- border: Default::default(),
- corner_radii: gpui::scene::CornerRadii {
- top_left: 20.,
- ..Default::default()
- },
- });
-
- cx.scene().push_quad(Quad {
- bounds: RectF::new(vec2f(200., 100.), vec2f(100., 100.)),
- background: Some(Color::green()),
- border: Default::default(),
- corner_radii: gpui::scene::CornerRadii {
- top_right: 20.,
- ..Default::default()
- },
- });
-
- cx.scene().push_quad(Quad {
- bounds: RectF::new(vec2f(100., 200.), vec2f(100., 100.)),
- background: Some(Color::blue()),
- border: Default::default(),
- corner_radii: gpui::scene::CornerRadii {
- bottom_left: 20.,
- ..Default::default()
- },
- });
-
- cx.scene().push_quad(Quad {
- bounds: RectF::new(vec2f(200., 200.), vec2f(100., 100.)),
- background: Some(Color::yellow()),
- border: Default::default(),
- corner_radii: gpui::scene::CornerRadii {
- bottom_right: 20.,
- ..Default::default()
- },
- });
-
- cx.scene().push_shadow(Shadow {
- bounds: RectF::new(vec2f(400., 100.), vec2f(100., 100.)),
- corner_radii: gpui::scene::CornerRadii {
- bottom_right: 20.,
- ..Default::default()
- },
- sigma: 20.0,
- color: Color::black(),
- });
-
- cx.scene().push_layer(None);
- cx.scene().push_quad(Quad {
- bounds: RectF::new(vec2f(400., 100.), vec2f(100., 100.)),
- background: Some(Color::red()),
- border: Default::default(),
- corner_radii: gpui::scene::CornerRadii {
- bottom_right: 20.,
- ..Default::default()
- },
- });
-
- cx.scene().pop_layer();
- cx.scene().pop_layer();
- }
-
- fn rect_for_text_range(
- &self,
- _: std::ops::Range<usize>,
- _: pathfinder_geometry::rect::RectF,
- _: pathfinder_geometry::rect::RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- _: &V,
- _: &gpui::ViewContext<V>,
- ) -> Option<pathfinder_geometry::rect::RectF> {
- unimplemented!()
- }
-
- fn debug(
- &self,
- _: pathfinder_geometry::rect::RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- _: &V,
- _: &gpui::ViewContext<V>,
- ) -> serde_json::Value {
- unimplemented!()
- }
-}
@@ -1,81 +0,0 @@
-use gpui::{
- color::Color,
- elements::Text,
- fonts::{HighlightStyle, TextStyle},
- platform::{CursorStyle, MouseButton},
- AnyElement, CursorRegion, Element, MouseRegion,
-};
-use log::LevelFilter;
-use simplelog::SimpleLogger;
-
-fn main() {
- SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
-
- gpui::App::new(()).unwrap().run(|cx| {
- cx.platform().activate(true);
- cx.add_window(Default::default(), |_| TextView);
- });
-}
-
-struct TextView;
-
-impl gpui::Entity for TextView {
- type Event = ();
-}
-
-impl gpui::View for TextView {
- fn ui_name() -> &'static str {
- "View"
- }
-
- fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> AnyElement<TextView> {
- let font_size = 12.;
- let family = cx
- .font_cache
- .load_family(&["Monaco"], &Default::default())
- .unwrap();
- let font_id = cx
- .font_cache
- .select_font(family, &Default::default())
- .unwrap();
- let view_id = cx.view_id();
-
- let underline = HighlightStyle {
- underline: Some(gpui::fonts::Underline {
- thickness: 1.0.into(),
- ..Default::default()
- }),
- ..Default::default()
- };
-
- Text::new(
- "The text:\nHello, beautiful world, hello!",
- TextStyle {
- font_id,
- font_size,
- color: Color::red(),
- font_family_name: "".into(),
- font_family_id: family,
- underline: Default::default(),
- font_properties: Default::default(),
- soft_wrap: false,
- },
- )
- .with_highlights(vec![(17..26, underline), (34..40, underline)])
- .with_custom_runs(vec![(17..26), (34..40)], move |ix, bounds, cx| {
- cx.scene().push_cursor_region(CursorRegion {
- bounds,
- style: CursorStyle::PointingHand,
- });
- cx.scene().push_mouse_region(
- MouseRegion::new::<Self>(view_id, ix, bounds).on_click::<Self, _>(
- MouseButton::Left,
- move |_, _, _| {
- eprintln!("clicked link {ix}");
- },
- ),
- );
- })
- .into_any()
- }
-}
@@ -1,624 +1,312 @@
-pub mod action;
-mod callback_collection;
-mod menu;
-pub(crate) mod ref_counts;
+mod async_context;
+mod entity_map;
+mod model_context;
#[cfg(any(test, feature = "test-support"))]
-pub mod test_app_context;
-pub(crate) mod window;
-mod window_input_handler;
+mod test_context;
+
+pub use async_context::*;
+use derive_more::{Deref, DerefMut};
+pub use entity_map::*;
+pub use model_context::*;
+use refineable::Refineable;
+use smol::future::FutureExt;
+#[cfg(any(test, feature = "test-support"))]
+pub use test_context::*;
+use time::UtcOffset;
use crate::{
- elements::{AnyElement, AnyRootElement, RootElement},
- executor::{self, Task},
- image_cache::ImageCache,
- json,
- keymap_matcher::{self, Binding, KeymapContext, KeymapMatcher, Keystroke, MatchResult},
- platform::{
- self, FontSystem, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent, MouseButton,
- PathPromptOptions, Platform, PromptLevel, WindowBounds, WindowOptions,
- },
- util::post_inc,
- window::{Window, WindowContext},
- AssetCache, AssetSource, ClipboardItem, FontCache, MouseRegionId,
+ current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
+ AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
+ DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, KeyBinding, Keymap,
+ Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render,
+ SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement,
+ TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId,
};
-pub use action::*;
-use anyhow::{anyhow, Context, Result};
-use callback_collection::CallbackCollection;
-use collections::{hash_map::Entry, BTreeMap, HashMap, HashSet, VecDeque};
-use derive_more::Deref;
-pub use menu::*;
+use anyhow::{anyhow, Result};
+use collections::{FxHashMap, FxHashSet, VecDeque};
+use futures::{channel::oneshot, future::LocalBoxFuture, Future};
use parking_lot::Mutex;
-use pathfinder_geometry::rect::RectF;
-use platform::Event;
-use postage::oneshot;
-#[cfg(any(test, feature = "test-support"))]
-use ref_counts::LeakDetector;
-use ref_counts::RefCounts;
-use smallvec::SmallVec;
-use smol::prelude::*;
+use slotmap::SlotMap;
use std::{
- any::{type_name, Any, TypeId},
- cell::RefCell,
- fmt::{self, Debug},
- hash::{Hash, Hasher},
+ any::{type_name, TypeId},
+ cell::{Ref, RefCell, RefMut},
marker::PhantomData,
mem,
- ops::{Deref, DerefMut, Range},
+ ops::{Deref, DerefMut},
path::{Path, PathBuf},
- pin::Pin,
- rc::{self, Rc},
- sync::{Arc, Weak},
+ rc::{Rc, Weak},
+ sync::{atomic::Ordering::SeqCst, Arc},
time::Duration,
};
-#[cfg(any(test, feature = "test-support"))]
-pub use test_app_context::{ContextHandle, TestAppContext};
use util::{
http::{self, HttpClient},
ResultExt,
};
-use uuid::Uuid;
-pub use window::MeasureParams;
-use window_input_handler::WindowInputHandler;
-pub trait Entity: 'static {
- type Event;
-
- fn release(&mut self, _: &mut AppContext) {}
- fn app_will_quit(
- &mut self,
- _: &mut AppContext,
- ) -> Option<Pin<Box<dyn 'static + Future<Output = ()>>>> {
- None
- }
+/// Temporary(?) wrapper around RefCell<AppContext> to help us debug any double borrows.
+/// Strongly consider removing after stabilization.
+pub struct AppCell {
+ app: RefCell<AppContext>,
}
-pub trait View: Entity + Sized {
- fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self>;
- fn focus_in(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {}
- fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {}
- fn ui_name() -> &'static str {
- type_name::<Self>()
- }
- fn key_down(&mut self, _: &KeyDownEvent, _: &mut ViewContext<Self>) -> bool {
- false
- }
- fn key_up(&mut self, _: &KeyUpEvent, _: &mut ViewContext<Self>) -> bool {
- false
- }
- fn modifiers_changed(&mut self, _: &ModifiersChangedEvent, _: &mut ViewContext<Self>) -> bool {
- false
- }
-
- fn update_keymap_context(&self, keymap: &mut keymap_matcher::KeymapContext, _: &AppContext) {
- Self::reset_to_default_keymap_context(keymap);
+impl AppCell {
+ #[track_caller]
+ pub fn borrow(&self) -> AppRef {
+ if option_env!("TRACK_THREAD_BORROWS").is_some() {
+ let thread_id = std::thread::current().id();
+ eprintln!("borrowed {thread_id:?}");
+ }
+ AppRef(self.app.borrow())
}
- fn reset_to_default_keymap_context(keymap: &mut keymap_matcher::KeymapContext) {
- keymap.clear();
- keymap.add_identifier(Self::ui_name());
+ #[track_caller]
+ pub fn borrow_mut(&self) -> AppRefMut {
+ if option_env!("TRACK_THREAD_BORROWS").is_some() {
+ let thread_id = std::thread::current().id();
+ eprintln!("borrowed {thread_id:?}");
+ }
+ AppRefMut(self.app.borrow_mut())
}
+}
- fn debug_json(&self, _: &AppContext) -> serde_json::Value {
- serde_json::Value::Null
- }
+#[derive(Deref, DerefMut)]
+pub struct AppRef<'a>(Ref<'a, AppContext>);
- fn text_for_range(&self, _: Range<usize>, _: &AppContext) -> Option<String> {
- None
- }
- fn selected_text_range(&self, _: &AppContext) -> Option<Range<usize>> {
- None
- }
- fn marked_text_range(&self, _: &AppContext) -> Option<Range<usize>> {
- None
- }
- fn unmark_text(&mut self, _: &mut ViewContext<Self>) {}
- fn replace_text_in_range(
- &mut self,
- _: Option<Range<usize>>,
- _: &str,
- _: &mut ViewContext<Self>,
- ) {
- }
- fn replace_and_mark_text_in_range(
- &mut self,
- _: Option<Range<usize>>,
- _: &str,
- _: Option<Range<usize>>,
- _: &mut ViewContext<Self>,
- ) {
+impl<'a> Drop for AppRef<'a> {
+ fn drop(&mut self) {
+ if option_env!("TRACK_THREAD_BORROWS").is_some() {
+ let thread_id = std::thread::current().id();
+ eprintln!("dropped borrow from {thread_id:?}");
+ }
}
}
-pub trait BorrowAppContext {
- fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T;
- fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T;
-}
-
-pub trait BorrowWindowContext {
- type Result<T>;
+#[derive(Deref, DerefMut)]
+pub struct AppRefMut<'a>(RefMut<'a, AppContext>);
- fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
- where
- F: FnOnce(&WindowContext) -> T;
- fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
- where
- F: FnOnce(&WindowContext) -> Option<T>;
- fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Self::Result<T>
- where
- F: FnOnce(&mut WindowContext) -> T;
- fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
- where
- F: FnOnce(&mut WindowContext) -> Option<T>;
+impl<'a> Drop for AppRefMut<'a> {
+ fn drop(&mut self) {
+ if option_env!("TRACK_THREAD_BORROWS").is_some() {
+ let thread_id = std::thread::current().id();
+ eprintln!("dropped {thread_id:?}");
+ }
+ }
}
-#[derive(Clone)]
-pub struct App(Rc<RefCell<AppContext>>);
+pub struct App(Rc<AppCell>);
+/// Represents an application before it is fully launched. Once your app is
+/// configured, you'll start the app with `App::run`.
impl App {
- pub fn new(asset_source: impl AssetSource) -> Result<Self> {
- let platform = platform::current::platform();
- let foreground = Rc::new(executor::Foreground::platform(platform.dispatcher())?);
- let foreground_platform = platform::current::foreground_platform(foreground.clone());
- let http_client = http::client();
- let app = Self(Rc::new(RefCell::new(AppContext::new(
- foreground,
- Arc::new(executor::Background::new()),
- platform.clone(),
- foreground_platform.clone(),
- Arc::new(FontCache::new(platform.fonts())),
- http_client,
- Default::default(),
+ /// Builds an app with the given asset source.
+ pub fn production(asset_source: Arc<dyn AssetSource>) -> Self {
+ Self(AppContext::new(
+ current_platform(),
asset_source,
- ))));
-
- foreground_platform.on_event(Box::new({
- let cx = app.0.clone();
- move |event| {
- if let Event::KeyDown(KeyDownEvent { keystroke, .. }) = &event {
- // Allow system menu "cmd-?" shortcut to be overridden
- if keystroke.cmd
- && !keystroke.shift
- && !keystroke.alt
- && !keystroke.function
- && keystroke.key == "?"
- {
- if cx
- .borrow_mut()
- .update_active_window(|cx| cx.dispatch_keystroke(keystroke))
- .unwrap_or(false)
- {
- return true;
- }
- }
- }
- false
- }
- }));
- foreground_platform.on_quit(Box::new({
- let cx = app.0.clone();
- move || {
- cx.borrow_mut().quit();
- }
- }));
- setup_menu_handlers(foreground_platform.as_ref(), &app);
-
- app.0.borrow_mut().weak_self = Some(Rc::downgrade(&app.0));
- Ok(app)
- }
-
- pub fn background(&self) -> Arc<executor::Background> {
- self.0.borrow().background().clone()
- }
-
- pub fn on_become_active<F>(self, mut callback: F) -> Self
- where
- F: 'static + FnMut(&mut AppContext),
- {
- let cx = self.0.clone();
- self.0
- .borrow_mut()
- .foreground_platform
- .on_become_active(Box::new(move || callback(&mut *cx.borrow_mut())));
- self
- }
-
- pub fn on_resign_active<F>(self, mut callback: F) -> Self
- where
- F: 'static + FnMut(&mut AppContext),
- {
- let cx = self.0.clone();
- self.0
- .borrow_mut()
- .foreground_platform
- .on_resign_active(Box::new(move || callback(&mut *cx.borrow_mut())));
- self
- }
-
- pub fn on_quit<F>(&mut self, mut callback: F) -> &mut Self
- where
- F: 'static + FnMut(&mut AppContext),
- {
- let cx = self.0.clone();
- self.0
- .borrow_mut()
- .foreground_platform
- .on_quit(Box::new(move || callback(&mut *cx.borrow_mut())));
- self
- }
-
- /// Handle the application being re-activated when no windows are open.
- pub fn on_reopen<F>(&mut self, mut callback: F) -> &mut Self
- where
- F: 'static + FnMut(&mut AppContext),
- {
- let cx = self.0.clone();
- self.0
- .borrow_mut()
- .foreground_platform
- .on_reopen(Box::new(move || callback(&mut *cx.borrow_mut())));
- self
- }
-
- pub fn on_event<F>(&mut self, mut callback: F) -> &mut Self
- where
- F: 'static + FnMut(Event, &mut AppContext) -> bool,
- {
- let cx = self.0.clone();
- self.0
- .borrow_mut()
- .foreground_platform
- .on_event(Box::new(move |event| {
- callback(event, &mut *cx.borrow_mut())
- }));
- self
- }
-
- pub fn on_open_urls<F>(&mut self, mut callback: F) -> &mut Self
- where
- F: 'static + FnMut(Vec<String>, &mut AppContext),
- {
- let cx = self.0.clone();
- self.0
- .borrow_mut()
- .foreground_platform
- .on_open_urls(Box::new(move |urls| callback(urls, &mut *cx.borrow_mut())));
- self
+ http::client(),
+ ))
}
+ /// Start the application. The provided callback will be called once the
+ /// app is fully launched.
pub fn run<F>(self, on_finish_launching: F)
where
F: 'static + FnOnce(&mut AppContext),
{
- let platform = self.0.borrow().foreground_platform.clone();
+ let this = self.0.clone();
+ let platform = self.0.borrow().platform.clone();
platform.run(Box::new(move || {
- let mut cx = self.0.borrow_mut();
- let cx = &mut *cx;
- crate::views::init(cx);
+ let cx = &mut *this.borrow_mut();
on_finish_launching(cx);
- }))
- }
-
- pub fn platform(&self) -> Arc<dyn Platform> {
- self.0.borrow().platform.clone()
- }
-
- pub fn font_cache(&self) -> Arc<FontCache> {
- self.0.borrow().font_cache.clone()
- }
-
- fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, callback: F) -> T {
- let mut state = self.0.borrow_mut();
- let result = state.update(callback);
- state.pending_notifications.clear();
- result
- }
-
- fn update_window<T, F>(&mut self, window: AnyWindowHandle, callback: F) -> Option<T>
- where
- F: FnOnce(&mut WindowContext) -> T,
- {
- let mut state = self.0.borrow_mut();
- let result = state.update_window(window, callback);
- state.pending_notifications.clear();
- result
- }
-}
-
-#[derive(Clone)]
-pub struct AsyncAppContext(Rc<RefCell<AppContext>>);
-
-impl AsyncAppContext {
- pub fn spawn<F, Fut, T>(&self, f: F) -> Task<T>
- where
- F: FnOnce(AsyncAppContext) -> Fut,
- Fut: 'static + Future<Output = T>,
- T: 'static,
- {
- self.0.borrow().foreground.spawn(f(self.clone()))
- }
-
- pub fn read<T, F: FnOnce(&AppContext) -> T>(&self, callback: F) -> T {
- callback(&*self.0.borrow())
- }
-
- pub fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, callback: F) -> T {
- self.0.borrow_mut().update(callback)
- }
-
- pub fn windows(&self) -> Vec<AnyWindowHandle> {
- self.0.borrow().windows().collect()
+ }));
}
- pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
+ /// Register a handler to be invoked when the platform instructs the application
+ /// to open one or more URLs.
+ pub fn on_open_urls<F>(&self, mut callback: F) -> &Self
where
- T: Entity,
- F: FnOnce(&mut ModelContext<T>) -> T,
+ F: 'static + FnMut(Vec<String>, &mut AppContext),
{
- self.update(|cx| cx.add_model(build_model))
+ let this = Rc::downgrade(&self.0);
+ self.0.borrow().platform.on_open_urls(Box::new(move |urls| {
+ if let Some(app) = this.upgrade() {
+ callback(urls, &mut app.borrow_mut());
+ }
+ }));
+ self
}
- pub fn add_window<T, F>(
- &mut self,
- window_options: WindowOptions,
- build_root_view: F,
- ) -> WindowHandle<T>
+ pub fn on_reopen<F>(&self, mut callback: F) -> &Self
where
- T: View,
- F: FnOnce(&mut ViewContext<T>) -> T,
+ F: 'static + FnMut(&mut AppContext),
{
- self.update(|cx| cx.add_window(window_options, build_root_view))
- }
-
- pub fn platform(&self) -> Arc<dyn Platform> {
- self.0.borrow().platform().clone()
+ let this = Rc::downgrade(&self.0);
+ self.0.borrow_mut().platform.on_reopen(Box::new(move || {
+ if let Some(app) = this.upgrade() {
+ callback(&mut app.borrow_mut());
+ }
+ }));
+ self
}
- pub fn foreground(&self) -> Rc<executor::Foreground> {
- self.0.borrow().foreground.clone()
+ pub fn metadata(&self) -> AppMetadata {
+ self.0.borrow().app_metadata.clone()
}
- pub fn background(&self) -> Arc<executor::Background> {
- self.0.borrow().background.clone()
+ pub fn background_executor(&self) -> BackgroundExecutor {
+ self.0.borrow().background_executor.clone()
}
-}
-impl BorrowAppContext for AsyncAppContext {
- fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
- self.0.borrow().read_with(f)
+ pub fn foreground_executor(&self) -> ForegroundExecutor {
+ self.0.borrow().foreground_executor.clone()
}
- fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
- self.0.borrow_mut().update(f)
+ pub fn text_system(&self) -> Arc<TextSystem> {
+ self.0.borrow().text_system.clone()
}
}
-impl BorrowWindowContext for AsyncAppContext {
- type Result<T> = Option<T>;
-
- fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
- where
- F: FnOnce(&WindowContext) -> T,
- {
- self.0.borrow().read_with(|cx| cx.read_window(window, f))
- }
-
- fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
- where
- F: FnOnce(&WindowContext) -> Option<T>,
- {
- self.0
- .borrow_mut()
- .update(|cx| cx.read_window_optional(window, f))
- }
-
- fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Self::Result<T>
- where
- F: FnOnce(&mut WindowContext) -> T,
- {
- self.0.borrow_mut().update(|cx| cx.update_window(window, f))
- }
-
- fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
- where
- F: FnOnce(&mut WindowContext) -> Option<T>,
- {
- self.0
- .borrow_mut()
- .update(|cx| cx.update_window_optional(window, f))
- }
-}
+pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
+type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
+type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
+type KeystrokeObserver = Box<dyn FnMut(&KeystrokeEvent, &mut WindowContext) + 'static>;
+type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, ()> + 'static>;
+type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
+type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
-type ActionCallback = dyn FnMut(&mut dyn AnyView, &dyn Action, &mut WindowContext, usize);
-type GlobalActionCallback = dyn FnMut(&dyn Action, &mut AppContext);
-
-type SubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool>;
-type GlobalSubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut AppContext)>;
-type ObservationCallback = Box<dyn FnMut(&mut AppContext) -> bool>;
-type GlobalObservationCallback = Box<dyn FnMut(&mut AppContext)>;
-type FocusObservationCallback = Box<dyn FnMut(bool, &mut WindowContext) -> bool>;
-type ReleaseObservationCallback = Box<dyn FnMut(&dyn Any, &mut AppContext)>;
-type ActionObservationCallback = Box<dyn FnMut(TypeId, &mut AppContext)>;
-type WindowActivationCallback = Box<dyn FnMut(bool, &mut WindowContext) -> bool>;
-type WindowFullscreenCallback = Box<dyn FnMut(bool, &mut WindowContext) -> bool>;
-type WindowBoundsCallback = Box<dyn FnMut(WindowBounds, Uuid, &mut WindowContext) -> bool>;
-type KeystrokeCallback =
- Box<dyn FnMut(&Keystroke, &MatchResult, Option<&Box<dyn Action>>, &mut WindowContext) -> bool>;
-type ActiveLabeledTasksCallback = Box<dyn FnMut(&mut AppContext) -> bool>;
-type DeserializeActionCallback = fn(json: serde_json::Value) -> anyhow::Result<Box<dyn Action>>;
-type WindowShouldCloseSubscriptionCallback = Box<dyn FnMut(&mut AppContext) -> bool>;
+// struct FrameConsumer {
+// next_frame_callbacks: Vec<FrameCallback>,
+// task: Task<()>,
+// display_linker
+// }
pub struct AppContext {
- models: HashMap<usize, Box<dyn AnyModel>>,
- views: HashMap<(AnyWindowHandle, usize), Box<dyn AnyView>>,
- views_metadata: HashMap<(AnyWindowHandle, usize), ViewMetadata>,
- windows: HashMap<AnyWindowHandle, Window>,
- globals: HashMap<TypeId, Box<dyn Any>>,
- element_states: HashMap<ElementStateId, Box<dyn Any>>,
- background: Arc<executor::Background>,
- ref_counts: Arc<Mutex<RefCounts>>,
-
- weak_self: Option<rc::Weak<RefCell<Self>>>,
- platform: Arc<dyn Platform>,
- foreground_platform: Rc<dyn platform::ForegroundPlatform>,
- pub asset_cache: Arc<AssetCache>,
- font_system: Arc<dyn FontSystem>,
- pub font_cache: Arc<FontCache>,
- pub image_cache: Arc<ImageCache>,
- action_deserializers: HashMap<&'static str, (TypeId, DeserializeActionCallback)>,
- capture_actions: HashMap<TypeId, HashMap<TypeId, Vec<Box<ActionCallback>>>>,
- // Entity Types -> { Action Types -> Action Handlers }
- actions: HashMap<TypeId, HashMap<TypeId, Vec<Box<ActionCallback>>>>,
- // Action Types -> Action Handlers
- global_actions: HashMap<TypeId, Box<GlobalActionCallback>>,
- keystroke_matcher: KeymapMatcher,
- next_id: usize,
- // next_window: AnyWindowHandle,
- next_subscription_id: usize,
- frame_count: usize,
-
- subscriptions: CallbackCollection<usize, SubscriptionCallback>,
- global_subscriptions: CallbackCollection<TypeId, GlobalSubscriptionCallback>,
- observations: CallbackCollection<usize, ObservationCallback>,
- global_observations: CallbackCollection<TypeId, GlobalObservationCallback>,
- focus_observations: CallbackCollection<usize, FocusObservationCallback>,
- release_observations: CallbackCollection<usize, ReleaseObservationCallback>,
- action_dispatch_observations: CallbackCollection<(), ActionObservationCallback>,
- window_activation_observations: CallbackCollection<AnyWindowHandle, WindowActivationCallback>,
- window_fullscreen_observations: CallbackCollection<AnyWindowHandle, WindowFullscreenCallback>,
- window_bounds_observations: CallbackCollection<AnyWindowHandle, WindowBoundsCallback>,
- keystroke_observations: CallbackCollection<AnyWindowHandle, KeystrokeCallback>,
- active_labeled_task_observations: CallbackCollection<(), ActiveLabeledTasksCallback>,
-
- foreground: Rc<executor::Foreground>,
- pending_effects: VecDeque<Effect>,
- pending_notifications: HashSet<usize>,
- pending_global_notifications: HashSet<TypeId>,
- pending_flushes: usize,
+ pub(crate) this: Weak<AppCell>,
+ pub(crate) platform: Rc<dyn Platform>,
+ app_metadata: AppMetadata,
+ text_system: Arc<TextSystem>,
flushing_effects: bool,
- halt_action_dispatch: bool,
- next_labeled_task_id: usize,
- active_labeled_tasks: BTreeMap<usize, &'static str>,
+ pending_updates: usize,
+ pub(crate) actions: Rc<ActionRegistry>,
+ pub(crate) active_drag: Option<AnyDrag>,
+ pub(crate) active_tooltip: Option<AnyTooltip>,
+ pub(crate) next_frame_callbacks: FxHashMap<DisplayId, Vec<FrameCallback>>,
+ pub(crate) frame_consumers: FxHashMap<DisplayId, Task<()>>,
+ pub(crate) background_executor: BackgroundExecutor,
+ pub(crate) foreground_executor: ForegroundExecutor,
+ pub(crate) svg_renderer: SvgRenderer,
+ asset_source: Arc<dyn AssetSource>,
+ pub(crate) image_cache: ImageCache,
+ pub(crate) text_style_stack: Vec<TextStyleRefinement>,
+ pub(crate) globals_by_type: FxHashMap<TypeId, Box<dyn Any>>,
+ pub(crate) entities: EntityMap,
+ pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>,
+ pub(crate) windows: SlotMap<WindowId, Option<Window>>,
+ pub(crate) keymap: Arc<Mutex<Keymap>>,
+ pub(crate) global_action_listeners:
+ FxHashMap<TypeId, Vec<Rc<dyn Fn(&dyn Any, DispatchPhase, &mut Self)>>>,
+ pending_effects: VecDeque<Effect>,
+ pub(crate) pending_notifications: FxHashSet<EntityId>,
+ pub(crate) pending_global_notifications: FxHashSet<TypeId>,
+ pub(crate) observers: SubscriberSet<EntityId, Handler>,
+ // TypeId is the type of the event that the listener callback expects
+ pub(crate) event_listeners: SubscriberSet<EntityId, (TypeId, Listener)>,
+ pub(crate) keystroke_observers: SubscriberSet<(), KeystrokeObserver>,
+ pub(crate) release_listeners: SubscriberSet<EntityId, ReleaseListener>,
+ pub(crate) global_observers: SubscriberSet<TypeId, Handler>,
+ pub(crate) quit_observers: SubscriberSet<(), QuitHandler>,
+ pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
+ pub(crate) propagate_event: bool,
}
impl AppContext {
- fn new(
- foreground: Rc<executor::Foreground>,
- background: Arc<executor::Background>,
- platform: Arc<dyn platform::Platform>,
- foreground_platform: Rc<dyn platform::ForegroundPlatform>,
- font_cache: Arc<FontCache>,
+ pub(crate) fn new(
+ platform: Rc<dyn Platform>,
+ asset_source: Arc<dyn AssetSource>,
http_client: Arc<dyn HttpClient>,
- ref_counts: RefCounts,
- asset_source: impl AssetSource,
- ) -> Self {
- Self {
- models: Default::default(),
- views: Default::default(),
- views_metadata: Default::default(),
- windows: Default::default(),
- globals: Default::default(),
- element_states: Default::default(),
- ref_counts: Arc::new(Mutex::new(ref_counts)),
- background,
-
- weak_self: None,
- font_system: platform.fonts(),
- platform,
- foreground_platform,
- font_cache,
- image_cache: Arc::new(ImageCache::new(http_client)),
- asset_cache: Arc::new(AssetCache::new(asset_source)),
- action_deserializers: Default::default(),
- capture_actions: Default::default(),
- actions: Default::default(),
- global_actions: Default::default(),
- keystroke_matcher: KeymapMatcher::default(),
- next_id: 0,
- next_subscription_id: 0,
- frame_count: 0,
- subscriptions: Default::default(),
- global_subscriptions: Default::default(),
- observations: Default::default(),
- focus_observations: Default::default(),
- release_observations: Default::default(),
- global_observations: Default::default(),
- window_activation_observations: Default::default(),
- window_fullscreen_observations: Default::default(),
- window_bounds_observations: Default::default(),
- keystroke_observations: Default::default(),
- action_dispatch_observations: Default::default(),
- active_labeled_task_observations: Default::default(),
- foreground,
- pending_effects: VecDeque::new(),
- pending_notifications: Default::default(),
- pending_global_notifications: Default::default(),
- pending_flushes: 0,
- flushing_effects: false,
- halt_action_dispatch: false,
- next_labeled_task_id: 0,
- active_labeled_tasks: Default::default(),
- }
- }
-
- pub fn background(&self) -> &Arc<executor::Background> {
- &self.background
- }
+ ) -> Rc<AppCell> {
+ let executor = platform.background_executor();
+ let foreground_executor = platform.foreground_executor();
+ assert!(
+ executor.is_main_thread(),
+ "must construct App on main thread"
+ );
- pub fn font_cache(&self) -> &Arc<FontCache> {
- &self.font_cache
- }
+ let text_system = Arc::new(TextSystem::new(platform.text_system()));
+ let entities = EntityMap::new();
- pub fn platform(&self) -> &Arc<dyn Platform> {
- &self.platform
- }
+ let app_metadata = AppMetadata {
+ os_name: platform.os_name(),
+ os_version: platform.os_version().ok(),
+ app_version: platform.app_version().ok(),
+ };
- pub fn has_global<T: 'static>(&self) -> bool {
- self.globals.contains_key(&TypeId::of::<T>())
- }
+ let app = Rc::new_cyclic(|this| AppCell {
+ app: RefCell::new(AppContext {
+ this: this.clone(),
+ platform: platform.clone(),
+ app_metadata,
+ text_system,
+ actions: Rc::new(ActionRegistry::default()),
+ flushing_effects: false,
+ pending_updates: 0,
+ active_drag: None,
+ active_tooltip: None,
+ next_frame_callbacks: FxHashMap::default(),
+ frame_consumers: FxHashMap::default(),
+ background_executor: executor,
+ foreground_executor,
+ svg_renderer: SvgRenderer::new(asset_source.clone()),
+ asset_source,
+ image_cache: ImageCache::new(http_client),
+ text_style_stack: Vec::new(),
+ globals_by_type: FxHashMap::default(),
+ entities,
+ new_view_observers: SubscriberSet::new(),
+ windows: SlotMap::with_key(),
+ keymap: Arc::new(Mutex::new(Keymap::default())),
+ global_action_listeners: FxHashMap::default(),
+ pending_effects: VecDeque::new(),
+ pending_notifications: FxHashSet::default(),
+ pending_global_notifications: FxHashSet::default(),
+ observers: SubscriberSet::new(),
+ event_listeners: SubscriberSet::new(),
+ release_listeners: SubscriberSet::new(),
+ keystroke_observers: SubscriberSet::new(),
+ global_observers: SubscriberSet::new(),
+ quit_observers: SubscriberSet::new(),
+ layout_id_buffer: Default::default(),
+ propagate_event: true,
+ }),
+ });
- pub fn global<T: 'static>(&self) -> &T {
- if let Some(global) = self.globals.get(&TypeId::of::<T>()) {
- global.downcast_ref().unwrap()
- } else {
- panic!("no global has been added for {}", type_name::<T>());
- }
- }
+ init_app_menus(platform.as_ref(), &mut app.borrow_mut());
- pub fn optional_global<T: 'static>(&self) -> Option<&T> {
- if let Some(global) = self.globals.get(&TypeId::of::<T>()) {
- Some(global.downcast_ref().unwrap())
- } else {
- None
- }
- }
+ platform.on_quit(Box::new({
+ let cx = app.clone();
+ move || {
+ cx.borrow_mut().shutdown();
+ }
+ }));
- pub fn upgrade(&self) -> App {
- App(self.weak_self.as_ref().unwrap().upgrade().unwrap())
+ app
}
- fn quit(&mut self) {
+ /// Quit the application gracefully. Handlers registered with `ModelContext::on_app_quit`
+ /// will be given 100ms to complete before exiting.
+ pub fn shutdown(&mut self) {
let mut futures = Vec::new();
- self.update(|cx| {
- for model_id in cx.models.keys().copied().collect::<Vec<_>>() {
- let mut model = cx.models.remove(&model_id).unwrap();
- futures.extend(model.app_will_quit(cx));
- cx.models.insert(model_id, model);
- }
-
- for view_id in cx.views.keys().copied().collect::<Vec<_>>() {
- let mut view = cx.views.remove(&view_id).unwrap();
- futures.extend(view.app_will_quit(cx));
- cx.views.insert(view_id, view);
- }
- });
+ for observer in self.quit_observers.remove(&()) {
+ futures.push(observer(self));
+ }
self.windows.clear();
self.flush_effects();
let futures = futures::future::join_all(futures);
if self
- .background
+ .background_executor
.block_with_timeout(Duration::from_millis(100), futures)
.is_err()
{
@@ -626,6263 +314,951 @@ impl AppContext {
}
}
- pub fn foreground(&self) -> &Rc<executor::Foreground> {
- &self.foreground
+ pub fn quit(&mut self) {
+ self.platform.quit();
}
- pub fn deserialize_action(
- &self,
- name: &str,
- argument: Option<serde_json::Value>,
- ) -> Result<Box<dyn Action>> {
- let callback = self
- .action_deserializers
- .get(name)
- .ok_or_else(|| anyhow!("unknown action {}", name))?
- .1;
- callback(argument.unwrap_or_else(|| serde_json::Value::Object(Default::default())))
- .with_context(|| format!("invalid data for action {}", name))
+ pub fn app_metadata(&self) -> AppMetadata {
+ self.app_metadata.clone()
}
- pub fn add_action<A, V, F, R>(&mut self, handler: F)
- where
- A: Action,
- V: 'static,
- F: 'static + FnMut(&mut V, &A, &mut ViewContext<V>) -> R,
- {
- self.add_action_internal(handler, false)
+ /// Schedules all windows in the application to be redrawn. This can be called
+ /// multiple times in an update cycle and still result in a single redraw.
+ pub fn refresh(&mut self) {
+ self.pending_effects.push_back(Effect::Refresh);
+ }
+ pub(crate) fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
+ self.pending_updates += 1;
+ let result = update(self);
+ if !self.flushing_effects && self.pending_updates == 1 {
+ self.flushing_effects = true;
+ self.flush_effects();
+ self.flushing_effects = false;
+ }
+ self.pending_updates -= 1;
+ result
}
- pub fn capture_action<A, V, F>(&mut self, handler: F)
+ pub fn observe<W, E>(
+ &mut self,
+ entity: &E,
+ mut on_notify: impl FnMut(E, &mut AppContext) + 'static,
+ ) -> Subscription
where
- A: Action,
- V: 'static,
- F: 'static + FnMut(&mut V, &A, &mut ViewContext<V>),
+ W: 'static,
+ E: Entity<W>,
{
- self.add_action_internal(handler, true)
+ self.observe_internal(entity, move |e, cx| {
+ on_notify(e, cx);
+ true
+ })
}
- fn add_action_internal<A, V, F, R>(&mut self, mut handler: F, capture: bool)
+ pub fn observe_internal<W, E>(
+ &mut self,
+ entity: &E,
+ mut on_notify: impl FnMut(E, &mut AppContext) -> bool + 'static,
+ ) -> Subscription
where
- A: Action,
- V: 'static,
- F: 'static + FnMut(&mut V, &A, &mut ViewContext<V>) -> R,
+ W: 'static,
+ E: Entity<W>,
{
- let handler = Box::new(
- move |view: &mut dyn AnyView,
- action: &dyn Action,
- cx: &mut WindowContext,
- view_id: usize| {
- let action = action.as_any().downcast_ref().unwrap();
- let mut cx = ViewContext::mutable(cx, view_id);
- handler(
- view.as_any_mut()
- .downcast_mut()
- .expect("downcast is type safe"),
- action,
- &mut cx,
- );
- },
- );
- fn inner(
- this: &mut AppContext,
- name: &'static str,
- deserializer: fn(serde_json::Value) -> anyhow::Result<Box<dyn Action>>,
- action_id: TypeId,
- view_id: TypeId,
- handler: Box<ActionCallback>,
- capture: bool,
- ) {
- this.action_deserializers
- .entry(name)
- .or_insert((action_id.clone(), deserializer));
-
- let actions = if capture {
- &mut this.capture_actions
- } else {
- &mut this.actions
- };
-
- actions
- .entry(view_id)
- .or_default()
- .entry(action_id)
- .or_default()
- .push(handler);
- }
- inner(
- self,
- A::qualified_name(),
- A::from_json_str,
- TypeId::of::<A>(),
- TypeId::of::<V>(),
- handler,
- capture,
+ let entity_id = entity.entity_id();
+ let handle = entity.downgrade();
+ let (subscription, activate) = self.observers.insert(
+ entity_id,
+ Box::new(move |cx| {
+ if let Some(handle) = E::upgrade_from(&handle) {
+ on_notify(handle, cx)
+ } else {
+ false
+ }
+ }),
);
+ self.defer(move |_| activate());
+ subscription
}
- pub fn add_async_action<A, V, F>(&mut self, mut handler: F)
+ pub fn subscribe<T, E, Evt>(
+ &mut self,
+ entity: &E,
+ mut on_event: impl FnMut(E, &Evt, &mut AppContext) + 'static,
+ ) -> Subscription
where
- A: Action,
- V: 'static,
- F: 'static + FnMut(&mut V, &A, &mut ViewContext<V>) -> Option<Task<Result<()>>>,
+ T: 'static + EventEmitter<Evt>,
+ E: Entity<T>,
+ Evt: 'static,
{
- self.add_action(move |view, action, cx| {
- if let Some(task) = handler(view, action, cx) {
- task.detach_and_log_err(cx);
- }
+ self.subscribe_internal(entity, move |entity, event, cx| {
+ on_event(entity, event, cx);
+ true
})
}
- pub fn add_global_action<A, F>(&mut self, mut handler: F)
+ pub(crate) fn subscribe_internal<T, E, Evt>(
+ &mut self,
+ entity: &E,
+ mut on_event: impl FnMut(E, &Evt, &mut AppContext) -> bool + 'static,
+ ) -> Subscription
where
- A: Action,
- F: 'static + FnMut(&A, &mut AppContext),
+ T: 'static + EventEmitter<Evt>,
+ E: Entity<T>,
+ Evt: 'static,
{
- let handler = Box::new(move |action: &dyn Action, cx: &mut AppContext| {
- let action = action.as_any().downcast_ref().unwrap();
- handler(action, cx);
- });
+ let entity_id = entity.entity_id();
+ let entity = entity.downgrade();
+ let (subscription, activate) = self.event_listeners.insert(
+ entity_id,
+ (
+ TypeId::of::<Evt>(),
+ Box::new(move |event, cx| {
+ let event: &Evt = event.downcast_ref().expect("invalid event type");
+ if let Some(handle) = E::upgrade_from(&entity) {
+ on_event(handle, event, cx)
+ } else {
+ false
+ }
+ }),
+ ),
+ );
+ self.defer(move |_| activate());
+ subscription
+ }
- self.action_deserializers
- .entry(A::qualified_name())
- .or_insert((TypeId::of::<A>(), A::from_json_str));
+ pub fn windows(&self) -> Vec<AnyWindowHandle> {
+ self.windows
+ .values()
+ .filter_map(|window| Some(window.as_ref()?.handle))
+ .collect()
+ }
- if self
- .global_actions
- .insert(TypeId::of::<A>(), handler)
- .is_some()
- {
- panic!(
- "registered multiple global handlers for {}",
- type_name::<A>()
- );
- }
+ pub fn active_window(&self) -> Option<AnyWindowHandle> {
+ self.platform.active_window()
}
- pub fn view_ui_name(&self, window: AnyWindowHandle, view_id: usize) -> Option<&'static str> {
- Some(self.views.get(&(window, view_id))?.ui_name())
+ /// Opens a new window with the given option and the root view returned by the given function.
+ /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
+ /// functionality.
+ pub fn open_window<V: 'static + Render>(
+ &mut self,
+ options: crate::WindowOptions,
+ build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
+ ) -> WindowHandle<V> {
+ self.update(|cx| {
+ let id = cx.windows.insert(None);
+ let handle = WindowHandle::new(id);
+ let mut window = Window::new(handle.into(), options, cx);
+ let root_view = build_root_view(&mut WindowContext::new(cx, &mut window));
+ window.root_view.replace(root_view.into());
+ cx.windows.get_mut(id).unwrap().replace(window);
+ handle
+ })
}
- pub fn view_type_id(&self, window: AnyWindowHandle, view_id: usize) -> Option<TypeId> {
- self.views_metadata
- .get(&(window, view_id))
- .map(|metadata| metadata.type_id)
+ /// Instructs the platform to activate the application by bringing it to the foreground.
+ pub fn activate(&self, ignoring_other_apps: bool) {
+ self.platform.activate(ignoring_other_apps);
}
- pub fn active_labeled_tasks<'a>(
- &'a self,
- ) -> impl DoubleEndedIterator<Item = &'static str> + 'a {
- self.active_labeled_tasks.values().cloned()
+ pub fn hide(&self) {
+ self.platform.hide();
}
- pub(crate) fn start_frame(&mut self) {
- self.frame_count += 1;
+ pub fn hide_other_apps(&self) {
+ self.platform.hide_other_apps();
}
- pub fn update<T, F: FnOnce(&mut Self) -> T>(&mut self, callback: F) -> T {
- self.pending_flushes += 1;
- let result = callback(self);
- self.flush_effects();
- result
+ pub fn unhide_other_apps(&self) {
+ self.platform.unhide_other_apps();
}
- fn read_window<T, F: FnOnce(&WindowContext) -> T>(
- &self,
- handle: AnyWindowHandle,
- callback: F,
- ) -> Option<T> {
- let window = self.windows.get(&handle)?;
- let window_context = WindowContext::immutable(self, &window, handle);
- Some(callback(&window_context))
+ /// Returns the list of currently active displays.
+ pub fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
+ self.platform.displays()
}
- pub fn update_active_window<T, F: FnOnce(&mut WindowContext) -> T>(
- &mut self,
- callback: F,
- ) -> Option<T> {
- self.active_window()
- .and_then(|window| window.update(self, callback))
+ /// Writes data to the platform clipboard.
+ pub fn write_to_clipboard(&self, item: ClipboardItem) {
+ self.platform.write_to_clipboard(item)
+ }
+
+ /// Reads data from the platform clipboard.
+ pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
+ self.platform.read_from_clipboard()
+ }
+
+ /// Writes credentials to the platform keychain.
+ pub fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
+ self.platform.write_credentials(url, username, password)
+ }
+
+ /// Reads credentials from the platform keychain.
+ pub fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
+ self.platform.read_credentials(url)
+ }
+
+ /// Deletes credentials from the platform keychain.
+ pub fn delete_credentials(&self, url: &str) -> Result<()> {
+ self.platform.delete_credentials(url)
+ }
+
+ /// Directs the platform's default browser to open the given URL.
+ pub fn open_url(&self, url: &str) {
+ self.platform.open_url(url);
+ }
+
+ pub fn app_path(&self) -> Result<PathBuf> {
+ self.platform.app_path()
+ }
+
+ pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
+ self.platform.path_for_auxiliary_executable(name)
+ }
+
+ pub fn double_click_interval(&self) -> Duration {
+ self.platform.double_click_interval()
}
pub fn prompt_for_paths(
&self,
options: PathPromptOptions,
) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
- self.foreground_platform.prompt_for_paths(options)
+ self.platform.prompt_for_paths(options)
}
pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
- self.foreground_platform.prompt_for_new_path(directory)
+ self.platform.prompt_for_new_path(directory)
}
pub fn reveal_path(&self, path: &Path) {
- self.foreground_platform.reveal_path(path)
- }
-
- pub fn emit_global<E: Any>(&mut self, payload: E) {
- self.pending_effects.push_back(Effect::GlobalEvent {
- payload: Box::new(payload),
- });
+ self.platform.reveal_path(path)
}
- pub fn subscribe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
- where
- E: Entity,
- E::Event: 'static,
- H: Handle<E>,
- F: 'static + FnMut(H, &E::Event, &mut Self),
- {
- self.subscribe_internal(handle, move |handle, event, cx| {
- callback(handle, event, cx);
- true
- })
+ pub fn should_auto_hide_scrollbars(&self) -> bool {
+ self.platform.should_auto_hide_scrollbars()
}
- pub fn subscribe_global<E, F>(&mut self, mut callback: F) -> Subscription
- where
- E: Any,
- F: 'static + FnMut(&E, &mut Self),
- {
- let subscription_id = post_inc(&mut self.next_subscription_id);
- let type_id = TypeId::of::<E>();
- self.pending_effects.push_back(Effect::GlobalSubscription {
- type_id,
- subscription_id,
- callback: Box::new(move |payload, cx| {
- let payload = payload.downcast_ref().expect("downcast is type safe");
- callback(payload, cx)
- }),
- });
- Subscription::GlobalSubscription(
- self.global_subscriptions
- .subscribe(type_id, subscription_id),
- )
+ pub fn restart(&self) {
+ self.platform.restart()
}
- pub fn observe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
- where
- E: Entity,
- E::Event: 'static,
- H: Handle<E>,
- F: 'static + FnMut(H, &mut Self),
- {
- self.observe_internal(handle, move |handle, cx| {
- callback(handle, cx);
- true
- })
+ pub fn local_timezone(&self) -> UtcOffset {
+ self.platform.local_timezone()
}
- fn subscribe_internal<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
- where
- E: Entity,
- E::Event: 'static,
- H: Handle<E>,
- F: 'static + FnMut(H, &E::Event, &mut Self) -> bool,
- {
- let subscription_id = post_inc(&mut self.next_subscription_id);
- let emitter = handle.downgrade();
- self.pending_effects.push_back(Effect::Subscription {
- entity_id: handle.id(),
- subscription_id,
- callback: Box::new(move |payload, cx| {
- if let Some(emitter) = H::upgrade_from(&emitter, cx) {
- let payload = payload.downcast_ref().expect("downcast is type safe");
- callback(emitter, payload, cx)
- } else {
- false
+ pub(crate) fn push_effect(&mut self, effect: Effect) {
+ match &effect {
+ Effect::Notify { emitter } => {
+ if !self.pending_notifications.insert(*emitter) {
+ return;
}
- }),
- });
- Subscription::Subscription(self.subscriptions.subscribe(handle.id(), subscription_id))
- }
-
- fn observe_internal<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
- where
- E: Entity,
- E::Event: 'static,
- H: Handle<E>,
- F: 'static + FnMut(H, &mut Self) -> bool,
- {
- let subscription_id = post_inc(&mut self.next_subscription_id);
- let observed = handle.downgrade();
- let entity_id = handle.id();
- self.pending_effects.push_back(Effect::Observation {
- entity_id,
- subscription_id,
- callback: Box::new(move |cx| {
- if let Some(observed) = H::upgrade_from(&observed, cx) {
- callback(observed, cx)
- } else {
- false
+ }
+ Effect::NotifyGlobalObservers { global_type } => {
+ if !self.pending_global_notifications.insert(*global_type) {
+ return;
}
- }),
- });
- Subscription::Observation(self.observations.subscribe(entity_id, subscription_id))
- }
+ }
+ _ => {}
+ };
- fn observe_focus<F, V>(&mut self, handle: &ViewHandle<V>, mut callback: F) -> Subscription
- where
- V: 'static,
- F: 'static + FnMut(ViewHandle<V>, bool, &mut WindowContext) -> bool,
- {
- let subscription_id = post_inc(&mut self.next_subscription_id);
- let observed = handle.downgrade();
- let view_id = handle.id();
-
- self.pending_effects.push_back(Effect::FocusObservation {
- view_id,
- subscription_id,
- callback: Box::new(move |focused, cx| {
- if let Some(observed) = observed.upgrade(cx) {
- callback(observed, focused, cx)
- } else {
- false
- }
- }),
- });
- Subscription::FocusObservation(self.focus_observations.subscribe(view_id, subscription_id))
+ self.pending_effects.push_back(effect);
}
- pub fn observe_global<G, F>(&mut self, mut observe: F) -> Subscription
- where
- G: Any,
- F: 'static + FnMut(&mut AppContext),
- {
- let type_id = TypeId::of::<G>();
- let id = post_inc(&mut self.next_subscription_id);
+ /// Called at the end of AppContext::update to complete any side effects
+ /// such as notifying observers, emitting events, etc. Effects can themselves
+ /// cause effects, so we continue looping until all effects are processed.
+ fn flush_effects(&mut self) {
+ loop {
+ self.release_dropped_entities();
+ self.release_dropped_focus_handles();
- self.global_observations.add_callback(
- type_id,
- id,
- Box::new(move |cx: &mut AppContext| observe(cx)),
- );
- Subscription::GlobalObservation(self.global_observations.subscribe(type_id, id))
- }
+ if let Some(effect) = self.pending_effects.pop_front() {
+ match effect {
+ Effect::Notify { emitter } => {
+ self.apply_notify_effect(emitter);
+ }
- pub fn observe_default_global<G, F>(&mut self, observe: F) -> Subscription
- where
- G: Any + Default,
- F: 'static + FnMut(&mut AppContext),
- {
- if !self.has_global::<G>() {
- self.set_global(G::default());
+ Effect::Emit {
+ emitter,
+ event_type,
+ event,
+ } => self.apply_emit_effect(emitter, event_type, event),
+
+ Effect::Refresh => {
+ self.apply_refresh_effect();
+ }
+
+ Effect::NotifyGlobalObservers { global_type } => {
+ self.apply_notify_global_observers_effect(global_type);
+ }
+
+ Effect::Defer { callback } => {
+ self.apply_defer_effect(callback);
+ }
+ }
+ } else {
+ for window in self.windows.values() {
+ if let Some(window) = window.as_ref() {
+ if window.dirty {
+ window.platform_window.invalidate();
+ }
+ }
+ }
+
+ #[cfg(any(test, feature = "test-support"))]
+ for window in self
+ .windows
+ .values()
+ .filter_map(|window| {
+ let window = window.as_ref()?;
+ (window.dirty || window.focus_invalidated).then_some(window.handle)
+ })
+ .collect::<Vec<_>>()
+ {
+ self.update_window(window, |_, cx| cx.draw()).unwrap();
+ }
+
+ if self.pending_effects.is_empty() {
+ break;
+ }
+ }
}
- self.observe_global::<G, F>(observe)
}
- pub fn observe_release<E, H, F>(&mut self, handle: &H, callback: F) -> Subscription
- where
- E: Entity,
- E::Event: 'static,
- H: Handle<E>,
- F: 'static + FnOnce(&E, &mut Self),
- {
- let id = post_inc(&mut self.next_subscription_id);
- let mut callback = Some(callback);
- self.release_observations.add_callback(
- handle.id(),
- id,
- Box::new(move |released, cx| {
- let released = released.downcast_ref().unwrap();
- if let Some(callback) = callback.take() {
- callback(released, cx)
+ /// Repeatedly called during `flush_effects` to release any entities whose
+ /// reference count has become zero. We invoke any release observers before dropping
+ /// each entity.
+ fn release_dropped_entities(&mut self) {
+ loop {
+ let dropped = self.entities.take_dropped();
+ if dropped.is_empty() {
+ break;
+ }
+
+ for (entity_id, mut entity) in dropped {
+ self.observers.remove(&entity_id);
+ self.event_listeners.remove(&entity_id);
+ for release_callback in self.release_listeners.remove(&entity_id) {
+ release_callback(entity.as_mut(), self);
}
- }),
- );
- Subscription::ReleaseObservation(self.release_observations.subscribe(handle.id(), id))
+ }
+ }
}
- pub fn observe_actions<F>(&mut self, callback: F) -> Subscription
- where
- F: 'static + FnMut(TypeId, &mut AppContext),
- {
- let subscription_id = post_inc(&mut self.next_subscription_id);
- self.action_dispatch_observations
- .add_callback((), subscription_id, Box::new(callback));
- Subscription::ActionObservation(
- self.action_dispatch_observations
- .subscribe((), subscription_id),
- )
- }
+ /// Repeatedly called during `flush_effects` to handle a focused handle being dropped.
+ fn release_dropped_focus_handles(&mut self) {
+ for window_handle in self.windows() {
+ window_handle
+ .update(self, |_, cx| {
+ let mut blur_window = false;
+ let focus = cx.window.focus;
+ cx.window.focus_handles.write().retain(|handle_id, count| {
+ if count.load(SeqCst) == 0 {
+ if focus == Some(handle_id) {
+ blur_window = true;
+ }
+ false
+ } else {
+ true
+ }
+ });
- fn observe_active_labeled_tasks<F>(&mut self, callback: F) -> Subscription
- where
- F: 'static + FnMut(&mut AppContext) -> bool,
- {
- let subscription_id = post_inc(&mut self.next_subscription_id);
- self.active_labeled_task_observations
- .add_callback((), subscription_id, Box::new(callback));
- Subscription::ActiveLabeledTasksObservation(
- self.active_labeled_task_observations
- .subscribe((), subscription_id),
- )
+ if blur_window {
+ cx.blur();
+ }
+ })
+ .unwrap();
+ }
}
- pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut AppContext)) {
- self.pending_effects.push_back(Effect::Deferred {
- callback: Box::new(callback),
- after_window_update: false,
- })
+ fn apply_notify_effect(&mut self, emitter: EntityId) {
+ self.pending_notifications.remove(&emitter);
+
+ self.observers
+ .clone()
+ .retain(&emitter, |handler| handler(self));
}
- pub fn after_window_update(&mut self, callback: impl 'static + FnOnce(&mut AppContext)) {
- self.pending_effects.push_back(Effect::Deferred {
- callback: Box::new(callback),
- after_window_update: true,
- })
+ fn apply_emit_effect(&mut self, emitter: EntityId, event_type: TypeId, event: Box<dyn Any>) {
+ self.event_listeners
+ .clone()
+ .retain(&emitter, |(stored_type, handler)| {
+ if *stored_type == event_type {
+ handler(event.as_ref(), self)
+ } else {
+ true
+ }
+ });
}
- fn notify_model(&mut self, model_id: usize) {
- if self.pending_notifications.insert(model_id) {
- self.pending_effects
- .push_back(Effect::ModelNotification { model_id });
+ fn apply_refresh_effect(&mut self) {
+ for window in self.windows.values_mut() {
+ if let Some(window) = window.as_mut() {
+ window.dirty = true;
+ }
}
}
- fn notify_view(&mut self, window: AnyWindowHandle, view_id: usize) {
- if self.pending_notifications.insert(view_id) {
- self.pending_effects
- .push_back(Effect::ViewNotification { window, view_id });
- }
+ fn apply_notify_global_observers_effect(&mut self, type_id: TypeId) {
+ self.pending_global_notifications.remove(&type_id);
+ self.global_observers
+ .clone()
+ .retain(&type_id, |observer| observer(self));
+ }
+
+ fn apply_defer_effect(&mut self, callback: Box<dyn FnOnce(&mut Self) + 'static>) {
+ callback(self);
}
- fn notify_global(&mut self, type_id: TypeId) {
- if self.pending_global_notifications.insert(type_id) {
- self.pending_effects
- .push_back(Effect::GlobalNotification { type_id });
+ /// Creates an `AsyncAppContext`, which can be cloned and has a static lifetime
+ /// so it can be held across `await` points.
+ pub fn to_async(&self) -> AsyncAppContext {
+ AsyncAppContext {
+ app: unsafe { mem::transmute(self.this.clone()) },
+ background_executor: self.background_executor.clone(),
+ foreground_executor: self.foreground_executor.clone(),
}
}
- pub fn all_action_names<'a>(&'a self) -> impl Iterator<Item = &'static str> + 'a {
- self.action_deserializers.keys().copied()
+ /// Obtains a reference to the executor, which can be used to spawn futures.
+ pub fn background_executor(&self) -> &BackgroundExecutor {
+ &self.background_executor
}
- pub fn is_action_available(&self, action: &dyn Action) -> bool {
- let mut available_in_window = false;
- let action_id = action.id();
- if let Some(window) = self.active_window() {
- available_in_window = self
- .read_window(window, |cx| {
- if let Some(focused_view_id) = cx.focused_view_id() {
- for view_id in cx.ancestors(focused_view_id) {
- if let Some(view_metadata) =
- cx.views_metadata.get(&(cx.window_handle, view_id))
- {
- if let Some(actions) = cx.actions.get(&view_metadata.type_id) {
- if actions.contains_key(&action_id) {
- return true;
- }
- }
- }
- }
- }
- false
- })
- .unwrap_or(false);
- }
- available_in_window || self.global_actions.contains_key(&action_id)
+ /// Obtains a reference to the executor, which can be used to spawn futures.
+ pub fn foreground_executor(&self) -> &ForegroundExecutor {
+ &self.foreground_executor
}
- fn actions_mut(
- &mut self,
- capture_phase: bool,
- ) -> &mut HashMap<TypeId, HashMap<TypeId, Vec<Box<ActionCallback>>>> {
- if capture_phase {
- &mut self.capture_actions
- } else {
- &mut self.actions
- }
+ /// Spawns the future returned by the given function on the thread pool. The closure will be invoked
+ /// with AsyncAppContext, which allows the application state to be accessed across await points.
+ pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
+ where
+ Fut: Future<Output = R> + 'static,
+ R: 'static,
+ {
+ self.foreground_executor.spawn(f(self.to_async()))
}
- fn dispatch_global_action_any(&mut self, action: &dyn Action) -> bool {
- self.update(|this| {
- if let Some((name, mut handler)) = this.global_actions.remove_entry(&action.id()) {
- handler(action, this);
- this.global_actions.insert(name, handler);
- true
- } else {
- false
- }
- })
+ /// Schedules the given function to be run at the end of the current effect cycle, allowing entities
+ /// that are currently on the stack to be returned to the app.
+ pub fn defer(&mut self, f: impl FnOnce(&mut AppContext) + 'static) {
+ self.push_effect(Effect::Defer {
+ callback: Box::new(f),
+ });
}
- pub fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
- self.keystroke_matcher.add_bindings(bindings);
+ /// Accessor for the application's asset source, which is provided when constructing the `App`.
+ pub fn asset_source(&self) -> &Arc<dyn AssetSource> {
+ &self.asset_source
}
- pub fn clear_bindings(&mut self) {
- self.keystroke_matcher.clear_bindings();
+ /// Accessor for the text system.
+ pub fn text_system(&self) -> &Arc<TextSystem> {
+ &self.text_system
}
- pub fn binding_for_action(&self, action: &dyn Action) -> Option<&Binding> {
- self.keystroke_matcher
- .bindings_for_action(action.id())
- .find(|binding| binding.action().eq(action))
+ /// The current text style. Which is composed of all the style refinements provided to `with_text_style`.
+ pub fn text_style(&self) -> TextStyle {
+ let mut style = TextStyle::default();
+ for refinement in &self.text_style_stack {
+ style.refine(refinement);
+ }
+ style
}
- pub fn default_global<T: 'static + Default>(&mut self) -> &T {
- let type_id = TypeId::of::<T>();
- self.update(|this| {
- if let Entry::Vacant(entry) = this.globals.entry(type_id) {
- entry.insert(Box::new(T::default()));
- this.notify_global(type_id);
- }
- });
- self.globals.get(&type_id).unwrap().downcast_ref().unwrap()
+ /// Check whether a global of the given type has been assigned.
+ pub fn has_global<G: 'static>(&self) -> bool {
+ self.globals_by_type.contains_key(&TypeId::of::<G>())
}
- pub fn set_global<T: 'static>(&mut self, state: T) {
- self.update(|this| {
- let type_id = TypeId::of::<T>();
- this.globals.insert(type_id, Box::new(state));
- this.notify_global(type_id);
- });
+ /// Access the global of the given type. Panics if a global for that type has not been assigned.
+ #[track_caller]
+ pub fn global<G: 'static>(&self) -> &G {
+ self.globals_by_type
+ .get(&TypeId::of::<G>())
+ .map(|any_state| any_state.downcast_ref::<G>().unwrap())
+ .ok_or_else(|| anyhow!("no state of type {} exists", type_name::<G>()))
+ .unwrap()
}
- pub fn update_default_global<T, F, U>(&mut self, update: F) -> U
- where
- T: 'static + Default,
- F: FnOnce(&mut T, &mut AppContext) -> U,
- {
- self.update(|mut this| {
- Self::update_default_global_internal(&mut this, |global, cx| update(global, cx))
- })
+ /// Access the global of the given type if a value has been assigned.
+ pub fn try_global<G: 'static>(&self) -> Option<&G> {
+ self.globals_by_type
+ .get(&TypeId::of::<G>())
+ .map(|any_state| any_state.downcast_ref::<G>().unwrap())
}
- fn update_default_global_internal<C, T, F, U>(this: &mut C, update: F) -> U
- where
- C: DerefMut<Target = AppContext>,
- T: 'static + Default,
- F: FnOnce(&mut T, &mut C) -> U,
- {
- let type_id = TypeId::of::<T>();
- let mut state = this
- .globals
- .remove(&type_id)
- .unwrap_or_else(|| Box::new(T::default()));
- let result = update(state.downcast_mut().unwrap(), this);
- this.globals.insert(type_id, state);
- this.notify_global(type_id);
- result
+ /// Access the global of the given type mutably. Panics if a global for that type has not been assigned.
+ #[track_caller]
+ pub fn global_mut<G: 'static>(&mut self) -> &mut G {
+ let global_type = TypeId::of::<G>();
+ self.push_effect(Effect::NotifyGlobalObservers { global_type });
+ self.globals_by_type
+ .get_mut(&global_type)
+ .and_then(|any_state| any_state.downcast_mut::<G>())
+ .ok_or_else(|| anyhow!("no state of type {} exists", type_name::<G>()))
+ .unwrap()
}
- pub fn update_global<T, F, U>(&mut self, update: F) -> U
- where
- T: 'static,
- F: FnOnce(&mut T, &mut AppContext) -> U,
- {
- self.update(|mut this| {
- Self::update_global_internal(&mut this, |global, cx| update(global, cx))
- })
+ /// Access the global of the given type mutably. A default value is assigned if a global of this type has not
+ /// yet been assigned.
+ pub fn default_global<G: 'static + Default>(&mut self) -> &mut G {
+ let global_type = TypeId::of::<G>();
+ self.push_effect(Effect::NotifyGlobalObservers { global_type });
+ self.globals_by_type
+ .entry(global_type)
+ .or_insert_with(|| Box::<G>::default())
+ .downcast_mut::<G>()
+ .unwrap()
}
- fn update_global_internal<C, T, F, U>(this: &mut C, update: F) -> U
- where
- C: DerefMut<Target = AppContext>,
- T: 'static,
- F: FnOnce(&mut T, &mut C) -> U,
- {
- let type_id = TypeId::of::<T>();
- if let Some(mut state) = this.globals.remove(&type_id) {
- let result = update(state.downcast_mut().unwrap(), this);
- this.globals.insert(type_id, state);
- this.notify_global(type_id);
- result
- } else {
- panic!("no global added for {}", std::any::type_name::<T>());
- }
+ /// Set the value of the global of the given type.
+ pub fn set_global<G: Any>(&mut self, global: G) {
+ let global_type = TypeId::of::<G>();
+ self.push_effect(Effect::NotifyGlobalObservers { global_type });
+ self.globals_by_type.insert(global_type, Box::new(global));
}
+ /// Clear all stored globals. Does not notify global observers.
+ #[cfg(any(test, feature = "test-support"))]
pub fn clear_globals(&mut self) {
- self.globals.clear();
+ self.globals_by_type.drain();
}
- pub fn remove_global<T: 'static>(&mut self) -> T {
+ /// Remove the global of the given type from the app context. Does not notify global observers.
+ pub fn remove_global<G: Any>(&mut self) -> G {
+ let global_type = TypeId::of::<G>();
*self
- .globals
- .remove(&TypeId::of::<T>())
- .unwrap_or_else(|| panic!("no global added for {}", std::any::type_name::<T>()))
+ .globals_by_type
+ .remove(&global_type)
+ .unwrap_or_else(|| panic!("no global added for {}", std::any::type_name::<G>()))
.downcast()
.unwrap()
}
- pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
- where
- T: Entity,
- F: FnOnce(&mut ModelContext<T>) -> T,
- {
- self.update(|this| {
- let model_id = post_inc(&mut this.next_id);
- let handle = ModelHandle::new(model_id, &this.ref_counts);
- let mut cx = ModelContext::new(this, model_id);
- let model = build_model(&mut cx);
- this.models.insert(model_id, Box::new(model));
- handle
- })
- }
-
- pub fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
- if let Some(model) = self.models.get(&handle.model_id) {
- model
- .as_any()
- .downcast_ref()
- .expect("downcast is type safe")
- } else {
- panic!("circular model reference");
- }
+ /// Update the global of the given type with a closure. Unlike `global_mut`, this method provides
+ /// your closure with mutable access to the `AppContext` and the global simultaneously.
+ pub fn update_global<G: 'static, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R {
+ let mut global = self.lease_global::<G>();
+ let result = f(&mut global, self);
+ self.end_global_lease(global);
+ result
}
- fn update_model<T: Entity, V>(
+ /// Register a callback to be invoked when a global of the given type is updated.
+ pub fn observe_global<G: 'static>(
&mut self,
- handle: &ModelHandle<T>,
- update: &mut dyn FnMut(&mut T, &mut ModelContext<T>) -> V,
- ) -> V {
- if let Some(mut model) = self.models.remove(&handle.model_id) {
- self.update(|this| {
- let mut cx = ModelContext::new(this, handle.model_id);
- let result = update(
- model
- .as_any_mut()
- .downcast_mut()
- .expect("downcast is type safe"),
- &mut cx,
- );
- this.models.insert(handle.model_id, model);
- result
- })
- } else {
- panic!("circular model update for {}", std::any::type_name::<T>());
- }
- }
-
- fn upgrade_model_handle<T: Entity>(
- &self,
- handle: &WeakModelHandle<T>,
- ) -> Option<ModelHandle<T>> {
- if self.ref_counts.lock().is_entity_alive(handle.model_id) {
- Some(ModelHandle::new(handle.model_id, &self.ref_counts))
- } else {
- None
- }
+ mut f: impl FnMut(&mut Self) + 'static,
+ ) -> Subscription {
+ let (subscription, activate) = self.global_observers.insert(
+ TypeId::of::<G>(),
+ Box::new(move |cx| {
+ f(cx);
+ true
+ }),
+ );
+ self.defer(move |_| activate());
+ subscription
}
- fn model_handle_is_upgradable<T: Entity>(&self, handle: &WeakModelHandle<T>) -> bool {
- self.ref_counts.lock().is_entity_alive(handle.model_id)
+ /// Move the global of the given type to the stack.
+ pub(crate) fn lease_global<G: 'static>(&mut self) -> GlobalLease<G> {
+ GlobalLease::new(
+ self.globals_by_type
+ .remove(&TypeId::of::<G>())
+ .ok_or_else(|| anyhow!("no global registered of type {}", type_name::<G>()))
+ .unwrap(),
+ )
}
- fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> {
- if self.ref_counts.lock().is_entity_alive(handle.model_id) {
- Some(AnyModelHandle::new(
- handle.model_id,
- handle.model_type,
- self.ref_counts.clone(),
- ))
- } else {
- None
- }
+ /// Restore the global of the given type after it is moved to the stack.
+ pub(crate) fn end_global_lease<G: 'static>(&mut self, lease: GlobalLease<G>) {
+ let global_type = TypeId::of::<G>();
+ self.push_effect(Effect::NotifyGlobalObservers { global_type });
+ self.globals_by_type.insert(global_type, lease.global);
}
- pub fn add_window<V, F>(
+ pub fn observe_new_views<V: 'static>(
&mut self,
- window_options: WindowOptions,
- build_root_view: F,
- ) -> WindowHandle<V>
- where
- V: View,
- F: FnOnce(&mut ViewContext<V>) -> V,
- {
- self.update(|this| {
- let handle = WindowHandle::<V>::new(post_inc(&mut this.next_id));
- let platform_window =
- this.platform
- .open_window(handle.into(), window_options, this.foreground.clone());
- let window = this.build_window(handle.into(), platform_window, build_root_view);
- this.windows.insert(handle.into(), window);
- handle
- })
+ on_new: impl 'static + Fn(&mut V, &mut ViewContext<V>),
+ ) -> Subscription {
+ let (subscription, activate) = self.new_view_observers.insert(
+ TypeId::of::<V>(),
+ Box::new(move |any_view: AnyView, cx: &mut WindowContext| {
+ any_view
+ .downcast::<V>()
+ .unwrap()
+ .update(cx, |view_state, cx| {
+ on_new(view_state, cx);
+ })
+ }),
+ );
+ activate();
+ subscription
}
- pub fn add_status_bar_item<V, F>(&mut self, build_root_view: F) -> WindowHandle<V>
+ pub fn observe_release<E, T>(
+ &mut self,
+ handle: &E,
+ on_release: impl FnOnce(&mut T, &mut AppContext) + 'static,
+ ) -> Subscription
where
- V: View,
- F: FnOnce(&mut ViewContext<V>) -> V,
+ E: Entity<T>,
+ T: 'static,
{
- self.update(|this| {
- let handle = WindowHandle::<V>::new(post_inc(&mut this.next_id));
- let platform_window = this.platform.add_status_item(handle.into());
- let window = this.build_window(handle.into(), platform_window, build_root_view);
- this.windows.insert(handle.into(), window);
- handle.update_root(this, |view, cx| view.focus_in(cx.handle().into_any(), cx));
- handle
- })
+ let (subscription, activate) = self.release_listeners.insert(
+ handle.entity_id(),
+ Box::new(move |entity, cx| {
+ let entity = entity.downcast_mut().expect("invalid entity type");
+ on_release(entity, cx)
+ }),
+ );
+ activate();
+ subscription
}
- pub fn build_window<V, F>(
+ pub fn observe_keystrokes(
&mut self,
- handle: AnyWindowHandle,
- mut platform_window: Box<dyn platform::Window>,
- build_root_view: F,
- ) -> Window
- where
- V: View,
- F: FnOnce(&mut ViewContext<V>) -> V,
- {
- {
- let mut app = self.upgrade();
-
- platform_window.on_event(Box::new(move |event| {
- app.update_window(handle, |cx| {
- if let Event::KeyDown(KeyDownEvent { keystroke, .. }) = &event {
- if cx.dispatch_keystroke(keystroke) {
- return true;
- }
- }
-
- cx.dispatch_event(event, false)
- })
- .unwrap_or(false)
- }));
- }
-
- {
- let mut app = self.upgrade();
- platform_window.on_active_status_change(Box::new(move |is_active| {
- app.update(|cx| cx.window_changed_active_status(handle, is_active))
- }));
- }
+ f: impl FnMut(&KeystrokeEvent, &mut WindowContext) + 'static,
+ ) -> Subscription {
+ let (subscription, activate) = self.keystroke_observers.insert((), Box::new(f));
+ activate();
+ subscription
+ }
- {
- let mut app = self.upgrade();
- platform_window.on_resize(Box::new(move || {
- app.update(|cx| cx.window_was_resized(handle))
- }));
- }
+ pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) {
+ self.text_style_stack.push(text_style);
+ }
- {
- let mut app = self.upgrade();
- platform_window.on_moved(Box::new(move || {
- app.update(|cx| cx.window_was_moved(handle))
- }));
- }
+ pub(crate) fn pop_text_style(&mut self) {
+ self.text_style_stack.pop();
+ }
- {
- let mut app = self.upgrade();
- platform_window.on_fullscreen(Box::new(move |is_fullscreen| {
- app.update(|cx| cx.window_was_fullscreen_changed(handle, is_fullscreen))
- }));
- }
+ /// Register key bindings.
+ pub fn bind_keys(&mut self, bindings: impl IntoIterator<Item = KeyBinding>) {
+ self.keymap.lock().add_bindings(bindings);
+ self.pending_effects.push_back(Effect::Refresh);
+ }
- {
- let mut app = self.upgrade();
- platform_window.on_close(Box::new(move || {
- app.update(|cx| cx.update_window(handle, |cx| cx.remove_window()));
+ /// Register a global listener for actions invoked via the keyboard.
+ pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Self) + 'static) {
+ self.global_action_listeners
+ .entry(TypeId::of::<A>())
+ .or_default()
+ .push(Rc::new(move |action, phase, cx| {
+ if phase == DispatchPhase::Bubble {
+ let action = action.downcast_ref().unwrap();
+ listener(action, cx)
+ }
}));
- }
+ }
- {
- let mut app = self.upgrade();
- platform_window
- .on_appearance_changed(Box::new(move || app.update(|cx| cx.refresh_windows())));
- }
+ /// Event handlers propagate events by default. Call this method to stop dispatching to
+ /// event handlers with a lower z-index (mouse) or higher in the tree (keyboard). This is
+ /// the opposite of [propagate]. It's also possible to cancel a call to [propagate] by
+ /// calling this method before effects are flushed.
+ pub fn stop_propagation(&mut self) {
+ self.propagate_event = false;
+ }
- platform_window.set_input_handler(Box::new(WindowInputHandler {
- app: self.upgrade().0,
- window: handle,
- }));
+ /// Action handlers stop propagation by default during the bubble phase of action dispatch
+ /// dispatching to action handlers higher in the element tree. This is the opposite of
+ /// [stop_propagation]. It's also possible to cancel a call to [stop_propagate] by calling
+ /// this method before effects are flushed.
+ pub fn propagate(&mut self) {
+ self.propagate_event = true;
+ }
- let mut window = Window::new(handle, platform_window, self, build_root_view);
- let mut cx = WindowContext::mutable(self, &mut window, handle);
- cx.layout(false).expect("initial layout should not error");
- let scene = cx.paint().expect("initial paint should not error");
- window.platform_window.present_scene(scene);
- window
+ pub fn build_action(
+ &self,
+ name: &str,
+ data: Option<serde_json::Value>,
+ ) -> Result<Box<dyn Action>> {
+ self.actions.build_action(name, data)
}
- pub fn active_window(&self) -> Option<AnyWindowHandle> {
- self.platform.main_window()
+ pub fn all_action_names(&self) -> &[SharedString] {
+ self.actions.all_action_names()
}
- pub fn windows(&self) -> impl '_ + Iterator<Item = AnyWindowHandle> {
- self.windows.keys().copied()
+ pub fn on_app_quit<Fut>(
+ &mut self,
+ mut on_quit: impl FnMut(&mut AppContext) -> Fut + 'static,
+ ) -> Subscription
+ where
+ Fut: 'static + Future<Output = ()>,
+ {
+ let (subscription, activate) = self.quit_observers.insert(
+ (),
+ Box::new(move |cx| {
+ let future = on_quit(cx);
+ future.boxed_local()
+ }),
+ );
+ activate();
+ subscription
}
- pub fn read_view<V: 'static>(&self, handle: &ViewHandle<V>) -> &V {
- if let Some(view) = self.views.get(&(handle.window, handle.view_id)) {
- view.as_any().downcast_ref().expect("downcast is type safe")
- } else {
- panic!("circular view reference for type {}", type_name::<V>());
+ pub(crate) fn clear_pending_keystrokes(&mut self) {
+ for window in self.windows() {
+ window
+ .update(self, |_, cx| {
+ cx.window
+ .rendered_frame
+ .dispatch_tree
+ .clear_pending_keystrokes();
+ cx.window
+ .next_frame
+ .dispatch_tree
+ .clear_pending_keystrokes();
+ })
+ .ok();
}
}
- fn upgrade_view_handle<V: 'static>(&self, handle: &WeakViewHandle<V>) -> Option<ViewHandle<V>> {
- if self.ref_counts.lock().is_entity_alive(handle.view_id) {
- Some(ViewHandle::new(
- handle.window,
- handle.view_id,
- &self.ref_counts,
- ))
- } else {
- None
+ pub fn is_action_available(&mut self, action: &dyn Action) -> bool {
+ if let Some(window) = self.active_window() {
+ if let Ok(window_action_available) =
+ window.update(self, |_, cx| cx.is_action_available(action))
+ {
+ return window_action_available;
+ }
}
+
+ self.global_action_listeners
+ .contains_key(&action.as_any().type_id())
}
- fn upgrade_any_view_handle(&self, handle: &AnyWeakViewHandle) -> Option<AnyViewHandle> {
- if self.ref_counts.lock().is_entity_alive(handle.view_id) {
- Some(AnyViewHandle::new(
- handle.window,
- handle.view_id,
- handle.view_type,
- self.ref_counts.clone(),
- ))
- } else {
- None
- }
+ pub fn set_menus(&mut self, menus: Vec<Menu>) {
+ self.platform.set_menus(menus, &self.keymap.lock());
}
- fn remove_dropped_entities(&mut self) {
- loop {
- let (dropped_models, dropped_views, dropped_element_states) =
- self.ref_counts.lock().take_dropped();
- if dropped_models.is_empty()
- && dropped_views.is_empty()
- && dropped_element_states.is_empty()
+ pub fn dispatch_action(&mut self, action: &dyn Action) {
+ if let Some(active_window) = self.active_window() {
+ active_window
+ .update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
+ .log_err();
+ } else {
+ self.propagate_event = true;
+
+ if let Some(mut global_listeners) = self
+ .global_action_listeners
+ .remove(&action.as_any().type_id())
{
- break;
- }
+ for listener in &global_listeners {
+ listener(action.as_any(), DispatchPhase::Capture, self);
+ if !self.propagate_event {
+ break;
+ }
+ }
+
+ global_listeners.extend(
+ self.global_action_listeners
+ .remove(&action.as_any().type_id())
+ .unwrap_or_default(),
+ );
- for model_id in dropped_models {
- self.subscriptions.remove(model_id);
- self.observations.remove(model_id);
- let mut model = self.models.remove(&model_id).unwrap();
- model.release(self);
- self.pending_effects
- .push_back(Effect::ModelRelease { model_id, model });
+ self.global_action_listeners
+ .insert(action.as_any().type_id(), global_listeners);
}
- for (window, view_id) in dropped_views {
- self.subscriptions.remove(view_id);
- self.observations.remove(view_id);
- self.views_metadata.remove(&(window, view_id));
- let mut view = self.views.remove(&(window, view_id)).unwrap();
- view.release(self);
- if let Some(window) = self.windows.get_mut(&window) {
- window.parents.remove(&view_id);
- window
- .invalidation
- .get_or_insert_with(Default::default)
- .removed
- .push(view_id);
- }
+ if self.propagate_event {
+ if let Some(mut global_listeners) = self
+ .global_action_listeners
+ .remove(&action.as_any().type_id())
+ {
+ for listener in global_listeners.iter().rev() {
+ listener(action.as_any(), DispatchPhase::Bubble, self);
+ if !self.propagate_event {
+ break;
+ }
+ }
- self.pending_effects
- .push_back(Effect::ViewRelease { view_id, view });
- }
+ global_listeners.extend(
+ self.global_action_listeners
+ .remove(&action.as_any().type_id())
+ .unwrap_or_default(),
+ );
- for key in dropped_element_states {
- self.element_states.remove(&key);
+ self.global_action_listeners
+ .insert(action.as_any().type_id(), global_listeners);
+ }
}
}
}
- fn flush_effects(&mut self) {
- self.pending_flushes = self.pending_flushes.saturating_sub(1);
- let mut after_window_update_callbacks = Vec::new();
-
- if !self.flushing_effects && self.pending_flushes == 0 {
- self.flushing_effects = true;
+ pub fn has_active_drag(&self) -> bool {
+ self.active_drag.is_some()
+ }
- let mut refreshing = false;
- let mut updated_windows = HashSet::default();
- let mut focus_effects = HashMap::<AnyWindowHandle, FocusEffect>::default();
- loop {
- self.remove_dropped_entities();
- if let Some(effect) = self.pending_effects.pop_front() {
- match effect {
- Effect::Subscription {
- entity_id,
- subscription_id,
- callback,
- } => self
- .subscriptions
- .add_callback(entity_id, subscription_id, callback),
-
- Effect::Event { entity_id, payload } => {
- let mut subscriptions = self.subscriptions.clone();
- subscriptions
- .emit(entity_id, |callback| callback(payload.as_ref(), self))
- }
+ pub fn active_drag<T: 'static>(&self) -> Option<&T> {
+ self.active_drag
+ .as_ref()
+ .and_then(|drag| drag.value.downcast_ref())
+ }
+}
- Effect::GlobalSubscription {
- type_id,
- subscription_id,
- callback,
- } => self.global_subscriptions.add_callback(
- type_id,
- subscription_id,
- callback,
- ),
-
- Effect::GlobalEvent { payload } => self.emit_global_event(payload),
-
- Effect::Observation {
- entity_id,
- subscription_id,
- callback,
- } => self
- .observations
- .add_callback(entity_id, subscription_id, callback),
-
- Effect::ModelNotification { model_id } => {
- let mut observations = self.observations.clone();
- observations.emit(model_id, |callback| callback(self));
- }
+impl Context for AppContext {
+ type Result<T> = T;
- Effect::ViewNotification {
- window: window_id,
- view_id,
- } => self.handle_view_notification_effect(window_id, view_id),
-
- Effect::GlobalNotification { type_id } => {
- let mut subscriptions = self.global_observations.clone();
- subscriptions.emit(type_id, |callback| {
- callback(self);
- true
- });
- }
-
- Effect::Deferred {
- callback,
- after_window_update,
- } => {
- if after_window_update {
- after_window_update_callbacks.push(callback);
- } else {
- callback(self)
- }
- }
-
- Effect::ModelRelease { model_id, model } => {
- self.handle_entity_release_effect(model_id, model.as_any())
- }
-
- Effect::ViewRelease { view_id, view } => {
- self.handle_entity_release_effect(view_id, view.as_any())
- }
-
- Effect::Focus(mut effect) => {
- if focus_effects
- .get(&effect.window())
- .map_or(false, |prev_effect| prev_effect.is_forced())
- {
- effect.force();
- }
-
- focus_effects.insert(effect.window(), effect);
- }
-
- Effect::FocusObservation {
- view_id,
- subscription_id,
- callback,
- } => {
- self.focus_observations.add_callback(
- view_id,
- subscription_id,
- callback,
- );
- }
-
- Effect::ResizeWindow { window } => {
- if let Some(window) = self.windows.get_mut(&window) {
- window
- .invalidation
- .get_or_insert(WindowInvalidation::default());
- }
- self.handle_window_moved(window);
- }
-
- Effect::MoveWindow { window } => {
- self.handle_window_moved(window);
- }
-
- Effect::WindowActivationObservation {
- window,
- subscription_id,
- callback,
- } => self.window_activation_observations.add_callback(
- window,
- subscription_id,
- callback,
- ),
-
- Effect::ActivateWindow { window, is_active } => {
- if self.handle_window_activation_effect(window, is_active) && is_active
- {
- focus_effects
- .entry(window)
- .or_insert_with(|| FocusEffect::View {
- window,
- view_id: self
- .read_window(window, |cx| cx.focused_view_id())
- .flatten(),
- is_forced: true,
- })
- .force();
- }
- }
-
- Effect::WindowFullscreenObservation {
- window,
- subscription_id,
- callback,
- } => self.window_fullscreen_observations.add_callback(
- window,
- subscription_id,
- callback,
- ),
-
- Effect::FullscreenWindow {
- window,
- is_fullscreen,
- } => self.handle_fullscreen_effect(window, is_fullscreen),
-
- Effect::WindowBoundsObservation {
- window,
- subscription_id,
- callback,
- } => self.window_bounds_observations.add_callback(
- window,
- subscription_id,
- callback,
- ),
-
- Effect::RefreshWindows => {
- refreshing = true;
- }
-
- Effect::ActionDispatchNotification { action_id } => {
- self.handle_action_dispatch_notification_effect(action_id)
- }
- Effect::WindowShouldCloseSubscription { window, callback } => {
- self.handle_window_should_close_subscription_effect(window, callback)
- }
- Effect::Keystroke {
- window,
- keystroke,
- handled_by,
- result,
- } => self.handle_keystroke_effect(window, keystroke, handled_by, result),
- Effect::ActiveLabeledTasksChanged => {
- self.handle_active_labeled_tasks_changed_effect()
- }
- Effect::ActiveLabeledTasksObservation {
- subscription_id,
- callback,
- } => self.active_labeled_task_observations.add_callback(
- (),
- subscription_id,
- callback,
- ),
- Effect::RepaintWindow { window } => {
- self.handle_repaint_window_effect(window)
- }
- }
- self.pending_notifications.clear();
- } else {
- for window in self.windows().collect::<Vec<_>>() {
- self.update_window(window, |cx| {
- let invalidation = if refreshing {
- let mut invalidation =
- cx.window.invalidation.take().unwrap_or_default();
- invalidation
- .updated
- .extend(cx.window.rendered_views.keys().copied());
- Some(invalidation)
- } else {
- cx.window.invalidation.take()
- };
-
- if let Some(invalidation) = invalidation {
- let appearance = cx.window.platform_window.appearance();
- cx.invalidate(invalidation, appearance);
- if let Some(old_parents) = cx.layout(refreshing).log_err() {
- updated_windows.insert(window);
-
- if let Some(focused_view_id) = cx.focused_view_id() {
- let old_ancestors = std::iter::successors(
- Some(focused_view_id),
- |&view_id| old_parents.get(&view_id).copied(),
- )
- .collect::<HashSet<_>>();
- let new_ancestors =
- cx.ancestors(focused_view_id).collect::<HashSet<_>>();
-
- // Notify the old ancestors of the focused view when they don't contain it anymore.
- for old_ancestor in old_ancestors.iter().copied() {
- if !new_ancestors.contains(&old_ancestor) {
- if let Some(mut view) =
- cx.views.remove(&(window, old_ancestor))
- {
- view.focus_out(
- focused_view_id,
- cx,
- old_ancestor,
- );
- cx.views.insert((window, old_ancestor), view);
- }
- }
- }
-
- // Notify the new ancestors of the focused view if they contain it now.
- for new_ancestor in new_ancestors.iter().copied() {
- if !old_ancestors.contains(&new_ancestor) {
- if let Some(mut view) =
- cx.views.remove(&(window, new_ancestor))
- {
- view.focus_in(
- focused_view_id,
- cx,
- new_ancestor,
- );
- cx.views.insert((window, new_ancestor), view);
- }
- }
- }
-
- // When the previously-focused view has been dropped and
- // there isn't any pending focus, focus the root view.
- let root_view_id = cx.window.root_view().id();
- if focused_view_id != root_view_id
- && !cx.views.contains_key(&(window, focused_view_id))
- && !focus_effects.contains_key(&window)
- {
- focus_effects.insert(
- window,
- FocusEffect::View {
- window,
- view_id: Some(root_view_id),
- is_forced: false,
- },
- );
- }
- }
- }
- }
- });
- }
-
- for (_, effect) in focus_effects.drain() {
- self.handle_focus_effect(effect);
- }
-
- if self.pending_effects.is_empty() {
- for callback in after_window_update_callbacks.drain(..) {
- callback(self);
- }
-
- for window in updated_windows.drain() {
- self.update_window(window, |cx| {
- if let Some(scene) = cx.paint().log_err() {
- cx.window.platform_window.present_scene(scene);
- }
- });
- }
-
- if self.pending_effects.is_empty() {
- self.flushing_effects = false;
- self.pending_notifications.clear();
- self.pending_global_notifications.clear();
- break;
- }
- }
-
- refreshing = false;
- }
- }
- }
- }
-
- fn window_was_resized(&mut self, window: AnyWindowHandle) {
- self.pending_effects
- .push_back(Effect::ResizeWindow { window });
- }
-
- fn window_was_moved(&mut self, window: AnyWindowHandle) {
- self.pending_effects
- .push_back(Effect::MoveWindow { window });
- }
-
- fn window_was_fullscreen_changed(&mut self, window: AnyWindowHandle, is_fullscreen: bool) {
- self.pending_effects.push_back(Effect::FullscreenWindow {
- window,
- is_fullscreen,
- });
- }
-
- fn window_changed_active_status(&mut self, window: AnyWindowHandle, is_active: bool) {
- self.pending_effects
- .push_back(Effect::ActivateWindow { window, is_active });
- }
-
- fn keystroke(
- &mut self,
- window: AnyWindowHandle,
- keystroke: Keystroke,
- handled_by: Option<Box<dyn Action>>,
- result: MatchResult,
- ) {
- self.pending_effects.push_back(Effect::Keystroke {
- window,
- keystroke,
- handled_by,
- result,
- });
- }
-
- pub fn refresh_windows(&mut self) {
- self.pending_effects.push_back(Effect::RefreshWindows);
- }
-
- fn emit_global_event(&mut self, payload: Box<dyn Any>) {
- let type_id = (&*payload).type_id();
-
- let mut subscriptions = self.global_subscriptions.clone();
- subscriptions.emit(type_id, |callback| {
- callback(payload.as_ref(), self);
- true //Always alive
- });
- }
-
- fn handle_view_notification_effect(
- &mut self,
- observed_window: AnyWindowHandle,
- observed_view_id: usize,
- ) {
- let view_key = (observed_window, observed_view_id);
- if let Some((view, mut view_metadata)) = self
- .views
- .remove(&view_key)
- .zip(self.views_metadata.remove(&view_key))
- {
- if let Some(window) = self.windows.get_mut(&observed_window) {
- window
- .invalidation
- .get_or_insert_with(Default::default)
- .updated
- .insert(observed_view_id);
- }
-
- view.update_keymap_context(&mut view_metadata.keymap_context, self);
- self.views.insert(view_key, view);
- self.views_metadata.insert(view_key, view_metadata);
-
- let mut observations = self.observations.clone();
- observations.emit(observed_view_id, |callback| callback(self));
- }
- }
-
- fn handle_entity_release_effect(&mut self, entity_id: usize, entity: &dyn Any) {
- self.release_observations
- .clone()
- .emit(entity_id, |callback| {
- callback(entity, self);
- // Release observations happen one time. So clear the callback by returning false
- false
- })
- }
-
- fn handle_fullscreen_effect(&mut self, window: AnyWindowHandle, is_fullscreen: bool) {
- self.update_window(window, |cx| {
- cx.window.is_fullscreen = is_fullscreen;
-
- let mut fullscreen_observations = cx.window_fullscreen_observations.clone();
- fullscreen_observations.emit(window, |callback| callback(is_fullscreen, cx));
-
- if let Some(uuid) = cx.window_display_uuid() {
- let bounds = cx.window_bounds();
- let mut bounds_observations = cx.window_bounds_observations.clone();
- bounds_observations.emit(window, |callback| callback(bounds, uuid, cx));
- }
-
- Some(())
- });
- }
-
- fn handle_keystroke_effect(
+ /// Build an entity that is owned by the application. The given function will be invoked with
+ /// a `ModelContext` and must return an object representing the entity. A `Model` will be returned
+ /// which can be used to access the entity in a context.
+ fn new_model<T: 'static>(
&mut self,
- window: AnyWindowHandle,
- keystroke: Keystroke,
- handled_by: Option<Box<dyn Action>>,
- result: MatchResult,
- ) {
- self.update_window(window, |cx| {
- let mut observations = cx.keystroke_observations.clone();
- observations.emit(window, move |callback| {
- callback(&keystroke, &result, handled_by.as_ref(), cx)
- });
- });
- }
-
- fn handle_repaint_window_effect(&mut self, window: AnyWindowHandle) {
- self.update_window(window, |cx| {
- if let Some(scene) = cx.paint().log_err() {
- cx.window.platform_window.present_scene(scene);
- }
- });
- }
-
- fn handle_window_activation_effect(&mut self, window: AnyWindowHandle, active: bool) -> bool {
- self.update_window(window, |cx| {
- if cx.window.is_active == active {
- return false;
- }
- cx.window.is_active = active;
-
- let mut observations = cx.window_activation_observations.clone();
- observations.emit(window, |callback| callback(active, cx));
- true
+ build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
+ ) -> Model<T> {
+ self.update(|cx| {
+ let slot = cx.entities.reserve();
+ let entity = build_model(&mut ModelContext::new(cx, slot.downgrade()));
+ cx.entities.insert(slot, entity)
})
- .unwrap_or(false)
- }
-
- fn handle_focus_effect(&mut self, effect: FocusEffect) {
- let window = effect.window();
- self.update_window(window, |cx| {
- // Ensure the newly-focused view still exists, otherwise focus
- // the root view instead.
- let focused_id = match effect {
- FocusEffect::View { view_id, .. } => {
- if let Some(view_id) = view_id {
- if cx.views.contains_key(&(window, view_id)) {
- Some(view_id)
- } else {
- Some(cx.root_view().id())
- }
- } else {
- None
- }
- }
- FocusEffect::ViewParent { view_id, .. } => Some(
- cx.window
- .parents
- .get(&view_id)
- .copied()
- .unwrap_or(cx.root_view().id()),
- ),
- };
-
- let focus_changed = cx.window.focused_view_id != focused_id;
- let blurred_id = cx.window.focused_view_id;
- cx.window.focused_view_id = focused_id;
-
- if focus_changed {
- if let Some(blurred_id) = blurred_id {
- for view_id in cx.ancestors(blurred_id).collect::<Vec<_>>() {
- if let Some(mut view) = cx.views.remove(&(window, view_id)) {
- view.focus_out(blurred_id, cx, view_id);
- cx.views.insert((window, view_id), view);
- }
- }
-
- let mut subscriptions = cx.focus_observations.clone();
- subscriptions.emit(blurred_id, |callback| callback(false, cx));
- }
- }
-
- if focus_changed || effect.is_forced() {
- if let Some(focused_id) = focused_id {
- for view_id in cx.ancestors(focused_id).collect::<Vec<_>>() {
- if let Some(mut view) = cx.views.remove(&(window, view_id)) {
- view.focus_in(focused_id, cx, view_id);
- cx.views.insert((window, view_id), view);
- }
- }
-
- let mut subscriptions = cx.focus_observations.clone();
- subscriptions.emit(focused_id, |callback| callback(true, cx));
- }
- }
- });
- }
-
- fn handle_action_dispatch_notification_effect(&mut self, action_id: TypeId) {
- self.action_dispatch_observations
- .clone()
- .emit((), |callback| {
- callback(action_id, self);
- true
- });
}
- fn handle_window_should_close_subscription_effect(
+ /// Update the entity referenced by the given model. The function is passed a mutable reference to the
+ /// entity along with a `ModelContext` for the entity.
+ fn update_model<T: 'static, R>(
&mut self,
- window: AnyWindowHandle,
- mut callback: WindowShouldCloseSubscriptionCallback,
- ) {
- let mut app = self.upgrade();
- if let Some(window) = self.windows.get_mut(&window) {
- window
- .platform_window
- .on_should_close(Box::new(move || app.update(|cx| callback(cx))))
- }
- }
-
- fn handle_window_moved(&mut self, window: AnyWindowHandle) {
- self.update_window(window, |cx| {
- if let Some(display) = cx.window_display_uuid() {
- let bounds = cx.window_bounds();
- cx.window_bounds_observations
- .clone()
- .emit(window, move |callback| {
- callback(bounds, display, cx);
- true
- });
- }
- });
- }
-
- fn handle_active_labeled_tasks_changed_effect(&mut self) {
- self.active_labeled_task_observations
- .clone()
- .emit((), move |callback| {
- callback(self);
- true
- });
- }
-
- pub fn focus(&mut self, window: AnyWindowHandle, view_id: Option<usize>) {
- self.pending_effects
- .push_back(Effect::Focus(FocusEffect::View {
- window,
- view_id,
- is_forced: false,
- }));
+ model: &Model<T>,
+ update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
+ ) -> R {
+ self.update(|cx| {
+ let mut entity = cx.entities.lease(model);
+ let result = update(&mut entity, &mut ModelContext::new(cx, model.downgrade()));
+ cx.entities.end_lease(entity);
+ result
+ })
}
- fn spawn_internal<F, Fut, T>(&mut self, task_name: Option<&'static str>, f: F) -> Task<T>
+ fn update_window<T, F>(&mut self, handle: AnyWindowHandle, update: F) -> Result<T>
where
- F: FnOnce(AsyncAppContext) -> Fut,
- Fut: 'static + Future<Output = T>,
- T: 'static,
+ F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
{
- let label_id = task_name.map(|task_name| {
- let id = post_inc(&mut self.next_labeled_task_id);
- self.active_labeled_tasks.insert(id, task_name);
- self.pending_effects
- .push_back(Effect::ActiveLabeledTasksChanged);
- id
- });
-
- let future = f(self.to_async());
- let cx = self.to_async();
- self.foreground.spawn(async move {
- let result = future.await;
- let mut cx = cx.0.borrow_mut();
-
- if let Some(completed_label_id) = label_id {
- cx.active_labeled_tasks.remove(&completed_label_id);
- cx.pending_effects
- .push_back(Effect::ActiveLabeledTasksChanged);
+ self.update(|cx| {
+ let mut window = cx
+ .windows
+ .get_mut(handle.id)
+ .ok_or_else(|| anyhow!("window not found"))?
+ .take()
+ .unwrap();
+
+ let root_view = window.root_view.clone().unwrap();
+ let result = update(root_view, &mut WindowContext::new(cx, &mut window));
+
+ if window.removed {
+ cx.windows.remove(handle.id);
+ } else {
+ cx.windows
+ .get_mut(handle.id)
+ .ok_or_else(|| anyhow!("window not found"))?
+ .replace(window);
}
- cx.flush_effects();
- result
+
+ Ok(result)
})
}
- pub fn spawn_labeled<F, Fut, T>(&mut self, task_name: &'static str, f: F) -> Task<T>
+ fn read_model<T, R>(
+ &self,
+ handle: &Model<T>,
+ read: impl FnOnce(&T, &AppContext) -> R,
+ ) -> Self::Result<R>
where
- F: FnOnce(AsyncAppContext) -> Fut,
- Fut: 'static + Future<Output = T>,
T: 'static,
{
- self.spawn_internal(Some(task_name), f)
+ let entity = self.entities.read(handle);
+ read(entity, self)
}
- pub fn spawn<F, Fut, T>(&mut self, f: F) -> Task<T>
+ fn read_window<T, R>(
+ &self,
+ window: &WindowHandle<T>,
+ read: impl FnOnce(View<T>, &AppContext) -> R,
+ ) -> Result<R>
where
- F: FnOnce(AsyncAppContext) -> Fut,
- Fut: 'static + Future<Output = T>,
T: 'static,
{
- self.spawn_internal(None, f)
- }
-
- pub fn to_async(&self) -> AsyncAppContext {
- AsyncAppContext(self.weak_self.as_ref().unwrap().upgrade().unwrap())
- }
-
- pub fn open_url(&self, url: &str) {
- self.platform.open_url(url)
- }
-
- pub fn write_to_clipboard(&self, item: ClipboardItem) {
- self.platform.write_to_clipboard(item);
- }
-
- pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
- self.platform.read_from_clipboard()
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
- self.ref_counts.lock().leak_detector.clone()
- }
-}
-
-impl BorrowAppContext for AppContext {
- fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
- f(self)
- }
-
- fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
- f(self)
- }
-}
-
-impl BorrowWindowContext for AppContext {
- type Result<T> = Option<T>;
-
- fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
- where
- F: FnOnce(&WindowContext) -> T,
- {
- AppContext::read_window(self, window, f)
- }
-
- fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
- where
- F: FnOnce(&WindowContext) -> Option<T>,
- {
- AppContext::read_window(self, window, f).flatten()
- }
-
- fn update_window<T, F>(&mut self, handle: AnyWindowHandle, f: F) -> Self::Result<T>
- where
- F: FnOnce(&mut WindowContext) -> T,
- {
- self.update(|cx| {
- let mut window = cx.windows.remove(&handle)?;
- let mut window_context = WindowContext::mutable(cx, &mut window, handle);
- let result = f(&mut window_context);
- if !window_context.removed {
- cx.windows.insert(handle, window);
- }
- Some(result)
- })
- }
-
- fn update_window_optional<T, F>(&mut self, handle: AnyWindowHandle, f: F) -> Option<T>
- where
- F: FnOnce(&mut WindowContext) -> Option<T>,
- {
- AppContext::update_window(self, handle, f).flatten()
- }
-}
-
-#[derive(Debug)]
-pub enum ParentId {
- View(usize),
- Root,
-}
-
-struct ViewMetadata {
- type_id: TypeId,
- keymap_context: KeymapContext,
-}
-
-#[derive(Default, Clone, Debug)]
-pub struct WindowInvalidation {
- pub updated: HashSet<usize>,
- pub removed: Vec<usize>,
-}
-
-#[derive(Debug)]
-pub enum FocusEffect {
- View {
- window: AnyWindowHandle,
- view_id: Option<usize>,
- is_forced: bool,
- },
- ViewParent {
- window: AnyWindowHandle,
- view_id: usize,
- is_forced: bool,
- },
-}
-
-impl FocusEffect {
- fn window(&self) -> AnyWindowHandle {
- match self {
- FocusEffect::View { window, .. } => *window,
- FocusEffect::ViewParent { window, .. } => *window,
- }
- }
+ let window = self
+ .windows
+ .get(window.id)
+ .ok_or_else(|| anyhow!("window not found"))?
+ .as_ref()
+ .unwrap();
- fn is_forced(&self) -> bool {
- match self {
- FocusEffect::View { is_forced, .. } => *is_forced,
- FocusEffect::ViewParent { is_forced, .. } => *is_forced,
- }
- }
+ let root_view = window.root_view.clone().unwrap();
+ let view = root_view
+ .downcast::<T>()
+ .map_err(|_| anyhow!("root view's type has changed"))?;
- fn force(&mut self) {
- match self {
- FocusEffect::View { is_forced, .. } => *is_forced = true,
- FocusEffect::ViewParent { is_forced, .. } => *is_forced = true,
- }
+ Ok(read(view, self))
}
}
-pub enum Effect {
- Subscription {
- entity_id: usize,
- subscription_id: usize,
- callback: SubscriptionCallback,
- },
- Event {
- entity_id: usize,
- payload: Box<dyn Any>,
- },
- GlobalSubscription {
- type_id: TypeId,
- subscription_id: usize,
- callback: GlobalSubscriptionCallback,
- },
- GlobalEvent {
- payload: Box<dyn Any>,
- },
- Observation {
- entity_id: usize,
- subscription_id: usize,
- callback: ObservationCallback,
- },
- ModelNotification {
- model_id: usize,
- },
- ViewNotification {
- window: AnyWindowHandle,
- view_id: usize,
- },
- Deferred {
- callback: Box<dyn FnOnce(&mut AppContext)>,
- after_window_update: bool,
- },
- GlobalNotification {
- type_id: TypeId,
- },
- ModelRelease {
- model_id: usize,
- model: Box<dyn AnyModel>,
- },
- ViewRelease {
- view_id: usize,
- view: Box<dyn AnyView>,
- },
- Focus(FocusEffect),
- FocusObservation {
- view_id: usize,
- subscription_id: usize,
- callback: FocusObservationCallback,
+/// These effects are processed at the end of each application update cycle.
+pub(crate) enum Effect {
+ Notify {
+ emitter: EntityId,
},
- ResizeWindow {
- window: AnyWindowHandle,
+ Emit {
+ emitter: EntityId,
+ event_type: TypeId,
+ event: Box<dyn Any>,
},
- MoveWindow {
- window: AnyWindowHandle,
+ Refresh,
+ NotifyGlobalObservers {
+ global_type: TypeId,
},
- ActivateWindow {
- window: AnyWindowHandle,
- is_active: bool,
- },
- RepaintWindow {
- window: AnyWindowHandle,
- },
- WindowActivationObservation {
- window: AnyWindowHandle,
- subscription_id: usize,
- callback: WindowActivationCallback,
- },
- FullscreenWindow {
- window: AnyWindowHandle,
- is_fullscreen: bool,
- },
- WindowFullscreenObservation {
- window: AnyWindowHandle,
- subscription_id: usize,
- callback: WindowFullscreenCallback,
- },
- WindowBoundsObservation {
- window: AnyWindowHandle,
- subscription_id: usize,
- callback: WindowBoundsCallback,
- },
- Keystroke {
- window: AnyWindowHandle,
- keystroke: Keystroke,
- handled_by: Option<Box<dyn Action>>,
- result: MatchResult,
- },
- RefreshWindows,
- ActionDispatchNotification {
- action_id: TypeId,
- },
- WindowShouldCloseSubscription {
- window: AnyWindowHandle,
- callback: WindowShouldCloseSubscriptionCallback,
- },
- ActiveLabeledTasksChanged,
- ActiveLabeledTasksObservation {
- subscription_id: usize,
- callback: ActiveLabeledTasksCallback,
+ Defer {
+ callback: Box<dyn FnOnce(&mut AppContext) + 'static>,
},
}
-impl Debug for Effect {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Effect::Subscription {
- entity_id,
- subscription_id,
- ..
- } => f
- .debug_struct("Effect::Subscribe")
- .field("entity_id", entity_id)
- .field("subscription_id", subscription_id)
- .finish(),
- Effect::Event { entity_id, .. } => f
- .debug_struct("Effect::Event")
- .field("entity_id", entity_id)
- .finish(),
- Effect::GlobalSubscription {
- type_id,
- subscription_id,
- ..
- } => f
- .debug_struct("Effect::Subscribe")
- .field("type_id", type_id)
- .field("subscription_id", subscription_id)
- .finish(),
- Effect::GlobalEvent { payload, .. } => f
- .debug_struct("Effect::GlobalEvent")
- .field("type_id", &(&*payload).type_id())
- .finish(),
- Effect::Observation {
- entity_id,
- subscription_id,
- ..
- } => f
- .debug_struct("Effect::Observation")
- .field("entity_id", entity_id)
- .field("subscription_id", subscription_id)
- .finish(),
- Effect::ModelNotification { model_id } => f
- .debug_struct("Effect::ModelNotification")
- .field("model_id", model_id)
- .finish(),
- Effect::ViewNotification { window, view_id } => f
- .debug_struct("Effect::ViewNotification")
- .field("window_id", &window.id())
- .field("view_id", view_id)
- .finish(),
- Effect::GlobalNotification { type_id } => f
- .debug_struct("Effect::GlobalNotification")
- .field("type_id", type_id)
- .finish(),
- Effect::Deferred { .. } => f.debug_struct("Effect::Deferred").finish(),
- Effect::ModelRelease { model_id, .. } => f
- .debug_struct("Effect::ModelRelease")
- .field("model_id", model_id)
- .finish(),
- Effect::ViewRelease { view_id, .. } => f
- .debug_struct("Effect::ViewRelease")
- .field("view_id", view_id)
- .finish(),
- Effect::Focus(focus) => f.debug_tuple("Effect::Focus").field(focus).finish(),
- Effect::FocusObservation {
- view_id,
- subscription_id,
- ..
- } => f
- .debug_struct("Effect::FocusObservation")
- .field("view_id", view_id)
- .field("subscription_id", subscription_id)
- .finish(),
- Effect::ActionDispatchNotification { action_id, .. } => f
- .debug_struct("Effect::ActionDispatchNotification")
- .field("action_id", action_id)
- .finish(),
- Effect::ResizeWindow { window } => f
- .debug_struct("Effect::RefreshWindow")
- .field("window_id", &window.id())
- .finish(),
- Effect::MoveWindow { window } => f
- .debug_struct("Effect::MoveWindow")
- .field("window_id", &window.id())
- .finish(),
- Effect::WindowActivationObservation {
- window,
- subscription_id,
- ..
- } => f
- .debug_struct("Effect::WindowActivationObservation")
- .field("window_id", &window.id())
- .field("subscription_id", subscription_id)
- .finish(),
- Effect::ActivateWindow { window, is_active } => f
- .debug_struct("Effect::ActivateWindow")
- .field("window_id", &window.id())
- .field("is_active", is_active)
- .finish(),
- Effect::FullscreenWindow {
- window,
- is_fullscreen,
- } => f
- .debug_struct("Effect::FullscreenWindow")
- .field("window_id", &window.id())
- .field("is_fullscreen", is_fullscreen)
- .finish(),
- Effect::WindowFullscreenObservation {
- window,
- subscription_id,
- callback: _,
- } => f
- .debug_struct("Effect::WindowFullscreenObservation")
- .field("window_id", &window.id())
- .field("subscription_id", subscription_id)
- .finish(),
-
- Effect::WindowBoundsObservation {
- window,
- subscription_id,
- callback: _,
- } => f
- .debug_struct("Effect::WindowBoundsObservation")
- .field("window_id", &window.id())
- .field("subscription_id", subscription_id)
- .finish(),
- Effect::RefreshWindows => f.debug_struct("Effect::FullViewRefresh").finish(),
- Effect::WindowShouldCloseSubscription { window, .. } => f
- .debug_struct("Effect::WindowShouldCloseSubscription")
- .field("window_id", &window.id())
- .finish(),
- Effect::Keystroke {
- window,
- keystroke,
- handled_by,
- result,
- } => f
- .debug_struct("Effect::Keystroke")
- .field("window_id", &window.id())
- .field("keystroke", keystroke)
- .field(
- "keystroke",
- &handled_by.as_ref().map(|handled_by| handled_by.name()),
- )
- .field("result", result)
- .finish(),
- Effect::ActiveLabeledTasksChanged => {
- f.debug_struct("Effect::ActiveLabeledTasksChanged").finish()
- }
- Effect::ActiveLabeledTasksObservation {
- subscription_id,
- callback: _,
- } => f
- .debug_struct("Effect::ActiveLabeledTasksObservation")
- .field("subscription_id", subscription_id)
- .finish(),
- Effect::RepaintWindow { window } => f
- .debug_struct("Effect::RepaintWindow")
- .field("window_id", &window.id())
- .finish(),
- }
- }
+/// Wraps a global variable value during `update_global` while the value has been moved to the stack.
+pub(crate) struct GlobalLease<G: 'static> {
+ global: Box<dyn Any>,
+ global_type: PhantomData<G>,
}
-pub trait AnyModel {
- fn as_any(&self) -> &dyn Any;
- fn as_any_mut(&mut self) -> &mut dyn Any;
- fn release(&mut self, cx: &mut AppContext);
- fn app_will_quit(
- &mut self,
- cx: &mut AppContext,
- ) -> Option<Pin<Box<dyn 'static + Future<Output = ()>>>>;
+impl<G: 'static> GlobalLease<G> {
+ fn new(global: Box<dyn Any>) -> Self {
+ GlobalLease {
+ global,
+ global_type: PhantomData,
+ }
+ }
}
-impl<T> AnyModel for T
-where
- T: Entity,
-{
- fn as_any(&self) -> &dyn Any {
- self
- }
+impl<G: 'static> Deref for GlobalLease<G> {
+ type Target = G;
- fn as_any_mut(&mut self) -> &mut dyn Any {
- self
+ fn deref(&self) -> &Self::Target {
+ self.global.downcast_ref().unwrap()
}
+}
- fn release(&mut self, cx: &mut AppContext) {
- self.release(cx);
+impl<G: 'static> DerefMut for GlobalLease<G> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.global.downcast_mut().unwrap()
}
+}
- fn app_will_quit(
- &mut self,
- cx: &mut AppContext,
- ) -> Option<Pin<Box<dyn 'static + Future<Output = ()>>>> {
- self.app_will_quit(cx)
- }
+/// Contains state associated with an active drag operation, started by dragging an element
+/// within the window or by dragging into the app from the underlying platform.
+pub struct AnyDrag {
+ pub view: AnyView,
+ pub value: Box<dyn Any>,
+ pub cursor_offset: Point<Pixels>,
}
-pub trait AnyView {
- fn as_any(&self) -> &dyn Any;
- fn as_any_mut(&mut self) -> &mut dyn Any;
- fn release(&mut self, cx: &mut AppContext);
- fn app_will_quit(
- &mut self,
- cx: &mut AppContext,
- ) -> Option<Pin<Box<dyn 'static + Future<Output = ()>>>>;
- fn ui_name(&self) -> &'static str;
- fn render(&mut self, cx: &mut WindowContext, view_id: usize) -> Box<dyn AnyRootElement>;
- fn focus_in<'a, 'b>(&mut self, focused_id: usize, cx: &mut WindowContext<'a>, view_id: usize);
- fn focus_out(&mut self, focused_id: usize, cx: &mut WindowContext, view_id: usize);
- fn key_down(&mut self, event: &KeyDownEvent, cx: &mut WindowContext, view_id: usize) -> bool;
- fn key_up(&mut self, event: &KeyUpEvent, cx: &mut WindowContext, view_id: usize) -> bool;
- fn modifiers_changed(
- &mut self,
- event: &ModifiersChangedEvent,
- cx: &mut WindowContext,
- view_id: usize,
- ) -> bool;
- fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &AppContext);
- fn debug_json(&self, cx: &WindowContext) -> serde_json::Value;
-
- fn text_for_range(&self, range: Range<usize>, cx: &WindowContext) -> Option<String>;
- fn selected_text_range(&self, cx: &WindowContext) -> Option<Range<usize>>;
- fn marked_text_range(&self, cx: &WindowContext) -> Option<Range<usize>>;
- fn unmark_text(&mut self, cx: &mut WindowContext, view_id: usize);
- fn replace_text_in_range(
- &mut self,
- range: Option<Range<usize>>,
- text: &str,
- cx: &mut WindowContext,
- view_id: usize,
- );
- fn replace_and_mark_text_in_range(
- &mut self,
- range: Option<Range<usize>>,
- new_text: &str,
- new_selected_range: Option<Range<usize>>,
- cx: &mut WindowContext,
- view_id: usize,
- );
- fn any_handle(
- &self,
- window: AnyWindowHandle,
- view_id: usize,
- cx: &AppContext,
- ) -> AnyViewHandle {
- AnyViewHandle::new(
- window,
- view_id,
- self.as_any().type_id(),
- cx.ref_counts.clone(),
- )
- }
+#[derive(Clone)]
+pub(crate) struct AnyTooltip {
+ pub view: AnyView,
+ pub cursor_offset: Point<Pixels>,
}
-impl<V: View> AnyView for V {
- fn as_any(&self) -> &dyn Any {
- self
- }
-
- fn as_any_mut(&mut self) -> &mut dyn Any {
- self
- }
-
- fn release(&mut self, cx: &mut AppContext) {
- self.release(cx);
- }
-
- fn app_will_quit(
- &mut self,
- cx: &mut AppContext,
- ) -> Option<Pin<Box<dyn 'static + Future<Output = ()>>>> {
- self.app_will_quit(cx)
- }
-
- fn ui_name(&self) -> &'static str {
- V::ui_name()
- }
-
- fn render(&mut self, cx: &mut WindowContext, view_id: usize) -> Box<dyn AnyRootElement> {
- let mut view_context = ViewContext::mutable(cx, view_id);
- let element = V::render(self, &mut view_context);
- let view = WeakViewHandle::new(cx.window_handle, view_id);
- Box::new(RootElement::new(element, view))
- }
-
- fn focus_in(&mut self, focused_id: usize, cx: &mut WindowContext, view_id: usize) {
- let mut cx = ViewContext::mutable(cx, view_id);
- let focused_view_handle: AnyViewHandle = if view_id == focused_id {
- cx.handle().into_any()
- } else {
- let focused_type = cx
- .views_metadata
- .get(&(cx.window_handle, focused_id))
- .unwrap()
- .type_id;
- AnyViewHandle::new(
- cx.window_handle,
- focused_id,
- focused_type,
- cx.ref_counts.clone(),
- )
- };
- View::focus_in(self, focused_view_handle, &mut cx);
- }
-
- fn focus_out(&mut self, blurred_id: usize, cx: &mut WindowContext, view_id: usize) {
- let mut cx = ViewContext::mutable(cx, view_id);
- let blurred_view_handle: AnyViewHandle = if view_id == blurred_id {
- cx.handle().into_any()
- } else {
- let blurred_type = cx
- .views_metadata
- .get(&(cx.window_handle, blurred_id))
- .unwrap()
- .type_id;
- AnyViewHandle::new(
- cx.window_handle,
- blurred_id,
- blurred_type,
- cx.ref_counts.clone(),
- )
- };
- View::focus_out(self, blurred_view_handle, &mut cx);
- }
-
- fn key_down(&mut self, event: &KeyDownEvent, cx: &mut WindowContext, view_id: usize) -> bool {
- let mut cx = ViewContext::mutable(cx, view_id);
- View::key_down(self, event, &mut cx)
- }
-
- fn key_up(&mut self, event: &KeyUpEvent, cx: &mut WindowContext, view_id: usize) -> bool {
- let mut cx = ViewContext::mutable(cx, view_id);
- View::key_up(self, event, &mut cx)
- }
-
- fn modifiers_changed(
- &mut self,
- event: &ModifiersChangedEvent,
- cx: &mut WindowContext,
- view_id: usize,
- ) -> bool {
- let mut cx = ViewContext::mutable(cx, view_id);
- View::modifiers_changed(self, event, &mut cx)
- }
-
- fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &AppContext) {
- View::update_keymap_context(self, keymap, cx)
- }
-
- fn debug_json(&self, cx: &WindowContext) -> serde_json::Value {
- View::debug_json(self, cx)
- }
-
- fn text_for_range(&self, range: Range<usize>, cx: &WindowContext) -> Option<String> {
- View::text_for_range(self, range, cx)
- }
-
- fn selected_text_range(&self, cx: &WindowContext) -> Option<Range<usize>> {
- View::selected_text_range(self, cx)
- }
-
- fn marked_text_range(&self, cx: &WindowContext) -> Option<Range<usize>> {
- View::marked_text_range(self, cx)
- }
-
- fn unmark_text(&mut self, cx: &mut WindowContext, view_id: usize) {
- let mut cx = ViewContext::mutable(cx, view_id);
- View::unmark_text(self, &mut cx)
- }
-
- fn replace_text_in_range(
- &mut self,
- range: Option<Range<usize>>,
- text: &str,
- cx: &mut WindowContext,
- view_id: usize,
- ) {
- let mut cx = ViewContext::mutable(cx, view_id);
- View::replace_text_in_range(self, range, text, &mut cx)
- }
-
- fn replace_and_mark_text_in_range(
- &mut self,
- range: Option<Range<usize>>,
- new_text: &str,
- new_selected_range: Option<Range<usize>>,
- cx: &mut WindowContext,
- view_id: usize,
- ) {
- let mut cx = ViewContext::mutable(cx, view_id);
- View::replace_and_mark_text_in_range(self, range, new_text, new_selected_range, &mut cx)
- }
-}
-
-pub struct ModelContext<'a, T: ?Sized> {
- app: &'a mut AppContext,
- model_id: usize,
- model_type: PhantomData<T>,
- halt_stream: bool,
-}
-
-impl<'a, T: Entity> ModelContext<'a, T> {
- fn new(app: &'a mut AppContext, model_id: usize) -> Self {
- Self {
- app,
- model_id,
- model_type: PhantomData,
- halt_stream: false,
- }
- }
-
- pub fn background(&self) -> &Arc<executor::Background> {
- &self.app.background
- }
-
- pub fn halt_stream(&mut self) {
- self.halt_stream = true;
- }
-
- pub fn model_id(&self) -> usize {
- self.model_id
- }
-
- pub fn add_model<S, F>(&mut self, build_model: F) -> ModelHandle<S>
- where
- S: Entity,
- F: FnOnce(&mut ModelContext<S>) -> S,
- {
- self.app.add_model(build_model)
- }
-
- pub fn emit(&mut self, payload: T::Event) {
- self.app.pending_effects.push_back(Effect::Event {
- entity_id: self.model_id,
- payload: Box::new(payload),
- });
- }
-
- pub fn notify(&mut self) {
- self.app.notify_model(self.model_id);
- }
-
- pub fn subscribe<S: Entity, F>(
- &mut self,
- handle: &ModelHandle<S>,
- mut callback: F,
- ) -> Subscription
- where
- S::Event: 'static,
- F: 'static + FnMut(&mut T, ModelHandle<S>, &S::Event, &mut ModelContext<T>),
- {
- let subscriber = self.weak_handle();
- self.app
- .subscribe_internal(handle, move |emitter, event, cx| {
- if let Some(subscriber) = subscriber.upgrade(cx) {
- subscriber.update(cx, |subscriber, cx| {
- callback(subscriber, emitter, event, cx);
- });
- true
- } else {
- false
- }
- })
- }
-
- pub fn observe<S, F>(&mut self, handle: &ModelHandle<S>, mut callback: F) -> Subscription
- where
- S: Entity,
- F: 'static + FnMut(&mut T, ModelHandle<S>, &mut ModelContext<T>),
- {
- let observer = self.weak_handle();
- self.app.observe_internal(handle, move |observed, cx| {
- if let Some(observer) = observer.upgrade(cx) {
- observer.update(cx, |observer, cx| {
- callback(observer, observed, cx);
- });
- true
- } else {
- false
- }
- })
- }
-
- pub fn observe_global<G, F>(&mut self, mut callback: F) -> Subscription
- where
- G: Any,
- F: 'static + FnMut(&mut T, &mut ModelContext<T>),
- {
- let observer = self.weak_handle();
- self.app.observe_global::<G, _>(move |cx| {
- if let Some(observer) = observer.upgrade(cx) {
- observer.update(cx, |observer, cx| callback(observer, cx));
- }
- })
- }
-
- pub fn observe_release<S, F>(
- &mut self,
- handle: &ModelHandle<S>,
- mut callback: F,
- ) -> Subscription
- where
- S: Entity,
- F: 'static + FnMut(&mut T, &S, &mut ModelContext<T>),
- {
- let observer = self.weak_handle();
- self.app.observe_release(handle, move |released, cx| {
- if let Some(observer) = observer.upgrade(cx) {
- observer.update(cx, |observer, cx| {
- callback(observer, released, cx);
- });
- }
- })
- }
-
- pub fn handle(&self) -> ModelHandle<T> {
- ModelHandle::new(self.model_id, &self.app.ref_counts)
- }
-
- pub fn weak_handle(&self) -> WeakModelHandle<T> {
- WeakModelHandle::new(self.model_id)
- }
-
- pub fn spawn<F, Fut, S>(&mut self, f: F) -> Task<S>
- where
- F: FnOnce(ModelHandle<T>, AsyncAppContext) -> Fut,
- Fut: 'static + Future<Output = S>,
- S: 'static,
- {
- let handle = self.handle();
- self.app.spawn(|cx| f(handle, cx))
- }
-
- pub fn spawn_weak<F, Fut, S>(&mut self, f: F) -> Task<S>
- where
- F: FnOnce(WeakModelHandle<T>, AsyncAppContext) -> Fut,
- Fut: 'static + Future<Output = S>,
- S: 'static,
- {
- let handle = self.weak_handle();
- self.app.spawn(|cx| f(handle, cx))
- }
-}
-
-impl<M> AsRef<AppContext> for ModelContext<'_, M> {
- fn as_ref(&self) -> &AppContext {
- &self.app
- }
-}
-
-impl<M> AsMut<AppContext> for ModelContext<'_, M> {
- fn as_mut(&mut self) -> &mut AppContext {
- self.app
- }
-}
-
-impl<M> BorrowAppContext for ModelContext<'_, M> {
- fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
- self.app.read_with(f)
- }
-
- fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
- self.app.update(f)
- }
-}
-
-impl<M> Deref for ModelContext<'_, M> {
- type Target = AppContext;
-
- fn deref(&self) -> &Self::Target {
- self.app
- }
-}
-
-impl<M> DerefMut for ModelContext<'_, M> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.app
- }
-}
-
-pub struct ViewContext<'a, 'b, T: ?Sized> {
- window_context: Reference<'b, WindowContext<'a>>,
- view_id: usize,
- view_type: PhantomData<T>,
-}
-
-impl<'a, 'b, V> Deref for ViewContext<'a, 'b, V> {
- type Target = WindowContext<'a>;
-
- fn deref(&self) -> &Self::Target {
- &self.window_context
- }
-}
-
-impl<'a, 'b, V> DerefMut for ViewContext<'a, 'b, V> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.window_context
- }
-}
-
-impl<'a, 'b, V: 'static> ViewContext<'a, 'b, V> {
- pub fn mutable(window_context: &'b mut WindowContext<'a>, view_id: usize) -> Self {
- Self {
- window_context: Reference::Mutable(window_context),
- view_id,
- view_type: PhantomData,
- }
- }
-
- pub fn immutable(window_context: &'b WindowContext<'a>, view_id: usize) -> Self {
- Self {
- window_context: Reference::Immutable(window_context),
- view_id,
- view_type: PhantomData,
- }
- }
-
- pub fn window_context(&mut self) -> &mut WindowContext<'a> {
- &mut self.window_context
- }
-
- pub fn notify(&mut self) {
- let window = self.window_handle;
- let view_id = self.view_id;
- self.window_context.notify_view(window, view_id);
- }
-
- pub fn handle(&self) -> ViewHandle<V> {
- ViewHandle::new(
- self.window_handle,
- self.view_id,
- &self.window_context.ref_counts,
- )
- }
-
- pub fn weak_handle(&self) -> WeakViewHandle<V> {
- WeakViewHandle::new(self.window_handle, self.view_id)
- }
-
- pub fn window(&self) -> AnyWindowHandle {
- self.window_handle
- }
-
- pub fn view_id(&self) -> usize {
- self.view_id
- }
-
- pub fn foreground(&self) -> &Rc<executor::Foreground> {
- self.window_context.foreground()
- }
-
- pub fn background_executor(&self) -> &Arc<executor::Background> {
- &self.window_context.background
- }
-
- pub fn platform(&self) -> &Arc<dyn Platform> {
- self.window_context.platform()
- }
-
- pub fn prompt_for_paths(
- &self,
- options: PathPromptOptions,
- ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
- self.window_context.prompt_for_paths(options)
- }
-
- pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
- self.window_context.prompt_for_new_path(directory)
- }
-
- pub fn reveal_path(&self, path: &Path) {
- self.window_context.reveal_path(path)
- }
-
- pub fn focus(&mut self, handle: &AnyViewHandle) {
- self.window_context.focus(Some(handle.view_id));
- }
-
- pub fn focus_self(&mut self) {
- let view_id = self.view_id;
- self.window_context.focus(Some(view_id));
- }
-
- pub fn is_self_focused(&self) -> bool {
- self.window.focused_view_id == Some(self.view_id)
- }
-
- pub fn focus_parent(&mut self) {
- let window = self.window_handle;
- let view_id = self.view_id;
- self.pending_effects
- .push_back(Effect::Focus(FocusEffect::ViewParent {
- window,
- view_id,
- is_forced: false,
- }));
- }
-
- pub fn blur(&mut self) {
- self.window_context.focus(None);
- }
-
- pub fn on_window_should_close<F>(&mut self, mut callback: F)
- where
- F: 'static + FnMut(&mut V, &mut ViewContext<V>) -> bool,
- {
- let window = self.window_handle;
- let view = self.weak_handle();
- self.pending_effects
- .push_back(Effect::WindowShouldCloseSubscription {
- window,
- callback: Box::new(move |cx| {
- cx.update_window(window, |cx| {
- if let Some(view) = view.upgrade(cx) {
- view.update(cx, |view, cx| callback(view, cx))
- } else {
- true
- }
- })
- .unwrap_or(true)
- }),
- });
- }
-
- pub fn subscribe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
- where
- E: Entity,
- E::Event: 'static,
- H: Handle<E>,
- F: 'static + FnMut(&mut V, H, &E::Event, &mut ViewContext<V>),
- {
- let subscriber = self.weak_handle();
- self.window_context
- .subscribe_internal(handle, move |emitter, event, cx| {
- if let Some(subscriber) = subscriber.upgrade(cx) {
- subscriber.update(cx, |subscriber, cx| {
- callback(subscriber, emitter, event, cx);
- });
- true
- } else {
- false
- }
- })
- }
-
- pub fn observe<E, F, H>(&mut self, handle: &H, mut callback: F) -> Subscription
- where
- E: Entity,
- H: Handle<E>,
- F: 'static + FnMut(&mut V, H, &mut ViewContext<V>),
- {
- let window = self.window_handle;
- let observer = self.weak_handle();
- self.window_context
- .observe_internal(handle, move |observed, cx| {
- cx.update_window(window, |cx| {
- if let Some(observer) = observer.upgrade(cx) {
- observer.update(cx, |observer, cx| {
- callback(observer, observed, cx);
- });
- true
- } else {
- false
- }
- })
- .unwrap_or(false)
- })
- }
-
- pub fn observe_global<G, F>(&mut self, mut callback: F) -> Subscription
- where
- G: Any,
- F: 'static + FnMut(&mut V, &mut ViewContext<V>),
- {
- let window = self.window_handle;
- let observer = self.weak_handle();
- self.window_context.observe_global::<G, _>(move |cx| {
- cx.update_window(window, |cx| {
- if let Some(observer) = observer.upgrade(cx) {
- observer.update(cx, |observer, cx| callback(observer, cx));
- }
- });
- })
- }
-
- pub fn observe_focus<F, W>(&mut self, handle: &ViewHandle<W>, mut callback: F) -> Subscription
- where
- F: 'static + FnMut(&mut V, ViewHandle<W>, bool, &mut ViewContext<V>),
- W: View,
- {
- let observer = self.weak_handle();
- self.window_context
- .observe_focus(handle, move |observed, focused, cx| {
- if let Some(observer) = observer.upgrade(cx) {
- observer.update(cx, |observer, cx| {
- callback(observer, observed, focused, cx);
- });
- true
- } else {
- false
- }
- })
- }
-
- pub fn observe_release<E, F, H>(&mut self, handle: &H, mut callback: F) -> Subscription
- where
- E: Entity,
- H: Handle<E>,
- F: 'static + FnMut(&mut V, &E, &mut ViewContext<V>),
- {
- let window = self.window_handle;
- let observer = self.weak_handle();
- self.window_context
- .observe_release(handle, move |released, cx| {
- cx.update_window(window, |cx| {
- if let Some(observer) = observer.upgrade(cx) {
- observer.update(cx, |observer, cx| {
- callback(observer, released, cx);
- });
- }
- });
- })
- }
-
- pub fn observe_actions<F>(&mut self, mut callback: F) -> Subscription
- where
- F: 'static + FnMut(&mut V, TypeId, &mut ViewContext<V>),
- {
- let window = self.window_handle;
- let observer = self.weak_handle();
- self.window_context.observe_actions(move |action_id, cx| {
- cx.update_window(window, |cx| {
- if let Some(observer) = observer.upgrade(cx) {
- observer.update(cx, |observer, cx| {
- callback(observer, action_id, cx);
- });
- }
- });
- })
- }
-
- pub fn observe_window_activation<F>(&mut self, mut callback: F) -> Subscription
- where
- F: 'static + FnMut(&mut V, bool, &mut ViewContext<V>),
- {
- let observer = self.weak_handle();
- self.window_context
- .observe_window_activation(move |active, cx| {
- if let Some(observer) = observer.upgrade(cx) {
- observer.update(cx, |observer, cx| {
- callback(observer, active, cx);
- });
- true
- } else {
- false
- }
- })
- }
-
- pub fn observe_fullscreen<F>(&mut self, mut callback: F) -> Subscription
- where
- F: 'static + FnMut(&mut V, bool, &mut ViewContext<V>),
- {
- let observer = self.weak_handle();
- self.window_context.observe_fullscreen(move |active, cx| {
- if let Some(observer) = observer.upgrade(cx) {
- observer.update(cx, |observer, cx| {
- callback(observer, active, cx);
- });
- true
- } else {
- false
- }
- })
- }
-
- pub fn observe_keystrokes<F>(&mut self, mut callback: F) -> Subscription
- where
- F: 'static
- + FnMut(
- &mut V,
- &Keystroke,
- Option<&Box<dyn Action>>,
- &MatchResult,
- &mut ViewContext<V>,
- ) -> bool,
- {
- let observer = self.weak_handle();
- self.window_context
- .observe_keystrokes(move |keystroke, result, handled_by, cx| {
- if let Some(observer) = observer.upgrade(cx) {
- observer.update(cx, |observer, cx| {
- callback(observer, keystroke, handled_by, result, cx);
- });
- true
- } else {
- false
- }
- })
- }
-
- pub fn observe_window_bounds<F>(&mut self, mut callback: F) -> Subscription
- where
- F: 'static + FnMut(&mut V, WindowBounds, Uuid, &mut ViewContext<V>),
- {
- let observer = self.weak_handle();
- self.window_context
- .observe_window_bounds(move |bounds, display, cx| {
- if let Some(observer) = observer.upgrade(cx) {
- observer.update(cx, |observer, cx| {
- callback(observer, bounds, display, cx);
- });
- true
- } else {
- false
- }
- })
- }
-
- pub fn observe_active_labeled_tasks<F>(&mut self, mut callback: F) -> Subscription
- where
- F: 'static + FnMut(&mut V, &mut ViewContext<V>),
- {
- let window = self.window_handle;
- let observer = self.weak_handle();
- self.window_context.observe_active_labeled_tasks(move |cx| {
- cx.update_window(window, |cx| {
- if let Some(observer) = observer.upgrade(cx) {
- observer.update(cx, |observer, cx| {
- callback(observer, cx);
- });
- true
- } else {
- false
- }
- })
- .unwrap_or(false)
- })
- }
-
- pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut V, &mut ViewContext<V>)) {
- let handle = self.handle();
- self.window_context
- .defer(move |cx| handle.update(cx, |view, cx| callback(view, cx)))
- }
-
- pub fn after_window_update(
- &mut self,
- callback: impl 'static + FnOnce(&mut V, &mut ViewContext<V>),
- ) {
- let window = self.window_handle;
- let handle = self.handle();
- self.window_context.after_window_update(move |cx| {
- cx.update_window(window, |cx| {
- handle.update(cx, |view, cx| {
- callback(view, cx);
- })
- });
- })
- }
-
- pub fn propagate_action(&mut self) {
- self.window_context.halt_action_dispatch = false;
- }
-
- pub fn spawn_labeled<F, Fut, S>(&mut self, task_label: &'static str, f: F) -> Task<S>
- where
- F: FnOnce(WeakViewHandle<V>, AsyncAppContext) -> Fut,
- Fut: 'static + Future<Output = S>,
- S: 'static,
- {
- let handle = self.weak_handle();
- self.window_context
- .spawn_labeled(task_label, |cx| f(handle, cx))
- }
-
- pub fn spawn<F, Fut, S>(&mut self, f: F) -> Task<S>
- where
- F: FnOnce(WeakViewHandle<V>, AsyncAppContext) -> Fut,
- Fut: 'static + Future<Output = S>,
- S: 'static,
- {
- let handle = self.weak_handle();
- self.window_context.spawn(|cx| f(handle, cx))
- }
-
- pub fn mouse_state<Tag: 'static>(&self, region_id: usize) -> MouseState {
- self.mouse_state_dynamic(TypeTag::new::<Tag>(), region_id)
- }
-
- pub fn mouse_state_dynamic(&self, tag: TypeTag, region_id: usize) -> MouseState {
- let region_id = MouseRegionId::new(tag, self.view_id, region_id);
- MouseState {
- hovered: self.window.hovered_region_ids.contains(®ion_id),
- mouse_down: !self.window.clicked_region_ids.is_empty(),
- clicked: self
- .window
- .clicked_region_ids
- .iter()
- .find(|click_region_id| **click_region_id == region_id)
- // If we've gotten here, there should always be a clicked region.
- // But let's be defensive and return None if there isn't.
- .and_then(|_| self.window.clicked_region.map(|(_, button)| button)),
- accessed_hovered: false,
- accessed_clicked: false,
- }
- }
-
- pub fn element_state<Tag: 'static, T: 'static>(
- &mut self,
- element_id: usize,
- initial: T,
- ) -> ElementStateHandle<T> {
- self.element_state_dynamic(TypeTag::new::<Tag>(), element_id, initial)
- }
-
- pub fn element_state_dynamic<T: 'static>(
- &mut self,
- tag: TypeTag,
- element_id: usize,
- initial: T,
- ) -> ElementStateHandle<T> {
- let id = ElementStateId {
- view_id: self.view_id(),
- element_id,
- tag,
- };
- self.element_states
- .entry(id)
- .or_insert_with(|| Box::new(initial));
- ElementStateHandle::new(id, self.frame_count, &self.ref_counts)
- }
-
- pub fn default_element_state<Tag: 'static, T: 'static + Default>(
- &mut self,
- element_id: usize,
- ) -> ElementStateHandle<T> {
- self.element_state::<Tag, T>(element_id, T::default())
- }
-
- pub fn default_element_state_dynamic<T: 'static + Default>(
- &mut self,
- tag: TypeTag,
- element_id: usize,
- ) -> ElementStateHandle<T> {
- self.element_state_dynamic::<T>(tag, element_id, T::default())
- }
-
- /// Return keystrokes that would dispatch the given action on the given view.
- pub(crate) fn keystrokes_for_action(
- &mut self,
- view_id: usize,
- action: &dyn Action,
- ) -> Option<SmallVec<[Keystroke; 2]>> {
- self.notify_if_view_ancestors_change(view_id);
-
- let window = self.window_handle;
- let mut contexts = Vec::new();
- let mut handler_depth = None;
- for (i, view_id) in self.ancestors(view_id).enumerate() {
- if let Some(view_metadata) = self.views_metadata.get(&(window, view_id)) {
- if let Some(actions) = self.actions.get(&view_metadata.type_id) {
- if actions.contains_key(&action.id()) {
- handler_depth = Some(i);
- }
- }
- contexts.push(view_metadata.keymap_context.clone());
- }
- }
-
- if self.global_actions.contains_key(&action.id()) {
- handler_depth = Some(contexts.len())
- }
-
- let handler_depth = handler_depth.unwrap_or(0);
- (0..=handler_depth).find_map(|depth| {
- let contexts = &contexts[depth..];
- self.keystroke_matcher
- .keystrokes_for_action(action, contexts)
- })
- }
-
- fn notify_if_view_ancestors_change(&mut self, view_id: usize) {
- let self_view_id = self.view_id;
- self.window
- .views_to_notify_if_ancestors_change
- .entry(view_id)
- .or_default()
- .push(self_view_id);
- }
-
- pub fn paint_layer<F, R>(&mut self, clip_bounds: Option<RectF>, f: F) -> R
- where
- F: FnOnce(&mut Self) -> R,
- {
- self.scene().push_layer(clip_bounds);
- let result = f(self);
- self.scene().pop_layer();
- result
- }
-}
-
-impl<V: View> ViewContext<'_, '_, V> {
- pub fn emit(&mut self, event: V::Event) {
- self.window_context
- .pending_effects
- .push_back(Effect::Event {
- entity_id: self.view_id,
- payload: Box::new(event),
- });
- }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
-pub struct TypeTag {
- tag: TypeId,
- composed: Option<TypeId>,
- #[cfg(debug_assertions)]
- tag_type_name: &'static str,
-}
-
-impl TypeTag {
- pub fn new<Tag: 'static>() -> Self {
- Self {
- tag: TypeId::of::<Tag>(),
- composed: None,
- #[cfg(debug_assertions)]
- tag_type_name: std::any::type_name::<Tag>(),
- }
- }
-
- pub fn dynamic(tag: TypeId, #[cfg(debug_assertions)] type_name: &'static str) -> Self {
- Self {
- tag,
- composed: None,
- #[cfg(debug_assertions)]
- tag_type_name: type_name,
- }
- }
-
- pub fn compose(mut self, other: TypeTag) -> Self {
- self.composed = Some(other.tag);
- self
- }
-
- #[cfg(debug_assertions)]
- pub(crate) fn type_name(&self) -> &'static str {
- self.tag_type_name
- }
-}
-
-impl<V> BorrowAppContext for ViewContext<'_, '_, V> {
- fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
- BorrowAppContext::read_with(&*self.window_context, f)
- }
-
- fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
- BorrowAppContext::update(&mut *self.window_context, f)
- }
-}
-
-impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
- type Result<T> = T;
-
- fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
- BorrowWindowContext::read_window(&*self.window_context, window, f)
- }
-
- fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
- where
- F: FnOnce(&WindowContext) -> Option<T>,
- {
- BorrowWindowContext::read_window_optional(&*self.window_context, window, f)
- }
-
- fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
- &mut self,
- window: AnyWindowHandle,
- f: F,
- ) -> T {
- BorrowWindowContext::update_window(&mut *self.window_context, window, f)
- }
-
- fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
- where
- F: FnOnce(&mut WindowContext) -> Option<T>,
- {
- BorrowWindowContext::update_window_optional(&mut *self.window_context, window, f)
- }
-}
-
-pub struct EventContext<'a, 'b, 'c, V> {
- view_context: &'c mut ViewContext<'a, 'b, V>,
- pub(crate) handled: bool,
- // I would like to replace handled with this.
- // Being additive for now.
- pub bubble: bool,
-}
-
-impl<'a, 'b, 'c, V: 'static> EventContext<'a, 'b, 'c, V> {
- pub fn new(view_context: &'c mut ViewContext<'a, 'b, V>) -> Self {
- EventContext {
- view_context,
- handled: true,
- bubble: false,
- }
- }
-
- pub fn propagate_event(&mut self) {
- self.handled = false;
- }
-
- pub fn bubble_event(&mut self) {
- self.bubble = true;
- }
-
- pub fn event_bubbled(&self) -> bool {
- self.bubble
- }
-}
-
-impl<'a, 'b, 'c, V> Deref for EventContext<'a, 'b, 'c, V> {
- type Target = ViewContext<'a, 'b, V>;
-
- fn deref(&self) -> &Self::Target {
- &self.view_context
- }
-}
-
-impl<V> DerefMut for EventContext<'_, '_, '_, V> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.view_context
- }
-}
-
-impl<V> BorrowAppContext for EventContext<'_, '_, '_, V> {
- fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
- BorrowAppContext::read_with(&*self.view_context, f)
- }
-
- fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
- BorrowAppContext::update(&mut *self.view_context, f)
- }
-}
-
-impl<V> BorrowWindowContext for EventContext<'_, '_, '_, V> {
- type Result<T> = T;
-
- fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
- BorrowWindowContext::read_window(&*self.view_context, window, f)
- }
-
- fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
- where
- F: FnOnce(&WindowContext) -> Option<T>,
- {
- BorrowWindowContext::read_window_optional(&*self.view_context, window, f)
- }
-
- fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
- &mut self,
- window: AnyWindowHandle,
- f: F,
- ) -> T {
- BorrowWindowContext::update_window(&mut *self.view_context, window, f)
- }
-
- fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
- where
- F: FnOnce(&mut WindowContext) -> Option<T>,
- {
- BorrowWindowContext::update_window_optional(&mut *self.view_context, window, f)
- }
-}
-
-pub enum Reference<'a, T> {
- Immutable(&'a T),
- Mutable(&'a mut T),
-}
-
-impl<'a, T> Deref for Reference<'a, T> {
- type Target = T;
-
- fn deref(&self) -> &Self::Target {
- match self {
- Reference::Immutable(target) => target,
- Reference::Mutable(target) => target,
- }
- }
-}
-
-impl<'a, T> DerefMut for Reference<'a, T> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- match self {
- Reference::Immutable(_) => {
- panic!("cannot mutably deref an immutable reference. this is a bug in GPUI.");
- }
- Reference::Mutable(target) => target,
- }
- }
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct MouseState {
- pub(crate) hovered: bool,
- pub(crate) clicked: Option<MouseButton>,
- pub(crate) mouse_down: bool,
- pub(crate) accessed_hovered: bool,
- pub(crate) accessed_clicked: bool,
-}
-
-impl MouseState {
- pub fn dragging(&mut self) -> bool {
- self.accessed_hovered = true;
- self.hovered && self.mouse_down
- }
-
- pub fn hovered(&mut self) -> bool {
- self.accessed_hovered = true;
- self.hovered && (!self.mouse_down || self.clicked.is_some())
- }
-
- pub fn clicked(&mut self) -> Option<MouseButton> {
- self.accessed_clicked = true;
- self.clicked
- }
-
- pub fn accessed_hovered(&self) -> bool {
- self.accessed_hovered
- }
-
- pub fn accessed_clicked(&self) -> bool {
- self.accessed_clicked
- }
-}
-
-pub trait Handle<T> {
- type Weak: 'static;
- fn id(&self) -> usize;
- fn location(&self) -> EntityLocation;
- fn downgrade(&self) -> Self::Weak;
- fn upgrade_from(weak: &Self::Weak, cx: &AppContext) -> Option<Self>
- where
- Self: Sized;
-}
-
-pub trait WeakHandle {
- fn id(&self) -> usize;
-}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
-pub enum EntityLocation {
- Model(usize),
- View(usize, usize),
-}
-
-pub struct ModelHandle<T: Entity> {
- any_handle: AnyModelHandle,
- model_type: PhantomData<T>,
-}
-
-impl<T: Entity> Deref for ModelHandle<T> {
- type Target = AnyModelHandle;
-
- fn deref(&self) -> &Self::Target {
- &self.any_handle
- }
-}
-
-impl<T: Entity> ModelHandle<T> {
- fn new(model_id: usize, ref_counts: &Arc<Mutex<RefCounts>>) -> Self {
- Self {
- any_handle: AnyModelHandle::new(model_id, TypeId::of::<T>(), ref_counts.clone()),
- model_type: PhantomData,
- }
- }
-
- pub fn downgrade(&self) -> WeakModelHandle<T> {
- WeakModelHandle::new(self.model_id)
- }
-
- pub fn id(&self) -> usize {
- self.model_id
- }
-
- pub fn read<'a>(&self, cx: &'a AppContext) -> &'a T {
- cx.read_model(self)
- }
-
- pub fn read_with<C, F, S>(&self, cx: &C, read: F) -> S
- where
- C: BorrowAppContext,
- F: FnOnce(&T, &AppContext) -> S,
- {
- cx.read_with(|cx| read(self.read(cx), cx))
- }
-
- pub fn update<C, F, S>(&self, cx: &mut C, update: F) -> S
- where
- C: BorrowAppContext,
- F: FnOnce(&mut T, &mut ModelContext<T>) -> S,
- {
- let mut update = Some(update);
- cx.update(|cx| {
- cx.update_model(self, &mut |model, cx| {
- let update = update.take().unwrap();
- update(model, cx)
- })
- })
- }
-}
-
-impl<T: Entity> Clone for ModelHandle<T> {
- fn clone(&self) -> Self {
- Self::new(self.model_id, &self.ref_counts)
- }
-}
-
-impl<T: Entity> PartialEq for ModelHandle<T> {
- fn eq(&self, other: &Self) -> bool {
- self.model_id == other.model_id
- }
-}
-
-impl<T: Entity> Eq for ModelHandle<T> {}
-
-impl<T: Entity> PartialEq<WeakModelHandle<T>> for ModelHandle<T> {
- fn eq(&self, other: &WeakModelHandle<T>) -> bool {
- self.model_id == other.model_id
- }
-}
-
-impl<T: Entity> Hash for ModelHandle<T> {
- fn hash<H: Hasher>(&self, state: &mut H) {
- self.model_id.hash(state);
- }
-}
-
-impl<T: Entity> std::borrow::Borrow<usize> for ModelHandle<T> {
- fn borrow(&self) -> &usize {
- &self.model_id
- }
-}
-
-impl<T: Entity> Debug for ModelHandle<T> {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_tuple(&format!("ModelHandle<{}>", type_name::<T>()))
- .field(&self.model_id)
- .finish()
- }
-}
-
-unsafe impl<T: Entity> Send for ModelHandle<T> {}
-unsafe impl<T: Entity> Sync for ModelHandle<T> {}
-
-impl<T: Entity> Handle<T> for ModelHandle<T> {
- type Weak = WeakModelHandle<T>;
-
- fn id(&self) -> usize {
- self.model_id
- }
-
- fn location(&self) -> EntityLocation {
- EntityLocation::Model(self.model_id)
- }
-
- fn downgrade(&self) -> Self::Weak {
- self.downgrade()
- }
-
- fn upgrade_from(weak: &Self::Weak, cx: &AppContext) -> Option<Self>
- where
- Self: Sized,
- {
- weak.upgrade(cx)
- }
-}
-
-pub struct WeakModelHandle<T> {
- any_handle: AnyWeakModelHandle,
- model_type: PhantomData<T>,
-}
-
-impl<T> WeakModelHandle<T> {
- pub fn into_any(self) -> AnyWeakModelHandle {
- self.any_handle
- }
-}
-
-impl<T> Deref for WeakModelHandle<T> {
- type Target = AnyWeakModelHandle;
-
- fn deref(&self) -> &Self::Target {
- &self.any_handle
- }
-}
-
-impl<T> WeakHandle for WeakModelHandle<T> {
- fn id(&self) -> usize {
- self.model_id
- }
-}
-
-unsafe impl<T> Send for WeakModelHandle<T> {}
-unsafe impl<T> Sync for WeakModelHandle<T> {}
-
-impl<T: Entity> WeakModelHandle<T> {
- fn new(model_id: usize) -> Self {
- Self {
- any_handle: AnyWeakModelHandle {
- model_id,
- model_type: TypeId::of::<T>(),
- },
- model_type: PhantomData,
- }
- }
-
- pub fn id(&self) -> usize {
- self.model_id
- }
-
- pub fn is_upgradable(&self, cx: &impl BorrowAppContext) -> bool {
- cx.read_with(|cx| cx.model_handle_is_upgradable(self))
- }
-
- pub fn upgrade(&self, cx: &impl BorrowAppContext) -> Option<ModelHandle<T>> {
- cx.read_with(|cx| cx.upgrade_model_handle(self))
- }
-}
-
-impl<T> Hash for WeakModelHandle<T> {
- fn hash<H: Hasher>(&self, state: &mut H) {
- self.model_id.hash(state)
- }
-}
-
-impl<T> PartialEq for WeakModelHandle<T> {
- fn eq(&self, other: &Self) -> bool {
- self.model_id == other.model_id
- }
-}
-
-impl<T> Eq for WeakModelHandle<T> {}
-
-impl<T: Entity> PartialEq<ModelHandle<T>> for WeakModelHandle<T> {
- fn eq(&self, other: &ModelHandle<T>) -> bool {
- self.model_id == other.model_id
- }
-}
-
-impl<T> Clone for WeakModelHandle<T> {
- fn clone(&self) -> Self {
- Self {
- any_handle: self.any_handle.clone(),
- model_type: PhantomData,
- }
- }
-}
-
-impl<T> Copy for WeakModelHandle<T> {}
-
-#[derive(Deref)]
-pub struct WindowHandle<V> {
- #[deref]
- any_handle: AnyWindowHandle,
- root_view_type: PhantomData<V>,
-}
-
-impl<V> Clone for WindowHandle<V> {
- fn clone(&self) -> Self {
- Self {
- any_handle: self.any_handle.clone(),
- root_view_type: PhantomData,
- }
- }
-}
-
-impl<V> Copy for WindowHandle<V> {}
-
-impl<V: 'static> WindowHandle<V> {
- fn new(window_id: usize) -> Self {
- WindowHandle {
- any_handle: AnyWindowHandle::new(window_id, TypeId::of::<V>()),
- root_view_type: PhantomData,
- }
- }
-
- pub fn root<C: BorrowWindowContext>(&self, cx: &C) -> C::Result<ViewHandle<V>> {
- self.read_with(cx, |cx| cx.root_view().clone().downcast().unwrap())
- }
-
- pub fn read_root_with<C, F, R>(&self, cx: &C, read: F) -> C::Result<R>
- where
- C: BorrowWindowContext,
- F: FnOnce(&V, &ViewContext<V>) -> R,
- {
- self.read_with(cx, |cx| {
- cx.root_view()
- .downcast_ref::<V>()
- .unwrap()
- .read_with(cx, read)
- })
- }
-
- pub fn update_root<C, F, R>(&self, cx: &mut C, update: F) -> C::Result<R>
- where
- C: BorrowWindowContext,
- F: FnOnce(&mut V, &mut ViewContext<V>) -> R,
- {
- cx.update_window(self.any_handle, |cx| {
- cx.root_view()
- .clone()
- .downcast::<V>()
- .unwrap()
- .update(cx, update)
- })
- }
-}
-
-impl<V: View> WindowHandle<V> {
- pub fn replace_root<C, F>(&self, cx: &mut C, build_root: F) -> C::Result<ViewHandle<V>>
- where
- C: BorrowWindowContext,
- F: FnOnce(&mut ViewContext<V>) -> V,
- {
- cx.update_window(self.any_handle, |cx| {
- let root_view = self.add_view(cx, |cx| build_root(cx));
- cx.window.root_view = Some(root_view.clone().into_any());
- cx.window.focused_view_id = Some(root_view.id());
- root_view
- })
- }
-}
-
-impl<V> Into<AnyWindowHandle> for WindowHandle<V> {
- fn into(self) -> AnyWindowHandle {
- self.any_handle
- }
-}
-
-#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
-pub struct AnyWindowHandle {
- window_id: usize,
- root_view_type: TypeId,
-}
-
-impl AnyWindowHandle {
- fn new(window_id: usize, root_view_type: TypeId) -> Self {
- Self {
- window_id,
- root_view_type,
- }
- }
-
- pub fn id(&self) -> usize {
- self.window_id
- }
-
- pub fn read_with<C, F, R>(&self, cx: &C, read: F) -> C::Result<R>
- where
- C: BorrowWindowContext,
- F: FnOnce(&WindowContext) -> R,
- {
- cx.read_window(*self, |cx| read(cx))
- }
-
- pub fn read_optional_with<C, F, R>(&self, cx: &C, read: F) -> Option<R>
- where
- C: BorrowWindowContext,
- F: FnOnce(&WindowContext) -> Option<R>,
- {
- cx.read_window_optional(*self, |cx| read(cx))
- }
-
- pub fn update<C, F, R>(&self, cx: &mut C, update: F) -> C::Result<R>
- where
- C: BorrowWindowContext,
- F: FnOnce(&mut WindowContext) -> R,
- {
- cx.update_window(*self, update)
- }
-
- pub fn update_optional<C, F, R>(&self, cx: &mut C, update: F) -> Option<R>
- where
- C: BorrowWindowContext,
- F: FnOnce(&mut WindowContext) -> Option<R>,
- {
- cx.update_window_optional(*self, update)
- }
-
- pub fn add_view<C, U, F>(&self, cx: &mut C, build_view: F) -> C::Result<ViewHandle<U>>
- where
- C: BorrowWindowContext,
- U: View,
- F: FnOnce(&mut ViewContext<U>) -> U,
- {
- self.update(cx, |cx| cx.add_view(build_view))
- }
-
- pub fn downcast<V: 'static>(self) -> Option<WindowHandle<V>> {
- if self.root_view_type == TypeId::of::<V>() {
- Some(WindowHandle {
- any_handle: self,
- root_view_type: PhantomData,
- })
- } else {
- None
- }
- }
-
- pub fn root_is<V: 'static>(&self) -> bool {
- self.root_view_type == TypeId::of::<V>()
- }
-
- pub fn is_active<C: BorrowWindowContext>(&self, cx: &C) -> C::Result<bool> {
- self.read_with(cx, |cx| cx.window.is_active)
- }
-
- pub fn remove<C: BorrowWindowContext>(&self, cx: &mut C) -> C::Result<()> {
- self.update(cx, |cx| cx.remove_window())
- }
-
- pub fn debug_elements<C: BorrowWindowContext>(&self, cx: &C) -> Option<json::Value> {
- self.read_optional_with(cx, |cx| {
- let root_view = cx.window.root_view();
- let root_element = cx.window.rendered_views.get(&root_view.id())?;
- root_element.debug(cx).log_err()
- })
- }
-
- pub fn activate<C: BorrowWindowContext>(&mut self, cx: &mut C) -> C::Result<()> {
- self.update(cx, |cx| cx.activate_window())
- }
-
- pub fn prompt<C: BorrowWindowContext>(
- &self,
- level: PromptLevel,
- msg: &str,
- answers: &[&str],
- cx: &mut C,
- ) -> C::Result<oneshot::Receiver<usize>> {
- self.update(cx, |cx| cx.prompt(level, msg, answers))
- }
-
- pub fn dispatch_action<C: BorrowWindowContext>(
- &self,
- view_id: usize,
- action: &dyn Action,
- cx: &mut C,
- ) -> C::Result<()> {
- self.update(cx, |cx| {
- cx.dispatch_action(Some(view_id), action);
- })
- }
-
- pub fn available_actions<C: BorrowWindowContext>(
- &self,
- view_id: usize,
- cx: &C,
- ) -> C::Result<Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)>> {
- self.read_with(cx, |cx| cx.available_actions(view_id))
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn simulate_activation(&self, cx: &mut TestAppContext) {
- self.update(cx, |cx| {
- let other_windows = cx
- .windows()
- .filter(|window| *window != *self)
- .collect::<Vec<_>>();
-
- for window in other_windows {
- cx.window_changed_active_status(window, false)
- }
-
- cx.window_changed_active_status(*self, true)
- });
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn simulate_deactivation(&self, cx: &mut TestAppContext) {
- self.update(cx, |cx| {
- cx.window_changed_active_status(*self, false);
- })
- }
-}
-
-#[repr(transparent)]
-pub struct ViewHandle<V> {
- any_handle: AnyViewHandle,
- view_type: PhantomData<V>,
-}
-
-impl<T> Deref for ViewHandle<T> {
- type Target = AnyViewHandle;
-
- fn deref(&self) -> &Self::Target {
- &self.any_handle
- }
-}
-
-impl<V: 'static> ViewHandle<V> {
- fn new(window: AnyWindowHandle, view_id: usize, ref_counts: &Arc<Mutex<RefCounts>>) -> Self {
- Self {
- any_handle: AnyViewHandle::new(window, view_id, TypeId::of::<V>(), ref_counts.clone()),
- view_type: PhantomData,
- }
- }
-
- pub fn downgrade(&self) -> WeakViewHandle<V> {
- WeakViewHandle::new(self.window, self.view_id)
- }
-
- pub fn into_any(self) -> AnyViewHandle {
- self.any_handle
- }
-
- pub fn window(&self) -> AnyWindowHandle {
- self.window
- }
-
- pub fn id(&self) -> usize {
- self.view_id
- }
-
- pub fn read<'a>(&self, cx: &'a AppContext) -> &'a V {
- cx.read_view(self)
- }
-
- pub fn read_with<C, F, S>(&self, cx: &C, read: F) -> C::Result<S>
- where
- C: BorrowWindowContext,
- F: FnOnce(&V, &ViewContext<V>) -> S,
- {
- cx.read_window(self.window, |cx| {
- let cx = ViewContext::immutable(cx, self.view_id);
- read(cx.read_view(self), &cx)
- })
- }
-
- pub fn update<C, F, S>(&self, cx: &mut C, update: F) -> C::Result<S>
- where
- C: BorrowWindowContext,
- F: FnOnce(&mut V, &mut ViewContext<V>) -> S,
- {
- let mut update = Some(update);
-
- cx.update_window(self.window, |cx| {
- cx.update_view(self, &mut |view, cx| {
- let update = update.take().unwrap();
- update(view, cx)
- })
- })
- }
-
- pub fn is_focused(&self, cx: &WindowContext) -> bool {
- cx.focused_view_id() == Some(self.view_id)
- }
-}
-
-impl<T: View> Clone for ViewHandle<T> {
- fn clone(&self) -> Self {
- ViewHandle::new(self.window, self.view_id, &self.ref_counts)
- }
-}
-
-impl<T> PartialEq for ViewHandle<T> {
- fn eq(&self, other: &Self) -> bool {
- self.window == other.window && self.view_id == other.view_id
- }
-}
-
-impl<T> PartialEq<AnyViewHandle> for ViewHandle<T> {
- fn eq(&self, other: &AnyViewHandle) -> bool {
- self.window == other.window && self.view_id == other.view_id
- }
-}
-
-impl<T> PartialEq<WeakViewHandle<T>> for ViewHandle<T> {
- fn eq(&self, other: &WeakViewHandle<T>) -> bool {
- self.window == other.window && self.view_id == other.view_id
- }
-}
-
-impl<T> PartialEq<ViewHandle<T>> for WeakViewHandle<T> {
- fn eq(&self, other: &ViewHandle<T>) -> bool {
- self.window == other.window && self.view_id == other.view_id
- }
-}
-
-impl<T> Eq for ViewHandle<T> {}
-
-impl<T> Hash for ViewHandle<T> {
- fn hash<H: Hasher>(&self, state: &mut H) {
- self.window.hash(state);
- self.view_id.hash(state);
- }
-}
-
-impl<T> Debug for ViewHandle<T> {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_struct(&format!("ViewHandle<{}>", type_name::<T>()))
- .field("window_id", &self.window)
- .field("view_id", &self.view_id)
- .finish()
- }
-}
-
-impl<T: View> Handle<T> for ViewHandle<T> {
- type Weak = WeakViewHandle<T>;
-
- fn id(&self) -> usize {
- self.view_id
- }
-
- fn location(&self) -> EntityLocation {
- EntityLocation::View(self.window.id(), self.view_id)
- }
-
- fn downgrade(&self) -> Self::Weak {
- self.downgrade()
- }
-
- fn upgrade_from(weak: &Self::Weak, cx: &AppContext) -> Option<Self>
- where
- Self: Sized,
- {
- weak.upgrade(cx)
- }
-}
-
-pub struct AnyViewHandle {
- window: AnyWindowHandle,
- view_id: usize,
- view_type: TypeId,
- ref_counts: Arc<Mutex<RefCounts>>,
-
- #[cfg(any(test, feature = "test-support"))]
- handle_id: usize,
-}
-
-impl AnyViewHandle {
- fn new(
- window: AnyWindowHandle,
- view_id: usize,
- view_type: TypeId,
- ref_counts: Arc<Mutex<RefCounts>>,
- ) -> Self {
- ref_counts.lock().inc_view(window, view_id);
-
- #[cfg(any(test, feature = "test-support"))]
- let handle_id = ref_counts
- .lock()
- .leak_detector
- .lock()
- .handle_created(None, view_id);
-
- Self {
- window,
- view_id,
- view_type,
- ref_counts,
- #[cfg(any(test, feature = "test-support"))]
- handle_id,
- }
- }
-
- pub fn window(&self) -> AnyWindowHandle {
- self.window
- }
-
- pub fn id(&self) -> usize {
- self.view_id
- }
-
- pub fn is<T: 'static>(&self) -> bool {
- TypeId::of::<T>() == self.view_type
- }
-
- pub fn downcast<V: 'static>(self) -> Option<ViewHandle<V>> {
- if self.is::<V>() {
- Some(ViewHandle {
- any_handle: self,
- view_type: PhantomData,
- })
- } else {
- None
- }
- }
-
- pub fn downcast_ref<V: 'static>(&self) -> Option<&ViewHandle<V>> {
- if self.is::<V>() {
- Some(unsafe { mem::transmute(self) })
- } else {
- None
- }
- }
-
- pub fn downgrade(&self) -> AnyWeakViewHandle {
- AnyWeakViewHandle {
- window: self.window,
- view_id: self.view_id,
- view_type: self.view_type,
- }
- }
-
- pub fn view_type(&self) -> TypeId {
- self.view_type
- }
-
- pub fn debug_json<'a, 'b>(&self, cx: &'b WindowContext<'a>) -> serde_json::Value {
- cx.views
- .get(&(self.window, self.view_id))
- .map_or_else(|| serde_json::Value::Null, |view| view.debug_json(cx))
- }
-}
-
-impl Clone for AnyViewHandle {
- fn clone(&self) -> Self {
- Self::new(
- self.window,
- self.view_id,
- self.view_type,
- self.ref_counts.clone(),
- )
- }
-}
-
-impl PartialEq for AnyViewHandle {
- fn eq(&self, other: &Self) -> bool {
- self.window == other.window && self.view_id == other.view_id
- }
-}
-
-impl<T> PartialEq<ViewHandle<T>> for AnyViewHandle {
- fn eq(&self, other: &ViewHandle<T>) -> bool {
- self.window == other.window && self.view_id == other.view_id
- }
-}
-
-impl Drop for AnyViewHandle {
- fn drop(&mut self) {
- self.ref_counts.lock().dec_view(self.window, self.view_id);
- #[cfg(any(test, feature = "test-support"))]
- self.ref_counts
- .lock()
- .leak_detector
- .lock()
- .handle_dropped(self.view_id, self.handle_id);
- }
-}
-
-impl Debug for AnyViewHandle {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_struct("AnyViewHandle")
- .field("window_id", &self.window.id())
- .field("view_id", &self.view_id)
- .finish()
- }
-}
-
-pub struct AnyModelHandle {
- model_id: usize,
- model_type: TypeId,
- ref_counts: Arc<Mutex<RefCounts>>,
-
- #[cfg(any(test, feature = "test-support"))]
- handle_id: usize,
-}
-
-impl AnyModelHandle {
- fn new(model_id: usize, model_type: TypeId, ref_counts: Arc<Mutex<RefCounts>>) -> Self {
- ref_counts.lock().inc_model(model_id);
-
- #[cfg(any(test, feature = "test-support"))]
- let handle_id = ref_counts
- .lock()
- .leak_detector
- .lock()
- .handle_created(None, model_id);
-
- Self {
- model_id,
- model_type,
- ref_counts,
-
- #[cfg(any(test, feature = "test-support"))]
- handle_id,
- }
- }
-
- pub fn downcast<T: Entity>(self) -> Option<ModelHandle<T>> {
- if self.is::<T>() {
- Some(ModelHandle {
- any_handle: self,
- model_type: PhantomData,
- })
- } else {
- None
- }
- }
-
- pub fn downgrade(&self) -> AnyWeakModelHandle {
- AnyWeakModelHandle {
- model_id: self.model_id,
- model_type: self.model_type,
- }
- }
-
- pub fn is<T: Entity>(&self) -> bool {
- self.model_type == TypeId::of::<T>()
- }
-
- pub fn model_type(&self) -> TypeId {
- self.model_type
- }
-}
-
-impl Clone for AnyModelHandle {
- fn clone(&self) -> Self {
- Self::new(self.model_id, self.model_type, self.ref_counts.clone())
- }
-}
-
-impl Drop for AnyModelHandle {
- fn drop(&mut self) {
- let mut ref_counts = self.ref_counts.lock();
- ref_counts.dec_model(self.model_id);
-
- #[cfg(any(test, feature = "test-support"))]
- ref_counts
- .leak_detector
- .lock()
- .handle_dropped(self.model_id, self.handle_id);
- }
-}
-
-#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy)]
-pub struct AnyWeakModelHandle {
- model_id: usize,
- model_type: TypeId,
-}
-
-impl AnyWeakModelHandle {
- pub fn upgrade(&self, cx: &impl BorrowAppContext) -> Option<AnyModelHandle> {
- cx.read_with(|cx| cx.upgrade_any_model_handle(self))
- }
-
- pub fn model_type(&self) -> TypeId {
- self.model_type
- }
-
- fn is<T: 'static>(&self) -> bool {
- TypeId::of::<T>() == self.model_type
- }
-
- pub fn downcast<T: Entity>(self) -> Option<WeakModelHandle<T>> {
- if self.is::<T>() {
- let result = Some(WeakModelHandle {
- any_handle: self,
- model_type: PhantomData,
- });
-
- result
- } else {
- None
- }
- }
-}
-
-pub struct WeakViewHandle<T> {
- any_handle: AnyWeakViewHandle,
- view_type: PhantomData<T>,
-}
-
-impl<T> Copy for WeakViewHandle<T> {}
-
-impl<T> Debug for WeakViewHandle<T> {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_struct(&format!("WeakViewHandle<{}>", type_name::<T>()))
- .field("any_handle", &self.any_handle)
- .finish()
- }
-}
-
-impl<T> WeakHandle for WeakViewHandle<T> {
- fn id(&self) -> usize {
- self.view_id
- }
-}
-
-impl<V: 'static> WeakViewHandle<V> {
- fn new(window: AnyWindowHandle, view_id: usize) -> Self {
- Self {
- any_handle: AnyWeakViewHandle {
- window,
- view_id,
- view_type: TypeId::of::<V>(),
- },
- view_type: PhantomData,
- }
- }
-
- pub fn id(&self) -> usize {
- self.view_id
- }
-
- pub fn window(&self) -> AnyWindowHandle {
- self.window
- }
-
- pub fn window_id(&self) -> usize {
- self.window.id()
- }
-
- pub fn into_any(self) -> AnyWeakViewHandle {
- self.any_handle
- }
-
- pub fn upgrade(&self, cx: &impl BorrowAppContext) -> Option<ViewHandle<V>> {
- cx.read_with(|cx| cx.upgrade_view_handle(self))
- }
-
- pub fn read_with<T>(
- &self,
- cx: &AsyncAppContext,
- read: impl FnOnce(&V, &ViewContext<V>) -> T,
- ) -> Result<T> {
- cx.read(|cx| {
- let handle = cx
- .upgrade_view_handle(self)
- .ok_or_else(|| anyhow!("view was dropped"))?;
- cx.read_window(self.window, |cx| handle.read_with(cx, read))
- .ok_or_else(|| anyhow!("window was removed"))
- })
- }
-
- pub fn update<T, B>(
- &self,
- cx: &mut B,
- update: impl FnOnce(&mut V, &mut ViewContext<V>) -> T,
- ) -> Result<T>
- where
- B: BorrowWindowContext,
- B::Result<Option<T>>: Flatten<T>,
- {
- cx.update_window(self.window(), |cx| {
- cx.upgrade_view_handle(self)
- .map(|handle| handle.update(cx, update))
- })
- .flatten()
- .ok_or_else(|| anyhow!("window was removed"))
- }
-}
-
-pub trait Flatten<T> {
- fn flatten(self) -> Option<T>;
-}
-
-impl<T> Flatten<T> for Option<Option<T>> {
- fn flatten(self) -> Option<T> {
- self.flatten()
- }
-}
-
-impl<T> Flatten<T> for Option<T> {
- fn flatten(self) -> Option<T> {
- self
- }
-}
-
-impl<V> Deref for WeakViewHandle<V> {
- type Target = AnyWeakViewHandle;
-
- fn deref(&self) -> &Self::Target {
- &self.any_handle
- }
-}
-
-impl<V> Clone for WeakViewHandle<V> {
- fn clone(&self) -> Self {
- Self {
- any_handle: self.any_handle.clone(),
- view_type: PhantomData,
- }
- }
-}
-
-impl<T> PartialEq for WeakViewHandle<T> {
- fn eq(&self, other: &Self) -> bool {
- self.window == other.window && self.view_id == other.view_id
- }
-}
-
-impl<T> Eq for WeakViewHandle<T> {}
-
-impl<T> Hash for WeakViewHandle<T> {
- fn hash<H: Hasher>(&self, state: &mut H) {
- self.any_handle.hash(state);
- }
-}
-
-#[derive(Debug, Clone, Copy, Eq, PartialEq)]
-pub struct AnyWeakViewHandle {
- window: AnyWindowHandle,
- view_id: usize,
- view_type: TypeId,
-}
-
-impl AnyWeakViewHandle {
- pub fn id(&self) -> usize {
- self.view_id
- }
-
- fn is<T: 'static>(&self) -> bool {
- TypeId::of::<T>() == self.view_type
- }
-
- pub fn upgrade(&self, cx: &impl BorrowAppContext) -> Option<AnyViewHandle> {
- cx.read_with(|cx| cx.upgrade_any_view_handle(self))
- }
-
- pub fn downcast<T: View>(self) -> Option<WeakViewHandle<T>> {
- if self.is::<T>() {
- Some(WeakViewHandle {
- any_handle: self,
- view_type: PhantomData,
- })
- } else {
- None
- }
- }
-}
-
-impl Hash for AnyWeakViewHandle {
- fn hash<H: Hasher>(&self, state: &mut H) {
- self.window.hash(state);
- self.view_id.hash(state);
- self.view_type.hash(state);
- }
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-pub struct ElementStateId {
- view_id: usize,
- element_id: usize,
- tag: TypeTag,
-}
-
-pub struct ElementStateHandle<T> {
- value_type: PhantomData<T>,
- id: ElementStateId,
- ref_counts: Weak<Mutex<RefCounts>>,
-}
-
-impl<T: 'static> ElementStateHandle<T> {
- fn new(id: ElementStateId, frame_id: usize, ref_counts: &Arc<Mutex<RefCounts>>) -> Self {
- ref_counts.lock().inc_element_state(id, frame_id);
- Self {
- value_type: PhantomData,
- id,
- ref_counts: Arc::downgrade(ref_counts),
- }
- }
-
- pub fn id(&self) -> ElementStateId {
- self.id
- }
-
- pub fn read<'a>(&self, cx: &'a AppContext) -> &'a T {
- cx.element_states
- .get(&self.id)
- .unwrap()
- .downcast_ref()
- .unwrap()
- }
-
- pub fn update<C, D, R>(&self, cx: &mut C, f: impl FnOnce(&mut T, &mut C) -> R) -> R
- where
- C: DerefMut<Target = D>,
- D: DerefMut<Target = AppContext>,
- {
- let mut element_state = cx.deref_mut().element_states.remove(&self.id).unwrap();
- let result = f(element_state.downcast_mut().unwrap(), cx);
- cx.deref_mut().element_states.insert(self.id, element_state);
- result
- }
-}
-
-impl<T> Drop for ElementStateHandle<T> {
- fn drop(&mut self) {
- if let Some(ref_counts) = self.ref_counts.upgrade() {
- ref_counts.lock().dec_element_state(self.id);
- }
- }
-}
-
-#[must_use]
-pub enum Subscription {
- Subscription(callback_collection::Subscription<usize, SubscriptionCallback>),
- Observation(callback_collection::Subscription<usize, ObservationCallback>),
- GlobalSubscription(callback_collection::Subscription<TypeId, GlobalSubscriptionCallback>),
- GlobalObservation(callback_collection::Subscription<TypeId, GlobalObservationCallback>),
- FocusObservation(callback_collection::Subscription<usize, FocusObservationCallback>),
- WindowActivationObservation(
- callback_collection::Subscription<AnyWindowHandle, WindowActivationCallback>,
- ),
- WindowFullscreenObservation(
- callback_collection::Subscription<AnyWindowHandle, WindowFullscreenCallback>,
- ),
- WindowBoundsObservation(
- callback_collection::Subscription<AnyWindowHandle, WindowBoundsCallback>,
- ),
- KeystrokeObservation(callback_collection::Subscription<AnyWindowHandle, KeystrokeCallback>),
- ReleaseObservation(callback_collection::Subscription<usize, ReleaseObservationCallback>),
- ActionObservation(callback_collection::Subscription<(), ActionObservationCallback>),
- ActiveLabeledTasksObservation(
- callback_collection::Subscription<(), ActiveLabeledTasksCallback>,
- ),
-}
-
-impl Subscription {
- pub fn id(&self) -> usize {
- match self {
- Subscription::Subscription(subscription) => subscription.id(),
- Subscription::Observation(subscription) => subscription.id(),
- Subscription::GlobalSubscription(subscription) => subscription.id(),
- Subscription::GlobalObservation(subscription) => subscription.id(),
- Subscription::FocusObservation(subscription) => subscription.id(),
- Subscription::WindowActivationObservation(subscription) => subscription.id(),
- Subscription::WindowFullscreenObservation(subscription) => subscription.id(),
- Subscription::WindowBoundsObservation(subscription) => subscription.id(),
- Subscription::KeystrokeObservation(subscription) => subscription.id(),
- Subscription::ReleaseObservation(subscription) => subscription.id(),
- Subscription::ActionObservation(subscription) => subscription.id(),
- Subscription::ActiveLabeledTasksObservation(subscription) => subscription.id(),
- }
- }
-
- pub fn detach(&mut self) {
- match self {
- Subscription::Subscription(subscription) => subscription.detach(),
- Subscription::GlobalSubscription(subscription) => subscription.detach(),
- Subscription::Observation(subscription) => subscription.detach(),
- Subscription::GlobalObservation(subscription) => subscription.detach(),
- Subscription::FocusObservation(subscription) => subscription.detach(),
- Subscription::KeystrokeObservation(subscription) => subscription.detach(),
- Subscription::WindowActivationObservation(subscription) => subscription.detach(),
- Subscription::WindowFullscreenObservation(subscription) => subscription.detach(),
- Subscription::WindowBoundsObservation(subscription) => subscription.detach(),
- Subscription::ReleaseObservation(subscription) => subscription.detach(),
- Subscription::ActionObservation(subscription) => subscription.detach(),
- Subscription::ActiveLabeledTasksObservation(subscription) => subscription.detach(),
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::{
- actions,
- elements::*,
- impl_actions,
- platform::{MouseButton, MouseButtonEvent},
- window::ChildView,
- };
- use itertools::Itertools;
- use postage::{sink::Sink, stream::Stream};
- use serde::Deserialize;
- use smol::future::poll_once;
- use std::{
- cell::Cell,
- sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
- };
-
- #[crate::test(self)]
- fn test_model_handles(cx: &mut AppContext) {
- struct Model {
- other: Option<ModelHandle<Model>>,
- events: Vec<String>,
- }
-
- impl Entity for Model {
- type Event = usize;
- }
-
- impl Model {
- fn new(other: Option<ModelHandle<Self>>, cx: &mut ModelContext<Self>) -> Self {
- if let Some(other) = other.as_ref() {
- cx.observe(other, |me, _, _| {
- me.events.push("notified".into());
- })
- .detach();
- cx.subscribe(other, |me, _, event, _| {
- me.events.push(format!("observed event {}", event));
- })
- .detach();
- }
-
- Self {
- other,
- events: Vec::new(),
- }
- }
- }
-
- let handle_1 = cx.add_model(|cx| Model::new(None, cx));
- let handle_2 = cx.add_model(|cx| Model::new(Some(handle_1.clone()), cx));
- assert_eq!(cx.models.len(), 2);
-
- handle_1.update(cx, |model, cx| {
- model.events.push("updated".into());
- cx.emit(1);
- cx.notify();
- cx.emit(2);
- });
- assert_eq!(handle_1.read(cx).events, vec!["updated".to_string()]);
- assert_eq!(
- handle_2.read(cx).events,
- vec![
- "observed event 1".to_string(),
- "notified".to_string(),
- "observed event 2".to_string(),
- ]
- );
-
- handle_2.update(cx, |model, _| {
- drop(handle_1);
- model.other.take();
- });
-
- assert_eq!(cx.models.len(), 1);
- assert!(cx.subscriptions.is_empty());
- assert!(cx.observations.is_empty());
- }
-
- #[crate::test(self)]
- fn test_model_events(cx: &mut AppContext) {
- #[derive(Default)]
- struct Model {
- events: Vec<usize>,
- }
-
- impl Entity for Model {
- type Event = usize;
- }
-
- let handle_1 = cx.add_model(|_| Model::default());
- let handle_2 = cx.add_model(|_| Model::default());
-
- handle_1.update(cx, |_, cx| {
- cx.subscribe(&handle_2, move |model: &mut Model, emitter, event, cx| {
- model.events.push(*event);
-
- cx.subscribe(&emitter, |model, _, event, _| {
- model.events.push(*event * 2);
- })
- .detach();
- })
- .detach();
- });
-
- handle_2.update(cx, |_, c| c.emit(7));
- assert_eq!(handle_1.read(cx).events, vec![7]);
-
- handle_2.update(cx, |_, c| c.emit(5));
- assert_eq!(handle_1.read(cx).events, vec![7, 5, 10]);
- }
-
- #[crate::test(self)]
- fn test_model_emit_before_subscribe_in_same_update_cycle(cx: &mut AppContext) {
- #[derive(Default)]
- struct Model;
-
- impl Entity for Model {
- type Event = ();
- }
-
- let events = Rc::new(RefCell::new(Vec::new()));
- cx.add_model(|cx| {
- drop(cx.subscribe(&cx.handle(), {
- let events = events.clone();
- move |_, _, _, _| events.borrow_mut().push("dropped before flush")
- }));
- cx.subscribe(&cx.handle(), {
- let events = events.clone();
- move |_, _, _, _| events.borrow_mut().push("before emit")
- })
- .detach();
- cx.emit(());
- cx.subscribe(&cx.handle(), {
- let events = events.clone();
- move |_, _, _, _| events.borrow_mut().push("after emit")
- })
- .detach();
- Model
- });
- assert_eq!(*events.borrow(), ["before emit"]);
- }
-
- #[crate::test(self)]
- fn test_observe_and_notify_from_model(cx: &mut AppContext) {
- #[derive(Default)]
- struct Model {
- count: usize,
- events: Vec<usize>,
- }
-
- impl Entity for Model {
- type Event = ();
- }
-
- let handle_1 = cx.add_model(|_| Model::default());
- let handle_2 = cx.add_model(|_| Model::default());
-
- handle_1.update(cx, |_, c| {
- c.observe(&handle_2, move |model, observed, c| {
- model.events.push(observed.read(c).count);
- c.observe(&observed, |model, observed, c| {
- model.events.push(observed.read(c).count * 2);
- })
- .detach();
- })
- .detach();
- });
-
- handle_2.update(cx, |model, c| {
- model.count = 7;
- c.notify()
- });
- assert_eq!(handle_1.read(cx).events, vec![7]);
-
- handle_2.update(cx, |model, c| {
- model.count = 5;
- c.notify()
- });
- assert_eq!(handle_1.read(cx).events, vec![7, 5, 10])
- }
-
- #[crate::test(self)]
- fn test_model_notify_before_observe_in_same_update_cycle(cx: &mut AppContext) {
- #[derive(Default)]
- struct Model;
-
- impl Entity for Model {
- type Event = ();
- }
-
- let events = Rc::new(RefCell::new(Vec::new()));
- cx.add_model(|cx| {
- drop(cx.observe(&cx.handle(), {
- let events = events.clone();
- move |_, _, _| events.borrow_mut().push("dropped before flush")
- }));
- cx.observe(&cx.handle(), {
- let events = events.clone();
- move |_, _, _| events.borrow_mut().push("before notify")
- })
- .detach();
- cx.notify();
- cx.observe(&cx.handle(), {
- let events = events.clone();
- move |_, _, _| events.borrow_mut().push("after notify")
- })
- .detach();
- Model
- });
- assert_eq!(*events.borrow(), ["before notify"]);
- }
-
- #[crate::test(self)]
- fn test_defer_and_after_window_update(cx: &mut TestAppContext) {
- struct View {
- render_count: usize,
- }
-
- impl Entity for View {
- type Event = usize;
- }
-
- impl super::View for View {
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- post_inc(&mut self.render_count);
- Empty::new().into_any()
- }
-
- fn ui_name() -> &'static str {
- "View"
- }
- }
-
- let window = cx.add_window(|_| View { render_count: 0 });
- let called_defer = Rc::new(AtomicBool::new(false));
- let called_after_window_update = Rc::new(AtomicBool::new(false));
-
- window.root(cx).update(cx, |this, cx| {
- assert_eq!(this.render_count, 1);
- cx.defer({
- let called_defer = called_defer.clone();
- move |this, _| {
- assert_eq!(this.render_count, 1);
- called_defer.store(true, SeqCst);
- }
- });
- cx.after_window_update({
- let called_after_window_update = called_after_window_update.clone();
- move |this, cx| {
- assert_eq!(this.render_count, 2);
- called_after_window_update.store(true, SeqCst);
- cx.notify();
- }
- });
- assert!(!called_defer.load(SeqCst));
- assert!(!called_after_window_update.load(SeqCst));
- cx.notify();
- });
-
- assert!(called_defer.load(SeqCst));
- assert!(called_after_window_update.load(SeqCst));
- assert_eq!(window.read_root_with(cx, |view, _| view.render_count), 3);
- }
-
- #[crate::test(self)]
- fn test_view_handles(cx: &mut TestAppContext) {
- struct View {
- other: Option<ViewHandle<View>>,
- events: Vec<String>,
- }
-
- impl Entity for View {
- type Event = usize;
- }
-
- impl super::View for View {
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- Empty::new().into_any()
- }
-
- fn ui_name() -> &'static str {
- "View"
- }
- }
-
- impl View {
- fn new(other: Option<ViewHandle<View>>, cx: &mut ViewContext<Self>) -> Self {
- if let Some(other) = other.as_ref() {
- cx.subscribe(other, |me, _, event, _| {
- me.events.push(format!("observed event {}", event));
- })
- .detach();
- }
- Self {
- other,
- events: Vec::new(),
- }
- }
- }
-
- let window = cx.add_window(|cx| View::new(None, cx));
- let handle_1 = window.add_view(cx, |cx| View::new(None, cx));
- let handle_2 = window.add_view(cx, |cx| View::new(Some(handle_1.clone()), cx));
- assert_eq!(cx.read(|cx| cx.views.len()), 3);
-
- handle_1.update(cx, |view, cx| {
- view.events.push("updated".into());
- cx.emit(1);
- cx.emit(2);
- });
- handle_1.read_with(cx, |view, _| {
- assert_eq!(view.events, vec!["updated".to_string()]);
- });
- handle_2.read_with(cx, |view, _| {
- assert_eq!(
- view.events,
- vec![
- "observed event 1".to_string(),
- "observed event 2".to_string(),
- ]
- );
- });
-
- handle_2.update(cx, |view, _| {
- drop(handle_1);
- view.other.take();
- });
-
- cx.read(|cx| {
- assert_eq!(cx.views.len(), 2);
- assert!(cx.subscriptions.is_empty());
- assert!(cx.observations.is_empty());
- });
- }
-
- #[crate::test(self)]
- fn test_add_window(cx: &mut AppContext) {
- struct View {
- mouse_down_count: Arc<AtomicUsize>,
- }
-
- impl Entity for View {
- type Event = ();
- }
-
- impl super::View for View {
- fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- enum Handler {}
- let mouse_down_count = self.mouse_down_count.clone();
- MouseEventHandler::new::<Handler, _>(0, cx, |_, _| Empty::new())
- .on_down(MouseButton::Left, move |_, _, _| {
- mouse_down_count.fetch_add(1, SeqCst);
- })
- .into_any()
- }
-
- fn ui_name() -> &'static str {
- "View"
- }
- }
-
- let mouse_down_count = Arc::new(AtomicUsize::new(0));
- let window = cx.add_window(Default::default(), |_| View {
- mouse_down_count: mouse_down_count.clone(),
- });
-
- window.update(cx, |cx| {
- // Ensure window's root element is in a valid lifecycle state.
- cx.dispatch_event(
- Event::MouseDown(MouseButtonEvent {
- position: Default::default(),
- button: MouseButton::Left,
- modifiers: Default::default(),
- click_count: 1,
- is_down: true,
- }),
- false,
- );
- assert_eq!(mouse_down_count.load(SeqCst), 1);
- });
- }
-
- #[crate::test(self)]
- fn test_entity_release_hooks(cx: &mut TestAppContext) {
- struct Model {
- released: Rc<Cell<bool>>,
- }
-
- struct View {
- released: Rc<Cell<bool>>,
- }
-
- impl Entity for Model {
- type Event = ();
-
- fn release(&mut self, _: &mut AppContext) {
- self.released.set(true);
- }
- }
-
- impl Entity for View {
- type Event = ();
-
- fn release(&mut self, _: &mut AppContext) {
- self.released.set(true);
- }
- }
-
- impl super::View for View {
- fn ui_name() -> &'static str {
- "View"
- }
-
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- Empty::new().into_any()
- }
- }
-
- let model_released = Rc::new(Cell::new(false));
- let model_release_observed = Rc::new(Cell::new(false));
- let view_released = Rc::new(Cell::new(false));
- let view_release_observed = Rc::new(Cell::new(false));
-
- let model = cx.add_model(|_| Model {
- released: model_released.clone(),
- });
- let window = cx.add_window(|_| View {
- released: view_released.clone(),
- });
- let view = window.root(cx);
-
- assert!(!model_released.get());
- assert!(!view_released.get());
-
- cx.update(|cx| {
- cx.observe_release(&model, {
- let model_release_observed = model_release_observed.clone();
- move |_, _| model_release_observed.set(true)
- })
- .detach();
- cx.observe_release(&view, {
- let view_release_observed = view_release_observed.clone();
- move |_, _| view_release_observed.set(true)
- })
- .detach();
- });
-
- cx.update(move |_| {
- drop(model);
- });
- assert!(model_released.get());
- assert!(model_release_observed.get());
-
- drop(view);
- window.update(cx, |cx| cx.remove_window());
- assert!(view_released.get());
- assert!(view_release_observed.get());
- }
-
- #[crate::test(self)]
- fn test_view_events(cx: &mut TestAppContext) {
- struct Model;
-
- impl Entity for Model {
- type Event = String;
- }
-
- let window = cx.add_window(|_| TestView::default());
- let handle_1 = window.root(cx);
- let handle_2 = window.add_view(cx, |_| TestView::default());
- let handle_3 = cx.add_model(|_| Model);
-
- handle_1.update(cx, |_, cx| {
- cx.subscribe(&handle_2, move |me, emitter, event, cx| {
- me.events.push(event.clone());
-
- cx.subscribe(&emitter, |me, _, event, _| {
- me.events.push(format!("{event} from inner"));
- })
- .detach();
- })
- .detach();
-
- cx.subscribe(&handle_3, |me, _, event, _| {
- me.events.push(event.clone());
- })
- .detach();
- });
-
- handle_2.update(cx, |_, c| c.emit("7".into()));
- handle_1.read_with(cx, |view, _| assert_eq!(view.events, ["7"]));
-
- handle_2.update(cx, |_, c| c.emit("5".into()));
- handle_1.read_with(cx, |view, _| {
- assert_eq!(view.events, ["7", "5", "5 from inner"])
- });
-
- handle_3.update(cx, |_, c| c.emit("9".into()));
- handle_1.read_with(cx, |view, _| {
- assert_eq!(view.events, ["7", "5", "5 from inner", "9"])
- });
- }
-
- #[crate::test(self)]
- fn test_global_events(cx: &mut AppContext) {
- #[derive(Clone, Debug, Eq, PartialEq)]
- struct GlobalEvent(u64);
-
- let events = Rc::new(RefCell::new(Vec::new()));
- let first_subscription;
- let second_subscription;
-
- {
- let events = events.clone();
- first_subscription = cx.subscribe_global(move |e: &GlobalEvent, _| {
- events.borrow_mut().push(("First", e.clone()));
- });
- }
-
- {
- let events = events.clone();
- second_subscription = cx.subscribe_global(move |e: &GlobalEvent, _| {
- events.borrow_mut().push(("Second", e.clone()));
- });
- }
-
- cx.update(|cx| {
- cx.emit_global(GlobalEvent(1));
- cx.emit_global(GlobalEvent(2));
- });
-
- drop(first_subscription);
-
- cx.update(|cx| {
- cx.emit_global(GlobalEvent(3));
- });
-
- drop(second_subscription);
-
- cx.update(|cx| {
- cx.emit_global(GlobalEvent(4));
- });
-
- assert_eq!(
- &*events.borrow(),
- &[
- ("First", GlobalEvent(1)),
- ("Second", GlobalEvent(1)),
- ("First", GlobalEvent(2)),
- ("Second", GlobalEvent(2)),
- ("Second", GlobalEvent(3)),
- ]
- );
- }
-
- #[crate::test(self)]
- fn test_global_events_emitted_before_subscription_in_same_update_cycle(cx: &mut AppContext) {
- let events = Rc::new(RefCell::new(Vec::new()));
- cx.update(|cx| {
- {
- let events = events.clone();
- drop(cx.subscribe_global(move |_: &(), _| {
- events.borrow_mut().push("dropped before emit");
- }));
- }
-
- {
- let events = events.clone();
- cx.subscribe_global(move |_: &(), _| {
- events.borrow_mut().push("before emit");
- })
- .detach();
- }
-
- cx.emit_global(());
-
- {
- let events = events.clone();
- cx.subscribe_global(move |_: &(), _| {
- events.borrow_mut().push("after emit");
- })
- .detach();
- }
- });
-
- assert_eq!(*events.borrow(), ["before emit"]);
- }
-
- #[crate::test(self)]
- fn test_global_nested_events(cx: &mut AppContext) {
- #[derive(Clone, Debug, Eq, PartialEq)]
- struct GlobalEvent(u64);
-
- let events = Rc::new(RefCell::new(Vec::new()));
-
- {
- let events = events.clone();
- cx.subscribe_global(move |e: &GlobalEvent, cx| {
- events.borrow_mut().push(("Outer", e.clone()));
-
- if e.0 == 1 {
- let events = events.clone();
- cx.subscribe_global(move |e: &GlobalEvent, _| {
- events.borrow_mut().push(("Inner", e.clone()));
- })
- .detach();
- }
- })
- .detach();
- }
-
- cx.update(|cx| {
- cx.emit_global(GlobalEvent(1));
- cx.emit_global(GlobalEvent(2));
- cx.emit_global(GlobalEvent(3));
- });
- cx.update(|cx| {
- cx.emit_global(GlobalEvent(4));
- });
-
- assert_eq!(
- &*events.borrow(),
- &[
- ("Outer", GlobalEvent(1)),
- ("Outer", GlobalEvent(2)),
- ("Outer", GlobalEvent(3)),
- ("Outer", GlobalEvent(4)),
- ("Inner", GlobalEvent(4)),
- ]
- );
- }
-
- #[crate::test(self)]
- fn test_global(cx: &mut AppContext) {
- type Global = usize;
-
- let observation_count = Rc::new(RefCell::new(0));
- let subscription = cx.observe_global::<Global, _>({
- let observation_count = observation_count.clone();
- move |_| {
- *observation_count.borrow_mut() += 1;
- }
- });
-
- assert!(!cx.has_global::<Global>());
- assert_eq!(cx.default_global::<Global>(), &0);
- assert_eq!(*observation_count.borrow(), 1);
- assert!(cx.has_global::<Global>());
- assert_eq!(
- cx.update_global::<Global, _, _>(|global, _| {
- *global = 1;
- "Update Result"
- }),
- "Update Result"
- );
- assert_eq!(*observation_count.borrow(), 2);
- assert_eq!(cx.global::<Global>(), &1);
-
- drop(subscription);
- cx.update_global::<Global, _, _>(|global, _| {
- *global = 2;
- });
- assert_eq!(*observation_count.borrow(), 2);
-
- type OtherGlobal = f32;
-
- let observation_count = Rc::new(RefCell::new(0));
- cx.observe_global::<OtherGlobal, _>({
- let observation_count = observation_count.clone();
- move |_| {
- *observation_count.borrow_mut() += 1;
- }
- })
- .detach();
-
- assert_eq!(
- cx.update_default_global::<OtherGlobal, _, _>(|global, _| {
- assert_eq!(global, &0.0);
- *global = 2.0;
- "Default update result"
- }),
- "Default update result"
- );
- assert_eq!(cx.global::<OtherGlobal>(), &2.0);
- assert_eq!(*observation_count.borrow(), 1);
- }
-
- #[crate::test(self)]
- fn test_dropping_subscribers(cx: &mut TestAppContext) {
- struct Model;
-
- impl Entity for Model {
- type Event = ();
- }
-
- let window = cx.add_window(|_| TestView::default());
- let observing_view = window.add_view(cx, |_| TestView::default());
- let emitting_view = window.add_view(cx, |_| TestView::default());
- let observing_model = cx.add_model(|_| Model);
- let observed_model = cx.add_model(|_| Model);
-
- observing_view.update(cx, |_, cx| {
- cx.subscribe(&emitting_view, |_, _, _, _| {}).detach();
- cx.subscribe(&observed_model, |_, _, _, _| {}).detach();
- });
- observing_model.update(cx, |_, cx| {
- cx.subscribe(&observed_model, |_, _, _, _| {}).detach();
- });
-
- cx.update(|_| {
- drop(observing_view);
- drop(observing_model);
- });
-
- emitting_view.update(cx, |_, cx| cx.emit(Default::default()));
- observed_model.update(cx, |_, cx| cx.emit(()));
- }
-
- #[crate::test(self)]
- fn test_view_emit_before_subscribe_in_same_update_cycle(cx: &mut AppContext) {
- let window = cx.add_window::<TestView, _>(Default::default(), |cx| {
- drop(cx.subscribe(&cx.handle(), {
- move |this, _, _, _| this.events.push("dropped before flush".into())
- }));
- cx.subscribe(&cx.handle(), {
- move |this, _, _, _| this.events.push("before emit".into())
- })
- .detach();
- cx.emit("the event".into());
- cx.subscribe(&cx.handle(), {
- move |this, _, _, _| this.events.push("after emit".into())
- })
- .detach();
- TestView { events: Vec::new() }
- });
-
- window.read_root_with(cx, |view, _| assert_eq!(view.events, ["before emit"]));
- }
-
- #[crate::test(self)]
- fn test_observe_and_notify_from_view(cx: &mut TestAppContext) {
- #[derive(Default)]
- struct Model {
- state: String,
- }
-
- impl Entity for Model {
- type Event = ();
- }
-
- let window = cx.add_window(|_| TestView::default());
- let view = window.root(cx);
- let model = cx.add_model(|_| Model {
- state: "old-state".into(),
- });
-
- view.update(cx, |_, c| {
- c.observe(&model, |me, observed, cx| {
- me.events.push(observed.read(cx).state.clone())
- })
- .detach();
- });
-
- model.update(cx, |model, cx| {
- model.state = "new-state".into();
- cx.notify();
- });
- view.read_with(cx, |view, _| assert_eq!(view.events, ["new-state"]));
- }
-
- #[crate::test(self)]
- fn test_view_notify_before_observe_in_same_update_cycle(cx: &mut AppContext) {
- let window = cx.add_window::<TestView, _>(Default::default(), |cx| {
- drop(cx.observe(&cx.handle(), {
- move |this, _, _| this.events.push("dropped before flush".into())
- }));
- cx.observe(&cx.handle(), {
- move |this, _, _| this.events.push("before notify".into())
- })
- .detach();
- cx.notify();
- cx.observe(&cx.handle(), {
- move |this, _, _| this.events.push("after notify".into())
- })
- .detach();
- TestView { events: Vec::new() }
- });
-
- window.read_root_with(cx, |view, _| assert_eq!(view.events, ["before notify"]));
- }
-
- #[crate::test(self)]
- fn test_notify_and_drop_observe_subscription_in_same_update_cycle(cx: &mut TestAppContext) {
- struct Model;
- impl Entity for Model {
- type Event = ();
- }
-
- let model = cx.add_model(|_| Model);
- let window = cx.add_window(|_| TestView::default());
- let view = window.root(cx);
-
- view.update(cx, |_, cx| {
- model.update(cx, |_, cx| cx.notify());
- drop(cx.observe(&model, move |this, _, _| {
- this.events.push("model notified".into());
- }));
- model.update(cx, |_, cx| cx.notify());
- });
-
- for _ in 0..3 {
- model.update(cx, |_, cx| cx.notify());
- }
- view.read_with(cx, |view, _| assert_eq!(view.events, Vec::<&str>::new()));
- }
-
- #[crate::test(self)]
- fn test_dropping_observers(cx: &mut TestAppContext) {
- struct Model;
-
- impl Entity for Model {
- type Event = ();
- }
-
- let window = cx.add_window(|_| TestView::default());
- let observing_view = window.add_view(cx, |_| TestView::default());
- let observing_model = cx.add_model(|_| Model);
- let observed_model = cx.add_model(|_| Model);
-
- observing_view.update(cx, |_, cx| {
- cx.observe(&observed_model, |_, _, _| {}).detach();
- });
- observing_model.update(cx, |_, cx| {
- cx.observe(&observed_model, |_, _, _| {}).detach();
- });
-
- cx.update(|_| {
- drop(observing_view);
- drop(observing_model);
- });
-
- observed_model.update(cx, |_, cx| cx.notify());
- }
-
- #[crate::test(self)]
- fn test_dropping_subscriptions_during_callback(cx: &mut TestAppContext) {
- struct Model;
-
- impl Entity for Model {
- type Event = u64;
- }
-
- // Events
- let observing_model = cx.add_model(|_| Model);
- let observed_model = cx.add_model(|_| Model);
-
- let events = Rc::new(RefCell::new(Vec::new()));
-
- observing_model.update(cx, |_, cx| {
- let events = events.clone();
- let subscription = Rc::new(RefCell::new(None));
- *subscription.borrow_mut() = Some(cx.subscribe(&observed_model, {
- let subscription = subscription.clone();
- move |_, _, e, _| {
- subscription.borrow_mut().take();
- events.borrow_mut().push(*e);
- }
- }));
- });
-
- observed_model.update(cx, |_, cx| {
- cx.emit(1);
- cx.emit(2);
- });
-
- assert_eq!(*events.borrow(), [1]);
-
- // Global Events
- #[derive(Clone, Debug, Eq, PartialEq)]
- struct GlobalEvent(u64);
-
- let events = Rc::new(RefCell::new(Vec::new()));
-
- {
- let events = events.clone();
- let subscription = Rc::new(RefCell::new(None));
- *subscription.borrow_mut() = Some(cx.subscribe_global({
- let subscription = subscription.clone();
- move |e: &GlobalEvent, _| {
- subscription.borrow_mut().take();
- events.borrow_mut().push(e.clone());
- }
- }));
- }
-
- cx.update(|cx| {
- cx.emit_global(GlobalEvent(1));
- cx.emit_global(GlobalEvent(2));
- });
-
- assert_eq!(*events.borrow(), [GlobalEvent(1)]);
-
- // Model Observation
- let observing_model = cx.add_model(|_| Model);
- let observed_model = cx.add_model(|_| Model);
-
- let observation_count = Rc::new(RefCell::new(0));
-
- observing_model.update(cx, |_, cx| {
- let observation_count = observation_count.clone();
- let subscription = Rc::new(RefCell::new(None));
- *subscription.borrow_mut() = Some(cx.observe(&observed_model, {
- let subscription = subscription.clone();
- move |_, _, _| {
- subscription.borrow_mut().take();
- *observation_count.borrow_mut() += 1;
- }
- }));
- });
-
- observed_model.update(cx, |_, cx| {
- cx.notify();
- });
-
- observed_model.update(cx, |_, cx| {
- cx.notify();
- });
-
- assert_eq!(*observation_count.borrow(), 1);
-
- // View Observation
- struct View;
-
- impl Entity for View {
- type Event = ();
- }
-
- impl super::View for View {
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- Empty::new().into_any()
- }
-
- fn ui_name() -> &'static str {
- "View"
- }
- }
-
- let window = cx.add_window(|_| View);
- let observing_view = window.add_view(cx, |_| View);
- let observed_view = window.add_view(cx, |_| View);
-
- let observation_count = Rc::new(RefCell::new(0));
- observing_view.update(cx, |_, cx| {
- let observation_count = observation_count.clone();
- let subscription = Rc::new(RefCell::new(None));
- *subscription.borrow_mut() = Some(cx.observe(&observed_view, {
- let subscription = subscription.clone();
- move |_, _, _| {
- subscription.borrow_mut().take();
- *observation_count.borrow_mut() += 1;
- }
- }));
- });
-
- observed_view.update(cx, |_, cx| {
- cx.notify();
- });
-
- observed_view.update(cx, |_, cx| {
- cx.notify();
- });
-
- assert_eq!(*observation_count.borrow(), 1);
-
- // Global Observation
- let observation_count = Rc::new(RefCell::new(0));
- let subscription = Rc::new(RefCell::new(None));
- *subscription.borrow_mut() = Some(cx.observe_global::<(), _>({
- let observation_count = observation_count.clone();
- let subscription = subscription.clone();
- move |_| {
- subscription.borrow_mut().take();
- *observation_count.borrow_mut() += 1;
- }
- }));
-
- cx.update(|cx| {
- cx.default_global::<()>();
- cx.set_global(());
- });
- assert_eq!(*observation_count.borrow(), 1);
- }
-
- #[crate::test(self)]
- fn test_focus(cx: &mut TestAppContext) {
- struct View {
- name: String,
- events: Arc<Mutex<Vec<String>>>,
- child: Option<AnyViewHandle>,
- }
-
- impl Entity for View {
- type Event = ();
- }
-
- impl super::View for View {
- fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- self.child
- .as_ref()
- .map(|child| ChildView::new(child, cx).into_any())
- .unwrap_or(Empty::new().into_any())
- }
-
- fn ui_name() -> &'static str {
- "View"
- }
-
- fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
- if cx.handle().id() == focused.id() {
- self.events.lock().push(format!("{} focused", &self.name));
- }
- }
-
- fn focus_out(&mut self, blurred: AnyViewHandle, cx: &mut ViewContext<Self>) {
- if cx.handle().id() == blurred.id() {
- self.events.lock().push(format!("{} blurred", &self.name));
- }
- }
- }
-
- let view_events: Arc<Mutex<Vec<String>>> = Default::default();
- let window = cx.add_window(|_| View {
- events: view_events.clone(),
- name: "view 1".to_string(),
- child: None,
- });
- let view_1 = window.root(cx);
- let view_2 = window.update(cx, |cx| {
- let view_2 = cx.add_view(|_| View {
- events: view_events.clone(),
- name: "view 2".to_string(),
- child: None,
- });
- view_1.update(cx, |view_1, cx| {
- view_1.child = Some(view_2.clone().into_any());
- cx.notify();
- });
- view_2
- });
-
- let observed_events: Arc<Mutex<Vec<String>>> = Default::default();
- view_1.update(cx, |_, cx| {
- cx.observe_focus(&view_2, {
- let observed_events = observed_events.clone();
- move |this, view, focused, cx| {
- let label = if focused { "focus" } else { "blur" };
- observed_events.lock().push(format!(
- "{} observed {}'s {}",
- this.name,
- view.read(cx).name,
- label
- ))
- }
- })
- .detach();
- });
- view_2.update(cx, |_, cx| {
- cx.observe_focus(&view_1, {
- let observed_events = observed_events.clone();
- move |this, view, focused, cx| {
- let label = if focused { "focus" } else { "blur" };
- observed_events.lock().push(format!(
- "{} observed {}'s {}",
- this.name,
- view.read(cx).name,
- label
- ))
- }
- })
- .detach();
- });
- assert_eq!(mem::take(&mut *view_events.lock()), ["view 1 focused"]);
- assert_eq!(mem::take(&mut *observed_events.lock()), Vec::<&str>::new());
-
- view_1.update(cx, |_, cx| {
- // Ensure only the last focus event is honored.
- cx.focus(&view_2);
- cx.focus(&view_1);
- cx.focus(&view_2);
- });
-
- assert_eq!(
- mem::take(&mut *view_events.lock()),
- ["view 1 blurred", "view 2 focused"],
- );
- assert_eq!(
- mem::take(&mut *observed_events.lock()),
- [
- "view 2 observed view 1's blur",
- "view 1 observed view 2's focus"
- ]
- );
-
- view_1.update(cx, |_, cx| cx.focus(&view_1));
- assert_eq!(
- mem::take(&mut *view_events.lock()),
- ["view 2 blurred", "view 1 focused"],
- );
- assert_eq!(
- mem::take(&mut *observed_events.lock()),
- [
- "view 1 observed view 2's blur",
- "view 2 observed view 1's focus"
- ]
- );
-
- view_1.update(cx, |_, cx| cx.focus(&view_2));
- assert_eq!(
- mem::take(&mut *view_events.lock()),
- ["view 1 blurred", "view 2 focused"],
- );
- assert_eq!(
- mem::take(&mut *observed_events.lock()),
- [
- "view 2 observed view 1's blur",
- "view 1 observed view 2's focus"
- ]
- );
-
- println!("=====================");
- view_1.update(cx, |view, _| {
- drop(view_2);
- view.child = None;
- });
- assert_eq!(mem::take(&mut *view_events.lock()), ["view 1 focused"]);
- assert_eq!(mem::take(&mut *observed_events.lock()), Vec::<&str>::new());
- }
-
- #[crate::test(self)]
- fn test_deserialize_actions(cx: &mut AppContext) {
- #[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
- pub struct ComplexAction {
- arg: String,
- count: usize,
- }
-
- actions!(test::something, [SimpleAction]);
- impl_actions!(test::something, [ComplexAction]);
-
- cx.add_global_action(move |_: &SimpleAction, _: &mut AppContext| {});
- cx.add_global_action(move |_: &ComplexAction, _: &mut AppContext| {});
-
- let action1 = cx
- .deserialize_action(
- "test::something::ComplexAction",
- Some(serde_json::from_str(r#"{"arg": "a", "count": 5}"#).unwrap()),
- )
- .unwrap();
- let action2 = cx
- .deserialize_action("test::something::SimpleAction", None)
- .unwrap();
- assert_eq!(
- action1.as_any().downcast_ref::<ComplexAction>().unwrap(),
- &ComplexAction {
- arg: "a".to_string(),
- count: 5,
- }
- );
- assert_eq!(
- action2.as_any().downcast_ref::<SimpleAction>().unwrap(),
- &SimpleAction
- );
- }
-
- #[crate::test(self)]
- fn test_dispatch_action(cx: &mut TestAppContext) {
- struct ViewA {
- id: usize,
- child: Option<AnyViewHandle>,
- }
-
- impl Entity for ViewA {
- type Event = ();
- }
-
- impl View for ViewA {
- fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- self.child
- .as_ref()
- .map(|child| ChildView::new(child, cx).into_any())
- .unwrap_or(Empty::new().into_any())
- }
-
- fn ui_name() -> &'static str {
- "View"
- }
- }
-
- struct ViewB {
- id: usize,
- child: Option<AnyViewHandle>,
- }
-
- impl Entity for ViewB {
- type Event = ();
- }
-
- impl View for ViewB {
- fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- self.child
- .as_ref()
- .map(|child| ChildView::new(child, cx).into_any())
- .unwrap_or(Empty::new().into_any())
- }
-
- fn ui_name() -> &'static str {
- "View"
- }
- }
-
- #[derive(Clone, Default, Deserialize, PartialEq)]
- pub struct Action(pub String);
-
- impl_actions!(test, [Action]);
-
- let actions = Rc::new(RefCell::new(Vec::new()));
- let observed_actions = Rc::new(RefCell::new(Vec::new()));
-
- cx.update(|cx| {
- cx.add_global_action({
- let actions = actions.clone();
- move |_: &Action, _: &mut AppContext| {
- actions.borrow_mut().push("global".to_string());
- }
- });
-
- cx.add_action({
- let actions = actions.clone();
- move |view: &mut ViewA, action: &Action, cx| {
- assert_eq!(action.0, "bar");
- cx.propagate_action();
- actions.borrow_mut().push(format!("{} a", view.id));
- }
- });
-
- cx.add_action({
- let actions = actions.clone();
- move |view: &mut ViewA, _: &Action, cx| {
- if view.id != 1 {
- cx.add_view(|cx| {
- cx.propagate_action(); // Still works on a nested ViewContext
- ViewB { id: 5, child: None }
- });
- }
- actions.borrow_mut().push(format!("{} b", view.id));
- }
- });
-
- cx.add_action({
- let actions = actions.clone();
- move |view: &mut ViewB, _: &Action, cx| {
- cx.propagate_action();
- actions.borrow_mut().push(format!("{} c", view.id));
- }
- });
-
- cx.add_action({
- let actions = actions.clone();
- move |view: &mut ViewB, _: &Action, cx| {
- cx.propagate_action();
- actions.borrow_mut().push(format!("{} d", view.id));
- }
- });
-
- cx.capture_action({
- let actions = actions.clone();
- move |view: &mut ViewA, _: &Action, cx| {
- cx.propagate_action();
- actions.borrow_mut().push(format!("{} capture", view.id));
- }
- });
-
- cx.observe_actions({
- let observed_actions = observed_actions.clone();
- move |action_id, _| observed_actions.borrow_mut().push(action_id)
- })
- .detach();
- });
-
- let window = cx.add_window(|_| ViewA { id: 1, child: None });
- let view_1 = window.root(cx);
- let view_2 = window.update(cx, |cx| {
- let child = cx.add_view(|_| ViewB { id: 2, child: None });
- view_1.update(cx, |view, cx| {
- view.child = Some(child.clone().into_any());
- cx.notify();
- });
- child
- });
- let view_3 = window.update(cx, |cx| {
- let child = cx.add_view(|_| ViewA { id: 3, child: None });
- view_2.update(cx, |view, cx| {
- view.child = Some(child.clone().into_any());
- cx.notify();
- });
- child
- });
- let view_4 = window.update(cx, |cx| {
- let child = cx.add_view(|_| ViewB { id: 4, child: None });
- view_3.update(cx, |view, cx| {
- view.child = Some(child.clone().into_any());
- cx.notify();
- });
- child
- });
-
- window.update(cx, |cx| {
- cx.dispatch_action(Some(view_4.id()), &Action("bar".to_string()))
- });
-
- assert_eq!(
- *actions.borrow(),
- vec![
- "1 capture",
- "3 capture",
- "4 d",
- "4 c",
- "3 b",
- "3 a",
- "2 d",
- "2 c",
- "1 b"
- ]
- );
- assert_eq!(*observed_actions.borrow(), [Action::default().id()]);
-
- // Remove view_1, which doesn't propagate the action
-
- let window = cx.add_window(|_| ViewB { id: 2, child: None });
- let view_2 = window.root(cx);
- let view_3 = window.update(cx, |cx| {
- let child = cx.add_view(|_| ViewA { id: 3, child: None });
- view_2.update(cx, |view, cx| {
- view.child = Some(child.clone().into_any());
- cx.notify();
- });
- child
- });
- let view_4 = window.update(cx, |cx| {
- let child = cx.add_view(|_| ViewB { id: 4, child: None });
- view_3.update(cx, |view, cx| {
- view.child = Some(child.clone().into_any());
- cx.notify();
- });
- child
- });
-
- actions.borrow_mut().clear();
- window.update(cx, |cx| {
- cx.dispatch_action(Some(view_4.id()), &Action("bar".to_string()))
- });
-
- assert_eq!(
- *actions.borrow(),
- vec![
- "3 capture",
- "4 d",
- "4 c",
- "3 b",
- "3 a",
- "2 d",
- "2 c",
- "global"
- ]
- );
- assert_eq!(
- *observed_actions.borrow(),
- [Action::default().id(), Action::default().id()]
- );
- }
-
- #[crate::test(self)]
- fn test_dispatch_keystroke(cx: &mut AppContext) {
- #[derive(Clone, Deserialize, PartialEq)]
- pub struct Action(String);
-
- impl_actions!(test, [Action]);
-
- struct View {
- id: usize,
- keymap_context: KeymapContext,
- child: Option<AnyViewHandle>,
- }
-
- impl Entity for View {
- type Event = ();
- }
-
- impl super::View for View {
- fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- self.child
- .as_ref()
- .map(|child| ChildView::new(child, cx).into_any())
- .unwrap_or(Empty::new().into_any())
- }
-
- fn ui_name() -> &'static str {
- "View"
- }
-
- fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
- *keymap = self.keymap_context.clone();
- }
- }
-
- impl View {
- fn new(id: usize) -> Self {
- View {
- id,
- keymap_context: KeymapContext::default(),
- child: None,
- }
- }
- }
-
- let mut view_1 = View::new(1);
- let mut view_2 = View::new(2);
- let mut view_3 = View::new(3);
- view_1.keymap_context.add_identifier("a");
- view_2.keymap_context.add_identifier("a");
- view_2.keymap_context.add_identifier("b");
- view_3.keymap_context.add_identifier("a");
- view_3.keymap_context.add_identifier("b");
- view_3.keymap_context.add_identifier("c");
-
- let window = cx.add_window(Default::default(), |cx| {
- let view_2 = cx.add_view(|cx| {
- let view_3 = cx.add_view(|cx| {
- cx.focus_self();
- view_3
- });
- view_2.child = Some(view_3.into_any());
- view_2
- });
- view_1.child = Some(view_2.into_any());
- view_1
- });
-
- // This binding only dispatches an action on view 2 because that view will have
- // "a" and "b" in its context, but not "c".
- cx.add_bindings(vec![Binding::new(
- "a",
- Action("a".to_string()),
- Some("a && b && !c"),
- )]);
-
- cx.add_bindings(vec![Binding::new("b", Action("b".to_string()), None)]);
-
- // This binding only dispatches an action on views 2 and 3, because they have
- // a parent view with a in its context
- cx.add_bindings(vec![Binding::new(
- "c",
- Action("c".to_string()),
- Some("b > c"),
- )]);
-
- // This binding only dispatches an action on view 2, because they have
- // a parent view with a in its context
- cx.add_bindings(vec![Binding::new(
- "d",
- Action("d".to_string()),
- Some("a && !b > b"),
- )]);
-
- let actions = Rc::new(RefCell::new(Vec::new()));
- cx.add_action({
- let actions = actions.clone();
- move |view: &mut View, action: &Action, cx| {
- actions
- .borrow_mut()
- .push(format!("{} {}", view.id, action.0));
-
- if action.0 == "b" {
- cx.propagate_action();
- }
- }
- });
-
- cx.add_global_action({
- let actions = actions.clone();
- move |action: &Action, _| {
- actions.borrow_mut().push(format!("global {}", action.0));
- }
- });
-
- window.update(cx, |cx| {
- cx.dispatch_keystroke(&Keystroke::parse("a").unwrap())
- });
- assert_eq!(&*actions.borrow(), &["2 a"]);
- actions.borrow_mut().clear();
-
- window.update(cx, |cx| {
- cx.dispatch_keystroke(&Keystroke::parse("b").unwrap());
- });
-
- assert_eq!(&*actions.borrow(), &["3 b", "2 b", "1 b", "global b"]);
- actions.borrow_mut().clear();
-
- window.update(cx, |cx| {
- cx.dispatch_keystroke(&Keystroke::parse("c").unwrap());
- });
- assert_eq!(&*actions.borrow(), &["3 c"]);
- actions.borrow_mut().clear();
-
- window.update(cx, |cx| {
- cx.dispatch_keystroke(&Keystroke::parse("d").unwrap());
- });
- assert_eq!(&*actions.borrow(), &["2 d"]);
- actions.borrow_mut().clear();
- }
-
- #[crate::test(self)]
- fn test_keystrokes_for_action(cx: &mut TestAppContext) {
- actions!(test, [Action1, Action2, Action3, GlobalAction]);
-
- struct View1 {
- child: ViewHandle<View2>,
- }
- struct View2 {}
-
- impl Entity for View1 {
- type Event = ();
- }
- impl Entity for View2 {
- type Event = ();
- }
-
- impl super::View for View1 {
- fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- ChildView::new(&self.child, cx).into_any()
- }
- fn ui_name() -> &'static str {
- "View1"
- }
- }
- impl super::View for View2 {
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- Empty::new().into_any()
- }
- fn ui_name() -> &'static str {
- "View2"
- }
- }
-
- let window = cx.add_window(|cx| {
- let view_2 = cx.add_view(|cx| {
- cx.focus_self();
- View2 {}
- });
- View1 { child: view_2 }
- });
- let view_1 = window.root(cx);
- let view_2 = view_1.read_with(cx, |view, _| view.child.clone());
-
- cx.update(|cx| {
- cx.add_action(|_: &mut View1, _: &Action1, _cx| {});
- cx.add_action(|_: &mut View1, _: &Action3, _cx| {});
- cx.add_action(|_: &mut View2, _: &Action2, _cx| {});
- cx.add_global_action(|_: &GlobalAction, _| {});
- cx.add_bindings(vec![
- Binding::new("a", Action1, Some("View1")),
- Binding::new("b", Action2, Some("View1 > View2")),
- Binding::new("c", Action3, Some("View2")),
- Binding::new("d", GlobalAction, Some("View3")), // View 3 does not exist
- ]);
- });
-
- let view_1_id = view_1.id();
- view_1.update(cx, |_, cx| {
- view_2.update(cx, |_, cx| {
- // Sanity check
- assert_eq!(
- cx.keystrokes_for_action(view_1_id, &Action1)
- .unwrap()
- .as_slice(),
- &[Keystroke::parse("a").unwrap()]
- );
- assert_eq!(
- cx.keystrokes_for_action(view_2.id(), &Action2)
- .unwrap()
- .as_slice(),
- &[Keystroke::parse("b").unwrap()]
- );
- assert_eq!(cx.keystrokes_for_action(view_1.id(), &Action3), None);
- assert_eq!(
- cx.keystrokes_for_action(view_2.id(), &Action3)
- .unwrap()
- .as_slice(),
- &[Keystroke::parse("c").unwrap()]
- );
-
- // The 'a' keystroke propagates up the view tree from view_2
- // to view_1. The action, Action1, is handled by view_1.
- assert_eq!(
- cx.keystrokes_for_action(view_2.id(), &Action1)
- .unwrap()
- .as_slice(),
- &[Keystroke::parse("a").unwrap()]
- );
-
- // Actions that are handled below the current view don't have bindings
- assert_eq!(cx.keystrokes_for_action(view_1_id, &Action2), None);
-
- // Actions that are handled in other branches of the tree should not have a binding
- assert_eq!(cx.keystrokes_for_action(view_2.id(), &GlobalAction), None);
- });
- });
-
- // Check that global actions do not have a binding, even if a binding does exist in another view
- assert_eq!(
- &available_actions(window.into(), view_1.id(), cx),
- &[
- ("test::Action1", vec![Keystroke::parse("a").unwrap()]),
- ("test::Action3", vec![]),
- ("test::GlobalAction", vec![]),
- ],
- );
-
- // Check that view 1 actions and bindings are available even when called from view 2
- assert_eq!(
- &available_actions(window.into(), view_2.id(), cx),
- &[
- ("test::Action1", vec![Keystroke::parse("a").unwrap()]),
- ("test::Action2", vec![Keystroke::parse("b").unwrap()]),
- ("test::Action3", vec![Keystroke::parse("c").unwrap()]),
- ("test::GlobalAction", vec![]),
- ],
- );
-
- // Produces a list of actions and key bindings
- fn available_actions(
- window: AnyWindowHandle,
- view_id: usize,
- cx: &TestAppContext,
- ) -> Vec<(&'static str, Vec<Keystroke>)> {
- cx.available_actions(window.into(), view_id)
- .into_iter()
- .map(|(action_name, _, bindings)| {
- (
- action_name,
- bindings
- .iter()
- .map(|binding| binding.keystrokes()[0].clone())
- .collect::<Vec<_>>(),
- )
- })
- .sorted_by(|(name1, _), (name2, _)| name1.cmp(name2))
- .collect()
- }
- }
-
- #[crate::test(self)]
- fn test_keystrokes_for_action_with_data(cx: &mut TestAppContext) {
- #[derive(Clone, Debug, Deserialize, PartialEq)]
- struct ActionWithArg {
- #[serde(default)]
- arg: bool,
- }
-
- struct View;
- impl super::Entity for View {
- type Event = ();
- }
- impl super::View for View {
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- Empty::new().into_any()
- }
- fn ui_name() -> &'static str {
- "View"
- }
- }
-
- impl_actions!(test, [ActionWithArg]);
-
- let window = cx.add_window(|_| View);
- let view = window.root(cx);
- cx.update(|cx| {
- cx.add_global_action(|_: &ActionWithArg, _| {});
- cx.add_bindings(vec![
- Binding::new("a", ActionWithArg { arg: false }, None),
- Binding::new("shift-a", ActionWithArg { arg: true }, None),
- ]);
- });
-
- let actions = cx.available_actions(window.into(), view.id());
- assert_eq!(
- actions[0].1.as_any().downcast_ref::<ActionWithArg>(),
- Some(&ActionWithArg { arg: false })
- );
- assert_eq!(
- actions[0]
- .2
- .iter()
- .map(|b| b.keystrokes()[0].clone())
- .collect::<Vec<_>>(),
- vec![Keystroke::parse("a").unwrap()],
- );
- }
-
- #[crate::test(self)]
- async fn test_model_condition(cx: &mut TestAppContext) {
- struct Counter(usize);
-
- impl super::Entity for Counter {
- type Event = ();
- }
-
- impl Counter {
- fn inc(&mut self, cx: &mut ModelContext<Self>) {
- self.0 += 1;
- cx.notify();
- }
- }
-
- let model = cx.add_model(|_| Counter(0));
-
- let condition1 = model.condition(cx, |model, _| model.0 == 2);
- let condition2 = model.condition(cx, |model, _| model.0 == 3);
- smol::pin!(condition1, condition2);
-
- model.update(cx, |model, cx| model.inc(cx));
- assert_eq!(poll_once(&mut condition1).await, None);
- assert_eq!(poll_once(&mut condition2).await, None);
-
- model.update(cx, |model, cx| model.inc(cx));
- assert_eq!(poll_once(&mut condition1).await, Some(()));
- assert_eq!(poll_once(&mut condition2).await, None);
-
- model.update(cx, |model, cx| model.inc(cx));
- assert_eq!(poll_once(&mut condition2).await, Some(()));
-
- model.update(cx, |_, cx| cx.notify());
- }
-
- #[crate::test(self)]
- #[should_panic]
- async fn test_model_condition_timeout(cx: &mut TestAppContext) {
- struct Model;
-
- impl super::Entity for Model {
- type Event = ();
- }
-
- let model = cx.add_model(|_| Model);
- model.condition(cx, |_, _| false).await;
- }
-
- #[crate::test(self)]
- #[should_panic(expected = "model dropped with pending condition")]
- async fn test_model_condition_panic_on_drop(cx: &mut TestAppContext) {
- struct Model;
-
- impl super::Entity for Model {
- type Event = ();
- }
-
- let model = cx.add_model(|_| Model);
- let condition = model.condition(cx, |_, _| false);
- cx.update(|_| drop(model));
- condition.await;
- }
-
- #[crate::test(self)]
- async fn test_view_condition(cx: &mut TestAppContext) {
- struct Counter(usize);
-
- impl super::Entity for Counter {
- type Event = ();
- }
-
- impl super::View for Counter {
- fn ui_name() -> &'static str {
- "test view"
- }
-
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- Empty::new().into_any()
- }
- }
-
- impl Counter {
- fn inc(&mut self, cx: &mut ViewContext<Self>) {
- self.0 += 1;
- cx.notify();
- }
- }
-
- let window = cx.add_window(|_| Counter(0));
- let view = window.root(cx);
-
- let condition1 = view.condition(cx, |view, _| view.0 == 2);
- let condition2 = view.condition(cx, |view, _| view.0 == 3);
- smol::pin!(condition1, condition2);
-
- view.update(cx, |view, cx| view.inc(cx));
- assert_eq!(poll_once(&mut condition1).await, None);
- assert_eq!(poll_once(&mut condition2).await, None);
-
- view.update(cx, |view, cx| view.inc(cx));
- assert_eq!(poll_once(&mut condition1).await, Some(()));
- assert_eq!(poll_once(&mut condition2).await, None);
-
- view.update(cx, |view, cx| view.inc(cx));
- assert_eq!(poll_once(&mut condition2).await, Some(()));
- view.update(cx, |_, cx| cx.notify());
- }
-
- #[crate::test(self)]
- #[should_panic]
- async fn test_view_condition_timeout(cx: &mut TestAppContext) {
- let window = cx.add_window(|_| TestView::default());
- window.root(cx).condition(cx, |_, _| false).await;
- }
-
- #[crate::test(self)]
- #[should_panic(expected = "view dropped with pending condition")]
- async fn test_view_condition_panic_on_drop(cx: &mut TestAppContext) {
- let window = cx.add_window(|_| TestView::default());
- let view = window.add_view(cx, |_| TestView::default());
-
- let condition = view.condition(cx, |_, _| false);
- cx.update(|_| drop(view));
- condition.await;
- }
-
- #[crate::test(self)]
- fn test_refresh_windows(cx: &mut TestAppContext) {
- struct View(usize);
-
- impl super::Entity for View {
- type Event = ();
- }
-
- impl super::View for View {
- fn ui_name() -> &'static str {
- "test view"
- }
-
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- Empty::new().into_any_named(format!("render count: {}", post_inc(&mut self.0)))
- }
- }
-
- let window = cx.add_window(|_| View(0));
- let root_view = window.root(cx);
- window.update(cx, |cx| {
- assert_eq!(
- cx.window.rendered_views[&root_view.id()].name(),
- Some("render count: 0")
- );
- });
-
- let view = window.update(cx, |cx| {
- cx.refresh_windows();
- cx.add_view(|_| View(0))
- });
-
- window.update(cx, |cx| {
- assert_eq!(
- cx.window.rendered_views[&root_view.id()].name(),
- Some("render count: 1")
- );
- assert_eq!(
- cx.window.rendered_views[&view.id()].name(),
- Some("render count: 0")
- );
- });
-
- cx.update(|cx| cx.refresh_windows());
-
- window.update(cx, |cx| {
- assert_eq!(
- cx.window.rendered_views[&root_view.id()].name(),
- Some("render count: 2")
- );
- assert_eq!(
- cx.window.rendered_views[&view.id()].name(),
- Some("render count: 1")
- );
- });
-
- cx.update(|cx| {
- cx.refresh_windows();
- drop(view);
- });
-
- window.update(cx, |cx| {
- assert_eq!(
- cx.window.rendered_views[&root_view.id()].name(),
- Some("render count: 3")
- );
- assert_eq!(cx.window.rendered_views.len(), 1);
- });
- }
-
- #[crate::test(self)]
- async fn test_labeled_tasks(cx: &mut TestAppContext) {
- assert_eq!(None, cx.update(|cx| cx.active_labeled_tasks().next()));
- let (mut sender, mut receiver) = postage::oneshot::channel::<()>();
- let task = cx
- .update(|cx| cx.spawn_labeled("Test Label", |_| async move { receiver.recv().await }));
-
- assert_eq!(
- Some("Test Label"),
- cx.update(|cx| cx.active_labeled_tasks().next())
- );
- sender
- .send(())
- .await
- .expect("Could not send message to complete task");
- task.await;
-
- assert_eq!(None, cx.update(|cx| cx.active_labeled_tasks().next()));
- }
-
- #[crate::test(self)]
- async fn test_window_activation(cx: &mut TestAppContext) {
- struct View(&'static str);
-
- impl super::Entity for View {
- type Event = ();
- }
-
- impl super::View for View {
- fn ui_name() -> &'static str {
- "test view"
- }
-
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- Empty::new().into_any()
- }
- }
-
- let events = Rc::new(RefCell::new(Vec::new()));
- let window_1 = cx.add_window(|cx: &mut ViewContext<View>| {
- cx.observe_window_activation({
- let events = events.clone();
- move |this, active, _| events.borrow_mut().push((this.0, active))
- })
- .detach();
- View("window 1")
- });
- assert_eq!(mem::take(&mut *events.borrow_mut()), [("window 1", true)]);
-
- let window_2 = cx.add_window(|cx: &mut ViewContext<View>| {
- cx.observe_window_activation({
- let events = events.clone();
- move |this, active, _| events.borrow_mut().push((this.0, active))
- })
- .detach();
- View("window 2")
- });
- assert_eq!(
- mem::take(&mut *events.borrow_mut()),
- [("window 1", false), ("window 2", true)]
- );
-
- let window_3 = cx.add_window(|cx: &mut ViewContext<View>| {
- cx.observe_window_activation({
- let events = events.clone();
- move |this, active, _| events.borrow_mut().push((this.0, active))
- })
- .detach();
- View("window 3")
- });
- assert_eq!(
- mem::take(&mut *events.borrow_mut()),
- [("window 2", false), ("window 3", true)]
- );
-
- window_2.simulate_activation(cx);
- assert_eq!(
- mem::take(&mut *events.borrow_mut()),
- [("window 3", false), ("window 2", true)]
- );
-
- window_1.simulate_activation(cx);
- assert_eq!(
- mem::take(&mut *events.borrow_mut()),
- [("window 2", false), ("window 1", true)]
- );
-
- window_3.simulate_activation(cx);
- assert_eq!(
- mem::take(&mut *events.borrow_mut()),
- [("window 1", false), ("window 3", true)]
- );
-
- window_3.simulate_activation(cx);
- assert_eq!(mem::take(&mut *events.borrow_mut()), []);
- }
-
- #[crate::test(self)]
- fn test_child_view(cx: &mut TestAppContext) {
- struct Child {
- rendered: Rc<Cell<bool>>,
- dropped: Rc<Cell<bool>>,
- }
-
- impl super::Entity for Child {
- type Event = ();
- }
-
- impl super::View for Child {
- fn ui_name() -> &'static str {
- "child view"
- }
-
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- self.rendered.set(true);
- Empty::new().into_any()
- }
- }
-
- impl Drop for Child {
- fn drop(&mut self) {
- self.dropped.set(true);
- }
- }
-
- struct Parent {
- child: Option<ViewHandle<Child>>,
- }
-
- impl super::Entity for Parent {
- type Event = ();
- }
-
- impl super::View for Parent {
- fn ui_name() -> &'static str {
- "parent view"
- }
-
- fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- if let Some(child) = self.child.as_ref() {
- ChildView::new(child, cx).into_any()
- } else {
- Empty::new().into_any()
- }
- }
- }
-
- let child_rendered = Rc::new(Cell::new(false));
- let child_dropped = Rc::new(Cell::new(false));
- let window = cx.add_window(|cx| Parent {
- child: Some(cx.add_view(|_| Child {
- rendered: child_rendered.clone(),
- dropped: child_dropped.clone(),
- })),
- });
- let root_view = window.root(cx);
- assert!(child_rendered.take());
- assert!(!child_dropped.take());
-
- root_view.update(cx, |view, cx| {
- view.child.take();
- cx.notify();
- });
- assert!(!child_rendered.take());
- assert!(child_dropped.take());
- }
-
- #[derive(Default)]
- struct TestView {
- events: Vec<String>,
- }
-
- impl Entity for TestView {
- type Event = String;
- }
-
- impl View for TestView {
- fn ui_name() -> &'static str {
- "TestView"
- }
-
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- Empty::new().into_any()
- }
- }
+#[derive(Debug)]
+pub struct KeystrokeEvent {
+ pub keystroke: Keystroke,
+ pub action: Option<Box<dyn Action>>,
}
@@ -1,120 +0,0 @@
-use std::any::{Any, TypeId};
-
-use crate::TypeTag;
-
-pub trait Action: 'static {
- fn id(&self) -> TypeId;
- fn namespace(&self) -> &'static str;
- fn name(&self) -> &'static str;
- fn as_any(&self) -> &dyn Any;
- fn type_tag(&self) -> TypeTag;
- fn boxed_clone(&self) -> Box<dyn Action>;
- fn eq(&self, other: &dyn Action) -> bool;
-
- fn qualified_name() -> &'static str
- where
- Self: Sized;
- fn from_json_str(json: serde_json::Value) -> anyhow::Result<Box<dyn Action>>
- where
- Self: Sized;
-}
-
-impl std::fmt::Debug for dyn Action {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.debug_struct("dyn Action")
- .field("namespace", &self.namespace())
- .field("name", &self.name())
- .finish()
- }
-}
-/// Define a set of unit struct types that all implement the `Action` trait.
-///
-/// The first argument is a namespace that will be associated with each of
-/// the given action types, to ensure that they have globally unique
-/// qualified names for use in keymap files.
-#[macro_export]
-macro_rules! actions {
- ($namespace:path, [ $($name:ident),* $(,)? ]) => {
- $(
- #[derive(Clone, Debug, Default, PartialEq, Eq)]
- pub struct $name;
- $crate::__impl_action! {
- $namespace,
- $name,
- fn from_json_str(_: $crate::serde_json::Value) -> $crate::anyhow::Result<Box<dyn $crate::Action>> {
- Ok(Box::new(Self))
- }
- }
- )*
- };
-}
-
-/// Implement the `Action` trait for a set of existing types.
-///
-/// The first argument is a namespace that will be associated with each of
-/// the given action types, to ensure that they have globally unique
-/// qualified names for use in keymap files.
-#[macro_export]
-macro_rules! impl_actions {
- ($namespace:path, [ $($name:ident),* $(,)? ]) => {
- $(
- $crate::__impl_action! {
- $namespace,
- $name,
- fn from_json_str(json: $crate::serde_json::Value) -> $crate::anyhow::Result<Box<dyn $crate::Action>> {
- Ok(Box::new($crate::serde_json::from_value::<Self>(json)?))
- }
- }
- )*
- };
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! __impl_action {
- ($namespace:path, $name:ident, $from_json_fn:item) => {
- impl $crate::action::Action for $name {
- fn namespace(&self) -> &'static str {
- stringify!($namespace)
- }
-
- fn name(&self) -> &'static str {
- stringify!($name)
- }
-
- fn qualified_name() -> &'static str {
- concat!(
- stringify!($namespace),
- "::",
- stringify!($name),
- )
- }
-
- fn id(&self) -> std::any::TypeId {
- std::any::TypeId::of::<$name>()
- }
-
- fn as_any(&self) -> &dyn std::any::Any {
- self
- }
-
- fn boxed_clone(&self) -> Box<dyn $crate::Action> {
- Box::new(self.clone())
- }
-
- fn eq(&self, other: &dyn $crate::Action) -> bool {
- if let Some(other) = other.as_any().downcast_ref::<Self>() {
- self == other
- } else {
- false
- }
- }
-
- fn type_tag(&self) -> $crate::TypeTag {
- $crate::TypeTag::new::<Self>()
- }
-
- $from_json_fn
- }
- };
-}
@@ -1,164 +0,0 @@
-use collections::{BTreeMap, HashMap, HashSet};
-use parking_lot::Mutex;
-use std::sync::Arc;
-use std::{hash::Hash, sync::Weak};
-
-pub struct CallbackCollection<K: Clone + Hash + Eq, F> {
- internal: Arc<Mutex<Mapping<K, F>>>,
-}
-
-pub struct Subscription<K: Clone + Hash + Eq, F> {
- key: K,
- id: usize,
- mapping: Option<Weak<Mutex<Mapping<K, F>>>>,
-}
-
-struct Mapping<K, F> {
- callbacks: HashMap<K, BTreeMap<usize, F>>,
- dropped_subscriptions: HashMap<K, HashSet<usize>>,
-}
-
-impl<K: Hash + Eq, F> Mapping<K, F> {
- fn clear_dropped_state(&mut self, key: &K, subscription_id: usize) -> bool {
- if let Some(subscriptions) = self.dropped_subscriptions.get_mut(&key) {
- subscriptions.remove(&subscription_id)
- } else {
- false
- }
- }
-}
-
-impl<K, F> Default for Mapping<K, F> {
- fn default() -> Self {
- Self {
- callbacks: Default::default(),
- dropped_subscriptions: Default::default(),
- }
- }
-}
-
-impl<K: Clone + Hash + Eq, F> Clone for CallbackCollection<K, F> {
- fn clone(&self) -> Self {
- Self {
- internal: self.internal.clone(),
- }
- }
-}
-
-impl<K: Clone + Hash + Eq + Copy, F> Default for CallbackCollection<K, F> {
- fn default() -> Self {
- CallbackCollection {
- internal: Arc::new(Mutex::new(Default::default())),
- }
- }
-}
-
-impl<K: Clone + Hash + Eq + Copy, F> CallbackCollection<K, F> {
- #[cfg(test)]
- pub fn is_empty(&self) -> bool {
- self.internal.lock().callbacks.is_empty()
- }
-
- pub fn subscribe(&mut self, key: K, subscription_id: usize) -> Subscription<K, F> {
- Subscription {
- key,
- id: subscription_id,
- mapping: Some(Arc::downgrade(&self.internal)),
- }
- }
-
- pub fn add_callback(&mut self, key: K, subscription_id: usize, callback: F) {
- let mut this = self.internal.lock();
-
- // If this callback's subscription was dropped before the callback was
- // added, then just drop the callback.
- if this.clear_dropped_state(&key, subscription_id) {
- return;
- }
-
- this.callbacks
- .entry(key)
- .or_default()
- .insert(subscription_id, callback);
- }
-
- pub fn remove(&mut self, key: K) {
- // Drop these callbacks after releasing the lock, in case one of them
- // owns a subscription to this callback collection.
- let mut this = self.internal.lock();
- let callbacks = this.callbacks.remove(&key);
- this.dropped_subscriptions.remove(&key);
- drop(this);
- drop(callbacks);
- }
-
- pub fn emit<C>(&mut self, key: K, mut call_callback: C)
- where
- C: FnMut(&mut F) -> bool,
- {
- let callbacks = self.internal.lock().callbacks.remove(&key);
- if let Some(callbacks) = callbacks {
- for (subscription_id, mut callback) in callbacks {
- // If this callback's subscription was dropped while invoking an
- // earlier callback, then just drop the callback.
- let mut this = self.internal.lock();
- if this.clear_dropped_state(&key, subscription_id) {
- continue;
- }
-
- drop(this);
- let alive = call_callback(&mut callback);
-
- // If this callback's subscription was dropped while invoking the callback
- // itself, or if the callback returns false, then just drop the callback.
- let mut this = self.internal.lock();
- if this.clear_dropped_state(&key, subscription_id) || !alive {
- continue;
- }
-
- this.callbacks
- .entry(key)
- .or_default()
- .insert(subscription_id, callback);
- }
- }
- }
-}
-
-impl<K: Clone + Hash + Eq, F> Subscription<K, F> {
- pub fn id(&self) -> usize {
- self.id
- }
-
- pub fn detach(&mut self) {
- self.mapping.take();
- }
-}
-
-impl<K: Clone + Hash + Eq, F> Drop for Subscription<K, F> {
- fn drop(&mut self) {
- if let Some(mapping) = self.mapping.as_ref().and_then(|mapping| mapping.upgrade()) {
- let mut mapping = mapping.lock();
-
- // If the callback is present in the mapping, then just remove it.
- if let Some(callbacks) = mapping.callbacks.get_mut(&self.key) {
- let callback = callbacks.remove(&self.id);
- if callback.is_some() {
- drop(mapping);
- drop(callback);
- return;
- }
- }
-
- // If this subscription's callback is not present, then either it has been
- // temporarily removed during emit, or it has not yet been added. Record
- // that this subscription has been dropped so that the callback can be
- // removed later.
- mapping
- .dropped_subscriptions
- .entry(self.key.clone())
- .or_default()
- .insert(self.id);
- }
- }
-}
@@ -1,99 +0,0 @@
-use crate::{platform::ForegroundPlatform, Action, App, AppContext};
-
-pub struct Menu<'a> {
- pub name: &'a str,
- pub items: Vec<MenuItem<'a>>,
-}
-
-pub enum MenuItem<'a> {
- Separator,
- Submenu(Menu<'a>),
- Action {
- name: &'a str,
- action: Box<dyn Action>,
- os_action: Option<OsAction>,
- },
-}
-
-impl<'a> MenuItem<'a> {
- pub fn separator() -> Self {
- Self::Separator
- }
-
- pub fn submenu(menu: Menu<'a>) -> Self {
- Self::Submenu(menu)
- }
-
- pub fn action(name: &'a str, action: impl Action) -> Self {
- Self::Action {
- name,
- action: Box::new(action),
- os_action: None,
- }
- }
-
- pub fn os_action(name: &'a str, action: impl Action, os_action: OsAction) -> Self {
- Self::Action {
- name,
- action: Box::new(action),
- os_action: Some(os_action),
- }
- }
-}
-
-#[derive(Copy, Clone, Eq, PartialEq)]
-pub enum OsAction {
- Cut,
- Copy,
- Paste,
- SelectAll,
- Undo,
- Redo,
-}
-
-impl AppContext {
- pub fn set_menus(&mut self, menus: Vec<Menu>) {
- self.foreground_platform
- .set_menus(menus, &self.keystroke_matcher);
- }
-}
-
-pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform, app: &App) {
- foreground_platform.on_will_open_menu(Box::new({
- let cx = app.0.clone();
- move || {
- let mut cx = cx.borrow_mut();
- cx.keystroke_matcher.clear_pending();
- }
- }));
- foreground_platform.on_validate_menu_command(Box::new({
- let cx = app.0.clone();
- move |action| {
- let cx = cx.borrow_mut();
- !cx.keystroke_matcher.has_pending_keystrokes() && cx.is_action_available(action)
- }
- }));
- foreground_platform.on_menu_command(Box::new({
- let cx = app.0.clone();
- move |action| {
- let mut cx = cx.borrow_mut();
- if let Some(main_window) = cx.active_window() {
- let dispatched = main_window
- .update(&mut *cx, |cx| {
- if let Some(view_id) = cx.focused_view_id() {
- cx.dispatch_action(Some(view_id), action);
- true
- } else {
- false
- }
- })
- .unwrap_or(false);
-
- if dispatched {
- return;
- }
- }
- cx.dispatch_global_action_any(action);
- }
- }));
-}
@@ -1,220 +0,0 @@
-#[cfg(any(test, feature = "test-support"))]
-use std::sync::Arc;
-
-use lazy_static::lazy_static;
-#[cfg(any(test, feature = "test-support"))]
-use parking_lot::Mutex;
-
-use collections::{hash_map::Entry, HashMap, HashSet};
-
-#[cfg(any(test, feature = "test-support"))]
-use crate::util::post_inc;
-use crate::{AnyWindowHandle, ElementStateId};
-
-lazy_static! {
- static ref LEAK_BACKTRACE: bool =
- std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty());
-}
-
-struct ElementStateRefCount {
- ref_count: usize,
- frame_id: usize,
-}
-
-#[derive(Default)]
-pub struct RefCounts {
- entity_counts: HashMap<usize, usize>,
- element_state_counts: HashMap<ElementStateId, ElementStateRefCount>,
- dropped_models: HashSet<usize>,
- dropped_views: HashSet<(AnyWindowHandle, usize)>,
- dropped_element_states: HashSet<ElementStateId>,
-
- #[cfg(any(test, feature = "test-support"))]
- pub leak_detector: Arc<Mutex<LeakDetector>>,
-}
-
-impl RefCounts {
- #[cfg(any(test, feature = "test-support"))]
- pub fn new(leak_detector: Arc<Mutex<LeakDetector>>) -> Self {
- Self {
- #[cfg(any(test, feature = "test-support"))]
- leak_detector,
- ..Default::default()
- }
- }
-
- pub fn inc_model(&mut self, model_id: usize) {
- match self.entity_counts.entry(model_id) {
- Entry::Occupied(mut entry) => {
- *entry.get_mut() += 1;
- }
- Entry::Vacant(entry) => {
- entry.insert(1);
- self.dropped_models.remove(&model_id);
- }
- }
- }
-
- pub fn inc_view(&mut self, window: AnyWindowHandle, view_id: usize) {
- match self.entity_counts.entry(view_id) {
- Entry::Occupied(mut entry) => *entry.get_mut() += 1,
- Entry::Vacant(entry) => {
- entry.insert(1);
- self.dropped_views.remove(&(window, view_id));
- }
- }
- }
-
- pub fn inc_element_state(&mut self, id: ElementStateId, frame_id: usize) {
- match self.element_state_counts.entry(id) {
- Entry::Occupied(mut entry) => {
- let entry = entry.get_mut();
- if entry.frame_id == frame_id || entry.ref_count >= 2 {
- panic!("used the same element state more than once in the same frame");
- }
- entry.ref_count += 1;
- entry.frame_id = frame_id;
- }
- Entry::Vacant(entry) => {
- entry.insert(ElementStateRefCount {
- ref_count: 1,
- frame_id,
- });
- self.dropped_element_states.remove(&id);
- }
- }
- }
-
- pub fn dec_model(&mut self, model_id: usize) {
- let count = self.entity_counts.get_mut(&model_id).unwrap();
- *count -= 1;
- if *count == 0 {
- self.entity_counts.remove(&model_id);
- self.dropped_models.insert(model_id);
- }
- }
-
- pub fn dec_view(&mut self, window: AnyWindowHandle, view_id: usize) {
- let count = self.entity_counts.get_mut(&view_id).unwrap();
- *count -= 1;
- if *count == 0 {
- self.entity_counts.remove(&view_id);
- self.dropped_views.insert((window, view_id));
- }
- }
-
- pub fn dec_element_state(&mut self, id: ElementStateId) {
- let entry = self.element_state_counts.get_mut(&id).unwrap();
- entry.ref_count -= 1;
- if entry.ref_count == 0 {
- self.element_state_counts.remove(&id);
- self.dropped_element_states.insert(id);
- }
- }
-
- pub fn is_entity_alive(&self, entity_id: usize) -> bool {
- self.entity_counts.contains_key(&entity_id)
- }
-
- pub fn take_dropped(
- &mut self,
- ) -> (
- HashSet<usize>,
- HashSet<(AnyWindowHandle, usize)>,
- HashSet<ElementStateId>,
- ) {
- (
- std::mem::take(&mut self.dropped_models),
- std::mem::take(&mut self.dropped_views),
- std::mem::take(&mut self.dropped_element_states),
- )
- }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-#[derive(Default)]
-pub struct LeakDetector {
- next_handle_id: usize,
- #[allow(clippy::type_complexity)]
- handle_backtraces: HashMap<
- usize,
- (
- Option<&'static str>,
- HashMap<usize, Option<backtrace::Backtrace>>,
- ),
- >,
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl LeakDetector {
- pub fn handle_created(&mut self, type_name: Option<&'static str>, entity_id: usize) -> usize {
- let handle_id = post_inc(&mut self.next_handle_id);
- let entry = self.handle_backtraces.entry(entity_id).or_default();
- let backtrace = if *LEAK_BACKTRACE {
- Some(backtrace::Backtrace::new_unresolved())
- } else {
- None
- };
- if let Some(type_name) = type_name {
- entry.0.get_or_insert(type_name);
- }
- entry.1.insert(handle_id, backtrace);
- handle_id
- }
-
- pub fn handle_dropped(&mut self, entity_id: usize, handle_id: usize) {
- if let Some((_, backtraces)) = self.handle_backtraces.get_mut(&entity_id) {
- assert!(backtraces.remove(&handle_id).is_some());
- if backtraces.is_empty() {
- self.handle_backtraces.remove(&entity_id);
- }
- }
- }
-
- pub fn assert_dropped(&mut self, entity_id: usize) {
- if let Some((type_name, backtraces)) = self.handle_backtraces.get_mut(&entity_id) {
- for trace in backtraces.values_mut().flatten() {
- trace.resolve();
- eprintln!("{:?}", crate::util::CwdBacktrace(trace));
- }
-
- let hint = if *LEAK_BACKTRACE {
- ""
- } else {
- " – set LEAK_BACKTRACE=1 for more information"
- };
-
- panic!(
- "{} handles to {} {} still exist{}",
- backtraces.len(),
- type_name.unwrap_or("entity"),
- entity_id,
- hint
- );
- }
- }
-
- pub fn detect(&mut self) {
- let mut found_leaks = false;
- for (id, (type_name, backtraces)) in self.handle_backtraces.iter_mut() {
- eprintln!(
- "leaked {} handles to {} {}",
- backtraces.len(),
- type_name.unwrap_or("entity"),
- id
- );
- for trace in backtraces.values_mut().flatten() {
- trace.resolve();
- eprintln!("{:?}", crate::util::CwdBacktrace(trace));
- }
- found_leaks = true;
- }
-
- let hint = if *LEAK_BACKTRACE {
- ""
- } else {
- " – set LEAK_BACKTRACE=1 for more information"
- };
- assert!(!found_leaks, "detected leaked handles{}", hint);
- }
-}
@@ -1,661 +0,0 @@
-use crate::{
- executor,
- geometry::vector::Vector2F,
- keymap_matcher::{Binding, Keystroke},
- platform,
- platform::{Event, InputHandler, KeyDownEvent, Platform},
- Action, AnyWindowHandle, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache,
- Handle, ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle,
- WeakHandle, WindowContext, WindowHandle,
-};
-use collections::BTreeMap;
-use futures::Future;
-use itertools::Itertools;
-use parking_lot::{Mutex, RwLock};
-use smallvec::SmallVec;
-use smol::stream::StreamExt;
-use std::{
- any::Any,
- cell::RefCell,
- mem,
- path::PathBuf,
- rc::Rc,
- sync::{
- atomic::{AtomicUsize, Ordering},
- Arc,
- },
- time::Duration,
-};
-
-use super::{
- ref_counts::LeakDetector, window_input_handler::WindowInputHandler, AsyncAppContext, RefCounts,
-};
-
-#[derive(Clone)]
-pub struct TestAppContext {
- cx: Rc<RefCell<AppContext>>,
- foreground_platform: Rc<platform::test::ForegroundPlatform>,
- condition_duration: Option<Duration>,
- pub function_name: String,
- assertion_context: AssertionContextManager,
-}
-
-impl TestAppContext {
- pub fn new(
- foreground_platform: Rc<platform::test::ForegroundPlatform>,
- platform: Arc<dyn Platform>,
- foreground: Rc<executor::Foreground>,
- background: Arc<executor::Background>,
- font_cache: Arc<FontCache>,
- leak_detector: Arc<Mutex<LeakDetector>>,
- first_entity_id: usize,
- function_name: String,
- ) -> Self {
- let mut cx = AppContext::new(
- foreground,
- background,
- platform,
- foreground_platform.clone(),
- font_cache,
- util::http::FakeHttpClient::with_404_response(),
- RefCounts::new(leak_detector),
- (),
- );
- cx.next_id = first_entity_id;
- let cx = TestAppContext {
- cx: Rc::new(RefCell::new(cx)),
- foreground_platform,
- condition_duration: None,
- function_name,
- assertion_context: AssertionContextManager::new(),
- };
- cx.cx.borrow_mut().weak_self = Some(Rc::downgrade(&cx.cx));
- cx
- }
-
- pub fn dispatch_action<A: Action>(&mut self, window: AnyWindowHandle, action: A) {
- self.update_window(window, |window| {
- window.dispatch_action(window.focused_view_id(), &action);
- })
- .expect("window not found");
- }
-
- pub fn available_actions(
- &self,
- window: AnyWindowHandle,
- view_id: usize,
- ) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
- self.read_window(window, |cx| cx.available_actions(view_id))
- .unwrap_or_default()
- }
-
- pub fn dispatch_global_action<A: Action>(&mut self, action: A) {
- self.update(|cx| cx.dispatch_global_action_any(&action));
- }
-
- pub fn dispatch_keystroke(
- &mut self,
- window: AnyWindowHandle,
- keystroke: Keystroke,
- is_held: bool,
- ) {
- let handled = window.update(self, |cx| {
- if cx.dispatch_keystroke(&keystroke) {
- return true;
- }
-
- if cx.dispatch_event(
- Event::KeyDown(KeyDownEvent {
- keystroke: keystroke.clone(),
- is_held,
- }),
- false,
- ) {
- return true;
- }
-
- false
- });
-
- if !handled && !keystroke.cmd && !keystroke.ctrl {
- WindowInputHandler {
- app: self.cx.clone(),
- window,
- }
- .replace_text_in_range(None, &keystroke.key)
- }
- }
-
- pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
- &self,
- window: AnyWindowHandle,
- callback: F,
- ) -> Option<T> {
- self.cx.borrow().read_window(window, callback)
- }
-
- pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
- &mut self,
- window: AnyWindowHandle,
- callback: F,
- ) -> Option<T> {
- self.cx.borrow_mut().update_window(window, callback)
- }
-
- pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
- where
- T: Entity,
- F: FnOnce(&mut ModelContext<T>) -> T,
- {
- self.cx.borrow_mut().add_model(build_model)
- }
-
- pub fn add_window<V, F>(&mut self, build_root_view: F) -> WindowHandle<V>
- where
- V: View,
- F: FnOnce(&mut ViewContext<V>) -> V,
- {
- let window = self
- .cx
- .borrow_mut()
- .add_window(Default::default(), build_root_view);
- window.simulate_activation(self);
- window
- }
-
- pub fn observe_global<E, F>(&mut self, callback: F) -> Subscription
- where
- E: Any,
- F: 'static + FnMut(&mut AppContext),
- {
- self.cx.borrow_mut().observe_global::<E, F>(callback)
- }
-
- pub fn set_global<T: 'static>(&mut self, state: T) {
- self.cx.borrow_mut().set_global(state);
- }
-
- pub fn subscribe_global<E, F>(&mut self, callback: F) -> Subscription
- where
- E: Any,
- F: 'static + FnMut(&E, &mut AppContext),
- {
- self.cx.borrow_mut().subscribe_global(callback)
- }
-
- pub fn windows(&self) -> Vec<AnyWindowHandle> {
- self.cx.borrow().windows().collect()
- }
-
- pub fn remove_all_windows(&mut self) {
- self.update(|cx| cx.windows.clear());
- }
-
- pub fn read<T, F: FnOnce(&AppContext) -> T>(&self, callback: F) -> T {
- callback(&*self.cx.borrow())
- }
-
- pub fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, callback: F) -> T {
- let mut state = self.cx.borrow_mut();
- // Don't increment pending flushes in order for effects to be flushed before the callback
- // completes, which is helpful in tests.
- let result = callback(&mut *state);
- // Flush effects after the callback just in case there are any. This can happen in edge
- // cases such as the closure dropping handles.
- state.flush_effects();
- result
- }
-
- pub fn to_async(&self) -> AsyncAppContext {
- AsyncAppContext(self.cx.clone())
- }
-
- pub fn font_cache(&self) -> Arc<FontCache> {
- self.cx.borrow().font_cache.clone()
- }
-
- pub fn foreground_platform(&self) -> Rc<platform::test::ForegroundPlatform> {
- self.foreground_platform.clone()
- }
-
- pub fn platform(&self) -> Arc<dyn platform::Platform> {
- self.cx.borrow().platform.clone()
- }
-
- pub fn foreground(&self) -> Rc<executor::Foreground> {
- self.cx.borrow().foreground().clone()
- }
-
- pub fn background(&self) -> Arc<executor::Background> {
- self.cx.borrow().background().clone()
- }
-
- pub fn spawn<F, Fut, T>(&self, f: F) -> Task<T>
- where
- F: FnOnce(AsyncAppContext) -> Fut,
- Fut: 'static + Future<Output = T>,
- T: 'static,
- {
- let foreground = self.foreground();
- let future = f(self.to_async());
- let cx = self.to_async();
- foreground.spawn(async move {
- let result = future.await;
- cx.0.borrow_mut().flush_effects();
- result
- })
- }
-
- pub fn simulate_new_path_selection(&self, result: impl FnOnce(PathBuf) -> Option<PathBuf>) {
- self.foreground_platform.simulate_new_path_selection(result);
- }
-
- pub fn did_prompt_for_new_path(&self) -> bool {
- self.foreground_platform.as_ref().did_prompt_for_new_path()
- }
-
- pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
- self.cx.borrow().leak_detector()
- }
-
- pub fn assert_dropped(&self, handle: impl WeakHandle) {
- self.cx
- .borrow()
- .leak_detector()
- .lock()
- .assert_dropped(handle.id())
- }
-
- /// Drop a handle, assuming it is the last. If it is not the last, panic with debug information about
- /// where the stray handles were created.
- pub fn drop_last<T, W: WeakHandle, H: Handle<T, Weak = W>>(&mut self, handle: H) {
- let weak = handle.downgrade();
- self.update(|_| drop(handle));
- self.assert_dropped(weak);
- }
-
- pub fn set_condition_duration(&mut self, duration: Option<Duration>) {
- self.condition_duration = duration;
- }
-
- pub fn condition_duration(&self) -> Duration {
- self.condition_duration.unwrap_or_else(|| {
- if std::env::var("CI").is_ok() {
- Duration::from_secs(2)
- } else {
- Duration::from_millis(500)
- }
- })
- }
-
- pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) {
- self.update(|cx| {
- let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned());
- let expected_content = expected_content.map(|content| content.to_owned());
- assert_eq!(actual_content, expected_content);
- })
- }
-
- pub fn add_assertion_context(&self, context: String) -> ContextHandle {
- self.assertion_context.add_context(context)
- }
-
- pub fn assertion_context(&self) -> String {
- self.assertion_context.context()
- }
-}
-
-impl BorrowAppContext for TestAppContext {
- fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
- self.cx.borrow().read_with(f)
- }
-
- fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
- self.cx.borrow_mut().update(f)
- }
-}
-
-impl BorrowWindowContext for TestAppContext {
- type Result<T> = T;
-
- fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
- self.cx
- .borrow()
- .read_window(window, f)
- .expect("window was closed")
- }
-
- fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
- where
- F: FnOnce(&WindowContext) -> Option<T>,
- {
- BorrowWindowContext::read_window(self, window, f)
- }
-
- fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
- &mut self,
- window: AnyWindowHandle,
- f: F,
- ) -> T {
- self.cx
- .borrow_mut()
- .update_window(window, f)
- .expect("window was closed")
- }
-
- fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
- where
- F: FnOnce(&mut WindowContext) -> Option<T>,
- {
- BorrowWindowContext::update_window(self, window, f)
- }
-}
-
-impl<T: Entity> ModelHandle<T> {
- pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
- let (tx, mut rx) = futures::channel::mpsc::unbounded();
- let mut cx = cx.cx.borrow_mut();
- let subscription = cx.observe(self, move |_, _| {
- tx.unbounded_send(()).ok();
- });
-
- let duration = if std::env::var("CI").is_ok() {
- Duration::from_secs(5)
- } else {
- Duration::from_secs(1)
- };
-
- let executor = cx.background().clone();
- async move {
- executor.start_waiting();
- let notification = crate::util::timeout(duration, rx.next())
- .await
- .expect("next notification timed out");
- drop(subscription);
- notification.expect("model dropped while test was waiting for its next notification")
- }
- }
-
- pub fn next_event(&self, cx: &TestAppContext) -> impl Future<Output = T::Event>
- where
- T::Event: Clone,
- {
- let (tx, mut rx) = futures::channel::mpsc::unbounded();
- let mut cx = cx.cx.borrow_mut();
- let subscription = cx.subscribe(self, move |_, event, _| {
- tx.unbounded_send(event.clone()).ok();
- });
-
- let duration = if std::env::var("CI").is_ok() {
- Duration::from_secs(5)
- } else {
- Duration::from_secs(1)
- };
-
- cx.foreground.start_waiting();
- async move {
- let event = crate::util::timeout(duration, rx.next())
- .await
- .expect("next event timed out");
- drop(subscription);
- event.expect("model dropped while test was waiting for its next event")
- }
- }
-
- pub fn condition(
- &self,
- cx: &TestAppContext,
- mut predicate: impl FnMut(&T, &AppContext) -> bool,
- ) -> impl Future<Output = ()> {
- let (tx, mut rx) = futures::channel::mpsc::unbounded();
-
- let mut cx = cx.cx.borrow_mut();
- let subscriptions = (
- cx.observe(self, {
- let tx = tx.clone();
- move |_, _| {
- tx.unbounded_send(()).ok();
- }
- }),
- cx.subscribe(self, {
- move |_, _, _| {
- tx.unbounded_send(()).ok();
- }
- }),
- );
-
- let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
- let handle = self.downgrade();
- let duration = if std::env::var("CI").is_ok() {
- Duration::from_secs(5)
- } else {
- Duration::from_secs(1)
- };
-
- async move {
- crate::util::timeout(duration, async move {
- loop {
- {
- let cx = cx.borrow();
- let cx = &*cx;
- if predicate(
- handle
- .upgrade(cx)
- .expect("model dropped with pending condition")
- .read(cx),
- cx,
- ) {
- break;
- }
- }
-
- cx.borrow().foreground().start_waiting();
- rx.next()
- .await
- .expect("model dropped with pending condition");
- cx.borrow().foreground().finish_waiting();
- }
- })
- .await
- .expect("condition timed out");
- drop(subscriptions);
- }
- }
-}
-
-impl AnyWindowHandle {
- pub fn has_pending_prompt(&self, cx: &mut TestAppContext) -> bool {
- let window = self.platform_window_mut(cx);
- let prompts = window.pending_prompts.borrow_mut();
- !prompts.is_empty()
- }
-
- pub fn current_title(&self, cx: &mut TestAppContext) -> Option<String> {
- self.platform_window_mut(cx).title.clone()
- }
-
- pub fn simulate_close(&self, cx: &mut TestAppContext) -> bool {
- let handler = self.platform_window_mut(cx).should_close_handler.take();
- if let Some(mut handler) = handler {
- let should_close = handler();
- self.platform_window_mut(cx).should_close_handler = Some(handler);
- should_close
- } else {
- false
- }
- }
-
- pub fn simulate_resize(&self, size: Vector2F, cx: &mut TestAppContext) {
- let mut window = self.platform_window_mut(cx);
- window.size = size;
- let mut handlers = mem::take(&mut window.resize_handlers);
- drop(window);
- for handler in &mut handlers {
- handler();
- }
- self.platform_window_mut(cx).resize_handlers = handlers;
- }
-
- pub fn is_edited(&self, cx: &mut TestAppContext) -> bool {
- self.platform_window_mut(cx).edited
- }
-
- pub fn simulate_prompt_answer(&self, answer: usize, cx: &mut TestAppContext) {
- use postage::prelude::Sink as _;
-
- let mut done_tx = self
- .platform_window_mut(cx)
- .pending_prompts
- .borrow_mut()
- .pop_front()
- .expect("prompt was not called");
- done_tx.try_send(answer).ok();
- }
-
- fn platform_window_mut<'a>(
- &self,
- cx: &'a mut TestAppContext,
- ) -> std::cell::RefMut<'a, platform::test::Window> {
- std::cell::RefMut::map(cx.cx.borrow_mut(), |state| {
- let window = state.windows.get_mut(&self).unwrap();
- let test_window = window
- .platform_window
- .as_any_mut()
- .downcast_mut::<platform::test::Window>()
- .unwrap();
- test_window
- })
- }
-}
-
-impl<T: View> ViewHandle<T> {
- pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
- use postage::prelude::{Sink as _, Stream as _};
-
- let (mut tx, mut rx) = postage::mpsc::channel(1);
- let mut cx = cx.cx.borrow_mut();
- let subscription = cx.observe(self, move |_, _| {
- tx.try_send(()).ok();
- });
-
- let duration = if std::env::var("CI").is_ok() {
- Duration::from_secs(5)
- } else {
- Duration::from_secs(1)
- };
-
- async move {
- let notification = crate::util::timeout(duration, rx.recv())
- .await
- .expect("next notification timed out");
- drop(subscription);
- notification.expect("model dropped while test was waiting for its next notification")
- }
- }
-
- pub fn condition(
- &self,
- cx: &TestAppContext,
- mut predicate: impl FnMut(&T, &AppContext) -> bool,
- ) -> impl Future<Output = ()> {
- use postage::prelude::{Sink as _, Stream as _};
-
- let (tx, mut rx) = postage::mpsc::channel(1024);
- let timeout_duration = cx.condition_duration();
-
- let mut cx = cx.cx.borrow_mut();
- let subscriptions = (
- cx.observe(self, {
- let mut tx = tx.clone();
- move |_, _| {
- tx.blocking_send(()).ok();
- }
- }),
- cx.subscribe(self, {
- let mut tx = tx.clone();
- move |_, _, _| {
- tx.blocking_send(()).ok();
- }
- }),
- );
-
- let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
- let handle = self.downgrade();
-
- async move {
- crate::util::timeout(timeout_duration, async move {
- loop {
- {
- let cx = cx.borrow();
- let cx = &*cx;
- if predicate(
- handle
- .upgrade(cx)
- .expect("view dropped with pending condition")
- .read(cx),
- cx,
- ) {
- break;
- }
- }
-
- cx.borrow().foreground().start_waiting();
- rx.recv()
- .await
- .expect("view dropped with pending condition");
- cx.borrow().foreground().finish_waiting();
- }
- })
- .await
- .expect("condition timed out");
- drop(subscriptions);
- }
- }
-}
-
-/// Tracks string context to be printed when assertions fail.
-/// Often this is done by storing a context string in the manager and returning the handle.
-#[derive(Clone)]
-pub struct AssertionContextManager {
- id: Arc<AtomicUsize>,
- contexts: Arc<RwLock<BTreeMap<usize, String>>>,
-}
-
-impl AssertionContextManager {
- pub fn new() -> Self {
- Self {
- id: Arc::new(AtomicUsize::new(0)),
- contexts: Arc::new(RwLock::new(BTreeMap::new())),
- }
- }
-
- pub fn add_context(&self, context: String) -> ContextHandle {
- let id = self.id.fetch_add(1, Ordering::Relaxed);
- let mut contexts = self.contexts.write();
- contexts.insert(id, context);
- ContextHandle {
- id,
- manager: self.clone(),
- }
- }
-
- pub fn context(&self) -> String {
- let contexts = self.contexts.read();
- format!("\n{}\n", contexts.values().join("\n"))
- }
-}
-
-/// Used to track the lifetime of a piece of context so that it can be provided when an assertion fails.
-/// For example, in the EditorTestContext, `set_state` returns a context handle so that if an assertion fails,
-/// the state that was set initially for the failure can be printed in the error message
-pub struct ContextHandle {
- id: usize,
- manager: AssertionContextManager,
-}
-
-impl Drop for ContextHandle {
- fn drop(&mut self) {
- let mut contexts = self.manager.contexts.write();
- contexts.remove(&self.id);
- }
-}
@@ -1,1767 +0,0 @@
-use crate::{
- elements::AnyRootElement,
- fonts::{TextStyle, TextStyleRefinement},
- geometry::{rect::RectF, Size},
- json::ToJson,
- keymap_matcher::{Binding, KeymapContext, Keystroke, MatchResult},
- platform::{
- self, Appearance, CursorStyle, Event, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent,
- MouseButton, MouseMovedEvent, PromptLevel, WindowBounds,
- },
- scene::{
- CursorRegion, EventHandler, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag,
- MouseEvent, MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
- Scene,
- },
- text_layout::TextLayoutCache,
- util::post_inc,
- Action, AnyView, AnyViewHandle, AnyWindowHandle, AppContext, BorrowAppContext,
- BorrowWindowContext, Effect, Element, Entity, Handle, MouseRegion, MouseRegionId, SceneBuilder,
- Subscription, View, ViewContext, ViewHandle, WindowInvalidation,
-};
-use anyhow::{anyhow, bail, Result};
-use collections::{HashMap, HashSet};
-use pathfinder_geometry::vector::{vec2f, Vector2F};
-use postage::oneshot;
-use serde_json::json;
-use smallvec::SmallVec;
-use sqlez::{
- bindable::{Bind, Column, StaticColumnCount},
- statement::Statement,
-};
-use std::{
- any::{type_name, Any, TypeId},
- mem,
- ops::{Deref, DerefMut, Range, Sub},
- sync::Arc,
-};
-use taffy::{
- tree::{Measurable, MeasureFunc},
- Taffy,
-};
-use util::ResultExt;
-use uuid::Uuid;
-
-use super::{Reference, ViewMetadata};
-
-pub struct Window {
- layout_engines: Vec<LayoutEngine>,
- pub(crate) root_view: Option<AnyViewHandle>,
- pub(crate) focused_view_id: Option<usize>,
- pub(crate) parents: HashMap<usize, usize>,
- pub(crate) is_active: bool,
- pub(crate) is_fullscreen: bool,
- inspector_enabled: bool,
- pub(crate) invalidation: Option<WindowInvalidation>,
- pub(crate) platform_window: Box<dyn platform::Window>,
- pub(crate) rendered_views: HashMap<usize, Box<dyn AnyRootElement>>,
- scene: SceneBuilder,
- pub(crate) text_style_stack: Vec<TextStyle>,
- pub(crate) theme_stack: Vec<Arc<dyn Any + Send + Sync>>,
- pub(crate) new_parents: HashMap<usize, usize>,
- pub(crate) views_to_notify_if_ancestors_change: HashMap<usize, SmallVec<[usize; 2]>>,
- titlebar_height: f32,
- appearance: Appearance,
- cursor_regions: Vec<CursorRegion>,
- mouse_regions: Vec<(MouseRegion, usize)>,
- event_handlers: Vec<EventHandler>,
- last_mouse_moved_event: Option<Event>,
- last_mouse_position: Vector2F,
- pressed_buttons: HashSet<MouseButton>,
- pub(crate) hovered_region_ids: Vec<MouseRegionId>,
- pub(crate) clicked_region_ids: Vec<MouseRegionId>,
- pub(crate) clicked_region: Option<(MouseRegionId, MouseButton)>,
- text_layout_cache: Arc<TextLayoutCache>,
- refreshing: bool,
-}
-
-impl Window {
- pub fn new<V, F>(
- handle: AnyWindowHandle,
- platform_window: Box<dyn platform::Window>,
- cx: &mut AppContext,
- build_view: F,
- ) -> Self
- where
- V: View,
- F: FnOnce(&mut ViewContext<V>) -> V,
- {
- let titlebar_height = platform_window.titlebar_height();
- let appearance = platform_window.appearance();
- let mut window = Self {
- layout_engines: Vec::new(),
- root_view: None,
- focused_view_id: None,
- parents: Default::default(),
- is_active: false,
- invalidation: None,
- is_fullscreen: false,
- inspector_enabled: false,
- platform_window,
- rendered_views: Default::default(),
- scene: SceneBuilder::new(),
- text_style_stack: Vec::new(),
- theme_stack: Vec::new(),
- new_parents: HashMap::default(),
- views_to_notify_if_ancestors_change: HashMap::default(),
- cursor_regions: Default::default(),
- mouse_regions: Default::default(),
- event_handlers: Default::default(),
- text_layout_cache: Arc::new(TextLayoutCache::new(cx.font_system.clone())),
- last_mouse_moved_event: None,
- last_mouse_position: Vector2F::zero(),
- pressed_buttons: Default::default(),
- hovered_region_ids: Default::default(),
- clicked_region_ids: Default::default(),
- clicked_region: None,
- titlebar_height,
- appearance,
- refreshing: false,
- };
-
- let mut window_context = WindowContext::mutable(cx, &mut window, handle);
- let root_view = window_context.add_view(|cx| build_view(cx));
- if let Some(invalidation) = window_context.window.invalidation.take() {
- window_context.invalidate(invalidation, appearance);
- }
- window.focused_view_id = Some(root_view.id());
- window.root_view = Some(root_view.into_any());
- window
- }
-
- pub fn root_view(&self) -> &AnyViewHandle {
- &self
- .root_view
- .as_ref()
- .expect("root_view called during window construction")
- }
-
- pub fn take_event_handlers(&mut self) -> Vec<EventHandler> {
- mem::take(&mut self.event_handlers)
- }
-}
-
-pub struct WindowContext<'a> {
- pub(crate) app_context: Reference<'a, AppContext>,
- pub(crate) window: Reference<'a, Window>,
- pub(crate) window_handle: AnyWindowHandle,
- pub(crate) removed: bool,
-}
-
-impl Deref for WindowContext<'_> {
- type Target = AppContext;
-
- fn deref(&self) -> &Self::Target {
- &self.app_context
- }
-}
-
-impl DerefMut for WindowContext<'_> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.app_context
- }
-}
-
-impl BorrowAppContext for WindowContext<'_> {
- fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
- self.app_context.read_with(f)
- }
-
- fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
- self.app_context.update(f)
- }
-}
-
-impl BorrowWindowContext for WindowContext<'_> {
- type Result<T> = T;
-
- fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, handle: AnyWindowHandle, f: F) -> T {
- if self.window_handle == handle {
- f(self)
- } else {
- panic!("read_with called with id of window that does not belong to this context")
- }
- }
-
- fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
- where
- F: FnOnce(&WindowContext) -> Option<T>,
- {
- BorrowWindowContext::read_window(self, window, f)
- }
-
- fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
- &mut self,
- handle: AnyWindowHandle,
- f: F,
- ) -> T {
- if self.window_handle == handle {
- f(self)
- } else {
- panic!("update called with id of window that does not belong to this context")
- }
- }
-
- fn update_window_optional<T, F>(&mut self, handle: AnyWindowHandle, f: F) -> Option<T>
- where
- F: FnOnce(&mut WindowContext) -> Option<T>,
- {
- BorrowWindowContext::update_window(self, handle, f)
- }
-}
-
-impl<'a> WindowContext<'a> {
- pub fn mutable(
- app_context: &'a mut AppContext,
- window: &'a mut Window,
- handle: AnyWindowHandle,
- ) -> Self {
- Self {
- app_context: Reference::Mutable(app_context),
- window: Reference::Mutable(window),
- window_handle: handle,
- removed: false,
- }
- }
-
- pub fn immutable(
- app_context: &'a AppContext,
- window: &'a Window,
- handle: AnyWindowHandle,
- ) -> Self {
- Self {
- app_context: Reference::Immutable(app_context),
- window: Reference::Immutable(window),
- window_handle: handle,
- removed: false,
- }
- }
-
- pub fn repaint(&mut self) {
- let window = self.window();
- self.pending_effects
- .push_back(Effect::RepaintWindow { window });
- }
-
- pub fn scene(&mut self) -> &mut SceneBuilder {
- &mut self.window.scene
- }
-
- pub fn enable_inspector(&mut self) {
- self.window.inspector_enabled = true;
- }
-
- pub fn is_inspector_enabled(&self) -> bool {
- self.window.inspector_enabled
- }
-
- pub fn is_mouse_down(&self, button: MouseButton) -> bool {
- self.window.pressed_buttons.contains(&button)
- }
-
- pub fn rem_size(&self) -> f32 {
- 16.
- }
-
- pub fn layout_engine(&mut self) -> Option<&mut LayoutEngine> {
- self.window.layout_engines.last_mut()
- }
-
- pub fn push_layout_engine(&mut self, engine: LayoutEngine) {
- self.window.layout_engines.push(engine);
- }
-
- pub fn pop_layout_engine(&mut self) -> Option<LayoutEngine> {
- self.window.layout_engines.pop()
- }
-
- pub fn remove_window(&mut self) {
- self.removed = true;
- }
-
- pub fn window(&self) -> AnyWindowHandle {
- self.window_handle
- }
-
- pub fn app_context(&mut self) -> &mut AppContext {
- &mut self.app_context
- }
-
- pub fn root_view(&self) -> &AnyViewHandle {
- self.window.root_view()
- }
-
- pub fn window_size(&self) -> Vector2F {
- self.window.platform_window.content_size()
- }
-
- pub fn mouse_position(&self) -> Vector2F {
- self.window.platform_window.mouse_position()
- }
-
- pub fn refreshing(&self) -> bool {
- self.window.refreshing
- }
-
- pub fn text_layout_cache(&self) -> &Arc<TextLayoutCache> {
- &self.window.text_layout_cache
- }
-
- pub(crate) fn update_any_view<F, T>(&mut self, view_id: usize, f: F) -> Option<T>
- where
- F: FnOnce(&mut dyn AnyView, &mut Self) -> T,
- {
- let handle = self.window_handle;
- let mut view = self.views.remove(&(handle, view_id))?;
- let result = f(view.as_mut(), self);
- self.views.insert((handle, view_id), view);
- Some(result)
- }
-
- pub(crate) fn update_view<V: 'static, S>(
- &mut self,
- handle: &ViewHandle<V>,
- update: &mut dyn FnMut(&mut V, &mut ViewContext<V>) -> S,
- ) -> S {
- self.update_any_view(handle.view_id, |view, cx| {
- let mut cx = ViewContext::mutable(cx, handle.view_id);
- update(
- view.as_any_mut()
- .downcast_mut()
- .expect("downcast is type safe"),
- &mut cx,
- )
- })
- .expect("view is already on the stack")
- }
-
- pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut WindowContext)) {
- let handle = self.window_handle;
- self.app_context.defer(move |cx| {
- cx.update_window(handle, |cx| callback(cx));
- })
- }
-
- pub fn update_global<T, F, U>(&mut self, update: F) -> U
- where
- T: 'static,
- F: FnOnce(&mut T, &mut Self) -> U,
- {
- AppContext::update_global_internal(self, |global, cx| update(global, cx))
- }
-
- pub fn update_default_global<T, F, U>(&mut self, update: F) -> U
- where
- T: 'static + Default,
- F: FnOnce(&mut T, &mut Self) -> U,
- {
- AppContext::update_default_global_internal(self, |global, cx| update(global, cx))
- }
-
- pub fn subscribe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
- where
- E: Entity,
- E::Event: 'static,
- H: Handle<E>,
- F: 'static + FnMut(H, &E::Event, &mut WindowContext),
- {
- self.subscribe_internal(handle, move |emitter, event, cx| {
- callback(emitter, event, cx);
- true
- })
- }
-
- pub fn subscribe_internal<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
- where
- E: Entity,
- E::Event: 'static,
- H: Handle<E>,
- F: 'static + FnMut(H, &E::Event, &mut WindowContext) -> bool,
- {
- let window_handle = self.window_handle;
- self.app_context
- .subscribe_internal(handle, move |emitter, event, cx| {
- cx.update_window(window_handle, |cx| callback(emitter, event, cx))
- .unwrap_or(false)
- })
- }
-
- pub(crate) fn observe_window_activation<F>(&mut self, callback: F) -> Subscription
- where
- F: 'static + FnMut(bool, &mut WindowContext) -> bool,
- {
- let handle = self.window_handle;
- let subscription_id = post_inc(&mut self.next_subscription_id);
- self.pending_effects
- .push_back(Effect::WindowActivationObservation {
- window: handle,
- subscription_id,
- callback: Box::new(callback),
- });
- Subscription::WindowActivationObservation(
- self.window_activation_observations
- .subscribe(handle, subscription_id),
- )
- }
-
- pub(crate) fn observe_fullscreen<F>(&mut self, callback: F) -> Subscription
- where
- F: 'static + FnMut(bool, &mut WindowContext) -> bool,
- {
- let window = self.window_handle;
- let subscription_id = post_inc(&mut self.next_subscription_id);
- self.pending_effects
- .push_back(Effect::WindowFullscreenObservation {
- window,
- subscription_id,
- callback: Box::new(callback),
- });
- Subscription::WindowActivationObservation(
- self.window_activation_observations
- .subscribe(window, subscription_id),
- )
- }
-
- pub(crate) fn observe_window_bounds<F>(&mut self, callback: F) -> Subscription
- where
- F: 'static + FnMut(WindowBounds, Uuid, &mut WindowContext) -> bool,
- {
- let window = self.window_handle;
- let subscription_id = post_inc(&mut self.next_subscription_id);
- self.pending_effects
- .push_back(Effect::WindowBoundsObservation {
- window,
- subscription_id,
- callback: Box::new(callback),
- });
- Subscription::WindowBoundsObservation(
- self.window_bounds_observations
- .subscribe(window, subscription_id),
- )
- }
-
- pub fn observe_keystrokes<F>(&mut self, callback: F) -> Subscription
- where
- F: 'static
- + FnMut(&Keystroke, &MatchResult, Option<&Box<dyn Action>>, &mut WindowContext) -> bool,
- {
- let window = self.window_handle;
- let subscription_id = post_inc(&mut self.next_subscription_id);
- self.keystroke_observations
- .add_callback(window, subscription_id, Box::new(callback));
- Subscription::KeystrokeObservation(
- self.keystroke_observations
- .subscribe(window, subscription_id),
- )
- }
-
- pub(crate) fn available_actions(
- &self,
- view_id: usize,
- ) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
- let handle = self.window_handle;
- let mut contexts = Vec::new();
- let mut handler_depths_by_action_id = HashMap::<TypeId, usize>::default();
- for (depth, view_id) in self.ancestors(view_id).enumerate() {
- if let Some(view_metadata) = self.views_metadata.get(&(handle, view_id)) {
- contexts.push(view_metadata.keymap_context.clone());
- if let Some(actions) = self.actions.get(&view_metadata.type_id) {
- handler_depths_by_action_id
- .extend(actions.keys().copied().map(|action_id| (action_id, depth)));
- }
- } else {
- log::error!(
- "view {} not found when computing available actions",
- view_id
- );
- }
- }
-
- handler_depths_by_action_id.extend(
- self.global_actions
- .keys()
- .copied()
- .map(|action_id| (action_id, contexts.len())),
- );
-
- self.action_deserializers
- .iter()
- .filter_map(move |(name, (action_id, deserialize))| {
- if let Some(action_depth) = handler_depths_by_action_id.get(action_id).copied() {
- let action = deserialize(serde_json::Value::Object(Default::default())).ok()?;
- let bindings = self
- .keystroke_matcher
- .bindings_for_action(*action_id)
- .filter(|b| {
- action.eq(b.action())
- && (0..=action_depth)
- .any(|depth| b.match_context(&contexts[depth..]))
- })
- .cloned()
- .collect();
- Some((*name, action, bindings))
- } else {
- None
- }
- })
- .collect()
- }
-
- pub(crate) fn dispatch_keystroke(&mut self, keystroke: &Keystroke) -> bool {
- let handle = self.window_handle;
- if let Some(focused_view_id) = self.focused_view_id() {
- let dispatch_path = self
- .ancestors(focused_view_id)
- .filter_map(|view_id| {
- self.views_metadata
- .get(&(handle, view_id))
- .map(|view| (view_id, view.keymap_context.clone()))
- })
- .collect();
-
- let match_result = self
- .keystroke_matcher
- .push_keystroke(keystroke.clone(), dispatch_path);
- let mut handled_by = None;
-
- let keystroke_handled = match &match_result {
- MatchResult::None => false,
- MatchResult::Pending => true,
- MatchResult::Matches(matches) => {
- for (view_id, action) in matches {
- if self.dispatch_action(Some(*view_id), action.as_ref()) {
- self.keystroke_matcher.clear_pending();
- handled_by = Some(action.boxed_clone());
- break;
- }
- }
- handled_by.is_some()
- }
- };
-
- self.keystroke(handle, keystroke.clone(), handled_by, match_result.clone());
- keystroke_handled
- } else {
- self.keystroke(handle, keystroke.clone(), None, MatchResult::None);
- false
- }
- }
-
- pub(crate) fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
- if !event_reused {
- self.dispatch_event_2(&event);
- }
-
- let mut mouse_events = SmallVec::<[_; 2]>::new();
- let mut notified_views: HashSet<usize> = Default::default();
- let handle = self.window_handle;
-
- // 1. Handle platform event. Keyboard events get dispatched immediately, while mouse events
- // get mapped into the mouse-specific MouseEvent type.
- // -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?]
- // -> Also updates mouse-related state
- match &event {
- Event::KeyDown(e) => return self.dispatch_key_down(e),
-
- Event::KeyUp(e) => return self.dispatch_key_up(e),
-
- Event::ModifiersChanged(e) => return self.dispatch_modifiers_changed(e),
-
- Event::MouseDown(e) => {
- // Click events are weird because they can be fired after a drag event.
- // MDN says that browsers handle this by starting from 'the most
- // specific ancestor element that contained both [positions]'
- // So we need to store the overlapping regions on mouse down.
-
- // If there is already region being clicked, don't replace it.
- if self.window.clicked_region.is_none() {
- self.window.clicked_region_ids = self
- .window
- .mouse_regions
- .iter()
- .filter_map(|(region, _)| {
- if region.bounds.contains_point(e.position) {
- Some(region.id())
- } else {
- None
- }
- })
- .collect();
-
- let mut highest_z_index = 0;
- let mut clicked_region_id = None;
- for (region, z_index) in self.window.mouse_regions.iter() {
- if region.bounds.contains_point(e.position) && *z_index >= highest_z_index {
- highest_z_index = *z_index;
- clicked_region_id = Some(region.id());
- }
- }
-
- self.window.clicked_region =
- clicked_region_id.map(|region_id| (region_id, e.button));
- }
-
- mouse_events.push(MouseEvent::Down(MouseDown {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- mouse_events.push(MouseEvent::DownOut(MouseDownOut {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- }
-
- Event::MouseUp(e) => {
- mouse_events.push(MouseEvent::Up(MouseUp {
- region: Default::default(),
- platform_event: e.clone(),
- }));
-
- // Synthesize one last drag event to end the drag
- mouse_events.push(MouseEvent::Drag(MouseDrag {
- region: Default::default(),
- prev_mouse_position: self.window.last_mouse_position,
- platform_event: MouseMovedEvent {
- position: e.position,
- pressed_button: Some(e.button),
- modifiers: e.modifiers,
- },
- end: true,
- }));
-
- mouse_events.push(MouseEvent::UpOut(MouseUpOut {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- mouse_events.push(MouseEvent::Click(MouseClick {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- mouse_events.push(MouseEvent::ClickOut(MouseClickOut {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- }
-
- Event::MouseMoved(
- e @ MouseMovedEvent {
- position,
- pressed_button,
- ..
- },
- ) => {
- let mut style_to_assign = CursorStyle::Arrow;
- for region in self.window.cursor_regions.iter().rev() {
- if region.bounds.contains_point(*position) {
- style_to_assign = region.style;
- break;
- }
- }
-
- if pressed_button.is_none()
- && self
- .window
- .platform_window
- .is_topmost_for_position(*position)
- {
- self.platform().set_cursor_style(style_to_assign);
- }
-
- if !event_reused {
- if pressed_button.is_some() {
- mouse_events.push(MouseEvent::Drag(MouseDrag {
- region: Default::default(),
- prev_mouse_position: self.window.last_mouse_position,
- platform_event: e.clone(),
- end: false,
- }));
- } else if let Some((_, clicked_button)) = self.window.clicked_region {
- mouse_events.push(MouseEvent::Drag(MouseDrag {
- region: Default::default(),
- prev_mouse_position: self.window.last_mouse_position,
- platform_event: e.clone(),
- end: true,
- }));
-
- // Mouse up event happened outside the current window. Simulate mouse up button event
- let button_event = e.to_button_event(clicked_button);
- mouse_events.push(MouseEvent::Up(MouseUp {
- region: Default::default(),
- platform_event: button_event.clone(),
- }));
- mouse_events.push(MouseEvent::UpOut(MouseUpOut {
- region: Default::default(),
- platform_event: button_event.clone(),
- }));
- mouse_events.push(MouseEvent::Click(MouseClick {
- region: Default::default(),
- platform_event: button_event.clone(),
- }));
- }
-
- mouse_events.push(MouseEvent::Move(MouseMove {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- }
-
- mouse_events.push(MouseEvent::Hover(MouseHover {
- region: Default::default(),
- platform_event: e.clone(),
- started: false,
- }));
- mouse_events.push(MouseEvent::MoveOut(MouseMoveOut {
- region: Default::default(),
- }));
-
- self.window.last_mouse_moved_event = Some(event.clone());
- }
-
- Event::MouseExited(event) => {
- // When the platform sends a MouseExited event, synthesize
- // a MouseMoved event whose position is outside the window's
- // bounds so that hover and cursor state can be updated.
- return self.dispatch_event(
- Event::MouseMoved(MouseMovedEvent {
- position: event.position,
- pressed_button: event.pressed_button,
- modifiers: event.modifiers,
- }),
- event_reused,
- );
- }
-
- Event::ScrollWheel(e) => mouse_events.push(MouseEvent::ScrollWheel(MouseScrollWheel {
- region: Default::default(),
- platform_event: e.clone(),
- })),
- }
-
- if let Some(position) = event.position() {
- self.window.last_mouse_position = position;
- }
-
- // 2. Dispatch mouse events on regions
- let mut any_event_handled = false;
- for mut mouse_event in mouse_events {
- let mut valid_regions = Vec::new();
-
- // GPUI elements are arranged by z_index but sibling elements can register overlapping
- // mouse regions. As such, hover events are only fired on overlapping elements which
- // are at the same z-index as the topmost element which overlaps with the mouse.
- match &mouse_event {
- MouseEvent::Hover(_) => {
- let mut highest_z_index = None;
- let mouse_position = self.mouse_position();
- let window = &mut *self.window;
- let prev_hovered_regions = mem::take(&mut window.hovered_region_ids);
- for (region, z_index) in window.mouse_regions.iter().rev() {
- // Allow mouse regions to appear transparent to hovers
- if !region.hoverable {
- continue;
- }
-
- let contains_mouse = region.bounds.contains_point(mouse_position);
-
- if contains_mouse && highest_z_index.is_none() {
- highest_z_index = Some(z_index);
- }
-
- // This unwrap relies on short circuiting boolean expressions
- // The right side of the && is only executed when contains_mouse
- // is true, and we know above that when contains_mouse is true
- // highest_z_index is set.
- if contains_mouse && z_index == highest_z_index.unwrap() {
- //Ensure that hover entrance events aren't sent twice
- if let Err(ix) = window.hovered_region_ids.binary_search(®ion.id()) {
- window.hovered_region_ids.insert(ix, region.id());
- }
- // window.hovered_region_ids.insert(region.id());
- if !prev_hovered_regions.contains(®ion.id()) {
- valid_regions.push(region.clone());
- if region.notify_on_hover {
- notified_views.insert(region.id().view_id());
- }
- }
- } else {
- // Ensure that hover exit events aren't sent twice
- if prev_hovered_regions.contains(®ion.id()) {
- valid_regions.push(region.clone());
- if region.notify_on_hover {
- notified_views.insert(region.id().view_id());
- }
- }
- }
- }
- }
-
- MouseEvent::Down(_) | MouseEvent::Up(_) => {
- for (region, _) in self.window.mouse_regions.iter().rev() {
- if region.bounds.contains_point(self.mouse_position()) {
- valid_regions.push(region.clone());
- if region.notify_on_click {
- notified_views.insert(region.id().view_id());
- }
- }
- }
- }
-
- MouseEvent::Click(e) => {
- // Only raise click events if the released button is the same as the one stored
- if self
- .window
- .clicked_region
- .map(|(_, clicked_button)| clicked_button == e.button)
- .unwrap_or(false)
- {
- // Clear clicked regions and clicked button
- let clicked_region_ids = std::mem::replace(
- &mut self.window.clicked_region_ids,
- Default::default(),
- );
- self.window.clicked_region = None;
-
- // Find regions which still overlap with the mouse since the last MouseDown happened
- for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
- if clicked_region_ids.contains(&mouse_region.id()) {
- if mouse_region.bounds.contains_point(self.mouse_position()) {
- valid_regions.push(mouse_region.clone());
- } else {
- // Let the view know that it hasn't been clicked anymore
- if mouse_region.notify_on_click {
- notified_views.insert(mouse_region.id().view_id());
- }
- }
- }
- }
- }
- }
-
- MouseEvent::Drag(_) => {
- for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
- if self.window.clicked_region_ids.contains(&mouse_region.id()) {
- valid_regions.push(mouse_region.clone());
- }
- }
- }
-
- MouseEvent::MoveOut(_)
- | MouseEvent::UpOut(_)
- | MouseEvent::DownOut(_)
- | MouseEvent::ClickOut(_) => {
- for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
- // NOT contains
- if !mouse_region.bounds.contains_point(self.mouse_position()) {
- valid_regions.push(mouse_region.clone());
- }
- }
- }
-
- _ => {
- for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
- // Contains
- if mouse_region.bounds.contains_point(self.mouse_position()) {
- valid_regions.push(mouse_region.clone());
- }
- }
- }
- }
-
- //3. Fire region events
- let hovered_region_ids = self.window.hovered_region_ids.clone();
- for valid_region in valid_regions.into_iter() {
- let mut handled = false;
- mouse_event.set_region(valid_region.bounds);
- if let MouseEvent::Hover(e) = &mut mouse_event {
- e.started = hovered_region_ids.contains(&valid_region.id())
- }
- // Handle Down events if the MouseRegion has a Click or Drag handler. This makes the api more intuitive as you would
- // not expect a MouseRegion to be transparent to Down events if it also has a Click handler.
- // This behavior can be overridden by adding a Down handler
- if let MouseEvent::Down(e) = &mouse_event {
- let has_click = valid_region
- .handlers
- .contains(MouseEvent::click_disc(), Some(e.button));
- let has_drag = valid_region
- .handlers
- .contains(MouseEvent::drag_disc(), Some(e.button));
- let has_down = valid_region
- .handlers
- .contains(MouseEvent::down_disc(), Some(e.button));
- if !has_down && (has_click || has_drag) {
- handled = true;
- }
- }
-
- // `event_consumed` should only be true if there are any handlers for this event.
- let mut event_consumed = handled;
- if let Some(callbacks) = valid_region.handlers.get(&mouse_event.handler_key()) {
- for callback in callbacks {
- handled = true;
- let view_id = valid_region.id().view_id();
- self.update_any_view(view_id, |view, cx| {
- handled = callback(mouse_event.clone(), view.as_any_mut(), cx, view_id);
- });
- event_consumed |= handled;
- any_event_handled |= handled;
- }
- }
-
- any_event_handled |= handled;
-
- // For bubbling events, if the event was handled, don't continue dispatching.
- // This only makes sense for local events which return false from is_capturable.
- if event_consumed && mouse_event.is_capturable() {
- break;
- }
- }
- }
-
- for view_id in notified_views {
- self.notify_view(handle, view_id);
- }
-
- any_event_handled
- }
-
- fn dispatch_event_2(&mut self, event: &Event) {
- match event {
- Event::MouseDown(event) => {
- self.window.pressed_buttons.insert(event.button);
- }
- Event::MouseUp(event) => {
- self.window.pressed_buttons.remove(&event.button);
- }
- _ => {}
- }
-
- if let Some(mouse_event) = event.mouse_event() {
- let event_handlers = self.window.take_event_handlers();
- for event_handler in event_handlers.iter().rev() {
- if event_handler.event_type == mouse_event.type_id() {
- if !(event_handler.handler)(mouse_event, self) {
- break;
- }
- }
- }
- self.window.event_handlers = event_handlers;
- }
- }
-
- pub(crate) fn dispatch_key_down(&mut self, event: &KeyDownEvent) -> bool {
- let handle = self.window_handle;
- if let Some(focused_view_id) = self.window.focused_view_id {
- for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
- if let Some(mut view) = self.views.remove(&(handle, view_id)) {
- let handled = view.key_down(event, self, view_id);
- self.views.insert((handle, view_id), view);
- if handled {
- return true;
- }
- } else {
- log::error!("view {} does not exist", view_id)
- }
- }
- }
-
- false
- }
-
- pub(crate) fn dispatch_key_up(&mut self, event: &KeyUpEvent) -> bool {
- let handle = self.window_handle;
- if let Some(focused_view_id) = self.window.focused_view_id {
- for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
- if let Some(mut view) = self.views.remove(&(handle, view_id)) {
- let handled = view.key_up(event, self, view_id);
- self.views.insert((handle, view_id), view);
- if handled {
- return true;
- }
- } else {
- log::error!("view {} does not exist", view_id)
- }
- }
- }
-
- false
- }
-
- pub(crate) fn dispatch_modifiers_changed(&mut self, event: &ModifiersChangedEvent) -> bool {
- let handle = self.window_handle;
- if let Some(focused_view_id) = self.window.focused_view_id {
- for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
- if let Some(mut view) = self.views.remove(&(handle, view_id)) {
- let handled = view.modifiers_changed(event, self, view_id);
- self.views.insert((handle, view_id), view);
- if handled {
- return true;
- }
- } else {
- log::error!("view {} does not exist", view_id)
- }
- }
- }
-
- false
- }
-
- pub fn invalidate(&mut self, mut invalidation: WindowInvalidation, appearance: Appearance) {
- self.start_frame();
- self.window.appearance = appearance;
- for view_id in &invalidation.removed {
- invalidation.updated.remove(view_id);
- self.window.rendered_views.remove(view_id);
- }
- for view_id in &invalidation.updated {
- let titlebar_height = self.window.titlebar_height;
- let element = self
- .render_view(RenderParams {
- view_id: *view_id,
- titlebar_height,
- refreshing: false,
- appearance,
- })
- .unwrap();
- self.window.rendered_views.insert(*view_id, element);
- }
- }
-
- pub fn render_view(&mut self, params: RenderParams) -> Result<Box<dyn AnyRootElement>> {
- let handle = self.window_handle;
- let view_id = params.view_id;
- let mut view = self
- .views
- .remove(&(handle, view_id))
- .ok_or_else(|| anyhow!("view not found"))?;
- let element = view.render(self, view_id);
- self.views.insert((handle, view_id), view);
- Ok(element)
- }
-
- pub fn layout(&mut self, refreshing: bool) -> Result<HashMap<usize, usize>> {
- let window_size = self.window.platform_window.content_size();
- let root_view_id = self.window.root_view().id();
-
- let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap();
-
- self.window.refreshing = refreshing;
- rendered_root.layout(SizeConstraint::strict(window_size), self)?;
- self.window.refreshing = false;
-
- let views_to_notify_if_ancestors_change =
- mem::take(&mut self.window.views_to_notify_if_ancestors_change);
- for (view_id, view_ids_to_notify) in views_to_notify_if_ancestors_change {
- let mut current_view_id = view_id;
- loop {
- let old_parent_id = self.window.parents.get(¤t_view_id);
- let new_parent_id = self.window.new_parents.get(¤t_view_id);
- if old_parent_id.is_none() && new_parent_id.is_none() {
- break;
- } else if old_parent_id == new_parent_id {
- current_view_id = *old_parent_id.unwrap();
- } else {
- let handle = self.window_handle;
- for view_id_to_notify in view_ids_to_notify {
- self.notify_view(handle, view_id_to_notify);
- }
- break;
- }
- }
- }
-
- let new_parents = mem::take(&mut self.window.new_parents);
- let old_parents = mem::replace(&mut self.window.parents, new_parents);
- self.window
- .rendered_views
- .insert(root_view_id, rendered_root);
- Ok(old_parents)
- }
-
- pub fn paint(&mut self) -> Result<Scene> {
- let window_size = self.window.platform_window.content_size();
- let scale_factor = self.window.platform_window.scale_factor();
-
- let root_view_id = self.window.root_view().id();
- let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap();
-
- rendered_root.paint(
- Vector2F::zero(),
- RectF::from_points(Vector2F::zero(), window_size),
- self,
- )?;
- self.window
- .rendered_views
- .insert(root_view_id, rendered_root);
-
- self.window.text_layout_cache.finish_frame();
- let mut scene = self.window.scene.build(scale_factor);
- self.window.cursor_regions = scene.cursor_regions();
- self.window.mouse_regions = scene.mouse_regions();
- self.window.event_handlers = scene.take_event_handlers();
-
- if self.window_is_active() {
- if let Some(event) = self.window.last_mouse_moved_event.clone() {
- self.dispatch_event(event, true);
- }
- }
-
- Ok(scene)
- }
-
- pub fn root_element(&self) -> &Box<dyn AnyRootElement> {
- let view_id = self.window.root_view().id();
- self.window.rendered_views.get(&view_id).unwrap()
- }
-
- pub fn rect_for_text_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
- let focused_view_id = self.window.focused_view_id?;
- self.window
- .rendered_views
- .get(&focused_view_id)?
- .rect_for_text_range(range_utf16, self)
- .log_err()
- .flatten()
- }
-
- pub fn set_window_title(&mut self, title: &str) {
- self.window.platform_window.set_title(title);
- }
-
- pub fn set_window_edited(&mut self, edited: bool) {
- self.window.platform_window.set_edited(edited);
- }
-
- pub fn is_topmost_window_for_position(&self, position: Vector2F) -> bool {
- self.window
- .platform_window
- .is_topmost_for_position(position)
- }
-
- pub fn activate_window(&self) {
- self.window.platform_window.activate();
- }
-
- pub fn window_is_active(&self) -> bool {
- self.window.is_active
- }
-
- pub fn window_is_fullscreen(&self) -> bool {
- self.window.is_fullscreen
- }
-
- pub fn dispatch_action(&mut self, view_id: Option<usize>, action: &dyn Action) -> bool {
- if let Some(view_id) = view_id {
- self.halt_action_dispatch = false;
- self.visit_dispatch_path(view_id, |view_id, capture_phase, cx| {
- cx.update_any_view(view_id, |view, cx| {
- let type_id = view.as_any().type_id();
- if let Some((name, mut handlers)) = cx
- .actions_mut(capture_phase)
- .get_mut(&type_id)
- .and_then(|h| h.remove_entry(&action.id()))
- {
- for handler in handlers.iter_mut().rev() {
- cx.halt_action_dispatch = true;
- handler(view, action, cx, view_id);
- if cx.halt_action_dispatch {
- break;
- }
- }
- cx.actions_mut(capture_phase)
- .get_mut(&type_id)
- .unwrap()
- .insert(name, handlers);
- }
- });
-
- !cx.halt_action_dispatch
- });
- }
-
- if !self.halt_action_dispatch {
- self.halt_action_dispatch = self.dispatch_global_action_any(action);
- }
-
- self.pending_effects
- .push_back(Effect::ActionDispatchNotification {
- action_id: action.id(),
- });
- self.halt_action_dispatch
- }
-
- /// Returns an iterator over all of the view ids from the passed view up to the root of the window
- /// Includes the passed view itself
- pub(crate) fn ancestors(&self, mut view_id: usize) -> impl Iterator<Item = usize> + '_ {
- std::iter::once(view_id)
- .into_iter()
- .chain(std::iter::from_fn(move || {
- if let Some(parent_id) = self.window.parents.get(&view_id) {
- view_id = *parent_id;
- Some(view_id)
- } else {
- None
- }
- }))
- }
-
- // Traverses the parent tree. Walks down the tree toward the passed
- // view calling visit with true. Then walks back up the tree calling visit with false.
- // If `visit` returns false this function will immediately return.
- fn visit_dispatch_path(
- &mut self,
- view_id: usize,
- mut visit: impl FnMut(usize, bool, &mut WindowContext) -> bool,
- ) {
- // List of view ids from the leaf to the root of the window
- let path = self.ancestors(view_id).collect::<Vec<_>>();
-
- // Walk down from the root to the leaf calling visit with capture_phase = true
- for view_id in path.iter().rev() {
- if !visit(*view_id, true, self) {
- return;
- }
- }
-
- // Walk up from the leaf to the root calling visit with capture_phase = false
- for view_id in path.iter() {
- if !visit(*view_id, false, self) {
- return;
- }
- }
- }
-
- pub fn focused_view_id(&self) -> Option<usize> {
- self.window.focused_view_id
- }
-
- pub fn focus(&mut self, view_id: Option<usize>) {
- self.app_context.focus(self.window_handle, view_id);
- }
-
- pub fn window_bounds(&self) -> WindowBounds {
- self.window.platform_window.bounds()
- }
-
- pub fn titlebar_height(&self) -> f32 {
- self.window.titlebar_height
- }
-
- pub fn window_appearance(&self) -> Appearance {
- self.window.appearance
- }
-
- pub fn window_display_uuid(&self) -> Option<Uuid> {
- self.window.platform_window.screen().display_uuid()
- }
-
- pub fn show_character_palette(&self) {
- self.window.platform_window.show_character_palette();
- }
-
- pub fn minimize_window(&self) {
- self.window.platform_window.minimize();
- }
-
- pub fn zoom_window(&self) {
- self.window.platform_window.zoom();
- }
-
- pub fn toggle_full_screen(&self) {
- self.window.platform_window.toggle_full_screen();
- }
-
- pub fn prompt(
- &self,
- level: PromptLevel,
- msg: &str,
- answers: &[&str],
- ) -> oneshot::Receiver<usize> {
- self.window.platform_window.prompt(level, msg, answers)
- }
-
- pub fn add_view<T, F>(&mut self, build_view: F) -> ViewHandle<T>
- where
- T: View,
- F: FnOnce(&mut ViewContext<T>) -> T,
- {
- self.add_option_view(|cx| Some(build_view(cx))).unwrap()
- }
-
- pub fn add_option_view<T, F>(&mut self, build_view: F) -> Option<ViewHandle<T>>
- where
- T: View,
- F: FnOnce(&mut ViewContext<T>) -> Option<T>,
- {
- let handle = self.window_handle;
- let view_id = post_inc(&mut self.next_id);
- let mut cx = ViewContext::mutable(self, view_id);
- let handle = if let Some(view) = build_view(&mut cx) {
- let mut keymap_context = KeymapContext::default();
- view.update_keymap_context(&mut keymap_context, cx.app_context());
- self.views_metadata.insert(
- (handle, view_id),
- ViewMetadata {
- type_id: TypeId::of::<T>(),
- keymap_context,
- },
- );
- self.views.insert((handle, view_id), Box::new(view));
- self.window
- .invalidation
- .get_or_insert_with(Default::default)
- .updated
- .insert(view_id);
- Some(ViewHandle::new(handle, view_id, &self.ref_counts))
- } else {
- None
- };
- handle
- }
-
- pub fn text_style(&self) -> TextStyle {
- self.window
- .text_style_stack
- .last()
- .cloned()
- .unwrap_or(TextStyle::default(&self.font_cache))
- }
-
- pub fn push_text_style(&mut self, refinement: &TextStyleRefinement) -> Result<()> {
- let mut style = self.text_style();
- style.refine(refinement, self.font_cache())?;
- self.window.text_style_stack.push(style);
- Ok(())
- }
-
- pub fn pop_text_style(&mut self) {
- self.window.text_style_stack.pop();
- }
-
- pub fn theme<T: 'static + Send + Sync>(&self) -> Arc<T> {
- self.window
- .theme_stack
- .iter()
- .rev()
- .find_map(|theme| {
- let entry = Arc::clone(theme);
- entry.downcast::<T>().ok()
- })
- .ok_or_else(|| anyhow!("no theme provided of type {}", type_name::<T>()))
- .unwrap()
- }
-
- pub fn push_theme<T: 'static + Send + Sync>(&mut self, theme: T) {
- self.window.theme_stack.push(Arc::new(theme));
- }
-
- pub fn pop_theme(&mut self) {
- self.window.theme_stack.pop();
- }
-}
-
-#[derive(Default)]
-pub struct LayoutEngine(Taffy);
-pub use taffy::style::Style as LayoutStyle;
-
-impl LayoutEngine {
- pub fn new() -> Self {
- Default::default()
- }
-
- pub fn add_node<C>(&mut self, style: LayoutStyle, children: C) -> Result<LayoutId>
- where
- C: IntoIterator<Item = LayoutId>,
- {
- let children = children.into_iter().collect::<Vec<_>>();
- if children.is_empty() {
- Ok(self.0.new_leaf(style)?)
- } else {
- Ok(self.0.new_with_children(style, &children)?)
- }
- }
-
- pub fn add_measured_node<F>(&mut self, style: LayoutStyle, measure: F) -> Result<LayoutId>
- where
- F: Fn(MeasureParams) -> Size<f32> + Sync + Send + 'static,
- {
- Ok(self
- .0
- .new_leaf_with_measure(style, MeasureFunc::Boxed(Box::new(MeasureFn(measure))))?)
- }
-
- pub fn compute_layout(&mut self, root: LayoutId, available_space: Vector2F) -> Result<()> {
- self.0.compute_layout(
- root,
- taffy::geometry::Size {
- width: available_space.x().into(),
- height: available_space.y().into(),
- },
- )?;
- Ok(())
- }
-
- pub fn computed_layout(&mut self, node: LayoutId) -> Result<Layout> {
- Ok(Layout::from(self.0.layout(node)?))
- }
-}
-
-pub struct MeasureFn<F>(F);
-
-impl<F: Send + Sync> Measurable for MeasureFn<F>
-where
- F: Fn(MeasureParams) -> Size<f32>,
-{
- fn measure(
- &self,
- known_dimensions: taffy::prelude::Size<Option<f32>>,
- available_space: taffy::prelude::Size<taffy::style::AvailableSpace>,
- ) -> taffy::prelude::Size<f32> {
- (self.0)(MeasureParams {
- known_dimensions: known_dimensions.into(),
- available_space: available_space.into(),
- })
- .into()
- }
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct Layout {
- pub bounds: RectF,
- pub order: u32,
-}
-
-pub struct MeasureParams {
- pub known_dimensions: Size<Option<f32>>,
- pub available_space: Size<AvailableSpace>,
-}
-
-#[derive(Clone, Debug)]
-pub enum AvailableSpace {
- /// The amount of space available is the specified number of pixels
- Pixels(f32),
- /// The amount of space available is indefinite and the node should be laid out under a min-content constraint
- MinContent,
- /// The amount of space available is indefinite and the node should be laid out under a max-content constraint
- MaxContent,
-}
-
-impl Default for AvailableSpace {
- fn default() -> Self {
- Self::Pixels(0.)
- }
-}
-
-impl From<taffy::prelude::AvailableSpace> for AvailableSpace {
- fn from(value: taffy::prelude::AvailableSpace) -> Self {
- match value {
- taffy::prelude::AvailableSpace::Definite(pixels) => Self::Pixels(pixels),
- taffy::prelude::AvailableSpace::MinContent => Self::MinContent,
- taffy::prelude::AvailableSpace::MaxContent => Self::MaxContent,
- }
- }
-}
-
-impl From<&taffy::tree::Layout> for Layout {
- fn from(value: &taffy::tree::Layout) -> Self {
- Self {
- bounds: RectF::new(
- vec2f(value.location.x, value.location.y),
- vec2f(value.size.width, value.size.height),
- ),
- order: value.order,
- }
- }
-}
-
-pub type LayoutId = taffy::prelude::NodeId;
-
-pub struct RenderParams {
- pub view_id: usize,
- pub titlebar_height: f32,
- pub refreshing: bool,
- pub appearance: Appearance,
-}
-
-#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
-pub enum Axis {
- #[default]
- Horizontal,
- Vertical,
-}
-
-impl Axis {
- pub fn invert(self) -> Self {
- match self {
- Self::Horizontal => Self::Vertical,
- Self::Vertical => Self::Horizontal,
- }
- }
-
- pub fn component(&self, point: Vector2F) -> f32 {
- match self {
- Self::Horizontal => point.x(),
- Self::Vertical => point.y(),
- }
- }
-}
-
-impl ToJson for Axis {
- fn to_json(&self) -> serde_json::Value {
- match self {
- Axis::Horizontal => json!("horizontal"),
- Axis::Vertical => json!("vertical"),
- }
- }
-}
-
-impl StaticColumnCount for Axis {}
-impl Bind for Axis {
- fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
- match self {
- Axis::Horizontal => "Horizontal",
- Axis::Vertical => "Vertical",
- }
- .bind(statement, start_index)
- }
-}
-
-impl Column for Axis {
- fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
- String::column(statement, start_index).and_then(|(axis_text, next_index)| {
- Ok((
- match axis_text.as_str() {
- "Horizontal" => Axis::Horizontal,
- "Vertical" => Axis::Vertical,
- _ => bail!("Stored serialized item kind is incorrect"),
- },
- next_index,
- ))
- })
- }
-}
-
-pub trait Vector2FExt {
- fn along(self, axis: Axis) -> f32;
-}
-
-impl Vector2FExt for Vector2F {
- fn along(self, axis: Axis) -> f32 {
- match axis {
- Axis::Horizontal => self.x(),
- Axis::Vertical => self.y(),
- }
- }
-}
-
-pub trait RectFExt {
- fn length_along(self, axis: Axis) -> f32;
-}
-
-impl RectFExt for RectF {
- fn length_along(self, axis: Axis) -> f32 {
- match axis {
- Axis::Horizontal => self.width(),
- Axis::Vertical => self.height(),
- }
- }
-}
-
-#[derive(Copy, Clone, Debug)]
-pub struct SizeConstraint {
- pub min: Vector2F,
- pub max: Vector2F,
-}
-
-impl SizeConstraint {
- pub fn new(min: Vector2F, max: Vector2F) -> Self {
- Self { min, max }
- }
-
- pub fn strict(size: Vector2F) -> Self {
- Self {
- min: size,
- max: size,
- }
- }
- pub fn loose(max: Vector2F) -> Self {
- Self {
- min: Vector2F::zero(),
- max,
- }
- }
-
- pub fn strict_along(axis: Axis, max: f32) -> Self {
- match axis {
- Axis::Horizontal => Self {
- min: vec2f(max, 0.0),
- max: vec2f(max, f32::INFINITY),
- },
- Axis::Vertical => Self {
- min: vec2f(0.0, max),
- max: vec2f(f32::INFINITY, max),
- },
- }
- }
-
- pub fn max_along(&self, axis: Axis) -> f32 {
- match axis {
- Axis::Horizontal => self.max.x(),
- Axis::Vertical => self.max.y(),
- }
- }
-
- pub fn min_along(&self, axis: Axis) -> f32 {
- match axis {
- Axis::Horizontal => self.min.x(),
- Axis::Vertical => self.min.y(),
- }
- }
-
- pub fn constrain(&self, size: Vector2F) -> Vector2F {
- vec2f(
- size.x().min(self.max.x()).max(self.min.x()),
- size.y().min(self.max.y()).max(self.min.y()),
- )
- }
-}
-
-impl Sub<Vector2F> for SizeConstraint {
- type Output = SizeConstraint;
-
- fn sub(self, rhs: Vector2F) -> SizeConstraint {
- SizeConstraint {
- min: self.min - rhs,
- max: self.max - rhs,
- }
- }
-}
-
-impl Default for SizeConstraint {
- fn default() -> Self {
- SizeConstraint {
- min: Vector2F::zero(),
- max: Vector2F::splat(f32::INFINITY),
- }
- }
-}
-
-impl ToJson for SizeConstraint {
- fn to_json(&self) -> serde_json::Value {
- json!({
- "min": self.min.to_json(),
- "max": self.max.to_json(),
- })
- }
-}
-
-#[derive(Clone)]
-pub struct ChildView {
- view_id: usize,
- view_name: &'static str,
-}
-
-impl ChildView {
- pub fn new(view: &AnyViewHandle, cx: &AppContext) -> Self {
- let view_name = cx.view_ui_name(view.window, view.id()).unwrap();
- Self {
- view_id: view.id(),
- view_name,
- }
- }
-}
-
-impl<V: 'static> Element<V> for ChildView {
- type LayoutState = ();
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- _: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
- let parent_id = cx.view_id();
- cx.window.new_parents.insert(self.view_id, parent_id);
- let size = rendered_view
- .layout(constraint, cx)
- .log_err()
- .unwrap_or(Vector2F::zero());
- cx.window.rendered_views.insert(self.view_id, rendered_view);
- (size, ())
- } else {
- log::error!(
- "layout called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
- self.view_id,
- self.view_name
- );
- (Vector2F::zero(), ())
- }
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- _: &mut Self::LayoutState,
- _: &mut V,
- cx: &mut ViewContext<V>,
- ) {
- if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
- rendered_view
- .paint(bounds.origin(), visible_bounds, cx)
- .log_err();
- cx.window.rendered_views.insert(self.view_id, rendered_view);
- } else {
- log::error!(
- "paint called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
- self.view_id,
- self.view_name
- );
- }
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- _: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- if let Some(rendered_view) = cx.window.rendered_views.get(&self.view_id) {
- rendered_view
- .rect_for_text_range(range_utf16, &cx.window_context)
- .log_err()
- .flatten()
- } else {
- log::error!(
- "rect_for_text_range called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
- self.view_id,
- self.view_name
- );
- None
- }
- }
-
- fn debug(
- &self,
- bounds: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- _: &V,
- cx: &ViewContext<V>,
- ) -> serde_json::Value {
- json!({
- "type": "ChildView",
- "bounds": bounds.to_json(),
- "child": if let Some(element) = cx.window.rendered_views.get(&self.view_id) {
- element.debug(&cx.window_context).log_err().unwrap_or_else(|| json!(null))
- } else {
- json!(null)
- }
- })
- }
-}
@@ -1,90 +0,0 @@
-use std::{cell::RefCell, ops::Range, rc::Rc};
-
-use pathfinder_geometry::rect::RectF;
-
-use crate::{platform::InputHandler, window::WindowContext, AnyView, AnyWindowHandle, AppContext};
-
-pub struct WindowInputHandler {
- pub app: Rc<RefCell<AppContext>>,
- pub window: AnyWindowHandle,
-}
-
-impl WindowInputHandler {
- fn read_focused_view<T, F>(&self, f: F) -> Option<T>
- where
- F: FnOnce(&dyn AnyView, &WindowContext) -> T,
- {
- // Input-related application hooks are sometimes called by the OS during
- // a call to a window-manipulation API, like prompting the user for file
- // paths. In that case, the AppContext will already be borrowed, so any
- // InputHandler methods need to fail gracefully.
- //
- // See https://github.com/zed-industries/community/issues/444
- let mut app = self.app.try_borrow_mut().ok()?;
- self.window.update_optional(&mut *app, |cx| {
- let view_id = cx.window.focused_view_id?;
- let view = cx.views.get(&(self.window, view_id))?;
- let result = f(view.as_ref(), &cx);
- Some(result)
- })
- }
-
- fn update_focused_view<T, F>(&mut self, f: F) -> Option<T>
- where
- F: FnOnce(&mut dyn AnyView, &mut WindowContext, usize) -> T,
- {
- let mut app = self.app.try_borrow_mut().ok()?;
- self.window
- .update(&mut *app, |cx| {
- let view_id = cx.window.focused_view_id?;
- cx.update_any_view(view_id, |view, cx| f(view, cx, view_id))
- })
- .flatten()
- }
-}
-
-impl InputHandler for WindowInputHandler {
- fn text_for_range(&self, range: Range<usize>) -> Option<String> {
- self.read_focused_view(|view, cx| view.text_for_range(range.clone(), cx))
- .flatten()
- }
-
- fn selected_text_range(&self) -> Option<Range<usize>> {
- self.read_focused_view(|view, cx| view.selected_text_range(cx))
- .flatten()
- }
-
- fn replace_text_in_range(&mut self, range: Option<Range<usize>>, text: &str) {
- self.update_focused_view(|view, cx, view_id| {
- view.replace_text_in_range(range, text, cx, view_id);
- });
- }
-
- fn marked_text_range(&self) -> Option<Range<usize>> {
- self.read_focused_view(|view, cx| view.marked_text_range(cx))
- .flatten()
- }
-
- fn unmark_text(&mut self) {
- self.update_focused_view(|view, cx, view_id| {
- view.unmark_text(cx, view_id);
- });
- }
-
- fn replace_and_mark_text_in_range(
- &mut self,
- range: Option<Range<usize>>,
- new_text: &str,
- new_selected_range: Option<Range<usize>>,
- ) {
- self.update_focused_view(|view, cx, view_id| {
- view.replace_and_mark_text_in_range(range, new_text, new_selected_range, cx, view_id);
- });
- }
-
- fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
- self.window.read_optional_with(&*self.app.borrow(), |cx| {
- cx.rect_for_text_range(range_utf16)
- })
- }
-}
@@ -1,12 +1,16 @@
-use anyhow::{anyhow, Result};
-use image::ImageFormat;
-use std::{borrow::Cow, cell::RefCell, collections::HashMap, sync::Arc};
-
-use crate::ImageData;
+use crate::{size, DevicePixels, Result, SharedString, Size};
+use anyhow::anyhow;
+use image::{Bgra, ImageBuffer};
+use std::{
+ borrow::Cow,
+ fmt,
+ hash::Hash,
+ sync::atomic::{AtomicUsize, Ordering::SeqCst},
+};
pub trait AssetSource: 'static + Send + Sync {
fn load(&self, path: &str) -> Result<Cow<[u8]>>;
- fn list(&self, path: &str) -> Vec<Cow<'static, str>>;
+ fn list(&self, path: &str) -> Result<Vec<SharedString>>;
}
impl AssetSource for () {
@@ -17,49 +21,44 @@ impl AssetSource for () {
))
}
- fn list(&self, _: &str) -> Vec<Cow<'static, str>> {
- vec![]
+ fn list(&self, _path: &str) -> Result<Vec<SharedString>> {
+ Ok(vec![])
}
}
-pub struct AssetCache {
- source: Box<dyn AssetSource>,
- svgs: RefCell<HashMap<String, usvg::Tree>>,
- pngs: RefCell<HashMap<String, Arc<ImageData>>>,
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct ImageId(usize);
+
+pub struct ImageData {
+ pub id: ImageId,
+ data: ImageBuffer<Bgra<u8>, Vec<u8>>,
}
-impl AssetCache {
- pub fn new(source: impl AssetSource) -> Self {
+impl ImageData {
+ pub fn new(data: ImageBuffer<Bgra<u8>, Vec<u8>>) -> Self {
+ static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
+
Self {
- source: Box::new(source),
- svgs: RefCell::new(HashMap::new()),
- pngs: RefCell::new(HashMap::new()),
+ id: ImageId(NEXT_ID.fetch_add(1, SeqCst)),
+ data,
}
}
- pub fn svg(&self, path: &str) -> Result<usvg::Tree> {
- let mut svgs = self.svgs.borrow_mut();
- if let Some(svg) = svgs.get(path) {
- Ok(svg.clone())
- } else {
- let bytes = self.source.load(path)?;
- let svg = usvg::Tree::from_data(&bytes, &usvg::Options::default())?;
- svgs.insert(path.to_string(), svg.clone());
- Ok(svg)
- }
+ pub fn as_bytes(&self) -> &[u8] {
+ &self.data
}
- pub fn png(&self, path: &str) -> Result<Arc<ImageData>> {
- let mut pngs = self.pngs.borrow_mut();
- if let Some(png) = pngs.get(path) {
- Ok(png.clone())
- } else {
- let bytes = self.source.load(path)?;
- let image = ImageData::new(
- image::load_from_memory_with_format(&bytes, ImageFormat::Png)?.into_bgra8(),
- );
- pngs.insert(path.to_string(), image.clone());
- Ok(image)
- }
+ pub fn size(&self) -> Size<DevicePixels> {
+ let (width, height) = self.data.dimensions();
+ size(width.into(), height.into())
+ }
+}
+
+impl fmt::Debug for ImageData {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("ImageData")
+ .field("id", &self.id)
+ .field("size", &self.data.dimensions())
+ .finish()
}
}
@@ -1,42 +0,0 @@
-use seahash::SeaHasher;
-use serde::{Deserialize, Serialize};
-use std::hash::{Hash, Hasher};
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct ClipboardItem {
- pub(crate) text: String,
- pub(crate) metadata: Option<String>,
-}
-
-impl ClipboardItem {
- pub fn new(text: String) -> Self {
- Self {
- text,
- metadata: None,
- }
- }
-
- pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
- self.metadata = Some(serde_json::to_string(&metadata).unwrap());
- self
- }
-
- pub fn text(&self) -> &String {
- &self.text
- }
-
- pub fn metadata<T>(&self) -> Option<T>
- where
- T: for<'a> Deserialize<'a>,
- {
- self.metadata
- .as_ref()
- .and_then(|m| serde_json::from_str(m).ok())
- }
-
- pub(crate) fn text_hash(text: &str) -> u64 {
- let mut hasher = SeaHasher::new();
- text.hash(&mut hasher);
- hasher.finish()
- }
-}
@@ -1,65 +1,223 @@
-use std::{
- borrow::Cow,
- fmt,
- ops::{Deref, DerefMut},
-};
+use anyhow::bail;
+use serde::de::{self, Deserialize, Deserializer, Visitor};
+use std::fmt;
+
+pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
+ let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
+ let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
+ let b = (hex & 0xFF) as f32 / 255.0;
+ Rgba { r, g, b, a: 1.0 }.into()
+}
-use crate::json::ToJson;
-use pathfinder_color::{ColorF, ColorU};
-use schemars::JsonSchema;
-use serde::{
- de::{self, Unexpected},
- Deserialize, Deserializer,
-};
-use serde_json::json;
+pub fn rgba(hex: u32) -> Rgba {
+ let r = ((hex >> 24) & 0xFF) as f32 / 255.0;
+ let g = ((hex >> 16) & 0xFF) as f32 / 255.0;
+ let b = ((hex >> 8) & 0xFF) as f32 / 255.0;
+ let a = (hex & 0xFF) as f32 / 255.0;
+ Rgba { r, g, b, a }
+}
-#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, JsonSchema)]
-#[repr(transparent)]
-pub struct Color(#[schemars(with = "String")] pub ColorU);
+#[derive(PartialEq, Clone, Copy, Default)]
+pub struct Rgba {
+ pub r: f32,
+ pub g: f32,
+ pub b: f32,
+ pub a: f32,
+}
+
+impl fmt::Debug for Rgba {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "rgba({:#010x})", u32::from(*self))
+ }
+}
-pub fn color(rgba: u32) -> Color {
- Color::from_u32(rgba)
+impl Rgba {
+ pub fn blend(&self, other: Rgba) -> Self {
+ if other.a >= 1.0 {
+ other
+ } else if other.a <= 0.0 {
+ return *self;
+ } else {
+ return Rgba {
+ r: (self.r * (1.0 - other.a)) + (other.r * other.a),
+ g: (self.g * (1.0 - other.a)) + (other.g * other.a),
+ b: (self.b * (1.0 - other.a)) + (other.b * other.a),
+ a: self.a,
+ };
+ }
+ }
}
-pub fn rgb(r: f32, g: f32, b: f32) -> Color {
- Color(ColorF::new(r, g, b, 1.).to_u8())
+impl From<Rgba> for u32 {
+ fn from(rgba: Rgba) -> Self {
+ let r = (rgba.r * 255.0) as u32;
+ let g = (rgba.g * 255.0) as u32;
+ let b = (rgba.b * 255.0) as u32;
+ let a = (rgba.a * 255.0) as u32;
+ (r << 24) | (g << 16) | (b << 8) | a
+ }
}
-pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Color {
- Color(ColorF::new(r, g, b, a).to_u8())
+struct RgbaVisitor;
+
+impl<'de> Visitor<'de> for RgbaVisitor {
+ type Value = Rgba;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("a string in the format #rrggbb or #rrggbbaa")
+ }
+
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<Rgba, E> {
+ Rgba::try_from(value).map_err(E::custom)
+ }
}
-pub fn transparent_black() -> Color {
- Color(ColorU::transparent_black())
+impl<'de> Deserialize<'de> for Rgba {
+ fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+ deserializer.deserialize_str(RgbaVisitor)
+ }
}
-pub fn black() -> Color {
- Color(ColorU::black())
+impl From<Hsla> for Rgba {
+ fn from(color: Hsla) -> Self {
+ let h = color.h;
+ let s = color.s;
+ let l = color.l;
+
+ let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
+ let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
+ let m = l - c / 2.0;
+ let cm = c + m;
+ let xm = x + m;
+
+ let (r, g, b) = match (h * 6.0).floor() as i32 {
+ 0 | 6 => (cm, xm, m),
+ 1 => (xm, cm, m),
+ 2 => (m, cm, xm),
+ 3 => (m, xm, cm),
+ 4 => (xm, m, cm),
+ _ => (cm, m, xm),
+ };
+
+ Rgba {
+ r,
+ g,
+ b,
+ a: color.a,
+ }
+ }
}
-pub fn white() -> Color {
- Color(ColorU::white())
+impl TryFrom<&'_ str> for Rgba {
+ type Error = anyhow::Error;
+
+ fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
+ const RGB: usize = "rgb".len();
+ const RGBA: usize = "rgba".len();
+ const RRGGBB: usize = "rrggbb".len();
+ const RRGGBBAA: usize = "rrggbbaa".len();
+
+ const EXPECTED_FORMATS: &str = "Expected #rgb, #rgba, #rrggbb, or #rrggbbaa";
+
+ let Some(("", hex)) = value.trim().split_once('#') else {
+ bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}");
+ };
+
+ let (r, g, b, a) = match hex.len() {
+ RGB | RGBA => {
+ let r = u8::from_str_radix(&hex[0..1], 16)?;
+ let g = u8::from_str_radix(&hex[1..2], 16)?;
+ let b = u8::from_str_radix(&hex[2..3], 16)?;
+ let a = if hex.len() == RGBA {
+ u8::from_str_radix(&hex[3..4], 16)?
+ } else {
+ 0xf
+ };
+
+ /// Duplicates a given hex digit.
+ /// E.g., `0xf` -> `0xff`.
+ const fn duplicate(value: u8) -> u8 {
+ value << 4 | value
+ }
+
+ (duplicate(r), duplicate(g), duplicate(b), duplicate(a))
+ }
+ RRGGBB | RRGGBBAA => {
+ let r = u8::from_str_radix(&hex[0..2], 16)?;
+ let g = u8::from_str_radix(&hex[2..4], 16)?;
+ let b = u8::from_str_radix(&hex[4..6], 16)?;
+ let a = if hex.len() == RRGGBBAA {
+ u8::from_str_radix(&hex[6..8], 16)?
+ } else {
+ 0xff
+ };
+ (r, g, b, a)
+ }
+ _ => bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}"),
+ };
+
+ Ok(Rgba {
+ r: r as f32 / 255.,
+ g: g as f32 / 255.,
+ b: b as f32 / 255.,
+ a: a as f32 / 255.,
+ })
+ }
}
-pub fn red() -> Color {
- color(0xff0000ff)
+#[derive(Default, Copy, Clone, Debug)]
+#[repr(C)]
+pub struct Hsla {
+ pub h: f32,
+ pub s: f32,
+ pub l: f32,
+ pub a: f32,
}
-pub fn green() -> Color {
- color(0x00ff00ff)
+impl PartialEq for Hsla {
+ fn eq(&self, other: &Self) -> bool {
+ self.h
+ .total_cmp(&other.h)
+ .then(self.s.total_cmp(&other.s))
+ .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a)))
+ .is_eq()
+ }
}
-pub fn blue() -> Color {
- color(0x0000ffff)
+impl PartialOrd for Hsla {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ // SAFETY: The total ordering relies on this always being Some()
+ Some(
+ self.h
+ .total_cmp(&other.h)
+ .then(self.s.total_cmp(&other.s))
+ .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a))),
+ )
+ }
}
-pub fn yellow() -> Color {
- color(0xffff00ff)
+impl Ord for Hsla {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ // SAFETY: The partial comparison is a total comparison
+ unsafe { self.partial_cmp(other).unwrap_unchecked() }
+ }
}
-impl Color {
- pub fn transparent_black() -> Self {
- transparent_black()
+impl Hsla {
+ pub fn to_rgb(self) -> Rgba {
+ self.into()
+ }
+
+ pub fn red() -> Self {
+ red()
+ }
+
+ pub fn green() -> Self {
+ green()
+ }
+
+ pub fn blue() -> Self {
+ blue()
}
pub fn black() -> Self {
@@ -70,107 +228,230 @@ impl Color {
white()
}
- pub fn red() -> Self {
- Color::from_u32(0xff0000ff)
+ pub fn transparent_black() -> Self {
+ transparent_black()
+ }
+}
+
+impl Eq for Hsla {}
+
+pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
+ Hsla {
+ h: h.clamp(0., 1.),
+ s: s.clamp(0., 1.),
+ l: l.clamp(0., 1.),
+ a: a.clamp(0., 1.),
}
+}
- pub fn green() -> Self {
- Color::from_u32(0x00ff00ff)
+pub fn black() -> Hsla {
+ Hsla {
+ h: 0.,
+ s: 0.,
+ l: 0.,
+ a: 1.,
}
+}
- pub fn blue() -> Self {
- Color::from_u32(0x0000ffff)
+pub fn transparent_black() -> Hsla {
+ Hsla {
+ h: 0.,
+ s: 0.,
+ l: 0.,
+ a: 0.,
+ }
+}
+
+pub fn white() -> Hsla {
+ Hsla {
+ h: 0.,
+ s: 0.,
+ l: 1.,
+ a: 1.,
}
+}
- pub fn yellow() -> Self {
- Color::from_u32(0xffff00ff)
+pub fn red() -> Hsla {
+ Hsla {
+ h: 0.,
+ s: 1.,
+ l: 0.5,
+ a: 1.,
}
+}
- pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
- Self(ColorU::new(r, g, b, a))
+pub fn blue() -> Hsla {
+ Hsla {
+ h: 0.6,
+ s: 1.,
+ l: 0.5,
+ a: 1.,
}
+}
- pub fn from_u32(rgba: u32) -> Self {
- Self(ColorU::from_u32(rgba))
+pub fn green() -> Hsla {
+ Hsla {
+ h: 0.33,
+ s: 1.,
+ l: 0.5,
+ a: 1.,
}
+}
- pub fn blend(source: Color, dest: Color) -> Color {
- // Skip blending if we don't need it.
- if source.a == 255 {
- return source;
- } else if source.a == 0 {
- return dest;
- }
+pub fn yellow() -> Hsla {
+ Hsla {
+ h: 0.16,
+ s: 1.,
+ l: 0.5,
+ a: 1.,
+ }
+}
- let source = source.0.to_f32();
- let dest = dest.0.to_f32();
+impl Hsla {
+ /// Returns true if the HSLA color is fully transparent, false otherwise.
+ pub fn is_transparent(&self) -> bool {
+ self.a == 0.0
+ }
- let a = source.a() + (dest.a() * (1. - source.a()));
- let r = ((source.r() * source.a()) + (dest.r() * dest.a() * (1. - source.a()))) / a;
- let g = ((source.g() * source.a()) + (dest.g() * dest.a() * (1. - source.a()))) / a;
- let b = ((source.b() * source.a()) + (dest.b() * dest.a() * (1. - source.a()))) / a;
+ /// Blends `other` on top of `self` based on `other`'s alpha value. The resulting color is a combination of `self`'s and `other`'s colors.
+ ///
+ /// If `other`'s alpha value is 1.0 or greater, `other` color is fully opaque, thus `other` is returned as the output color.
+ /// If `other`'s alpha value is 0.0 or less, `other` color is fully transparent, thus `self` is returned as the output color.
+ /// Else, the output color is calculated as a blend of `self` and `other` based on their weighted alpha values.
+ ///
+ /// Assumptions:
+ /// - Alpha values are contained in the range [0, 1], with 1 as fully opaque and 0 as fully transparent.
+ /// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing it's own alpha value.
+ /// - RGB color components are contained in the range [0, 1].
+ /// - If `self` and `other` colors are out of the valid range, the blend operation's output and behavior is undefined.
+ pub fn blend(self, other: Hsla) -> Hsla {
+ let alpha = other.a;
+
+ if alpha >= 1.0 {
+ other
+ } else if alpha <= 0.0 {
+ return self;
+ } else {
+ let converted_self = Rgba::from(self);
+ let converted_other = Rgba::from(other);
+ let blended_rgb = converted_self.blend(converted_other);
+ return Hsla::from(blended_rgb);
+ }
+ }
- Self(ColorF::new(r, g, b, a).to_u8())
+ /// Fade out the color by a given factor. This factor should be between 0.0 and 1.0.
+ /// Where 0.0 will leave the color unchanged, and 1.0 will completely fade out the color.
+ pub fn fade_out(&mut self, factor: f32) {
+ self.a *= 1.0 - factor.clamp(0., 1.);
}
+}
- pub fn fade_out(&mut self, fade: f32) {
- let fade = fade.clamp(0., 1.);
- self.0.a = (self.0.a as f32 * (1. - fade)) as u8;
+// impl From<Hsla> for Rgba {
+// fn from(value: Hsla) -> Self {
+// let h = value.h;
+// let s = value.s;
+// let l = value.l;
+
+// let c = (1 - |2L - 1|) X s
+// }
+// }
+
+impl From<Rgba> for Hsla {
+ fn from(color: Rgba) -> Self {
+ let r = color.r;
+ let g = color.g;
+ let b = color.b;
+
+ let max = r.max(g.max(b));
+ let min = r.min(g.min(b));
+ let delta = max - min;
+
+ let l = (max + min) / 2.0;
+ let s = if l == 0.0 || l == 1.0 {
+ 0.0
+ } else if l < 0.5 {
+ delta / (2.0 * l)
+ } else {
+ delta / (2.0 - 2.0 * l)
+ };
+
+ let h = if delta == 0.0 {
+ 0.0
+ } else if max == r {
+ ((g - b) / delta).rem_euclid(6.0) / 6.0
+ } else if max == g {
+ ((b - r) / delta + 2.0) / 6.0
+ } else {
+ ((r - g) / delta + 4.0) / 6.0
+ };
+
+ Hsla {
+ h,
+ s,
+ l,
+ a: color.a,
+ }
}
}
-impl<'de> Deserialize<'de> for Color {
+impl<'de> Deserialize<'de> for Hsla {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
- let literal: Cow<str> = Deserialize::deserialize(deserializer)?;
- if let Some(digits) = literal.strip_prefix('#') {
- if let Ok(value) = u32::from_str_radix(digits, 16) {
- if digits.len() == 6 {
- return Ok(Color::from_u32((value << 8) | 0xFF));
- } else if digits.len() == 8 {
- return Ok(Color::from_u32(value));
- }
- }
- }
- Err(de::Error::invalid_value(
- Unexpected::Str(literal.as_ref()),
- &"#RRGGBB[AA]",
- ))
+ // First, deserialize it into Rgba
+ let rgba = Rgba::deserialize(deserializer)?;
+
+ // Then, use the From<Rgba> for Hsla implementation to convert it
+ Ok(Hsla::from(rgba))
}
}
-impl From<u32> for Color {
- fn from(value: u32) -> Self {
- Self(ColorU::from_u32(value))
+#[cfg(test)]
+mod tests {
+ use serde_json::json;
+
+ use super::*;
+
+ #[test]
+ fn test_deserialize_three_value_hex_to_rgba() {
+ let actual: Rgba = serde_json::from_value(json!("#f09")).unwrap();
+
+ assert_eq!(actual, rgba(0xff0099ff))
}
-}
-impl ToJson for Color {
- fn to_json(&self) -> serde_json::Value {
- json!(format!(
- "0x{:x}{:x}{:x}{:x}",
- self.0.r, self.0.g, self.0.b, self.0.a
- ))
+ #[test]
+ fn test_deserialize_four_value_hex_to_rgba() {
+ let actual: Rgba = serde_json::from_value(json!("#f09f")).unwrap();
+
+ assert_eq!(actual, rgba(0xff0099ff))
}
-}
-impl Deref for Color {
- type Target = ColorU;
- fn deref(&self) -> &Self::Target {
- &self.0
+ #[test]
+ fn test_deserialize_six_value_hex_to_rgba() {
+ let actual: Rgba = serde_json::from_value(json!("#ff0099")).unwrap();
+
+ assert_eq!(actual, rgba(0xff0099ff))
}
-}
-impl DerefMut for Color {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.0
+ #[test]
+ fn test_deserialize_eight_value_hex_to_rgba() {
+ let actual: Rgba = serde_json::from_value(json!("#ff0099ff")).unwrap();
+
+ assert_eq!(actual, rgba(0xff0099ff))
}
-}
-impl fmt::Debug for Color {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- self.0.fmt(f)
+ #[test]
+ fn test_deserialize_eight_value_hex_with_padding_to_rgba() {
+ let actual: Rgba = serde_json::from_value(json!(" #f5f5f5ff ")).unwrap();
+
+ assert_eq!(actual, rgba(0xf5f5f5ff))
+ }
+
+ #[test]
+ fn test_deserialize_eight_value_hex_with_mixed_case_to_rgba() {
+ let actual: Rgba = serde_json::from_value(json!("#DeAdbEeF")).unwrap();
+
+ assert_eq!(actual, rgba(0xdeadbeef))
}
}
@@ -1 +0,0 @@
-
@@ -1,740 +0,0 @@
-mod align;
-mod canvas;
-mod clipped;
-mod component;
-mod constrained_box;
-mod container;
-mod empty;
-mod expanded;
-mod flex;
-mod hook;
-mod image;
-mod keystroke_label;
-mod label;
-mod list;
-mod mouse_event_handler;
-mod overlay;
-mod resizable;
-mod stack;
-mod svg;
-mod text;
-mod tooltip;
-mod uniform_list;
-
-pub use self::{
- align::*, canvas::*, component::*, constrained_box::*, container::*, empty::*, flex::*,
- hook::*, image::*, keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*,
- resizable::*, stack::*, svg::*, text::*, tooltip::*, uniform_list::*,
-};
-pub use crate::window::ChildView;
-
-use self::{clipped::Clipped, expanded::Expanded};
-use crate::{
- geometry::{
- rect::RectF,
- vector::{vec2f, Vector2F},
- },
- json, Action, Entity, SizeConstraint, TypeTag, View, ViewContext, WeakViewHandle,
- WindowContext,
-};
-use anyhow::{anyhow, Result};
-use core::panic;
-use json::ToJson;
-use std::{
- any::{type_name, Any},
- borrow::Cow,
- mem,
- ops::Range,
-};
-
-pub trait Element<V: 'static>: 'static {
- type LayoutState;
- type PaintState;
-
- fn view_name(&self) -> &'static str {
- type_name::<V>()
- }
-
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState);
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- layout: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState;
-
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- bounds: RectF,
- visible_bounds: RectF,
- layout: &Self::LayoutState,
- paint: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF>;
-
- fn metadata(&self) -> Option<&dyn Any> {
- None
- }
-
- fn debug(
- &self,
- bounds: RectF,
- layout: &Self::LayoutState,
- paint: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> serde_json::Value;
-
- fn into_any(self) -> AnyElement<V>
- where
- Self: 'static + Sized,
- {
- AnyElement {
- state: Box::new(ElementState::Init { element: self }),
- name: None,
- }
- }
-
- fn into_any_named(self, name: impl Into<Cow<'static, str>>) -> AnyElement<V>
- where
- Self: 'static + Sized,
- {
- AnyElement {
- state: Box::new(ElementState::Init { element: self }),
- name: Some(name.into()),
- }
- }
-
- fn into_root_element(self, cx: &ViewContext<V>) -> RootElement<V>
- where
- Self: 'static + Sized,
- {
- RootElement {
- element: self.into_any(),
- view: cx.handle().downgrade(),
- }
- }
-
- fn constrained(self) -> ConstrainedBox<V>
- where
- Self: 'static + Sized,
- {
- ConstrainedBox::new(self.into_any())
- }
-
- fn aligned(self) -> Align<V>
- where
- Self: 'static + Sized,
- {
- Align::new(self.into_any())
- }
-
- fn clipped(self) -> Clipped<V>
- where
- Self: 'static + Sized,
- {
- Clipped::new(self.into_any())
- }
-
- fn contained(self) -> Container<V>
- where
- Self: 'static + Sized,
- {
- Container::new(self.into_any())
- }
-
- fn expanded(self) -> Expanded<V>
- where
- Self: 'static + Sized,
- {
- Expanded::new(self.into_any())
- }
-
- fn flex(self, flex: f32, expanded: bool) -> FlexItem<V>
- where
- Self: 'static + Sized,
- {
- FlexItem::new(self.into_any()).flex(flex, expanded)
- }
-
- fn flex_float(self) -> FlexItem<V>
- where
- Self: 'static + Sized,
- {
- FlexItem::new(self.into_any()).float()
- }
-
- fn with_dynamic_tooltip(
- self,
- tag: TypeTag,
- id: usize,
- text: impl Into<Cow<'static, str>>,
- action: Option<Box<dyn Action>>,
- style: TooltipStyle,
- cx: &mut ViewContext<V>,
- ) -> Tooltip<V>
- where
- Self: 'static + Sized,
- {
- Tooltip::new_dynamic(tag, id, text, action, style, self.into_any(), cx)
- }
- fn with_tooltip<Tag: 'static>(
- self,
- id: usize,
- text: impl Into<Cow<'static, str>>,
- action: Option<Box<dyn Action>>,
- style: TooltipStyle,
- cx: &mut ViewContext<V>,
- ) -> Tooltip<V>
- where
- Self: 'static + Sized,
- {
- Tooltip::new::<Tag>(id, text, action, style, self.into_any(), cx)
- }
-
- /// Uses the the given element to calculate resizes for the given tag
- fn provide_resize_bounds<Tag: 'static>(self) -> BoundsProvider<V, Tag>
- where
- Self: 'static + Sized,
- {
- BoundsProvider::<_, Tag>::new(self.into_any())
- }
-
- /// Calls the given closure with the new size of the element whenever the
- /// handle is dragged. This will be calculated in relation to the bounds
- /// provided by the given tag
- fn resizable<Tag: 'static>(
- self,
- side: HandleSide,
- size: f32,
- on_resize: impl 'static + FnMut(&mut V, Option<f32>, &mut ViewContext<V>),
- ) -> Resizable<V>
- where
- Self: 'static + Sized,
- {
- Resizable::new::<Tag>(self.into_any(), side, size, on_resize)
- }
-
- fn mouse<Tag: 'static>(self, region_id: usize) -> MouseEventHandler<V>
- where
- Self: Sized,
- {
- MouseEventHandler::for_child::<Tag>(self.into_any(), region_id)
- }
-
- fn component(self) -> StatelessElementAdapter
- where
- Self: Sized,
- {
- StatelessElementAdapter::new(self.into_any())
- }
-
- fn stateful_component(self) -> StatefulElementAdapter<V>
- where
- Self: Sized,
- {
- StatefulElementAdapter::new(self.into_any())
- }
-
- fn styleable_component(self) -> StylableAdapter<StatelessElementAdapter>
- where
- Self: Sized,
- {
- StatelessElementAdapter::new(self.into_any()).stylable()
- }
-}
-
-trait AnyElementState<V> {
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Vector2F;
-
- fn paint(
- &mut self,
- origin: Vector2F,
- visible_bounds: RectF,
- view: &mut V,
- cx: &mut ViewContext<V>,
- );
-
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF>;
-
- fn debug(&self, view: &V, cx: &ViewContext<V>) -> serde_json::Value;
-
- fn size(&self) -> Vector2F;
-
- fn metadata(&self) -> Option<&dyn Any>;
-}
-
-enum ElementState<V: 'static, E: Element<V>> {
- Empty,
- Init {
- element: E,
- },
- PostLayout {
- element: E,
- constraint: SizeConstraint,
- size: Vector2F,
- layout: E::LayoutState,
- },
- PostPaint {
- element: E,
- constraint: SizeConstraint,
- bounds: RectF,
- visible_bounds: RectF,
- layout: E::LayoutState,
- paint: E::PaintState,
- },
-}
-
-impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Vector2F {
- let result;
- *self = match mem::take(self) {
- ElementState::Empty => unreachable!(),
- ElementState::Init { mut element }
- | ElementState::PostLayout { mut element, .. }
- | ElementState::PostPaint { mut element, .. } => {
- let (size, layout) = element.layout(constraint, view, cx);
- debug_assert!(
- size.x().is_finite(),
- "Element for {:?} had infinite x size after layout",
- element.view_name()
- );
- debug_assert!(
- size.y().is_finite(),
- "Element for {:?} had infinite y size after layout",
- element.view_name()
- );
-
- result = size;
- ElementState::PostLayout {
- element,
- constraint,
- size,
- layout,
- }
- }
- };
- result
- }
-
- fn paint(
- &mut self,
- origin: Vector2F,
- visible_bounds: RectF,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) {
- *self = match mem::take(self) {
- ElementState::PostLayout {
- mut element,
- constraint,
- size,
- mut layout,
- } => {
- let bounds = RectF::new(origin, size);
- let paint = element.paint(bounds, visible_bounds, &mut layout, view, cx);
- ElementState::PostPaint {
- element,
- constraint,
- bounds,
- visible_bounds,
- layout,
- paint,
- }
- }
- ElementState::PostPaint {
- mut element,
- constraint,
- bounds,
- mut layout,
- ..
- } => {
- let bounds = RectF::new(origin, bounds.size());
- let paint = element.paint(bounds, visible_bounds, &mut layout, view, cx);
- ElementState::PostPaint {
- element,
- constraint,
- bounds,
- visible_bounds,
- layout,
- paint,
- }
- }
- ElementState::Empty => panic!("invalid element lifecycle state"),
- ElementState::Init { .. } => {
- panic!("invalid element lifecycle state, paint called before layout")
- }
- }
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- if let ElementState::PostPaint {
- element,
- bounds,
- visible_bounds,
- layout,
- paint,
- ..
- } = self
- {
- element.rect_for_text_range(
- range_utf16,
- *bounds,
- *visible_bounds,
- layout,
- paint,
- view,
- cx,
- )
- } else {
- None
- }
- }
-
- fn size(&self) -> Vector2F {
- match self {
- ElementState::Empty | ElementState::Init { .. } => {
- panic!("invalid element lifecycle state")
- }
- ElementState::PostLayout { size, .. } => *size,
- ElementState::PostPaint { bounds, .. } => bounds.size(),
- }
- }
-
- fn metadata(&self) -> Option<&dyn Any> {
- match self {
- ElementState::Empty => unreachable!(),
- ElementState::Init { element }
- | ElementState::PostLayout { element, .. }
- | ElementState::PostPaint { element, .. } => element.metadata(),
- }
- }
-
- fn debug(&self, view: &V, cx: &ViewContext<V>) -> serde_json::Value {
- match self {
- ElementState::PostPaint {
- element,
- constraint,
- bounds,
- visible_bounds,
- layout,
- paint,
- } => {
- let mut value = element.debug(*bounds, layout, paint, view, cx);
- if let json::Value::Object(map) = &mut value {
- let mut new_map: crate::json::Map<String, serde_json::Value> =
- Default::default();
- if let Some(typ) = map.remove("type") {
- new_map.insert("type".into(), typ);
- }
- new_map.insert("constraint".into(), constraint.to_json());
- new_map.insert("bounds".into(), bounds.to_json());
- new_map.insert("visible_bounds".into(), visible_bounds.to_json());
- new_map.append(map);
- json::Value::Object(new_map)
- } else {
- value
- }
- }
-
- _ => panic!("invalid element lifecycle state"),
- }
- }
-}
-
-impl<V, E: Element<V>> Default for ElementState<V, E> {
- fn default() -> Self {
- Self::Empty
- }
-}
-
-pub struct AnyElement<V> {
- state: Box<dyn AnyElementState<V>>,
- name: Option<Cow<'static, str>>,
-}
-
-impl<V> AnyElement<V> {
- pub fn name(&self) -> Option<&str> {
- self.name.as_deref()
- }
-
- pub fn metadata<T: 'static>(&self) -> Option<&T> {
- self.state
- .metadata()
- .and_then(|data| data.downcast_ref::<T>())
- }
-
- pub fn layout(
- &mut self,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Vector2F {
- self.state.layout(constraint, view, cx)
- }
-
- pub fn paint(
- &mut self,
- origin: Vector2F,
- visible_bounds: RectF,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) {
- self.state.paint(origin, visible_bounds, view, cx);
- }
-
- pub fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- self.state.rect_for_text_range(range_utf16, view, cx)
- }
-
- pub fn size(&self) -> Vector2F {
- self.state.size()
- }
-
- pub fn debug(&self, view: &V, cx: &ViewContext<V>) -> json::Value {
- let mut value = self.state.debug(view, cx);
-
- if let Some(name) = &self.name {
- if let json::Value::Object(map) = &mut value {
- let mut new_map: crate::json::Map<String, serde_json::Value> = Default::default();
- new_map.insert("name".into(), json::Value::String(name.to_string()));
- new_map.append(map);
- return json::Value::Object(new_map);
- }
- }
-
- value
- }
-
- pub fn with_metadata<T, F, R>(&self, f: F) -> R
- where
- T: 'static,
- F: FnOnce(Option<&T>) -> R,
- {
- f(self.state.metadata().and_then(|m| m.downcast_ref()))
- }
-}
-
-impl<V: 'static> Element<V> for AnyElement<V> {
- type LayoutState = ();
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- let size = self.layout(constraint, view, cx);
- (size, ())
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- _: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- self.paint(bounds.origin(), visible_bounds, view, cx);
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- self.rect_for_text_range(range_utf16, view, cx)
- }
-
- fn debug(
- &self,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> serde_json::Value {
- self.debug(view, cx)
- }
-
- fn into_any(self) -> AnyElement<V>
- where
- Self: Sized,
- {
- self
- }
-}
-
-impl Entity for AnyElement<()> {
- type Event = ();
-}
-
-// impl View for AnyElement<()> {}
-
-pub struct RootElement<V> {
- element: AnyElement<V>,
- view: WeakViewHandle<V>,
-}
-
-impl<V> RootElement<V> {
- pub fn new(element: AnyElement<V>, view: WeakViewHandle<V>) -> Self {
- Self { element, view }
- }
-}
-
-pub trait AnyRootElement {
- fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result<Vector2F>;
- fn paint(
- &mut self,
- origin: Vector2F,
- visible_bounds: RectF,
- cx: &mut WindowContext,
- ) -> Result<()>;
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- cx: &WindowContext,
- ) -> Result<Option<RectF>>;
- fn debug(&self, cx: &WindowContext) -> Result<serde_json::Value>;
- fn name(&self) -> Option<&str>;
-}
-
-impl<V: View> AnyRootElement for RootElement<V> {
- fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result<Vector2F> {
- let view = self
- .view
- .upgrade(cx)
- .ok_or_else(|| anyhow!("layout called on a root element for a dropped view"))?;
- view.update(cx, |view, cx| Ok(self.element.layout(constraint, view, cx)))
- }
-
- fn paint(
- &mut self,
- origin: Vector2F,
- visible_bounds: RectF,
- cx: &mut WindowContext,
- ) -> Result<()> {
- let view = self
- .view
- .upgrade(cx)
- .ok_or_else(|| anyhow!("paint called on a root element for a dropped view"))?;
-
- view.update(cx, |view, cx| {
- self.element.paint(origin, visible_bounds, view, cx);
- Ok(())
- })
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- cx: &WindowContext,
- ) -> Result<Option<RectF>> {
- let view = self.view.upgrade(cx).ok_or_else(|| {
- anyhow!("rect_for_text_range called on a root element for a dropped view")
- })?;
- let view = view.read(cx);
- let view_context = ViewContext::immutable(cx, self.view.id());
- Ok(self
- .element
- .rect_for_text_range(range_utf16, view, &view_context))
- }
-
- fn debug(&self, cx: &WindowContext) -> Result<serde_json::Value> {
- let view = self
- .view
- .upgrade(cx)
- .ok_or_else(|| anyhow!("debug called on a root element for a dropped view"))?;
- let view = view.read(cx);
- let view_context = ViewContext::immutable(cx, self.view.id());
- Ok(serde_json::json!({
- "view_id": self.view.id(),
- "view_name": V::ui_name(),
- "view": view.debug_json(cx),
- "element": self.element.debug(view, &view_context)
- }))
- }
-
- fn name(&self) -> Option<&str> {
- self.element.name()
- }
-}
-
-pub trait ParentElement<'a, V: 'static>: Extend<AnyElement<V>> + Sized {
- fn add_children<E: Element<V>>(&mut self, children: impl IntoIterator<Item = E>) {
- self.extend(children.into_iter().map(|child| child.into_any()));
- }
-
- fn add_child<D: Element<V>>(&mut self, child: D) {
- self.extend(Some(child.into_any()));
- }
-
- fn with_children<D: Element<V>>(mut self, children: impl IntoIterator<Item = D>) -> Self {
- self.extend(children.into_iter().map(|child| child.into_any()));
- self
- }
-
- fn with_child<D: Element<V>>(mut self, child: D) -> Self {
- self.extend(Some(child.into_any()));
- self
- }
-}
-
-impl<'a, V, T> ParentElement<'a, V> for T
-where
- V: 'static,
- T: Extend<AnyElement<V>>,
-{
-}
-
-pub fn constrain_size_preserving_aspect_ratio(max_size: Vector2F, size: Vector2F) -> Vector2F {
- if max_size.x().is_infinite() && max_size.y().is_infinite() {
- size
- } else if max_size.x().is_infinite() || max_size.x() / max_size.y() > size.x() / size.y() {
- vec2f(size.x() * max_size.y() / size.y(), max_size.y())
- } else {
- vec2f(max_size.x(), size.y() * max_size.x() / size.x())
- }
-}
@@ -1,115 +0,0 @@
-use crate::{
- geometry::{rect::RectF, vector::Vector2F},
- json, AnyElement, Element, SizeConstraint, ViewContext,
-};
-use json::ToJson;
-
-use serde_json::json;
-
-pub struct Align<V> {
- child: AnyElement<V>,
- alignment: Vector2F,
-}
-
-impl<V> Align<V> {
- pub fn new(child: AnyElement<V>) -> Self {
- Self {
- child,
- alignment: Vector2F::zero(),
- }
- }
-
- pub fn top(mut self) -> Self {
- self.alignment.set_y(-1.0);
- self
- }
-
- pub fn bottom(mut self) -> Self {
- self.alignment.set_y(1.0);
- self
- }
-
- pub fn left(mut self) -> Self {
- self.alignment.set_x(-1.0);
- self
- }
-
- pub fn right(mut self) -> Self {
- self.alignment.set_x(1.0);
- self
- }
-}
-
-impl<V: 'static> Element<V> for Align<V> {
- type LayoutState = ();
- type PaintState = ();
-
- fn layout(
- &mut self,
- mut constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- let mut size = constraint.max;
- constraint.min = Vector2F::zero();
- let child_size = self.child.layout(constraint, view, cx);
- if size.x().is_infinite() {
- size.set_x(child_size.x());
- }
- if size.y().is_infinite() {
- size.set_y(child_size.y());
- }
- (size, ())
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- _: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- let my_center = bounds.size() / 2.;
- let my_target = my_center + my_center * self.alignment;
-
- let child_center = self.child.size() / 2.;
- let child_target = child_center + child_center * self.alignment;
-
- self.child.paint(
- bounds.origin() - (child_target - my_target),
- visible_bounds,
- view,
- cx,
- );
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: std::ops::Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, view, cx)
- }
-
- fn debug(
- &self,
- bounds: pathfinder_geometry::rect::RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> json::Value {
- json!({
- "type": "Align",
- "bounds": bounds.to_json(),
- "alignment": self.alignment.to_json(),
- "child": self.child.debug(view, cx),
- })
- }
-}
@@ -1,85 +1,54 @@
-use std::marker::PhantomData;
+use refineable::Refineable as _;
-use super::Element;
-use crate::{
- json::{self, json},
- ViewContext,
-};
-use json::ToJson;
-use pathfinder_geometry::{
- rect::RectF,
- vector::{vec2f, Vector2F},
-};
+use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled, WindowContext};
-pub struct Canvas<V, F>(F, PhantomData<V>);
-
-impl<V, F> Canvas<V, F>
-where
- F: FnMut(RectF, RectF, &mut V, &mut ViewContext<V>),
-{
- pub fn new(f: F) -> Self {
- Self(f, PhantomData)
+pub fn canvas(callback: impl 'static + FnOnce(&Bounds<Pixels>, &mut WindowContext)) -> Canvas {
+ Canvas {
+ paint_callback: Some(Box::new(callback)),
+ style: StyleRefinement::default(),
}
}
-impl<V: 'static, F> Element<V> for Canvas<V, F>
-where
- F: 'static + FnMut(RectF, RectF, &mut V, &mut ViewContext<V>),
-{
- type LayoutState = ();
- type PaintState = ();
+pub struct Canvas {
+ paint_callback: Option<Box<dyn FnOnce(&Bounds<Pixels>, &mut WindowContext)>>,
+ style: StyleRefinement,
+}
- fn layout(
- &mut self,
- constraint: crate::SizeConstraint,
- _: &mut V,
- _: &mut crate::ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- let x = if constraint.max.x().is_finite() {
- constraint.max.x()
- } else {
- constraint.min.x()
- };
- let y = if constraint.max.y().is_finite() {
- constraint.max.y()
- } else {
- constraint.min.y()
- };
- (vec2f(x, y), ())
+impl IntoElement for Canvas {
+ type Element = Self;
+
+ fn element_id(&self) -> Option<crate::ElementId> {
+ None
}
- fn paint(
+ fn into_element(self) -> Self::Element {
+ self
+ }
+}
+
+impl Element for Canvas {
+ type State = Style;
+
+ fn request_layout(
&mut self,
- bounds: RectF,
- visible_bounds: RectF,
- _: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- self.0(bounds, visible_bounds, view, cx)
+ _: Option<Self::State>,
+ cx: &mut WindowContext,
+ ) -> (crate::LayoutId, Self::State) {
+ let mut style = Style::default();
+ style.refine(&self.style);
+ let layout_id = cx.request_layout(&style, []);
+ (layout_id, style)
}
- fn rect_for_text_range(
- &self,
- _: std::ops::Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- _: &V,
- _: &ViewContext<V>,
- ) -> Option<RectF> {
- None
+ fn paint(&mut self, bounds: Bounds<Pixels>, style: &mut Style, cx: &mut WindowContext) {
+ style.paint(bounds, cx, |cx| {
+ (self.paint_callback.take().unwrap())(&bounds, cx)
+ });
}
+}
- fn debug(
- &self,
- bounds: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- _: &V,
- _: &ViewContext<V>,
- ) -> json::Value {
- json!({"type": "Canvas", "bounds": bounds.to_json()})
+impl Styled for Canvas {
+ fn style(&mut self) -> &mut crate::StyleRefinement {
+ &mut self.style
}
}
@@ -1,71 +0,0 @@
-use std::ops::Range;
-
-use pathfinder_geometry::{rect::RectF, vector::Vector2F};
-use serde_json::json;
-
-use crate::{json, AnyElement, Element, SizeConstraint, ViewContext};
-
-pub struct Clipped<V> {
- child: AnyElement<V>,
-}
-
-impl<V> Clipped<V> {
- pub fn new(child: AnyElement<V>) -> Self {
- Self { child }
- }
-}
-
-impl<V: 'static> Element<V> for Clipped<V> {
- type LayoutState = ();
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- (self.child.layout(constraint, view, cx), ())
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- _: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- cx.scene().push_layer(Some(bounds));
- let state = self.child.paint(bounds.origin(), visible_bounds, view, cx);
- cx.scene().pop_layer();
- state
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, view, cx)
- }
-
- fn debug(
- &self,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> json::Value {
- json!({
- "type": "Clipped",
- "child": self.child.debug(view, cx)
- })
- }
-}
@@ -1,342 +0,0 @@
-use std::{any::Any, marker::PhantomData};
-
-use pathfinder_geometry::{rect::RectF, vector::Vector2F};
-
-use crate::{AnyElement, Element, SizeConstraint, ViewContext};
-
-use super::Empty;
-
-/// The core stateless component trait, simply rendering an element tree
-pub trait Component {
- fn render<V: 'static>(self, cx: &mut ViewContext<V>) -> AnyElement<V>;
-
- fn element<V: 'static>(self) -> ComponentAdapter<V, Self>
- where
- Self: Sized,
- {
- ComponentAdapter::new(self)
- }
-
- fn stylable(self) -> StylableAdapter<Self>
- where
- Self: Sized,
- {
- StylableAdapter::new(self)
- }
-
- fn stateful<V: 'static>(self) -> StatefulAdapter<Self, V>
- where
- Self: Sized,
- {
- StatefulAdapter::new(self)
- }
-}
-
-/// Allows a a component's styles to be rebound in a simple way.
-pub trait Stylable: Component {
- type Style: Clone;
-
- fn with_style(self, style: Self::Style) -> Self;
-}
-
-/// This trait models the typestate pattern for a component's style,
-/// enforcing at compile time that a component is only usable after
-/// it has been styled while still allowing for late binding of the
-/// styling information
-pub trait SafeStylable {
- type Style: Clone;
- type Output: Component;
-
- fn with_style(self, style: Self::Style) -> Self::Output;
-}
-
-/// All stylable components can trivially implement SafeStylable
-impl<C: Stylable> SafeStylable for C {
- type Style = C::Style;
-
- type Output = C;
-
- fn with_style(self, style: Self::Style) -> Self::Output {
- self.with_style(style)
- }
-}
-
-/// Allows converting an unstylable component into a stylable one
-/// by using `()` as the style type
-pub struct StylableAdapter<C: Component> {
- component: C,
-}
-
-impl<C: Component> StylableAdapter<C> {
- pub fn new(component: C) -> Self {
- Self { component }
- }
-}
-
-impl<C: Component> SafeStylable for StylableAdapter<C> {
- type Style = ();
-
- type Output = C;
-
- fn with_style(self, _: Self::Style) -> Self::Output {
- self.component
- }
-}
-
-/// This is a secondary trait for components that can be styled
-/// which rely on their view's state. This is useful for components that, for example,
-/// want to take click handler callbacks Unfortunately, the generic bound on the
-/// Component trait makes it incompatible with the stateless components above.
-// So let's just replicate them for now
-pub trait StatefulComponent<V: 'static> {
- fn render(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
-
- fn element(self) -> ComponentAdapter<V, Self>
- where
- Self: Sized,
- {
- ComponentAdapter::new(self)
- }
-
- fn styleable(self) -> StatefulStylableAdapter<Self, V>
- where
- Self: Sized,
- {
- StatefulStylableAdapter::new(self)
- }
-
- fn stateless(self) -> StatelessElementAdapter
- where
- Self: Sized + 'static,
- {
- StatelessElementAdapter::new(self.element().into_any())
- }
-}
-
-/// It is trivial to convert stateless components to stateful components, so lets
-/// do so en masse. Note that the reverse is impossible without a helper.
-impl<V: 'static, C: Component> StatefulComponent<V> for C {
- fn render(self, _: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
- self.render(cx)
- }
-}
-
-/// Same as stylable, but generic over a view type
-pub trait StatefulStylable<V: 'static>: StatefulComponent<V> {
- type Style: Clone;
-
- fn with_style(self, style: Self::Style) -> Self;
-}
-
-/// Same as SafeStylable, but generic over a view type
-pub trait StatefulSafeStylable<V: 'static> {
- type Style: Clone;
- type Output: StatefulComponent<V>;
-
- fn with_style(self, style: Self::Style) -> Self::Output;
-}
-
-/// Converting from stateless to stateful
-impl<V: 'static, C: SafeStylable> StatefulSafeStylable<V> for C {
- type Style = C::Style;
-
- type Output = C::Output;
-
- fn with_style(self, style: Self::Style) -> Self::Output {
- self.with_style(style)
- }
-}
-
-// A helper for converting stateless components into stateful ones
-pub struct StatefulAdapter<C, V> {
- component: C,
- phantom: std::marker::PhantomData<V>,
-}
-
-impl<C: Component, V: 'static> StatefulAdapter<C, V> {
- pub fn new(component: C) -> Self {
- Self {
- component,
- phantom: std::marker::PhantomData,
- }
- }
-}
-
-impl<C: Component, V: 'static> StatefulComponent<V> for StatefulAdapter<C, V> {
- fn render(self, _: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
- self.component.render(cx)
- }
-}
-
-// A helper for converting stateful but style-less components into stylable ones
-// by using `()` as the style type
-pub struct StatefulStylableAdapter<C: StatefulComponent<V>, V: 'static> {
- component: C,
- phantom: std::marker::PhantomData<V>,
-}
-
-impl<C: StatefulComponent<V>, V: 'static> StatefulStylableAdapter<C, V> {
- pub fn new(component: C) -> Self {
- Self {
- component,
- phantom: std::marker::PhantomData,
- }
- }
-}
-
-impl<C: StatefulComponent<V>, V: 'static> StatefulSafeStylable<V>
- for StatefulStylableAdapter<C, V>
-{
- type Style = ();
-
- type Output = C;
-
- fn with_style(self, _: Self::Style) -> Self::Output {
- self.component
- }
-}
-
-/// A way of erasing the view generic from an element, useful
-/// for wrapping up an explicit element tree into stateless
-/// components
-pub struct StatelessElementAdapter {
- element: Box<dyn Any>,
-}
-
-impl StatelessElementAdapter {
- pub fn new<V: 'static>(element: AnyElement<V>) -> Self {
- StatelessElementAdapter {
- element: Box::new(element) as Box<dyn Any>,
- }
- }
-}
-
-impl Component for StatelessElementAdapter {
- fn render<V: 'static>(self, _: &mut ViewContext<V>) -> AnyElement<V> {
- *self
- .element
- .downcast::<AnyElement<V>>()
- .expect("Don't move elements out of their view :(")
- }
-}
-
-// For converting elements into stateful components
-pub struct StatefulElementAdapter<V: 'static> {
- element: AnyElement<V>,
- _phantom: std::marker::PhantomData<V>,
-}
-
-impl<V: 'static> StatefulElementAdapter<V> {
- pub fn new(element: AnyElement<V>) -> Self {
- Self {
- element,
- _phantom: std::marker::PhantomData,
- }
- }
-}
-
-impl<V: 'static> StatefulComponent<V> for StatefulElementAdapter<V> {
- fn render(self, _: &mut V, _: &mut ViewContext<V>) -> AnyElement<V> {
- self.element
- }
-}
-
-/// A convenient shorthand for creating an empty component.
-impl Component for () {
- fn render<V: 'static>(self, _: &mut ViewContext<V>) -> AnyElement<V> {
- Empty::new().into_any()
- }
-}
-
-impl Stylable for () {
- type Style = ();
-
- fn with_style(self, _: Self::Style) -> Self {
- ()
- }
-}
-
-// For converting components back into Elements
-pub struct ComponentAdapter<V: 'static, E> {
- component: Option<E>,
- element: Option<AnyElement<V>>,
- phantom: PhantomData<V>,
-}
-
-impl<E, V: 'static> ComponentAdapter<V, E> {
- pub fn new(e: E) -> Self {
- Self {
- component: Some(e),
- element: None,
- phantom: PhantomData,
- }
- }
-}
-
-impl<V: 'static, C: StatefulComponent<V> + 'static> Element<V> for ComponentAdapter<V, C> {
- type LayoutState = ();
-
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- if self.element.is_none() {
- let element = self
- .component
- .take()
- .expect("Component can only be rendered once")
- .render(view, cx);
- self.element = Some(element);
- }
- let constraint = self.element.as_mut().unwrap().layout(constraint, view, cx);
- (constraint, ())
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- _: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- self.element
- .as_mut()
- .expect("Layout should always be called before paint")
- .paint(bounds.origin(), visible_bounds, view, cx)
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: std::ops::Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- self.element
- .as_ref()
- .and_then(|el| el.rect_for_text_range(range_utf16, view, cx))
- }
-
- fn debug(
- &self,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> serde_json::Value {
- serde_json::json!({
- "type": "ComponentAdapter",
- "component": std::any::type_name::<C>(),
- "child": self.element.as_ref().map(|el| el.debug(view, cx)),
- })
- }
-}
@@ -1,187 +0,0 @@
-use std::ops::Range;
-
-use json::ToJson;
-use serde_json::json;
-
-use crate::{
- geometry::{rect::RectF, vector::Vector2F},
- json, AnyElement, Element, SizeConstraint, ViewContext,
-};
-
-pub struct ConstrainedBox<V> {
- child: AnyElement<V>,
- constraint: Constraint<V>,
-}
-
-pub enum Constraint<V> {
- Static(SizeConstraint),
- Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint>),
-}
-
-impl<V> ToJson for Constraint<V> {
- fn to_json(&self) -> serde_json::Value {
- match self {
- Constraint::Static(constraint) => constraint.to_json(),
- Constraint::Dynamic(_) => "dynamic".into(),
- }
- }
-}
-
-impl<V: 'static> ConstrainedBox<V> {
- pub fn new(child: impl Element<V>) -> Self {
- Self {
- child: child.into_any(),
- constraint: Constraint::Static(Default::default()),
- }
- }
-
- pub fn dynamically(
- mut self,
- constraint: impl 'static + FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint,
- ) -> Self {
- self.constraint = Constraint::Dynamic(Box::new(constraint));
- self
- }
-
- pub fn with_min_width(mut self, min_width: f32) -> Self {
- if let Constraint::Dynamic(_) = self.constraint {
- self.constraint = Constraint::Static(Default::default());
- }
-
- if let Constraint::Static(constraint) = &mut self.constraint {
- constraint.min.set_x(min_width);
- } else {
- unreachable!()
- }
-
- self
- }
-
- pub fn with_max_width(mut self, max_width: f32) -> Self {
- if let Constraint::Dynamic(_) = self.constraint {
- self.constraint = Constraint::Static(Default::default());
- }
-
- if let Constraint::Static(constraint) = &mut self.constraint {
- constraint.max.set_x(max_width);
- } else {
- unreachable!()
- }
-
- self
- }
-
- pub fn with_max_height(mut self, max_height: f32) -> Self {
- if let Constraint::Dynamic(_) = self.constraint {
- self.constraint = Constraint::Static(Default::default());
- }
-
- if let Constraint::Static(constraint) = &mut self.constraint {
- constraint.max.set_y(max_height);
- } else {
- unreachable!()
- }
-
- self
- }
-
- pub fn with_width(mut self, width: f32) -> Self {
- if let Constraint::Dynamic(_) = self.constraint {
- self.constraint = Constraint::Static(Default::default());
- }
-
- if let Constraint::Static(constraint) = &mut self.constraint {
- constraint.min.set_x(width);
- constraint.max.set_x(width);
- } else {
- unreachable!()
- }
-
- self
- }
-
- pub fn with_height(mut self, height: f32) -> Self {
- if let Constraint::Dynamic(_) = self.constraint {
- self.constraint = Constraint::Static(Default::default());
- }
-
- if let Constraint::Static(constraint) = &mut self.constraint {
- constraint.min.set_y(height);
- constraint.max.set_y(height);
- } else {
- unreachable!()
- }
-
- self
- }
-
- fn constraint(
- &mut self,
- input_constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> SizeConstraint {
- match &mut self.constraint {
- Constraint::Static(constraint) => *constraint,
- Constraint::Dynamic(compute_constraint) => {
- compute_constraint(input_constraint, view, cx)
- }
- }
- }
-}
-
-impl<V: 'static> Element<V> for ConstrainedBox<V> {
- type LayoutState = ();
- type PaintState = ();
-
- fn layout(
- &mut self,
- mut parent_constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- let constraint = self.constraint(parent_constraint, view, cx);
- parent_constraint.min = parent_constraint.min.max(constraint.min);
- parent_constraint.max = parent_constraint.max.min(constraint.max);
- parent_constraint.max = parent_constraint.max.max(parent_constraint.min);
- let size = self.child.layout(parent_constraint, view, cx);
- (size, ())
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- _: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- cx.scene().push_layer(Some(visible_bounds));
- self.child.paint(bounds.origin(), visible_bounds, view, cx);
- cx.scene().pop_layer();
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, view, cx)
- }
-
- fn debug(
- &self,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> json::Value {
- json!({"type": "ConstrainedBox", "assigned_constraint": self.constraint.to_json(), "child": self.child.debug(view, cx)})
- }
-}
@@ -1,684 +0,0 @@
-use std::ops::Range;
-
-use crate::{
- color::Color,
- geometry::{
- deserialize_vec2f,
- rect::RectF,
- vector::{vec2f, Vector2F},
- },
- json::ToJson,
- platform::CursorStyle,
- scene::{self, CornerRadii, CursorRegion, Quad},
- AnyElement, Element, SizeConstraint, ViewContext,
-};
-use schemars::JsonSchema;
-use serde::Deserialize;
-use serde_json::json;
-
-#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
-pub struct ContainerStyle {
- #[serde(default)]
- pub margin: Margin,
- #[serde(default)]
- pub padding: Padding,
- #[serde(rename = "background")]
- pub background_color: Option<Color>,
- #[serde(rename = "overlay")]
- pub overlay_color: Option<Color>,
- #[serde(default)]
- pub border: Border,
- #[serde(default)]
- #[serde(alias = "corner_radius")]
- pub corner_radii: CornerRadii,
- #[serde(default)]
- pub shadow: Option<Shadow>,
- #[serde(default)]
- pub cursor: Option<CursorStyle>,
-}
-
-impl ContainerStyle {
- pub fn fill(color: Color) -> Self {
- Self {
- background_color: Some(color),
- ..Default::default()
- }
- }
-
- pub fn additional_length(&self) -> f32 {
- self.padding.left
- + self.padding.right
- + self.border.width * 2.
- + self.margin.left
- + self.margin.right
- }
-}
-
-pub struct Container<V> {
- child: AnyElement<V>,
- style: ContainerStyle,
-}
-
-impl<V> Container<V> {
- pub fn new(child: AnyElement<V>) -> Self {
- Self {
- child,
- style: Default::default(),
- }
- }
-
- pub fn with_style(mut self, style: ContainerStyle) -> Self {
- self.style = style;
- self
- }
-
- pub fn with_margin_top(mut self, margin: f32) -> Self {
- self.style.margin.top = margin;
- self
- }
-
- pub fn with_margin_bottom(mut self, margin: f32) -> Self {
- self.style.margin.bottom = margin;
- self
- }
-
- pub fn with_margin_left(mut self, margin: f32) -> Self {
- self.style.margin.left = margin;
- self
- }
-
- pub fn with_margin_right(mut self, margin: f32) -> Self {
- self.style.margin.right = margin;
- self
- }
-
- pub fn with_horizontal_padding(mut self, padding: f32) -> Self {
- self.style.padding.left = padding;
- self.style.padding.right = padding;
- self
- }
-
- pub fn with_vertical_padding(mut self, padding: f32) -> Self {
- self.style.padding.top = padding;
- self.style.padding.bottom = padding;
- self
- }
-
- pub fn with_uniform_padding(mut self, padding: f32) -> Self {
- self.style.padding = Padding {
- top: padding,
- left: padding,
- bottom: padding,
- right: padding,
- };
- self
- }
-
- pub fn with_padding_left(mut self, padding: f32) -> Self {
- self.style.padding.left = padding;
- self
- }
-
- pub fn with_padding_right(mut self, padding: f32) -> Self {
- self.style.padding.right = padding;
- self
- }
-
- pub fn with_padding_top(mut self, padding: f32) -> Self {
- self.style.padding.top = padding;
- self
- }
-
- pub fn with_padding_bottom(mut self, padding: f32) -> Self {
- self.style.padding.bottom = padding;
- self
- }
-
- pub fn with_background_color(mut self, color: Color) -> Self {
- self.style.background_color = Some(color);
- self
- }
-
- pub fn with_overlay_color(mut self, color: Color) -> Self {
- self.style.overlay_color = Some(color);
- self
- }
-
- pub fn with_border(mut self, border: Border) -> Self {
- self.style.border = border;
- self
- }
-
- pub fn with_corner_radius(mut self, radius: f32) -> Self {
- self.style.corner_radii.top_left = radius;
- self.style.corner_radii.top_right = radius;
- self.style.corner_radii.bottom_right = radius;
- self.style.corner_radii.bottom_left = radius;
- self
- }
-
- pub fn with_shadow(mut self, offset: Vector2F, blur: f32, color: Color) -> Self {
- self.style.shadow = Some(Shadow {
- offset,
- blur,
- color,
- });
- self
- }
-
- pub fn with_cursor(mut self, style: CursorStyle) -> Self {
- self.style.cursor = Some(style);
- self
- }
-
- fn margin_size(&self) -> Vector2F {
- vec2f(
- self.style.margin.left + self.style.margin.right,
- self.style.margin.top + self.style.margin.bottom,
- )
- }
-
- fn padding_size(&self) -> Vector2F {
- vec2f(
- self.style.padding.left + self.style.padding.right,
- self.style.padding.top + self.style.padding.bottom,
- )
- }
-
- fn border_size(&self) -> Vector2F {
- let mut x = 0.0;
- if self.style.border.left {
- x += self.style.border.width;
- }
- if self.style.border.right {
- x += self.style.border.width;
- }
-
- let mut y = 0.0;
- if self.style.border.top {
- y += self.style.border.width;
- }
- if self.style.border.bottom {
- y += self.style.border.width;
- }
-
- vec2f(x, y)
- }
-}
-
-#[derive(Copy, Clone, Debug, Default, JsonSchema)]
-pub struct Border {
- pub color: Color,
- pub width: f32,
- pub overlay: bool,
- pub top: bool,
- pub bottom: bool,
- pub left: bool,
- pub right: bool,
-}
-
-impl Into<scene::Border> for Border {
- fn into(self) -> scene::Border {
- scene::Border {
- color: self.color,
- left: if self.left { self.width } else { 0.0 },
- right: if self.right { self.width } else { 0.0 },
- top: if self.top { self.width } else { 0.0 },
- bottom: if self.bottom { self.width } else { 0.0 },
- }
- }
-}
-
-impl Border {
- pub fn new(width: f32, color: Color) -> Self {
- Self {
- width,
- color,
- overlay: false,
- top: false,
- left: false,
- bottom: false,
- right: false,
- }
- }
-
- pub fn all(width: f32, color: Color) -> Self {
- Self {
- width,
- color,
- overlay: false,
- top: true,
- left: true,
- bottom: true,
- right: true,
- }
- }
-
- pub fn top(width: f32, color: Color) -> Self {
- let mut border = Self::new(width, color);
- border.top = true;
- border
- }
-
- pub fn left(width: f32, color: Color) -> Self {
- let mut border = Self::new(width, color);
- border.left = true;
- border
- }
-
- pub fn bottom(width: f32, color: Color) -> Self {
- let mut border = Self::new(width, color);
- border.bottom = true;
- border
- }
-
- pub fn right(width: f32, color: Color) -> Self {
- let mut border = Self::new(width, color);
- border.right = true;
- border
- }
-
- pub fn with_sides(mut self, top: bool, left: bool, bottom: bool, right: bool) -> Self {
- self.top = top;
- self.left = left;
- self.bottom = bottom;
- self.right = right;
- self
- }
-
- pub fn top_width(&self) -> f32 {
- if self.top {
- self.width
- } else {
- 0.0
- }
- }
-
- pub fn left_width(&self) -> f32 {
- if self.left {
- self.width
- } else {
- 0.0
- }
- }
-}
-
-impl<'de> Deserialize<'de> for Border {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: serde::Deserializer<'de>,
- {
- #[derive(Deserialize)]
- struct BorderData {
- pub width: f32,
- pub color: Color,
- #[serde(default)]
- pub overlay: bool,
- #[serde(default)]
- pub top: bool,
- #[serde(default)]
- pub right: bool,
- #[serde(default)]
- pub bottom: bool,
- #[serde(default)]
- pub left: bool,
- }
-
- let data = BorderData::deserialize(deserializer)?;
- let mut border = Border {
- width: data.width,
- color: data.color,
- overlay: data.overlay,
- top: data.top,
- bottom: data.bottom,
- left: data.left,
- right: data.right,
- };
- if !border.top && !border.bottom && !border.left && !border.right {
- border.top = true;
- border.bottom = true;
- border.left = true;
- border.right = true;
- }
- Ok(border)
- }
-}
-
-impl ToJson for Border {
- fn to_json(&self) -> serde_json::Value {
- let mut value = json!({});
- if self.top {
- value["top"] = json!(self.width);
- }
- if self.right {
- value["right"] = json!(self.width);
- }
- if self.bottom {
- value["bottom"] = json!(self.width);
- }
- if self.left {
- value["left"] = json!(self.width);
- }
- value
- }
-}
-
-impl<V: 'static> Element<V> for Container<V> {
- type LayoutState = ();
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- let mut size_buffer = self.margin_size() + self.padding_size();
- if !self.style.border.overlay {
- size_buffer += self.border_size();
- }
- let child_constraint = SizeConstraint {
- min: (constraint.min - size_buffer).max(Vector2F::zero()),
- max: (constraint.max - size_buffer).max(Vector2F::zero()),
- };
- let child_size = self.child.layout(child_constraint, view, cx);
- (child_size + size_buffer, ())
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- _: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- let quad_bounds = RectF::from_points(
- bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),
- bounds.lower_right() - vec2f(self.style.margin.right, self.style.margin.bottom),
- );
-
- if let Some(shadow) = self.style.shadow.as_ref() {
- cx.scene().push_shadow(scene::Shadow {
- bounds: quad_bounds + shadow.offset,
- corner_radii: self.style.corner_radii,
- sigma: shadow.blur,
- color: shadow.color,
- });
- }
-
- if let Some(hit_bounds) = quad_bounds.intersection(visible_bounds) {
- if let Some(style) = self.style.cursor {
- cx.scene().push_cursor_region(CursorRegion {
- bounds: hit_bounds,
- style,
- });
- }
- }
-
- let child_origin =
- quad_bounds.origin() + vec2f(self.style.padding.left, self.style.padding.top);
-
- if self.style.border.overlay {
- cx.scene().push_quad(Quad {
- bounds: quad_bounds,
- background: self.style.background_color,
- border: Default::default(),
- corner_radii: self.style.corner_radii.into(),
- });
-
- self.child.paint(child_origin, visible_bounds, view, cx);
-
- cx.scene().push_layer(None);
- cx.scene().push_quad(Quad {
- bounds: quad_bounds,
- background: self.style.overlay_color,
- border: self.style.border.into(),
- corner_radii: self.style.corner_radii.into(),
- });
- cx.scene().pop_layer();
- } else {
- cx.scene().push_quad(Quad {
- bounds: quad_bounds,
- background: self.style.background_color,
- border: self.style.border.into(),
- corner_radii: self.style.corner_radii.into(),
- });
-
- let child_origin = child_origin
- + vec2f(
- self.style.border.left_width(),
- self.style.border.top_width(),
- );
- self.child.paint(child_origin, visible_bounds, view, cx);
-
- if self.style.overlay_color.is_some() {
- cx.scene().push_layer(None);
- cx.scene().push_quad(Quad {
- bounds: quad_bounds,
- background: self.style.overlay_color,
- border: Default::default(),
- corner_radii: self.style.corner_radii.into(),
- });
- cx.scene().pop_layer();
- }
- }
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, view, cx)
- }
-
- fn debug(
- &self,
- bounds: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> serde_json::Value {
- json!({
- "type": "Container",
- "bounds": bounds.to_json(),
- "details": self.style.to_json(),
- "child": self.child.debug(view, cx),
- })
- }
-}
-
-impl ToJson for ContainerStyle {
- fn to_json(&self) -> serde_json::Value {
- json!({
- "margin": self.margin.to_json(),
- "padding": self.padding.to_json(),
- "background_color": self.background_color.to_json(),
- "border": self.border.to_json(),
- "corner_radius": self.corner_radii,
- "shadow": self.shadow.to_json(),
- })
- }
-}
-
-#[derive(Clone, Copy, Debug, Default, JsonSchema)]
-pub struct Margin {
- pub top: f32,
- pub bottom: f32,
- pub left: f32,
- pub right: f32,
-}
-
-impl ToJson for Margin {
- fn to_json(&self) -> serde_json::Value {
- let mut value = json!({});
- if self.top > 0. {
- value["top"] = json!(self.top);
- }
- if self.right > 0. {
- value["right"] = json!(self.right);
- }
- if self.bottom > 0. {
- value["bottom"] = json!(self.bottom);
- }
- if self.left > 0. {
- value["left"] = json!(self.left);
- }
- value
- }
-}
-
-#[derive(Clone, Copy, Debug, Default, JsonSchema)]
-pub struct Padding {
- pub top: f32,
- pub left: f32,
- pub bottom: f32,
- pub right: f32,
-}
-
-impl Padding {
- pub fn horizontal(padding: f32) -> Self {
- Self {
- left: padding,
- right: padding,
- ..Default::default()
- }
- }
-
- pub fn vertical(padding: f32) -> Self {
- Self {
- top: padding,
- bottom: padding,
- ..Default::default()
- }
- }
-}
-
-impl<'de> Deserialize<'de> for Padding {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: serde::Deserializer<'de>,
- {
- let spacing = Spacing::deserialize(deserializer)?;
- Ok(match spacing {
- Spacing::Uniform(size) => Padding {
- top: size,
- left: size,
- bottom: size,
- right: size,
- },
- Spacing::Specific {
- top,
- left,
- bottom,
- right,
- } => Padding {
- top,
- left,
- bottom,
- right,
- },
- })
- }
-}
-
-impl<'de> Deserialize<'de> for Margin {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: serde::Deserializer<'de>,
- {
- let spacing = Spacing::deserialize(deserializer)?;
- Ok(match spacing {
- Spacing::Uniform(size) => Margin {
- top: size,
- left: size,
- bottom: size,
- right: size,
- },
- Spacing::Specific {
- top,
- left,
- bottom,
- right,
- } => Margin {
- top,
- left,
- bottom,
- right,
- },
- })
- }
-}
-#[derive(Deserialize)]
-#[serde(untagged)]
-enum Spacing {
- Uniform(f32),
- Specific {
- #[serde(default)]
- top: f32,
- #[serde(default)]
- left: f32,
- #[serde(default)]
- bottom: f32,
- #[serde(default)]
- right: f32,
- },
-}
-
-impl Padding {
- pub fn uniform(padding: f32) -> Self {
- Self {
- top: padding,
- left: padding,
- bottom: padding,
- right: padding,
- }
- }
-}
-
-impl ToJson for Padding {
- fn to_json(&self) -> serde_json::Value {
- let mut value = json!({});
- if self.top > 0. {
- value["top"] = json!(self.top);
- }
- if self.right > 0. {
- value["right"] = json!(self.right);
- }
- if self.bottom > 0. {
- value["bottom"] = json!(self.bottom);
- }
- if self.left > 0. {
- value["left"] = json!(self.left);
- }
- value
- }
-}
-
-#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
-pub struct Shadow {
- #[serde(default, deserialize_with = "deserialize_vec2f")]
- #[schemars(with = "Vec::<f32>")]
- offset: Vector2F,
- #[serde(default)]
- blur: f32,
- #[serde(default)]
- color: Color,
-}
-
-impl ToJson for Shadow {
- fn to_json(&self) -> serde_json::Value {
- json!({
- "offset": self.offset.to_json(),
- "blur": self.blur,
- "color": self.color.to_json()
- })
- }
-}
@@ -1,89 +0,0 @@
-use std::ops::Range;
-
-use crate::{
- geometry::{
- rect::RectF,
- vector::{vec2f, Vector2F},
- },
- json::{json, ToJson},
- ViewContext,
-};
-use crate::{Element, SizeConstraint};
-
-#[derive(Default)]
-pub struct Empty {
- collapsed: bool,
-}
-
-impl Empty {
- pub fn new() -> Self {
- Self::default()
- }
-
- pub fn collapsed(mut self) -> Self {
- self.collapsed = true;
- self
- }
-}
-
-impl<V: 'static> Element<V> for Empty {
- type LayoutState = ();
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- _: &mut V,
- _: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- let x = if constraint.max.x().is_finite() && !self.collapsed {
- constraint.max.x()
- } else {
- constraint.min.x()
- };
- let y = if constraint.max.y().is_finite() && !self.collapsed {
- constraint.max.y()
- } else {
- constraint.min.y()
- };
-
- (vec2f(x, y), ())
- }
-
- fn paint(
- &mut self,
- _: RectF,
- _: RectF,
- _: &mut Self::LayoutState,
- _: &mut V,
- _: &mut ViewContext<V>,
- ) -> Self::PaintState {
- }
-
- fn rect_for_text_range(
- &self,
- _: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- _: &V,
- _: &ViewContext<V>,
- ) -> Option<RectF> {
- None
- }
-
- fn debug(
- &self,
- bounds: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- _: &V,
- _: &ViewContext<V>,
- ) -> serde_json::Value {
- json!({
- "type": "Empty",
- "bounds": bounds.to_json(),
- })
- }
-}
@@ -1,96 +0,0 @@
-use std::ops::Range;
-
-use crate::{
- geometry::{rect::RectF, vector::Vector2F},
- json, AnyElement, Element, SizeConstraint, ViewContext,
-};
-use serde_json::json;
-
-pub struct Expanded<V> {
- child: AnyElement<V>,
- full_width: bool,
- full_height: bool,
-}
-
-impl<V: 'static> Expanded<V> {
- pub fn new(child: impl Element<V>) -> Self {
- Self {
- child: child.into_any(),
- full_width: true,
- full_height: true,
- }
- }
-
- pub fn full_width(mut self) -> Self {
- self.full_width = true;
- self.full_height = false;
- self
- }
-
- pub fn full_height(mut self) -> Self {
- self.full_width = false;
- self.full_height = true;
- self
- }
-}
-
-impl<V: 'static> Element<V> for Expanded<V> {
- type LayoutState = ();
- type PaintState = ();
-
- fn layout(
- &mut self,
- mut constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- if self.full_width {
- constraint.min.set_x(constraint.max.x());
- }
- if self.full_height {
- constraint.min.set_y(constraint.max.y());
- }
- let size = self.child.layout(constraint, view, cx);
- (size, ())
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- _: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- self.child.paint(bounds.origin(), visible_bounds, view, cx);
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, view, cx)
- }
-
- fn debug(
- &self,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> json::Value {
- json!({
- "type": "Expanded",
- "full_width": self.full_width,
- "full_height": self.full_height,
- "child": self.child.debug(view, cx)
- })
- }
-}
@@ -1,512 +0,0 @@
-use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
-
-use crate::{
- json::{self, ToJson, Value},
- AnyElement, Axis, Element, ElementStateHandle, SizeConstraint, TypeTag, Vector2FExt,
- ViewContext,
-};
-use pathfinder_geometry::{
- rect::RectF,
- vector::{vec2f, Vector2F},
-};
-use serde_json::json;
-
-struct ScrollState {
- scroll_to: Cell<Option<usize>>,
- scroll_position: Cell<f32>,
- type_tag: TypeTag,
-}
-
-pub struct Flex<V> {
- axis: Axis,
- children: Vec<AnyElement<V>>,
- scroll_state: Option<(ElementStateHandle<Rc<ScrollState>>, usize)>,
- child_alignment: f32,
- spacing: f32,
-}
-
-impl<V: 'static> Flex<V> {
- pub fn new(axis: Axis) -> Self {
- Self {
- axis,
- children: Default::default(),
- scroll_state: None,
- child_alignment: -1.,
- spacing: 0.,
- }
- }
-
- pub fn row() -> Self {
- Self::new(Axis::Horizontal)
- }
-
- pub fn column() -> Self {
- Self::new(Axis::Vertical)
- }
-
- /// Render children centered relative to the cross-axis of the parent flex.
- ///
- /// If this is a flex row, children will be centered vertically. If this is a
- /// flex column, children will be centered horizontally.
- pub fn align_children_center(mut self) -> Self {
- self.child_alignment = 0.;
- self
- }
-
- pub fn with_spacing(mut self, spacing: f32) -> Self {
- self.spacing = spacing;
- self
- }
-
- pub fn scrollable<Tag>(
- mut self,
- element_id: usize,
- scroll_to: Option<usize>,
- cx: &mut ViewContext<V>,
- ) -> Self
- where
- Tag: 'static,
- {
- // Don't assume that this initialization is what scroll_state really is in other panes:
- // `element_state` is shared and there could be init races.
- let scroll_state = cx.element_state::<Tag, Rc<ScrollState>>(
- element_id,
- Rc::new(ScrollState {
- type_tag: TypeTag::new::<Tag>(),
- scroll_to: Default::default(),
- scroll_position: Default::default(),
- }),
- );
- // Set scroll_to separately, because the default state is already picked as `None` by other panes
- // by the time we start setting it here, hence update all others' state too.
- scroll_state.update(cx, |this, _| {
- this.scroll_to.set(scroll_to);
- });
- self.scroll_state = Some((scroll_state, cx.handle().id()));
- self
- }
-
- pub fn is_empty(&self) -> bool {
- self.children.is_empty()
- }
-
- fn layout_flex_children(
- &mut self,
- layout_expanded: bool,
- constraint: SizeConstraint,
- remaining_space: &mut f32,
- remaining_flex: &mut f32,
- cross_axis_max: &mut f32,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) {
- let cross_axis = self.axis.invert();
- for child in self.children.iter_mut() {
- if let Some(metadata) = child.metadata::<FlexParentData>() {
- if let Some((flex, expanded)) = metadata.flex {
- if expanded != layout_expanded {
- continue;
- }
-
- let child_max = if *remaining_flex == 0.0 {
- *remaining_space
- } else {
- let space_per_flex = *remaining_space / *remaining_flex;
- space_per_flex * flex
- };
- let child_min = if expanded { child_max } else { 0. };
- let child_constraint = match self.axis {
- Axis::Horizontal => SizeConstraint::new(
- vec2f(child_min, constraint.min.y()),
- vec2f(child_max, constraint.max.y()),
- ),
- Axis::Vertical => SizeConstraint::new(
- vec2f(constraint.min.x(), child_min),
- vec2f(constraint.max.x(), child_max),
- ),
- };
- let child_size = child.layout(child_constraint, view, cx);
- *remaining_space -= child_size.along(self.axis);
- *remaining_flex -= flex;
- *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
- }
- }
- }
- }
-}
-
-impl<V> Extend<AnyElement<V>> for Flex<V> {
- fn extend<T: IntoIterator<Item = AnyElement<V>>>(&mut self, children: T) {
- self.children.extend(children);
- }
-}
-
-impl<V: 'static> Element<V> for Flex<V> {
- type LayoutState = f32;
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- let mut total_flex = None;
- let mut fixed_space = self.children.len().saturating_sub(1) as f32 * self.spacing;
- let mut contains_float = false;
-
- let cross_axis = self.axis.invert();
- let mut cross_axis_max: f32 = 0.0;
- for child in self.children.iter_mut() {
- let metadata = child.metadata::<FlexParentData>();
- contains_float |= metadata.map_or(false, |metadata| metadata.float);
-
- if let Some(flex) = metadata.and_then(|metadata| metadata.flex.map(|(flex, _)| flex)) {
- *total_flex.get_or_insert(0.) += flex;
- } else {
- let child_constraint = match self.axis {
- Axis::Horizontal => SizeConstraint::new(
- vec2f(0.0, constraint.min.y()),
- vec2f(INFINITY, constraint.max.y()),
- ),
- Axis::Vertical => SizeConstraint::new(
- vec2f(constraint.min.x(), 0.0),
- vec2f(constraint.max.x(), INFINITY),
- ),
- };
- let size = child.layout(child_constraint, view, cx);
- fixed_space += size.along(self.axis);
- cross_axis_max = cross_axis_max.max(size.along(cross_axis));
- }
- }
-
- let mut remaining_space = constraint.max_along(self.axis) - fixed_space;
- let mut size = if let Some(mut remaining_flex) = total_flex {
- if remaining_space.is_infinite() {
- panic!("flex contains flexible children but has an infinite constraint along the flex axis");
- }
-
- self.layout_flex_children(
- false,
- constraint,
- &mut remaining_space,
- &mut remaining_flex,
- &mut cross_axis_max,
- view,
- cx,
- );
- self.layout_flex_children(
- true,
- constraint,
- &mut remaining_space,
- &mut remaining_flex,
- &mut cross_axis_max,
- view,
- cx,
- );
-
- match self.axis {
- Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
- Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
- }
- } else {
- match self.axis {
- Axis::Horizontal => vec2f(fixed_space, cross_axis_max),
- Axis::Vertical => vec2f(cross_axis_max, fixed_space),
- }
- };
-
- if contains_float {
- match self.axis {
- Axis::Horizontal => size.set_x(size.x().max(constraint.max.x())),
- Axis::Vertical => size.set_y(size.y().max(constraint.max.y())),
- }
- }
-
- if constraint.min.x().is_finite() {
- size.set_x(size.x().max(constraint.min.x()));
- }
- if constraint.min.y().is_finite() {
- size.set_y(size.y().max(constraint.min.y()));
- }
-
- if size.x() > constraint.max.x() {
- size.set_x(constraint.max.x());
- }
- if size.y() > constraint.max.y() {
- size.set_y(constraint.max.y());
- }
-
- if let Some(scroll_state) = self.scroll_state.as_ref() {
- scroll_state.0.update(cx, |scroll_state, _| {
- if let Some(scroll_to) = scroll_state.scroll_to.take() {
- let visible_start = scroll_state.scroll_position.get();
- let visible_end = visible_start + size.along(self.axis);
- if let Some(child) = self.children.get(scroll_to) {
- let child_start: f32 = self.children[..scroll_to]
- .iter()
- .map(|c| c.size().along(self.axis))
- .sum();
- let child_end = child_start + child.size().along(self.axis);
- if child_start < visible_start {
- scroll_state.scroll_position.set(child_start);
- } else if child_end > visible_end {
- scroll_state
- .scroll_position
- .set(child_end - size.along(self.axis));
- }
- }
- }
-
- scroll_state.scroll_position.set(
- scroll_state
- .scroll_position
- .get()
- .min(-remaining_space)
- .max(0.),
- );
- });
- }
-
- (size, remaining_space)
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- remaining_space: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-
- let mut remaining_space = *remaining_space;
- let overflowing = remaining_space < 0.;
- if overflowing {
- cx.scene().push_layer(Some(visible_bounds));
- }
-
- if let Some((scroll_state, id)) = &self.scroll_state {
- let scroll_state = scroll_state.read(cx).clone();
- cx.scene().push_mouse_region(
- crate::MouseRegion::from_handlers(
- scroll_state.type_tag,
- *id,
- 0,
- bounds,
- Default::default(),
- )
- .on_scroll({
- let axis = self.axis;
- move |e, _: &mut V, cx| {
- if remaining_space < 0. {
- let scroll_delta = e.delta.raw();
-
- let mut delta = match axis {
- Axis::Horizontal => {
- if scroll_delta.x().abs() >= scroll_delta.y().abs() {
- scroll_delta.x()
- } else {
- scroll_delta.y()
- }
- }
- Axis::Vertical => scroll_delta.y(),
- };
- if !e.delta.precise() {
- delta *= 20.;
- }
-
- scroll_state
- .scroll_position
- .set(scroll_state.scroll_position.get() - delta);
-
- cx.notify();
- } else {
- cx.propagate_event();
- }
- }
- })
- .on_move(|_, _: &mut V, _| { /* Capture move events */ }),
- )
- }
-
- let mut child_origin = bounds.origin();
- if let Some(scroll_state) = self.scroll_state.as_ref() {
- let scroll_position = scroll_state.0.read(cx).scroll_position.get();
- match self.axis {
- Axis::Horizontal => child_origin.set_x(child_origin.x() - scroll_position),
- Axis::Vertical => child_origin.set_y(child_origin.y() - scroll_position),
- }
- }
-
- for child in self.children.iter_mut() {
- if remaining_space > 0. {
- if let Some(metadata) = child.metadata::<FlexParentData>() {
- if metadata.float {
- match self.axis {
- Axis::Horizontal => child_origin += vec2f(remaining_space, 0.0),
- Axis::Vertical => child_origin += vec2f(0.0, remaining_space),
- }
- remaining_space = 0.;
- }
- }
- }
-
- // We use the child_alignment f32 to determine a point along the cross axis of the
- // overall flex element and each child. We then align these points. So 0 would center
- // each child relative to the overall height/width of the flex. -1 puts children at
- // the start. 1 puts children at the end.
- let aligned_child_origin = {
- let cross_axis = self.axis.invert();
- let my_center = bounds.size().along(cross_axis) / 2.;
- let my_target = my_center + my_center * self.child_alignment;
-
- let child_center = child.size().along(cross_axis) / 2.;
- let child_target = child_center + child_center * self.child_alignment;
-
- let mut aligned_child_origin = child_origin;
- match self.axis {
- Axis::Horizontal => aligned_child_origin
- .set_y(aligned_child_origin.y() - (child_target - my_target)),
- Axis::Vertical => aligned_child_origin
- .set_x(aligned_child_origin.x() - (child_target - my_target)),
- }
-
- aligned_child_origin
- };
-
- child.paint(aligned_child_origin, visible_bounds, view, cx);
-
- match self.axis {
- Axis::Horizontal => child_origin += vec2f(child.size().x() + self.spacing, 0.0),
- Axis::Vertical => child_origin += vec2f(0.0, child.size().y() + self.spacing),
- }
- }
-
- if overflowing {
- cx.scene().pop_layer();
- }
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- self.children
- .iter()
- .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
- }
-
- fn debug(
- &self,
- bounds: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> json::Value {
- json!({
- "type": "Flex",
- "bounds": bounds.to_json(),
- "axis": self.axis.to_json(),
- "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
- })
- }
-}
-
-struct FlexParentData {
- flex: Option<(f32, bool)>,
- float: bool,
-}
-
-pub struct FlexItem<V> {
- metadata: FlexParentData,
- child: AnyElement<V>,
-}
-
-impl<V: 'static> FlexItem<V> {
- pub fn new(child: impl Element<V>) -> Self {
- FlexItem {
- metadata: FlexParentData {
- flex: None,
- float: false,
- },
- child: child.into_any(),
- }
- }
-
- pub fn flex(mut self, flex: f32, expanded: bool) -> Self {
- self.metadata.flex = Some((flex, expanded));
- self
- }
-
- pub fn float(mut self) -> Self {
- self.metadata.float = true;
- self
- }
-}
-
-impl<V: 'static> Element<V> for FlexItem<V> {
- type LayoutState = ();
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- let size = self.child.layout(constraint, view, cx);
- (size, ())
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- _: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- self.child.paint(bounds.origin(), visible_bounds, view, cx)
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, view, cx)
- }
-
- fn metadata(&self) -> Option<&dyn Any> {
- Some(&self.metadata)
- }
-
- fn debug(
- &self,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Value {
- json!({
- "type": "Flexible",
- "flex": self.metadata.flex,
- "child": self.child.debug(view, cx)
- })
- }
-}
@@ -1,85 +0,0 @@
-use std::ops::Range;
-
-use crate::{
- geometry::{rect::RectF, vector::Vector2F},
- json::json,
- AnyElement, Element, SizeConstraint, ViewContext,
-};
-
-pub struct Hook<V> {
- child: AnyElement<V>,
- after_layout: Option<Box<dyn FnMut(Vector2F, &mut ViewContext<V>)>>,
-}
-
-impl<V: 'static> Hook<V> {
- pub fn new(child: impl Element<V>) -> Self {
- Self {
- child: child.into_any(),
- after_layout: None,
- }
- }
-
- pub fn on_after_layout(
- mut self,
- f: impl 'static + FnMut(Vector2F, &mut ViewContext<V>),
- ) -> Self {
- self.after_layout = Some(Box::new(f));
- self
- }
-}
-
-impl<V: 'static> Element<V> for Hook<V> {
- type LayoutState = ();
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- let size = self.child.layout(constraint, view, cx);
- if let Some(handler) = self.after_layout.as_mut() {
- handler(size, cx);
- }
- (size, ())
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- _: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) {
- self.child.paint(bounds.origin(), visible_bounds, view, cx);
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, view, cx)
- }
-
- fn debug(
- &self,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> serde_json::Value {
- json!({
- "type": "Hooks",
- "child": self.child.debug(view, cx),
- })
- }
-}
@@ -1,137 +0,0 @@
-use super::{constrain_size_preserving_aspect_ratio, Border};
-use crate::{
- geometry::{
- rect::RectF,
- vector::{vec2f, Vector2F},
- },
- json::{json, ToJson},
- scene, Element, ImageData, SizeConstraint, ViewContext,
-};
-use schemars::JsonSchema;
-use serde::Deserialize;
-use std::{ops::Range, sync::Arc};
-
-enum ImageSource {
- Path(&'static str),
- Data(Arc<ImageData>),
-}
-
-pub struct Image {
- source: ImageSource,
- style: ImageStyle,
-}
-
-#[derive(Copy, Clone, Default, Deserialize, JsonSchema)]
-pub struct ImageStyle {
- #[serde(default)]
- pub border: Border,
- #[serde(default)]
- pub corner_radius: f32,
- #[serde(default)]
- pub height: Option<f32>,
- #[serde(default)]
- pub width: Option<f32>,
- #[serde(default)]
- pub grayscale: bool,
-}
-
-impl Image {
- pub fn new(asset_path: &'static str) -> Self {
- Self {
- source: ImageSource::Path(asset_path),
- style: Default::default(),
- }
- }
-
- pub fn from_data(data: Arc<ImageData>) -> Self {
- Self {
- source: ImageSource::Data(data),
- style: Default::default(),
- }
- }
-
- pub fn with_style(mut self, style: ImageStyle) -> Self {
- self.style = style;
- self
- }
-}
-
-impl<V: 'static> Element<V> for Image {
- type LayoutState = Option<Arc<ImageData>>;
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- _: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- let data = match &self.source {
- ImageSource::Path(path) => match cx.asset_cache.png(path) {
- Ok(data) => data,
- Err(error) => {
- log::error!("could not load image: {}", error);
- return (Vector2F::zero(), None);
- }
- },
- ImageSource::Data(data) => data.clone(),
- };
-
- let desired_size = vec2f(
- self.style.width.unwrap_or_else(|| constraint.max.x()),
- self.style.height.unwrap_or_else(|| constraint.max.y()),
- );
- let size = constrain_size_preserving_aspect_ratio(
- constraint.constrain(desired_size),
- data.size().to_f32(),
- );
-
- (size, Some(data))
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- _: RectF,
- layout: &mut Self::LayoutState,
- _: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- if let Some(data) = layout {
- cx.scene().push_image(scene::Image {
- bounds,
- border: self.style.border.into(),
- corner_radii: self.style.corner_radius.into(),
- grayscale: self.style.grayscale,
- data: data.clone(),
- });
- }
- }
-
- fn rect_for_text_range(
- &self,
- _: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- _: &V,
- _: &ViewContext<V>,
- ) -> Option<RectF> {
- None
- }
-
- fn debug(
- &self,
- bounds: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- _: &V,
- _: &ViewContext<V>,
- ) -> serde_json::Value {
- json!({
- "type": "Image",
- "bounds": bounds.to_json(),
- })
- }
-}
@@ -1,100 +0,0 @@
-use crate::{
- elements::*,
- fonts::TextStyle,
- geometry::{rect::RectF, vector::Vector2F},
- Action, AnyElement, SizeConstraint,
-};
-use serde_json::json;
-
-use super::ContainerStyle;
-
-pub struct KeystrokeLabel {
- action: Box<dyn Action>,
- container_style: ContainerStyle,
- text_style: TextStyle,
- view_id: usize,
-}
-
-impl KeystrokeLabel {
- pub fn new(
- view_id: usize,
- action: Box<dyn Action>,
- container_style: ContainerStyle,
- text_style: TextStyle,
- ) -> Self {
- Self {
- view_id,
- action,
- container_style,
- text_style,
- }
- }
-}
-
-impl<V: 'static> Element<V> for KeystrokeLabel {
- type LayoutState = AnyElement<V>;
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, AnyElement<V>) {
- let mut element = if let Some(keystrokes) =
- cx.keystrokes_for_action(self.view_id, self.action.as_ref())
- {
- Flex::row()
- .with_children(keystrokes.iter().map(|keystroke| {
- Label::new(keystroke.to_string(), self.text_style.clone())
- .contained()
- .with_style(self.container_style)
- }))
- .into_any()
- } else {
- Empty::new().collapsed().into_any()
- };
-
- let size = element.layout(constraint, view, cx);
- (size, element)
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- element: &mut AnyElement<V>,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) {
- element.paint(bounds.origin(), visible_bounds, view, cx);
- }
-
- fn rect_for_text_range(
- &self,
- _: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- _: &V,
- _: &ViewContext<V>,
- ) -> Option<RectF> {
- None
- }
-
- fn debug(
- &self,
- _: RectF,
- element: &AnyElement<V>,
- _: &(),
- view: &V,
- cx: &ViewContext<V>,
- ) -> serde_json::Value {
- json!({
- "type": "KeystrokeLabel",
- "action": self.action.name(),
- "child": element.debug(view, cx)
- })
- }
-}
@@ -1,280 +0,0 @@
-use std::{borrow::Cow, ops::Range};
-
-use crate::{
- fonts::TextStyle,
- geometry::{
- rect::RectF,
- vector::{vec2f, Vector2F},
- },
- json::{ToJson, Value},
- text_layout::{Line, RunStyle},
- Element, SizeConstraint, ViewContext,
-};
-use schemars::JsonSchema;
-use serde::Deserialize;
-use serde_json::json;
-use smallvec::{smallvec, SmallVec};
-
-pub struct Label {
- text: Cow<'static, str>,
- style: LabelStyle,
- highlight_indices: Vec<usize>,
-}
-
-#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
-pub struct LabelStyle {
- pub text: TextStyle,
- pub highlight_text: Option<TextStyle>,
-}
-
-impl From<TextStyle> for LabelStyle {
- fn from(text: TextStyle) -> Self {
- LabelStyle {
- text,
- highlight_text: None,
- }
- }
-}
-
-impl LabelStyle {
- pub fn with_font_size(mut self, font_size: f32) -> Self {
- self.text.font_size = font_size;
- self
- }
-}
-
-impl Label {
- pub fn new<I: Into<Cow<'static, str>>>(text: I, style: impl Into<LabelStyle>) -> Self {
- Self {
- text: text.into(),
- highlight_indices: Default::default(),
- style: style.into(),
- }
- }
-
- pub fn with_highlights(mut self, indices: Vec<usize>) -> Self {
- self.highlight_indices = indices;
- self
- }
-
- fn compute_runs(&self) -> SmallVec<[(usize, RunStyle); 8]> {
- let font_id = self.style.text.font_id;
- if self.highlight_indices.is_empty() {
- return smallvec![(
- self.text.len(),
- RunStyle {
- font_id,
- color: self.style.text.color,
- underline: self.style.text.underline,
- }
- )];
- }
-
- let highlight_font_id = self
- .style
- .highlight_text
- .as_ref()
- .map_or(font_id, |style| style.font_id);
-
- let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
- let mut runs = SmallVec::new();
- let highlight_style = self
- .style
- .highlight_text
- .as_ref()
- .unwrap_or(&self.style.text);
-
- for (char_ix, c) in self.text.char_indices() {
- let mut font_id = font_id;
- let mut color = self.style.text.color;
- let mut underline = self.style.text.underline;
- if let Some(highlight_ix) = highlight_indices.peek() {
- if char_ix == *highlight_ix {
- font_id = highlight_font_id;
- color = highlight_style.color;
- underline = highlight_style.underline;
- highlight_indices.next();
- }
- }
-
- let last_run: Option<&mut (usize, RunStyle)> = runs.last_mut();
- let push_new_run = if let Some((last_len, last_style)) = last_run {
- if font_id == last_style.font_id
- && color == last_style.color
- && underline == last_style.underline
- {
- *last_len += c.len_utf8();
- false
- } else {
- true
- }
- } else {
- true
- };
-
- if push_new_run {
- runs.push((
- c.len_utf8(),
- RunStyle {
- font_id,
- color,
- underline,
- },
- ));
- }
- }
-
- runs
- }
-}
-
-impl<V: 'static> Element<V> for Label {
- type LayoutState = Line;
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- _: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- let runs = self.compute_runs();
- let line = cx.text_layout_cache().layout_str(
- &self.text,
- self.style.text.font_size,
- runs.as_slice(),
- );
-
- let size = vec2f(
- line.width()
- .ceil()
- .max(constraint.min.x())
- .min(constraint.max.x()),
- cx.font_cache.line_height(self.style.text.font_size),
- );
-
- (size, line)
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- line: &mut Self::LayoutState,
- _: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
- line.paint(bounds.origin(), visible_bounds, bounds.size().y(), cx)
- }
-
- fn rect_for_text_range(
- &self,
- _: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- _: &V,
- _: &ViewContext<V>,
- ) -> Option<RectF> {
- None
- }
-
- fn debug(
- &self,
- bounds: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- _: &V,
- _: &ViewContext<V>,
- ) -> Value {
- json!({
- "type": "Label",
- "bounds": bounds.to_json(),
- "text": &self.text,
- "highlight_indices": self.highlight_indices,
- "style": self.style.to_json(),
- })
- }
-}
-
-impl ToJson for LabelStyle {
- fn to_json(&self) -> Value {
- json!({
- "text": self.text.to_json(),
- "highlight_text": self.highlight_text
- .as_ref()
- .map_or(serde_json::Value::Null, |style| style.to_json())
- })
- }
-}
-
-#[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),
- ]
- );
- }
-}
@@ -1,68 +1,54 @@
use crate::{
- geometry::{
- rect::RectF,
- vector::{vec2f, Vector2F},
- },
- json::json,
- AnyElement, Element, MouseRegion, SizeConstraint, ViewContext,
+ point, px, AnyElement, AvailableSpace, BorrowAppContext, Bounds, DispatchPhase, Element,
+ IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled,
+ WindowContext,
};
-use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc};
+use collections::VecDeque;
+use refineable::Refineable as _;
+use std::{cell::RefCell, ops::Range, rc::Rc};
use sum_tree::{Bias, SumTree};
-pub struct List<V> {
- state: ListState<V>,
+pub fn list(state: ListState) -> List {
+ List {
+ state,
+ style: StyleRefinement::default(),
+ }
}
-pub struct ListState<V>(Rc<RefCell<StateInner<V>>>);
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum Orientation {
- Top,
- Bottom,
+pub struct List {
+ state: ListState,
+ style: StyleRefinement,
}
-struct StateInner<V> {
- last_layout_width: Option<f32>,
- render_item: Box<dyn FnMut(&mut V, usize, &mut ViewContext<V>) -> AnyElement<V>>,
- rendered_range: Range<usize>,
- items: SumTree<ListItem<V>>,
+#[derive(Clone)]
+pub struct ListState(Rc<RefCell<StateInner>>);
+
+struct StateInner {
+ last_layout_bounds: Option<Bounds<Pixels>>,
+ render_item: Box<dyn FnMut(usize, &mut WindowContext) -> AnyElement>,
+ items: SumTree<ListItem>,
logical_scroll_top: Option<ListOffset>,
- orientation: Orientation,
- overdraw: f32,
+ alignment: ListAlignment,
+ overdraw: Pixels,
#[allow(clippy::type_complexity)]
- scroll_handler: Option<Box<dyn FnMut(Range<usize>, usize, &mut V, &mut ViewContext<V>)>>,
+ scroll_handler: Option<Box<dyn FnMut(&ListScrollEvent, &mut WindowContext)>>,
}
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
-pub struct ListOffset {
- pub item_ix: usize,
- pub offset_in_item: f32,
-}
-
-enum ListItem<V> {
- Unrendered,
- Rendered(Rc<RefCell<AnyElement<V>>>),
- Removed(f32),
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum ListAlignment {
+ Top,
+ Bottom,
}
-impl<V> Clone for ListItem<V> {
- fn clone(&self) -> Self {
- match self {
- Self::Unrendered => Self::Unrendered,
- Self::Rendered(element) => Self::Rendered(element.clone()),
- Self::Removed(height) => Self::Removed(*height),
- }
- }
+pub struct ListScrollEvent {
+ pub visible_range: Range<usize>,
+ pub count: usize,
}
-impl<V> Debug for ListItem<V> {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Self::Unrendered => write!(f, "Unrendered"),
- Self::Rendered(_) => f.debug_tuple("Rendered").finish(),
- Self::Removed(height) => f.debug_tuple("Removed").field(height).finish(),
- }
- }
+#[derive(Clone)]
+enum ListItem {
+ Unrendered,
+ Rendered { height: Pixels },
}
#[derive(Clone, Debug, Default, PartialEq)]
@@ -70,7 +56,7 @@ struct ListItemSummary {
count: usize,
rendered_count: usize,
unrendered_count: usize,
- height: f32,
+ height: Pixels,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
@@ -83,286 +69,26 @@ struct RenderedCount(usize);
struct UnrenderedCount(usize);
#[derive(Clone, Debug, Default)]
-struct Height(f32);
-
-impl<V> List<V> {
- pub fn new(state: ListState<V>) -> Self {
- Self { state }
- }
-}
-
-impl<V: 'static> Element<V> for List<V> {
- type LayoutState = ListOffset;
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- let state = &mut *self.state.0.borrow_mut();
- let size = constraint.max;
- let mut item_constraint = constraint;
- item_constraint.min.set_y(0.);
- item_constraint.max.set_y(f32::INFINITY);
-
- if cx.refreshing() || state.last_layout_width != Some(size.x()) {
- state.rendered_range = 0..0;
- state.items = SumTree::from_iter(
- (0..state.items.summary().count).map(|_| ListItem::Unrendered),
- &(),
- )
- }
-
- let old_items = state.items.clone();
- let mut new_items = SumTree::new();
- let mut rendered_items = VecDeque::new();
- let mut rendered_height = 0.;
- let mut scroll_top = state.logical_scroll_top();
+struct Height(Pixels);
- // Render items after the scroll top, including those in the trailing overdraw.
- let mut cursor = old_items.cursor::<Count>();
- cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
- for (ix, item) in cursor.by_ref().enumerate() {
- let visible_height = rendered_height - scroll_top.offset_in_item;
- if visible_height >= size.y() + state.overdraw {
- break;
- }
-
- // Force re-render if the item is visible, but attempt to re-use an existing one
- // if we are inside the overdraw.
- let existing_element = if visible_height >= size.y() {
- Some(item)
- } else {
- None
- };
- if let Some(element) = state.render_item(
- scroll_top.item_ix + ix,
- existing_element,
- item_constraint,
- view,
- cx,
- ) {
- rendered_height += element.borrow().size().y();
- rendered_items.push_back(ListItem::Rendered(element));
- }
- }
-
- // Prepare to start walking upward from the item at the scroll top.
- cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
-
- // If the rendered items do not fill the visible region, then adjust
- // the scroll top upward.
- if rendered_height - scroll_top.offset_in_item < size.y() {
- while rendered_height < size.y() {
- cursor.prev(&());
- if cursor.item().is_some() {
- if let Some(element) =
- state.render_item(cursor.start().0, None, item_constraint, view, cx)
- {
- rendered_height += element.borrow().size().y();
- rendered_items.push_front(ListItem::Rendered(element));
- }
- } else {
- break;
- }
- }
-
- scroll_top = ListOffset {
- item_ix: cursor.start().0,
- offset_in_item: rendered_height - size.y(),
- };
-
- match state.orientation {
- Orientation::Top => {
- scroll_top.offset_in_item = scroll_top.offset_in_item.max(0.);
- state.logical_scroll_top = Some(scroll_top);
- }
- Orientation::Bottom => {
- scroll_top = ListOffset {
- item_ix: cursor.start().0,
- offset_in_item: rendered_height - size.y(),
- };
- state.logical_scroll_top = None;
- }
- };
- }
-
- // Render items in the leading overdraw.
- let mut leading_overdraw = scroll_top.offset_in_item;
- while leading_overdraw < state.overdraw {
- cursor.prev(&());
- if let Some(item) = cursor.item() {
- if let Some(element) =
- state.render_item(cursor.start().0, Some(item), item_constraint, view, cx)
- {
- leading_overdraw += element.borrow().size().y();
- rendered_items.push_front(ListItem::Rendered(element));
- }
- } else {
- break;
- }
- }
-
- let new_rendered_range = cursor.start().0..(cursor.start().0 + rendered_items.len());
-
- let mut cursor = old_items.cursor::<Count>();
-
- if state.rendered_range.start < new_rendered_range.start {
- new_items.append(
- cursor.slice(&Count(state.rendered_range.start), Bias::Right, &()),
- &(),
- );
- let remove_to = state.rendered_range.end.min(new_rendered_range.start);
- while cursor.start().0 < remove_to {
- new_items.push(cursor.item().unwrap().remove(), &());
- cursor.next(&());
- }
- }
- new_items.append(
- cursor.slice(&Count(new_rendered_range.start), Bias::Right, &()),
- &(),
- );
-
- new_items.extend(rendered_items, &());
- cursor.seek(&Count(new_rendered_range.end), Bias::Right, &());
-
- if new_rendered_range.end < state.rendered_range.start {
- new_items.append(
- cursor.slice(&Count(state.rendered_range.start), Bias::Right, &()),
- &(),
- );
- }
- while cursor.start().0 < state.rendered_range.end {
- new_items.push(cursor.item().unwrap().remove(), &());
- cursor.next(&());
- }
-
- new_items.append(cursor.suffix(&()), &());
-
- state.items = new_items;
- state.rendered_range = new_rendered_range;
- state.last_layout_width = Some(size.x());
- (size, scroll_top)
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- scroll_top: &mut ListOffset,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) {
- let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
- cx.scene().push_layer(Some(visible_bounds));
- let view_id = cx.view_id();
- cx.scene()
- .push_mouse_region(MouseRegion::new::<Self>(view_id, 0, bounds).on_scroll({
- let state = self.state.clone();
- let height = bounds.height();
- let scroll_top = scroll_top.clone();
- move |e, view, cx| {
- state.0.borrow_mut().scroll(
- &scroll_top,
- height,
- *e.platform_event.delta.raw(),
- e.platform_event.delta.precise(),
- view,
- cx,
- )
- }
- }));
-
- let state = &mut *self.state.0.borrow_mut();
- for (element, origin) in state.visible_elements(bounds, scroll_top) {
- element.borrow_mut().paint(origin, visible_bounds, view, cx);
- }
-
- cx.scene().pop_layer();
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- bounds: RectF,
- _: RectF,
- scroll_top: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- let state = self.state.0.borrow();
- let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item);
- let mut cursor = state.items.cursor::<Count>();
- cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
- while let Some(item) = cursor.item() {
- if item_origin.y() > bounds.max_y() {
- break;
- }
-
- if let ListItem::Rendered(element) = item {
- if let Some(rect) =
- element
- .borrow()
- .rect_for_text_range(range_utf16.clone(), view, cx)
- {
- return Some(rect);
- }
-
- item_origin.set_y(item_origin.y() + element.borrow().size().y());
- cursor.next(&());
- } else {
- unreachable!();
- }
- }
-
- None
- }
-
- fn debug(
- &self,
- bounds: RectF,
- scroll_top: &Self::LayoutState,
- _: &(),
- view: &V,
- cx: &ViewContext<V>,
- ) -> serde_json::Value {
- let state = self.state.0.borrow_mut();
- let visible_elements = state
- .visible_elements(bounds, scroll_top)
- .map(|e| e.0.borrow().debug(view, cx))
- .collect::<Vec<_>>();
- let visible_range = scroll_top.item_ix..(scroll_top.item_ix + visible_elements.len());
- json!({
- "visible_range": visible_range,
- "visible_elements": visible_elements,
- "scroll_top": state.logical_scroll_top.map(|top| (top.item_ix, top.offset_in_item)),
- })
- }
-}
-
-impl<V: 'static> ListState<V> {
- pub fn new<D, F>(
+impl ListState {
+ pub fn new<F>(
element_count: usize,
- orientation: Orientation,
- overdraw: f32,
- mut render_item: F,
+ orientation: ListAlignment,
+ overdraw: Pixels,
+ render_item: F,
) -> Self
where
- D: Element<V>,
- F: 'static + FnMut(&mut V, usize, &mut ViewContext<V>) -> D,
+ F: 'static + FnMut(usize, &mut WindowContext) -> AnyElement,
{
let mut items = SumTree::new();
items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
Self(Rc::new(RefCell::new(StateInner {
- last_layout_width: None,
- render_item: Box::new(move |view, ix, cx| render_item(view, ix, cx).into_any()),
- rendered_range: 0..0,
+ last_layout_bounds: None,
+ render_item: Box::new(render_item),
items,
logical_scroll_top: None,
- orientation,
+ alignment: orientation,
overdraw,
scroll_handler: None,
})))
@@ -370,7 +96,6 @@ impl<V: 'static> ListState<V> {
pub fn reset(&self, element_count: usize) {
let state = &mut *self.0.borrow_mut();
- state.rendered_range = 0..0;
state.logical_scroll_top = None;
state.items = SumTree::new();
state
@@ -392,22 +117,12 @@ impl<V: 'static> ListState<V> {
{
if old_range.contains(item_ix) {
*item_ix = old_range.start;
- *offset_in_item = 0.;
+ *offset_in_item = px(0.);
} else if old_range.end <= *item_ix {
*item_ix = *item_ix - (old_range.end - old_range.start) + count;
}
}
- let new_end = old_range.start + count;
- if old_range.start < state.rendered_range.start {
- state.rendered_range.start =
- new_end + state.rendered_range.start.saturating_sub(old_range.end);
- }
- if old_range.start < state.rendered_range.end {
- state.rendered_range.end =
- new_end + state.rendered_range.end.saturating_sub(old_range.end);
- }
-
let mut old_heights = state.items.cursor::<Count>();
let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &());
old_heights.seek_forward(&Count(old_range.end), Bias::Right, &());
@@ -419,8 +134,8 @@ impl<V: 'static> ListState<V> {
}
pub fn set_scroll_handler(
- &mut self,
- handler: impl FnMut(Range<usize>, usize, &mut V, &mut ViewContext<V>) + 'static,
+ &self,
+ handler: impl FnMut(&ListScrollEvent, &mut WindowContext) + 'static,
) {
self.0.borrow_mut().scroll_handler = Some(Box::new(handler))
}
@@ -434,91 +149,92 @@ impl<V: 'static> ListState<V> {
let item_count = state.items.summary().count;
if scroll_top.item_ix >= item_count {
scroll_top.item_ix = item_count;
- scroll_top.offset_in_item = 0.;
+ scroll_top.offset_in_item = px(0.);
}
state.logical_scroll_top = Some(scroll_top);
}
-}
-impl<V> Clone for ListState<V> {
- fn clone(&self) -> Self {
- Self(self.0.clone())
- }
-}
+ pub fn scroll_to_reveal_item(&self, ix: usize) {
+ let state = &mut *self.0.borrow_mut();
+ let mut scroll_top = state.logical_scroll_top();
+ let height = state
+ .last_layout_bounds
+ .map_or(px(0.), |bounds| bounds.size.height);
-impl<V: 'static> StateInner<V> {
- fn render_item(
- &mut self,
- ix: usize,
- existing_element: Option<&ListItem<V>>,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Option<Rc<RefCell<AnyElement<V>>>> {
- if let Some(ListItem::Rendered(element)) = existing_element {
- Some(element.clone())
+ if ix <= scroll_top.item_ix {
+ scroll_top.item_ix = ix;
+ scroll_top.offset_in_item = px(0.);
} else {
- let mut element = (self.render_item)(view, ix, cx);
- element.layout(constraint, view, cx);
- Some(Rc::new(RefCell::new(element)))
+ let mut cursor = state.items.cursor::<ListItemSummary>();
+ cursor.seek(&Count(ix + 1), Bias::Right, &());
+ let bottom = cursor.start().height;
+ let goal_top = px(0.).max(bottom - height);
+
+ cursor.seek(&Height(goal_top), Bias::Left, &());
+ let start_ix = cursor.start().count;
+ let start_item_top = cursor.start().height;
+
+ if start_ix >= scroll_top.item_ix {
+ scroll_top.item_ix = start_ix;
+ scroll_top.offset_in_item = goal_top - start_item_top;
+ }
}
- }
- fn visible_range(&self, height: f32, scroll_top: &ListOffset) -> Range<usize> {
- let mut cursor = self.items.cursor::<ListItemSummary>();
- cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
- let start_y = cursor.start().height + scroll_top.offset_in_item;
- cursor.seek_forward(&Height(start_y + height), Bias::Left, &());
- scroll_top.item_ix..cursor.start().count + 1
+ state.logical_scroll_top = Some(scroll_top);
}
- fn visible_elements<'a>(
- &'a self,
- bounds: RectF,
- scroll_top: &ListOffset,
- ) -> impl Iterator<Item = (Rc<RefCell<AnyElement<V>>>, Vector2F)> + 'a {
- let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item);
- let mut cursor = self.items.cursor::<Count>();
- cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
- std::iter::from_fn(move || {
- while let Some(item) = cursor.item() {
- if item_origin.y() > bounds.max_y() {
- break;
- }
+ /// Get the bounds for the given item in window coordinates.
+ pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
+ let state = &*self.0.borrow();
+ let bounds = state.last_layout_bounds.unwrap_or_default();
+ let scroll_top = state.logical_scroll_top();
- if let ListItem::Rendered(element) = item {
- let result = (element.clone(), item_origin);
- item_origin.set_y(item_origin.y() + element.borrow().size().y());
- cursor.next(&());
- return Some(result);
- }
+ if ix < scroll_top.item_ix {
+ return None;
+ }
+
+ let mut cursor = state.items.cursor::<(Count, Height)>();
+ cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
- cursor.next(&());
+ let scroll_top = cursor.start().1 .0 + scroll_top.offset_in_item;
+
+ cursor.seek_forward(&Count(ix), Bias::Right, &());
+ if let Some(&ListItem::Rendered { height }) = cursor.item() {
+ let &(Count(count), Height(top)) = cursor.start();
+ if count == ix {
+ let top = bounds.top() + top - scroll_top;
+ return Some(Bounds::from_corners(
+ point(bounds.left(), top),
+ point(bounds.right(), top + height),
+ ));
}
+ }
+ None
+ }
+}
- None
- })
+impl StateInner {
+ fn visible_range(&self, height: Pixels, scroll_top: &ListOffset) -> Range<usize> {
+ let mut cursor = self.items.cursor::<ListItemSummary>();
+ cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
+ let start_y = cursor.start().height + scroll_top.offset_in_item;
+ cursor.seek_forward(&Height(start_y + height), Bias::Left, &());
+ scroll_top.item_ix..cursor.start().count + 1
}
fn scroll(
&mut self,
scroll_top: &ListOffset,
- height: f32,
- mut delta: Vector2F,
- precise: bool,
- view: &mut V,
- cx: &mut ViewContext<V>,
+ height: Pixels,
+ delta: Point<Pixels>,
+ cx: &mut WindowContext,
) {
- if !precise {
- delta *= 20.;
- }
-
- let scroll_max = (self.items.summary().height - height).max(0.);
- let new_scroll_top = (self.scroll_top(scroll_top) - delta.y())
- .max(0.)
+ let scroll_max = (self.items.summary().height - height).max(px(0.));
+ let new_scroll_top = (self.scroll_top(scroll_top) - delta.y)
+ .max(px(0.))
.min(scroll_max);
- if self.orientation == Orientation::Bottom && new_scroll_top == scroll_max {
+ if self.alignment == ListAlignment::Bottom && new_scroll_top == scroll_max {
self.logical_scroll_top = None;
} else {
let mut cursor = self.items.cursor::<ListItemSummary>();
@@ -534,9 +250,10 @@ impl<V: 'static> StateInner<V> {
if self.scroll_handler.is_some() {
let visible_range = self.visible_range(height, scroll_top);
self.scroll_handler.as_mut().unwrap()(
- visible_range,
- self.items.summary().count,
- view,
+ &ListScrollEvent {
+ visible_range,
+ count: self.items.summary().count,
+ },
cx,
);
}
@@ -546,36 +263,235 @@ impl<V: 'static> StateInner<V> {
fn logical_scroll_top(&self) -> ListOffset {
self.logical_scroll_top
- .unwrap_or_else(|| match self.orientation {
- Orientation::Top => ListOffset {
+ .unwrap_or_else(|| match self.alignment {
+ ListAlignment::Top => ListOffset {
item_ix: 0,
- offset_in_item: 0.,
+ offset_in_item: px(0.),
},
- Orientation::Bottom => ListOffset {
+ ListAlignment::Bottom => ListOffset {
item_ix: self.items.summary().count,
- offset_in_item: 0.,
+ offset_in_item: px(0.),
},
})
}
- fn scroll_top(&self, logical_scroll_top: &ListOffset) -> f32 {
+ fn scroll_top(&self, logical_scroll_top: &ListOffset) -> Pixels {
let mut cursor = self.items.cursor::<ListItemSummary>();
cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right, &());
cursor.start().height + logical_scroll_top.offset_in_item
}
}
-impl<V> ListItem<V> {
- fn remove(&self) -> Self {
+impl std::fmt::Debug for ListItem {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
- ListItem::Unrendered => ListItem::Unrendered,
- ListItem::Rendered(element) => ListItem::Removed(element.borrow().size().y()),
- ListItem::Removed(height) => ListItem::Removed(*height),
+ Self::Unrendered => write!(f, "Unrendered"),
+ Self::Rendered { height, .. } => {
+ f.debug_struct("Rendered").field("height", height).finish()
+ }
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, Default)]
+pub struct ListOffset {
+ pub item_ix: usize,
+ pub offset_in_item: Pixels,
+}
+
+impl Element for List {
+ type State = ();
+
+ fn request_layout(
+ &mut self,
+ _state: Option<Self::State>,
+ cx: &mut crate::WindowContext,
+ ) -> (crate::LayoutId, Self::State) {
+ let mut style = Style::default();
+ style.refine(&self.style);
+ let layout_id = cx.with_text_style(style.text_style().cloned(), |cx| {
+ cx.request_layout(&style, None)
+ });
+ (layout_id, ())
+ }
+
+ fn paint(
+ &mut self,
+ bounds: crate::Bounds<crate::Pixels>,
+ _state: &mut Self::State,
+ cx: &mut crate::WindowContext,
+ ) {
+ let state = &mut *self.state.0.borrow_mut();
+
+ // If the width of the list has changed, invalidate all cached item heights
+ if state.last_layout_bounds.map_or(true, |last_bounds| {
+ last_bounds.size.width != bounds.size.width
+ }) {
+ state.items = SumTree::from_iter(
+ (0..state.items.summary().count).map(|_| ListItem::Unrendered),
+ &(),
+ )
+ }
+
+ let old_items = state.items.clone();
+ let mut measured_items = VecDeque::new();
+ let mut item_elements = VecDeque::new();
+ let mut rendered_height = px(0.);
+ let mut scroll_top = state.logical_scroll_top();
+
+ let available_item_space = Size {
+ width: AvailableSpace::Definite(bounds.size.width),
+ height: AvailableSpace::MinContent,
+ };
+
+ // Render items after the scroll top, including those in the trailing overdraw
+ let mut cursor = old_items.cursor::<Count>();
+ cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
+ for (ix, item) in cursor.by_ref().enumerate() {
+ let visible_height = rendered_height - scroll_top.offset_in_item;
+ if visible_height >= bounds.size.height + state.overdraw {
+ break;
+ }
+
+ // Use the previously cached height if available
+ let mut height = if let ListItem::Rendered { height } = item {
+ Some(*height)
+ } else {
+ None
+ };
+
+ // If we're within the visible area or the height wasn't cached, render and measure the item's element
+ if visible_height < bounds.size.height || height.is_none() {
+ let mut element = (state.render_item)(scroll_top.item_ix + ix, cx);
+ let element_size = element.measure(available_item_space, cx);
+ height = Some(element_size.height);
+ if visible_height < bounds.size.height {
+ item_elements.push_back(element);
+ }
+ }
+
+ let height = height.unwrap();
+ rendered_height += height;
+ measured_items.push_back(ListItem::Rendered { height });
+ }
+
+ // Prepare to start walking upward from the item at the scroll top.
+ cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
+
+ // If the rendered items do not fill the visible region, then adjust
+ // the scroll top upward.
+ if rendered_height - scroll_top.offset_in_item < bounds.size.height {
+ while rendered_height < bounds.size.height {
+ cursor.prev(&());
+ if cursor.item().is_some() {
+ let mut element = (state.render_item)(cursor.start().0, cx);
+ let element_size = element.measure(available_item_space, cx);
+
+ rendered_height += element_size.height;
+ measured_items.push_front(ListItem::Rendered {
+ height: element_size.height,
+ });
+ item_elements.push_front(element)
+ } else {
+ break;
+ }
+ }
+
+ scroll_top = ListOffset {
+ item_ix: cursor.start().0,
+ offset_in_item: rendered_height - bounds.size.height,
+ };
+
+ match state.alignment {
+ ListAlignment::Top => {
+ scroll_top.offset_in_item = scroll_top.offset_in_item.max(px(0.));
+ state.logical_scroll_top = Some(scroll_top);
+ }
+ ListAlignment::Bottom => {
+ scroll_top = ListOffset {
+ item_ix: cursor.start().0,
+ offset_in_item: rendered_height - bounds.size.height,
+ };
+ state.logical_scroll_top = None;
+ }
+ };
+ }
+
+ // Measure items in the leading overdraw
+ let mut leading_overdraw = scroll_top.offset_in_item;
+ while leading_overdraw < state.overdraw {
+ cursor.prev(&());
+ if let Some(item) = cursor.item() {
+ let height = if let ListItem::Rendered { height } = item {
+ *height
+ } else {
+ let mut element = (state.render_item)(cursor.start().0, cx);
+ element.measure(available_item_space, cx).height
+ };
+
+ leading_overdraw += height;
+ measured_items.push_front(ListItem::Rendered { height });
+ } else {
+ break;
+ }
+ }
+
+ let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len());
+ let mut cursor = old_items.cursor::<Count>();
+ let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &());
+ new_items.extend(measured_items, &());
+ cursor.seek(&Count(measured_range.end), Bias::Right, &());
+ new_items.append(cursor.suffix(&()), &());
+
+ // Paint the visible items
+ let mut item_origin = bounds.origin;
+ item_origin.y -= scroll_top.offset_in_item;
+ for item_element in &mut item_elements {
+ let item_height = item_element.measure(available_item_space, cx).height;
+ item_element.draw(item_origin, available_item_space, cx);
+ item_origin.y += item_height;
}
+
+ state.items = new_items;
+ state.last_layout_bounds = Some(bounds);
+
+ let list_state = self.state.clone();
+ let height = bounds.size.height;
+ cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
+ if phase == DispatchPhase::Bubble
+ && bounds.contains(&event.position)
+ && cx.was_top_layer(&event.position, cx.stacking_order())
+ {
+ list_state.0.borrow_mut().scroll(
+ &scroll_top,
+ height,
+ event.delta.pixel_delta(px(20.)),
+ cx,
+ )
+ }
+ });
+ }
+}
+
+impl IntoElement for List {
+ type Element = Self;
+
+ fn element_id(&self) -> Option<crate::ElementId> {
+ None
+ }
+
+ fn into_element(self) -> Self::Element {
+ self
}
}
-impl<V> sum_tree::Item for ListItem<V> {
+impl Styled for List {
+ fn style(&mut self) -> &mut StyleRefinement {
+ &mut self.style
+ }
+}
+
+impl sum_tree::Item for ListItem {
type Summary = ListItemSummary;
fn summary(&self) -> Self::Summary {
@@ -584,18 +500,12 @@ impl<V> sum_tree::Item for ListItem<V> {
count: 1,
rendered_count: 0,
unrendered_count: 1,
- height: 0.,
+ height: px(0.),
},
- ListItem::Rendered(element) => ListItemSummary {
+ ListItem::Rendered { height } => ListItemSummary {
count: 1,
rendered_count: 1,
unrendered_count: 0,
- height: element.borrow().size().y(),
- },
- ListItem::Removed(height) => ListItemSummary {
- count: 1,
- rendered_count: 0,
- unrendered_count: 1,
height: *height,
},
}
@@ -648,339 +558,3 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
self.0.partial_cmp(&other.height).unwrap()
}
}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::{elements::Empty, geometry::vector::vec2f, Entity};
- 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 (size, _) = list.layout(constraint, &mut view, 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 (_, logical_scroll_top) = list.layout(constraint, &mut view, 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 (size, logical_scroll_top) = list.layout(constraint, &mut view, 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 (size, logical_scroll_top) = list.layout(
- SizeConstraint::new(vec2f(0., 0.), window_size),
- &mut view,
- 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 ViewContext<V>,
- ) -> (Vector2F, ()) {
- (self.size, ())
- }
-
- fn paint(&mut self, _: RectF, _: RectF, _: &mut (), _: &mut V, _: &mut ViewContext<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)
- }
- }
-}
@@ -1,323 +0,0 @@
-use super::Padding;
-use crate::{
- geometry::{
- rect::RectF,
- vector::{vec2f, Vector2F},
- },
- platform::CursorStyle,
- platform::MouseButton,
- scene::{
- CursorRegion, HandlerSet, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag,
- MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
- },
- AnyElement, Element, EventContext, MouseRegion, MouseState, SizeConstraint, TypeTag,
- ViewContext,
-};
-use serde_json::json;
-use std::ops::Range;
-
-pub struct MouseEventHandler<V: 'static> {
- child: AnyElement<V>,
- region_id: usize,
- cursor_style: Option<CursorStyle>,
- handlers: HandlerSet,
- hoverable: bool,
- notify_on_hover: bool,
- notify_on_click: bool,
- above: bool,
- padding: Padding,
- tag: TypeTag,
-}
-
-/// Element which provides a render_child callback with a MouseState and paints a mouse
-/// region under (or above) it for easy mouse event handling.
-impl<V: 'static> MouseEventHandler<V> {
- pub fn for_child<Tag: 'static>(child: impl Element<V>, region_id: usize) -> Self {
- Self {
- child: child.into_any(),
- region_id,
- cursor_style: None,
- handlers: Default::default(),
- notify_on_hover: false,
- notify_on_click: false,
- hoverable: false,
- above: false,
- padding: Default::default(),
- tag: TypeTag::new::<Tag>(),
- }
- }
-
- pub fn new<Tag: 'static, E>(
- region_id: usize,
- cx: &mut ViewContext<V>,
- render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> E,
- ) -> Self
- where
- E: Element<V>,
- {
- let mut mouse_state = cx.mouse_state_dynamic(TypeTag::new::<Tag>(), region_id);
- let child = render_child(&mut mouse_state, cx).into_any();
- let notify_on_hover = mouse_state.accessed_hovered();
- let notify_on_click = mouse_state.accessed_clicked();
- Self {
- child,
- region_id,
- cursor_style: None,
- handlers: Default::default(),
- notify_on_hover,
- notify_on_click,
- hoverable: true,
- above: false,
- padding: Default::default(),
- tag: TypeTag::new::<Tag>(),
- }
- }
-
- pub fn new_dynamic(
- tag: TypeTag,
- region_id: usize,
- cx: &mut ViewContext<V>,
- render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> AnyElement<V>,
- ) -> Self {
- let mut mouse_state = cx.mouse_state_dynamic(tag, region_id);
- let child = render_child(&mut mouse_state, cx);
- let notify_on_hover = mouse_state.accessed_hovered();
- let notify_on_click = mouse_state.accessed_clicked();
- Self {
- child,
- region_id,
- cursor_style: None,
- handlers: Default::default(),
- notify_on_hover,
- notify_on_click,
- hoverable: true,
- above: false,
- padding: Default::default(),
- tag,
- }
- }
-
- /// Modifies the MouseEventHandler to render the MouseRegion above the child element. Useful
- /// for drag and drop handling and similar events which should be captured before the child
- /// gets the opportunity
- pub fn above<Tag: 'static, D>(
- region_id: usize,
- cx: &mut ViewContext<V>,
- render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> D,
- ) -> Self
- where
- D: Element<V>,
- {
- let mut handler = Self::new::<Tag, _>(region_id, cx, render_child);
- handler.above = true;
- handler
- }
-
- pub fn with_cursor_style(mut self, cursor: CursorStyle) -> Self {
- self.cursor_style = Some(cursor);
- self
- }
-
- pub fn capture_all(mut self) -> Self {
- self.handlers = HandlerSet::capture_all();
- self
- }
-
- pub fn on_move(
- mut self,
- handler: impl Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
- ) -> Self {
- self.handlers = self.handlers.on_move(handler);
- self
- }
-
- pub fn on_move_out(
- mut self,
- handler: impl Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
- ) -> Self {
- self.handlers = self.handlers.on_move_out(handler);
- self
- }
-
- pub fn on_down(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
- ) -> Self {
- self.handlers = self.handlers.on_down(button, handler);
- self
- }
-
- pub fn on_up(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
- ) -> Self {
- self.handlers = self.handlers.on_up(button, handler);
- self
- }
-
- pub fn on_click(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
- ) -> Self {
- self.handlers = self.handlers.on_click(button, handler);
- self
- }
-
- pub fn on_click_out(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseClickOut, &mut V, &mut EventContext<V>) + 'static,
- ) -> Self {
- self.handlers = self.handlers.on_click_out(button, handler);
- self
- }
-
- pub fn on_down_out(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
- ) -> Self {
- self.handlers = self.handlers.on_down_out(button, handler);
- self
- }
-
- pub fn on_up_out(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
- ) -> Self {
- self.handlers = self.handlers.on_up_out(button, handler);
- self
- }
-
- pub fn on_drag(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
- ) -> Self {
- self.handlers = self.handlers.on_drag(button, handler);
- self
- }
-
- pub fn on_hover(
- mut self,
- handler: impl Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
- ) -> Self {
- self.handlers = self.handlers.on_hover(handler);
- self
- }
-
- pub fn on_scroll(
- mut self,
- handler: impl Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
- ) -> Self {
- self.handlers = self.handlers.on_scroll(handler);
- self
- }
-
- pub fn with_hoverable(mut self, is_hoverable: bool) -> Self {
- self.hoverable = is_hoverable;
- self
- }
-
- pub fn with_padding(mut self, padding: Padding) -> Self {
- self.padding = padding;
- self
- }
-
- fn hit_bounds(&self, bounds: RectF) -> RectF {
- RectF::from_points(
- bounds.origin() - vec2f(self.padding.left, self.padding.top),
- bounds.lower_right() + vec2f(self.padding.right, self.padding.bottom),
- )
- .round_out()
- }
-
- fn paint_regions(&self, bounds: RectF, visible_bounds: RectF, cx: &mut ViewContext<V>) {
- let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
- let hit_bounds = self.hit_bounds(visible_bounds);
-
- if let Some(style) = self.cursor_style {
- cx.scene().push_cursor_region(CursorRegion {
- bounds: hit_bounds,
- style,
- });
- }
- let view_id = cx.view_id();
- cx.scene().push_mouse_region(
- MouseRegion::from_handlers(
- self.tag,
- view_id,
- self.region_id,
- hit_bounds,
- self.handlers.clone(),
- )
- .with_hoverable(self.hoverable)
- .with_notify_on_hover(self.notify_on_hover)
- .with_notify_on_click(self.notify_on_click),
- );
- }
-}
-
-impl<V: 'static> Element<V> for MouseEventHandler<V> {
- type LayoutState = ();
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- (self.child.layout(constraint, view, cx), ())
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- _: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- if self.above {
- self.child.paint(bounds.origin(), visible_bounds, view, cx);
- cx.paint_layer(None, |cx| {
- self.paint_regions(bounds, visible_bounds, cx);
- });
- } else {
- self.paint_regions(bounds, visible_bounds, cx);
- self.child.paint(bounds.origin(), visible_bounds, view, cx);
- }
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, view, cx)
- }
-
- fn debug(
- &self,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> serde_json::Value {
- json!({
- "type": "MouseEventHandler",
- "child": self.child.debug(view, cx),
- })
- }
-}
@@ -1,260 +1,241 @@
-use std::ops::Range;
+use smallvec::SmallVec;
+use taffy::style::{Display, Position};
use crate::{
- geometry::{rect::RectF, vector::Vector2F},
- json::ToJson,
- AnyElement, Axis, Element, MouseRegion, SizeConstraint, ViewContext,
+ point, AnyElement, BorrowWindow, Bounds, Element, IntoElement, LayoutId, ParentElement, Pixels,
+ Point, Size, Style, WindowContext,
};
-use serde_json::json;
-pub struct Overlay<V> {
- child: AnyElement<V>,
- anchor_position: Option<Vector2F>,
- anchor_corner: AnchorCorner,
- fit_mode: OverlayFitMode,
- position_mode: OverlayPositionMode,
- hoverable: bool,
- z_index: Option<usize>,
-}
-
-#[derive(Copy, Clone)]
-pub enum OverlayFitMode {
- SnapToWindow,
- SwitchAnchor,
- None,
+pub struct OverlayState {
+ child_layout_ids: SmallVec<[LayoutId; 4]>,
}
-#[derive(Copy, Clone, PartialEq, Eq)]
-pub enum OverlayPositionMode {
- Window,
- Local,
-}
-
-#[derive(Clone, Copy, PartialEq, Eq)]
-pub enum AnchorCorner {
- TopLeft,
- TopRight,
- BottomLeft,
- BottomRight,
+pub struct Overlay {
+ children: SmallVec<[AnyElement; 2]>,
+ anchor_corner: AnchorCorner,
+ fit_mode: OverlayFitMode,
+ // todo!();
+ anchor_position: Option<Point<Pixels>>,
+ // position_mode: OverlayPositionMode,
}
-impl AnchorCorner {
- fn get_bounds(&self, anchor_position: Vector2F, size: Vector2F) -> RectF {
- match self {
- Self::TopLeft => RectF::from_points(anchor_position, anchor_position + size),
- Self::TopRight => RectF::from_points(
- anchor_position - Vector2F::new(size.x(), 0.),
- anchor_position + Vector2F::new(0., size.y()),
- ),
- Self::BottomLeft => RectF::from_points(
- anchor_position - Vector2F::new(0., size.y()),
- anchor_position + Vector2F::new(size.x(), 0.),
- ),
- Self::BottomRight => RectF::from_points(anchor_position - size, anchor_position),
- }
- }
-
- fn switch_axis(self, axis: Axis) -> Self {
- match axis {
- Axis::Vertical => match self {
- AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
- AnchorCorner::TopRight => AnchorCorner::BottomRight,
- AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
- AnchorCorner::BottomRight => AnchorCorner::TopRight,
- },
- Axis::Horizontal => match self {
- AnchorCorner::TopLeft => AnchorCorner::TopRight,
- AnchorCorner::TopRight => AnchorCorner::TopLeft,
- AnchorCorner::BottomLeft => AnchorCorner::BottomRight,
- AnchorCorner::BottomRight => AnchorCorner::BottomLeft,
- },
- }
+/// overlay gives you a floating element that will avoid overflowing the window bounds.
+/// Its children should have no margin to avoid measurement issues.
+pub fn overlay() -> Overlay {
+ Overlay {
+ children: SmallVec::new(),
+ anchor_corner: AnchorCorner::TopLeft,
+ fit_mode: OverlayFitMode::SwitchAnchor,
+ anchor_position: None,
}
}
-impl<V: 'static> Overlay<V> {
- pub fn new(child: impl Element<V>) -> Self {
- Self {
- child: child.into_any(),
- anchor_position: None,
- anchor_corner: AnchorCorner::TopLeft,
- fit_mode: OverlayFitMode::None,
- position_mode: OverlayPositionMode::Window,
- hoverable: false,
- z_index: None,
- }
- }
-
- pub fn with_anchor_position(mut self, position: Vector2F) -> Self {
- self.anchor_position = Some(position);
- self
- }
-
- pub fn with_anchor_corner(mut self, anchor_corner: AnchorCorner) -> Self {
- self.anchor_corner = anchor_corner;
- self
- }
-
- pub fn with_fit_mode(mut self, fit_mode: OverlayFitMode) -> Self {
- self.fit_mode = fit_mode;
+impl Overlay {
+ /// Sets which corner of the overlay should be anchored to the current position.
+ pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
+ self.anchor_corner = anchor;
self
}
- pub fn with_position_mode(mut self, position_mode: OverlayPositionMode) -> Self {
- self.position_mode = position_mode;
+ /// Sets the position in window co-ordinates
+ /// (otherwise the location the overlay is rendered is used)
+ pub fn position(mut self, anchor: Point<Pixels>) -> Self {
+ self.anchor_position = Some(anchor);
self
}
- pub fn with_hoverable(mut self, hoverable: bool) -> Self {
- self.hoverable = hoverable;
+ /// Snap to window edge instead of switching anchor corner when an overflow would occur.
+ pub fn snap_to_window(mut self) -> Self {
+ self.fit_mode = OverlayFitMode::SnapToWindow;
self
}
+}
- pub fn with_z_index(mut self, z_index: usize) -> Self {
- self.z_index = Some(z_index);
- self
+impl ParentElement for Overlay {
+ fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
+ &mut self.children
}
}
-impl<V: 'static> Element<V> for Overlay<V> {
- type LayoutState = Vector2F;
- type PaintState = ();
+impl Element for Overlay {
+ type State = OverlayState;
- fn layout(
+ fn request_layout(
&mut self,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- let constraint = if self.anchor_position.is_some() {
- SizeConstraint::new(Vector2F::zero(), cx.window_size())
- } else {
- constraint
+ _: Option<Self::State>,
+ cx: &mut WindowContext,
+ ) -> (crate::LayoutId, Self::State) {
+ let child_layout_ids = self
+ .children
+ .iter_mut()
+ .map(|child| child.request_layout(cx))
+ .collect::<SmallVec<_>>();
+
+ let overlay_style = Style {
+ position: Position::Absolute,
+ display: Display::Flex,
+ ..Style::default()
};
- let size = self.child.layout(constraint, view, cx);
- (Vector2F::zero(), size)
+
+ let layout_id = cx.request_layout(&overlay_style, child_layout_ids.iter().copied());
+
+ (layout_id, OverlayState { child_layout_ids })
}
fn paint(
&mut self,
- bounds: RectF,
- _: RectF,
- size: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
+ bounds: crate::Bounds<crate::Pixels>,
+ element_state: &mut Self::State,
+ cx: &mut WindowContext,
) {
- let (anchor_position, mut bounds) = match self.position_mode {
- OverlayPositionMode::Window => {
- let anchor_position = self.anchor_position.unwrap_or_else(|| bounds.origin());
- let bounds = self.anchor_corner.get_bounds(anchor_position, *size);
- (anchor_position, bounds)
- }
- OverlayPositionMode::Local => {
- let anchor_position = self.anchor_position.unwrap_or_default();
- let bounds = self
- .anchor_corner
- .get_bounds(bounds.origin() + anchor_position, *size);
- (anchor_position, bounds)
- }
+ if element_state.child_layout_ids.is_empty() {
+ return;
+ }
+
+ let mut child_min = point(Pixels::MAX, Pixels::MAX);
+ let mut child_max = Point::default();
+ for child_layout_id in &element_state.child_layout_ids {
+ let child_bounds = cx.layout_bounds(*child_layout_id);
+ child_min = child_min.min(&child_bounds.origin);
+ child_max = child_max.max(&child_bounds.lower_right());
+ }
+ let size: Size<Pixels> = (child_max - child_min).into();
+ let origin = self.anchor_position.unwrap_or(bounds.origin);
+
+ let mut desired = self.anchor_corner.get_bounds(origin, size);
+ let limits = Bounds {
+ origin: Point::default(),
+ size: cx.viewport_size(),
};
- match self.fit_mode {
- OverlayFitMode::SnapToWindow => {
- // Snap the horizontal edges of the overlay to the horizontal edges of the window if
- // its horizontal bounds overflow
- if bounds.max_x() > cx.window_size().x() {
- let mut lower_right = bounds.lower_right();
- lower_right.set_x(cx.window_size().x());
- bounds = RectF::from_points(lower_right - *size, lower_right);
- } else if bounds.min_x() < 0. {
- let mut upper_left = bounds.origin();
- upper_left.set_x(0.);
- bounds = RectF::from_points(upper_left, upper_left + *size);
- }
+ if self.fit_mode == OverlayFitMode::SwitchAnchor {
+ let mut anchor_corner = self.anchor_corner;
- // Snap the vertical edges of the overlay to the vertical edges of the window if
- // its vertical bounds overflow.
- if bounds.max_y() > cx.window_size().y() {
- let mut lower_right = bounds.lower_right();
- lower_right.set_y(cx.window_size().y());
- bounds = RectF::from_points(lower_right - *size, lower_right);
- } else if bounds.min_y() < 0. {
- let mut upper_left = bounds.origin();
- upper_left.set_y(0.);
- bounds = RectF::from_points(upper_left, upper_left + *size);
+ if desired.left() < limits.left() || desired.right() > limits.right() {
+ let switched = anchor_corner
+ .switch_axis(Axis::Horizontal)
+ .get_bounds(origin, size);
+ if !(switched.left() < limits.left() || switched.right() > limits.right()) {
+ anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
+ desired = switched
}
}
- OverlayFitMode::SwitchAnchor => {
- let mut anchor_corner = self.anchor_corner;
- if bounds.max_x() > cx.window_size().x() {
- anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
+ if desired.top() < limits.top() || desired.bottom() > limits.bottom() {
+ let switched = anchor_corner
+ .switch_axis(Axis::Vertical)
+ .get_bounds(origin, size);
+ if !(switched.top() < limits.top() || switched.bottom() > limits.bottom()) {
+ desired = switched;
}
+ }
+ }
- if bounds.max_y() > cx.window_size().y() {
- anchor_corner = anchor_corner.switch_axis(Axis::Vertical);
- }
+ // Snap the horizontal edges of the overlay to the horizontal edges of the window if
+ // its horizontal bounds overflow, aligning to the left if it is wider than the limits.
+ if desired.right() > limits.right() {
+ desired.origin.x -= desired.right() - limits.right();
+ }
+ if desired.left() < limits.left() {
+ desired.origin.x = limits.origin.x;
+ }
- if bounds.min_x() < 0. {
- anchor_corner = anchor_corner.switch_axis(Axis::Horizontal)
- }
+ // Snap the vertical edges of the overlay to the vertical edges of the window if
+ // its vertical bounds overflow, aligning to the top if it is taller than the limits.
+ if desired.bottom() > limits.bottom() {
+ desired.origin.y -= desired.bottom() - limits.bottom();
+ }
+ if desired.top() < limits.top() {
+ desired.origin.y = limits.origin.y;
+ }
- if bounds.min_y() < 0. {
- anchor_corner = anchor_corner.switch_axis(Axis::Vertical)
+ let mut offset = cx.element_offset() + desired.origin - bounds.origin;
+ offset = point(offset.x.round(), offset.y.round());
+ cx.with_absolute_element_offset(offset, |cx| {
+ cx.break_content_mask(|cx| {
+ for child in &mut self.children {
+ child.paint(cx);
}
+ })
+ })
+ }
+}
- // Update bounds if needed
- if anchor_corner != self.anchor_corner {
- bounds = anchor_corner.get_bounds(anchor_position, *size)
- }
- }
- OverlayFitMode::None => {}
- }
+impl IntoElement for Overlay {
+ type Element = Self;
- cx.scene().push_stacking_context(None, self.z_index);
- if self.hoverable {
- enum OverlayHoverCapture {}
- // Block hovers in lower stacking contexts
- let view_id = cx.view_id();
- cx.scene()
- .push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
- view_id, view_id, bounds,
- ));
- }
- self.child.paint(
- bounds.origin(),
- RectF::new(Vector2F::zero(), cx.window_size()),
- view,
- cx,
- );
- cx.scene().pop_stacking_context();
+ fn element_id(&self) -> Option<crate::ElementId> {
+ None
}
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, view, cx)
+ fn into_element(self) -> Self::Element {
+ self
}
+}
- fn debug(
- &self,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> serde_json::Value {
- json!({
- "type": "Overlay",
- "abs_position": self.anchor_position.to_json(),
- "child": self.child.debug(view, cx),
- })
+enum Axis {
+ Horizontal,
+ Vertical,
+}
+
+#[derive(Copy, Clone, PartialEq)]
+pub enum OverlayFitMode {
+ SnapToWindow,
+ SwitchAnchor,
+}
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum AnchorCorner {
+ TopLeft,
+ TopRight,
+ BottomLeft,
+ BottomRight,
+}
+
+impl AnchorCorner {
+ fn get_bounds(&self, origin: Point<Pixels>, size: Size<Pixels>) -> Bounds<Pixels> {
+ let origin = match self {
+ Self::TopLeft => origin,
+ Self::TopRight => Point {
+ x: origin.x - size.width,
+ y: origin.y,
+ },
+ Self::BottomLeft => Point {
+ x: origin.x,
+ y: origin.y - size.height,
+ },
+ Self::BottomRight => Point {
+ x: origin.x - size.width,
+ y: origin.y - size.height,
+ },
+ };
+
+ Bounds { origin, size }
+ }
+
+ pub fn corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> {
+ match self {
+ Self::TopLeft => bounds.origin,
+ Self::TopRight => bounds.upper_right(),
+ Self::BottomLeft => bounds.lower_left(),
+ Self::BottomRight => bounds.lower_right(),
+ }
+ }
+
+ fn switch_axis(self, axis: Axis) -> Self {
+ match axis {
+ Axis::Vertical => match self {
+ AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
+ AnchorCorner::TopRight => AnchorCorner::BottomRight,
+ AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
+ AnchorCorner::BottomRight => AnchorCorner::TopRight,
+ },
+ Axis::Horizontal => match self {
+ AnchorCorner::TopLeft => AnchorCorner::TopRight,
+ AnchorCorner::TopRight => AnchorCorner::TopLeft,
+ AnchorCorner::BottomLeft => AnchorCorner::BottomRight,
+ AnchorCorner::BottomRight => AnchorCorner::BottomLeft,
+ },
+ }
}
}
@@ -1,290 +0,0 @@
-use std::{cell::RefCell, rc::Rc};
-
-use collections::HashMap;
-use pathfinder_geometry::vector::{vec2f, Vector2F};
-use serde_json::json;
-
-use crate::{
- geometry::rect::RectF,
- platform::{CursorStyle, MouseButton},
- AnyElement, AppContext, Axis, Element, MouseRegion, SizeConstraint, TypeTag, View, ViewContext,
-};
-
-#[derive(Copy, Clone, Debug)]
-pub enum HandleSide {
- Top,
- Bottom,
- Left,
- Right,
-}
-
-impl HandleSide {
- fn axis(&self) -> Axis {
- match self {
- HandleSide::Left | HandleSide::Right => Axis::Horizontal,
- HandleSide::Top | HandleSide::Bottom => Axis::Vertical,
- }
- }
-
- fn relevant_component(&self, vector: Vector2F) -> f32 {
- match self.axis() {
- Axis::Horizontal => vector.x(),
- Axis::Vertical => vector.y(),
- }
- }
-
- fn of_rect(&self, bounds: RectF, handle_size: f32) -> RectF {
- match self {
- HandleSide::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)),
- HandleSide::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())),
- HandleSide::Bottom => {
- let mut origin = bounds.lower_left();
- origin.set_y(origin.y() - handle_size);
- RectF::new(origin, vec2f(bounds.width(), handle_size))
- }
- HandleSide::Right => {
- let mut origin = bounds.upper_right();
- origin.set_x(origin.x() - handle_size);
- RectF::new(origin, vec2f(handle_size, bounds.height()))
- }
- }
- }
-}
-
-fn get_bounds(tag: TypeTag, cx: &AppContext) -> Option<&(RectF, RectF)>
-where
-{
- cx.optional_global::<ProviderMap>()
- .and_then(|map| map.0.get(&tag))
-}
-
-pub struct Resizable<V: 'static> {
- child: AnyElement<V>,
- tag: TypeTag,
- handle_side: HandleSide,
- handle_size: f32,
- on_resize: Rc<RefCell<dyn FnMut(&mut V, Option<f32>, &mut ViewContext<V>)>>,
-}
-
-const DEFAULT_HANDLE_SIZE: f32 = 4.0;
-
-impl<V: 'static> Resizable<V> {
- pub fn new<Tag: 'static>(
- child: AnyElement<V>,
- handle_side: HandleSide,
- size: f32,
- on_resize: impl 'static + FnMut(&mut V, Option<f32>, &mut ViewContext<V>),
- ) -> Self {
- let child = match handle_side.axis() {
- Axis::Horizontal => child.constrained().with_max_width(size),
- Axis::Vertical => child.constrained().with_max_height(size),
- }
- .into_any();
-
- Self {
- child,
- handle_side,
- tag: TypeTag::new::<Tag>(),
- handle_size: DEFAULT_HANDLE_SIZE,
- on_resize: Rc::new(RefCell::new(on_resize)),
- }
- }
-
- pub fn with_handle_size(mut self, handle_size: f32) -> Self {
- self.handle_size = handle_size;
- self
- }
-}
-
-impl<V: 'static> Element<V> for Resizable<V> {
- type LayoutState = SizeConstraint;
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: crate::SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- (self.child.layout(constraint, view, cx), constraint)
- }
-
- fn paint(
- &mut self,
- bounds: pathfinder_geometry::rect::RectF,
- visible_bounds: pathfinder_geometry::rect::RectF,
- constraint: &mut SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- cx.scene().push_stacking_context(None, None);
-
- let handle_region = self.handle_side.of_rect(bounds, self.handle_size);
-
- enum ResizeHandle {}
- let view_id = cx.view_id();
- cx.scene().push_mouse_region(
- MouseRegion::new::<ResizeHandle>(view_id, self.handle_side as usize, handle_region)
- .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
- .on_click(MouseButton::Left, {
- let on_resize = self.on_resize.clone();
- move |click, v, cx| {
- if click.click_count == 2 {
- on_resize.borrow_mut()(v, None, cx);
- }
- }
- })
- .on_drag(MouseButton::Left, {
- let bounds = bounds.clone();
- let side = self.handle_side;
- let prev_size = side.relevant_component(bounds.size());
- let min_size = side.relevant_component(constraint.min);
- let max_size = side.relevant_component(constraint.max);
- let on_resize = self.on_resize.clone();
- let tag = self.tag;
- move |event, view: &mut V, cx| {
- if event.end {
- return;
- }
-
- let Some((bounds, _)) = get_bounds(tag, cx) else {
- return;
- };
-
- let new_size_raw = match side {
- // Handle on top side of element => Element is on bottom
- HandleSide::Top => {
- bounds.height() + bounds.origin_y() - event.position.y()
- }
- // Handle on right side of element => Element is on left
- HandleSide::Right => event.position.x() - bounds.lower_left().x(),
- // Handle on left side of element => Element is on the right
- HandleSide::Left => {
- bounds.width() + bounds.origin_x() - event.position.x()
- }
- // Handle on bottom side of element => Element is on the top
- HandleSide::Bottom => event.position.y() - bounds.lower_left().y(),
- };
-
- let new_size = min_size.max(new_size_raw).min(max_size).round();
- if new_size != prev_size {
- on_resize.borrow_mut()(view, Some(new_size), cx);
- }
- }
- }),
- );
-
- cx.scene().push_cursor_region(crate::CursorRegion {
- bounds: handle_region,
- style: match self.handle_side.axis() {
- Axis::Horizontal => CursorStyle::ResizeLeftRight,
- Axis::Vertical => CursorStyle::ResizeUpDown,
- },
- });
-
- cx.scene().pop_stacking_context();
-
- self.child.paint(bounds.origin(), visible_bounds, view, cx);
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: std::ops::Range<usize>,
- _bounds: pathfinder_geometry::rect::RectF,
- _visible_bounds: pathfinder_geometry::rect::RectF,
- _layout: &Self::LayoutState,
- _paint: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<pathfinder_geometry::rect::RectF> {
- self.child.rect_for_text_range(range_utf16, view, cx)
- }
-
- fn debug(
- &self,
- _bounds: pathfinder_geometry::rect::RectF,
- _layout: &Self::LayoutState,
- _paint: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> serde_json::Value {
- json!({
- "child": self.child.debug(view, cx),
- })
- }
-}
-
-#[derive(Debug, Default)]
-struct ProviderMap(HashMap<TypeTag, (RectF, RectF)>);
-
-pub struct BoundsProvider<V: 'static, P> {
- child: AnyElement<V>,
- phantom: std::marker::PhantomData<P>,
-}
-
-impl<V: 'static, P: 'static> BoundsProvider<V, P> {
- pub fn new(child: AnyElement<V>) -> Self {
- Self {
- child,
- phantom: std::marker::PhantomData,
- }
- }
-}
-
-impl<V: View, P: 'static> Element<V> for BoundsProvider<V, P> {
- type LayoutState = ();
-
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: crate::SizeConstraint,
- view: &mut V,
- cx: &mut crate::ViewContext<V>,
- ) -> (pathfinder_geometry::vector::Vector2F, Self::LayoutState) {
- (self.child.layout(constraint, view, cx), ())
- }
-
- fn paint(
- &mut self,
- bounds: pathfinder_geometry::rect::RectF,
- visible_bounds: pathfinder_geometry::rect::RectF,
- _: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut crate::ViewContext<V>,
- ) -> Self::PaintState {
- cx.update_default_global::<ProviderMap, _, _>(|map, _| {
- map.0.insert(TypeTag::new::<P>(), (bounds, visible_bounds));
- });
-
- self.child.paint(bounds.origin(), visible_bounds, view, cx)
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: std::ops::Range<usize>,
- _: pathfinder_geometry::rect::RectF,
- _: pathfinder_geometry::rect::RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &crate::ViewContext<V>,
- ) -> Option<pathfinder_geometry::rect::RectF> {
- self.child.rect_for_text_range(range_utf16, view, cx)
- }
-
- fn debug(
- &self,
- _: pathfinder_geometry::rect::RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &crate::ViewContext<V>,
- ) -> serde_json::Value {
- serde_json::json!({
- "type": "Provider",
- "providing": format!("{:?}", TypeTag::new::<P>()),
- "child": self.child.debug(view, cx),
- })
- }
-}
@@ -1,104 +0,0 @@
-use std::ops::Range;
-
-use crate::{
- geometry::{rect::RectF, vector::Vector2F},
- json::{self, json, ToJson},
- AnyElement, Element, SizeConstraint, ViewContext,
-};
-
-/// Element which renders it's children in a stack on top of each other.
-/// The first child determines the size of the others.
-pub struct Stack<V> {
- children: Vec<AnyElement<V>>,
-}
-
-impl<V> Default for Stack<V> {
- fn default() -> Self {
- Self {
- children: Vec::new(),
- }
- }
-}
-
-impl<V> Stack<V> {
- pub fn new() -> Self {
- Self::default()
- }
-}
-
-impl<V: 'static> Element<V> for Stack<V> {
- type LayoutState = ();
- type PaintState = ();
-
- fn layout(
- &mut self,
- mut constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- let mut size = constraint.min;
- let mut children = self.children.iter_mut();
- if let Some(bottom_child) = children.next() {
- size = bottom_child.layout(constraint, view, cx);
- constraint = SizeConstraint::strict(size);
- }
-
- for child in children {
- child.layout(constraint, view, cx);
- }
-
- (size, ())
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- _: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- for child in &mut self.children {
- cx.scene().push_layer(None);
- child.paint(bounds.origin(), visible_bounds, view, cx);
- cx.scene().pop_layer();
- }
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- self.children
- .iter()
- .rev()
- .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
- }
-
- fn debug(
- &self,
- bounds: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> json::Value {
- json!({
- "type": "Stack",
- "bounds": bounds.to_json(),
- "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
- })
- }
-}
-
-impl<V> Extend<AnyElement<V>> for Stack<V> {
- fn extend<T: IntoIterator<Item = AnyElement<V>>>(&mut self, children: T) {
- self.children.extend(children)
- }
-}
@@ -1,141 +1,78 @@
-use super::constrain_size_preserving_aspect_ratio;
-use crate::json::ToJson;
use crate::{
- color::Color,
- geometry::{
- rect::RectF,
- vector::{vec2f, Vector2F},
- },
- scene, Element, SizeConstraint, ViewContext,
+ Bounds, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity,
+ IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext,
};
-use schemars::JsonSchema;
-use serde_derive::Deserialize;
-use serde_json::json;
-use std::{borrow::Cow, ops::Range};
+use util::ResultExt;
pub struct Svg {
- path: Cow<'static, str>,
- color: Color,
+ interactivity: Interactivity,
+ path: Option<SharedString>,
}
-impl Svg {
- pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
- Self {
- path: path.into(),
- color: Color::black(),
- }
- }
-
- pub fn for_style<V: 'static>(style: SvgStyle) -> impl Element<V> {
- Self::new(style.asset)
- .with_color(style.color)
- .constrained()
- .with_width(style.dimensions.width)
- .with_height(style.dimensions.height)
+pub fn svg() -> Svg {
+ Svg {
+ interactivity: Interactivity::default(),
+ path: None,
}
+}
- pub fn with_color(mut self, color: Color) -> Self {
- self.color = color;
+impl Svg {
+ pub fn path(mut self, path: impl Into<SharedString>) -> Self {
+ self.path = Some(path.into());
self
}
}
-impl<V: 'static> Element<V> for Svg {
- type LayoutState = Option<usvg::Tree>;
- type PaintState = ();
+impl Element for Svg {
+ type State = InteractiveElementState;
- fn layout(
+ fn request_layout(
&mut self,
- constraint: SizeConstraint,
- _: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- match cx.asset_cache.svg(&self.path) {
- Ok(tree) => {
- let size = constrain_size_preserving_aspect_ratio(
- constraint.max,
- from_usvg_rect(tree.svg_node().view_box.rect).size(),
- );
- (size, Some(tree))
- }
- Err(_error) => {
- #[cfg(not(any(test, feature = "test-support")))]
- log::error!("{}", _error);
- (constraint.min, None)
- }
- }
+ element_state: Option<Self::State>,
+ cx: &mut WindowContext,
+ ) -> (LayoutId, Self::State) {
+ self.interactivity.layout(element_state, cx, |style, cx| {
+ cx.request_layout(&style, None)
+ })
}
fn paint(
&mut self,
- bounds: RectF,
- _visible_bounds: RectF,
- svg: &mut Self::LayoutState,
- _: &mut V,
- cx: &mut ViewContext<V>,
- ) {
- if let Some(svg) = svg.clone() {
- cx.scene().push_icon(scene::Icon {
- bounds,
- svg,
- path: self.path.clone(),
- color: self.color,
- });
- }
+ bounds: Bounds<Pixels>,
+ element_state: &mut Self::State,
+ cx: &mut WindowContext,
+ ) where
+ Self: Sized,
+ {
+ self.interactivity
+ .paint(bounds, bounds.size, element_state, cx, |style, _, cx| {
+ if let Some((path, color)) = self.path.as_ref().zip(style.text.color) {
+ cx.paint_svg(bounds, path.clone(), color).log_err();
+ }
+ })
}
+}
- fn rect_for_text_range(
- &self,
- _: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- _: &V,
- _: &ViewContext<V>,
- ) -> Option<RectF> {
- None
- }
+impl IntoElement for Svg {
+ type Element = Self;
- fn debug(
- &self,
- bounds: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- _: &V,
- _: &ViewContext<V>,
- ) -> serde_json::Value {
- json!({
- "type": "Svg",
- "bounds": bounds.to_json(),
- "path": self.path,
- "color": self.color.to_json(),
- })
+ fn element_id(&self) -> Option<ElementId> {
+ self.interactivity.element_id.clone()
}
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct SvgStyle {
- pub color: Color,
- pub asset: String,
- pub dimensions: Dimensions,
-}
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct Dimensions {
- pub width: f32,
- pub height: f32,
+ fn into_element(self) -> Self::Element {
+ self
+ }
}
-impl Dimensions {
- pub fn to_vec(&self) -> Vector2F {
- vec2f(self.width, self.height)
+impl Styled for Svg {
+ fn style(&mut self) -> &mut StyleRefinement {
+ &mut self.interactivity.base_style
}
}
-fn from_usvg_rect(rect: usvg::Rect) -> RectF {
- RectF::new(
- vec2f(rect.x() as f32, rect.y() as f32),
- vec2f(rect.width() as f32, rect.height() as f32),
- )
+impl InteractiveElement for Svg {
+ fn interactivity(&mut self) -> &mut Interactivity {
+ &mut self.interactivity
+ }
}
@@ -1,438 +1,423 @@
use crate::{
- color::Color,
- fonts::{HighlightStyle, TextStyle},
- geometry::{
- rect::RectF,
- vector::{vec2f, Vector2F},
- },
- json::{ToJson, Value},
- text_layout::{Line, RunStyle, ShapedBoundary},
- Element, FontCache, SizeConstraint, TextLayoutCache, ViewContext, WindowContext,
+ Bounds, DispatchPhase, Element, ElementId, HighlightStyle, IntoElement, LayoutId,
+ MouseDownEvent, MouseUpEvent, Pixels, Point, SharedString, Size, TextRun, TextStyle,
+ WhiteSpace, WindowContext, WrappedLine,
};
-use log::warn;
-use serde_json::json;
-use std::{borrow::Cow, ops::Range, sync::Arc};
-
-pub struct Text {
- text: Cow<'static, str>,
- style: TextStyle,
- soft_wrap: bool,
- highlights: Option<Box<[(Range<usize>, HighlightStyle)]>>,
- custom_runs: Option<(
- Box<[Range<usize>]>,
- Box<dyn FnMut(usize, RectF, &mut WindowContext)>,
- )>,
+use anyhow::anyhow;
+use parking_lot::{Mutex, MutexGuard};
+use smallvec::SmallVec;
+use std::{cell::Cell, mem, ops::Range, rc::Rc, sync::Arc};
+use util::ResultExt;
+
+impl Element for &'static str {
+ type State = TextState;
+
+ fn request_layout(
+ &mut self,
+ _: Option<Self::State>,
+ cx: &mut WindowContext,
+ ) -> (LayoutId, Self::State) {
+ let mut state = TextState::default();
+ let layout_id = state.layout(SharedString::from(*self), None, cx);
+ (layout_id, state)
+ }
+
+ fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
+ state.paint(bounds, self, cx)
+ }
}
-pub struct LayoutState {
- shaped_lines: Vec<Line>,
- wrap_boundaries: Vec<Vec<ShapedBoundary>>,
- line_height: f32,
+impl IntoElement for &'static str {
+ type Element = Self;
+
+ fn element_id(&self) -> Option<ElementId> {
+ None
+ }
+
+ fn into_element(self) -> Self::Element {
+ self
+ }
}
-impl Text {
- pub fn new<I: Into<Cow<'static, str>>>(text: I, style: TextStyle) -> Self {
- Self {
- text: text.into(),
- style,
- soft_wrap: true,
- highlights: None,
- custom_runs: None,
- }
+impl Element for SharedString {
+ type State = TextState;
+
+ fn request_layout(
+ &mut self,
+ _: Option<Self::State>,
+ cx: &mut WindowContext,
+ ) -> (LayoutId, Self::State) {
+ let mut state = TextState::default();
+ let layout_id = state.layout(self.clone(), None, cx);
+ (layout_id, state)
}
- pub fn with_default_color(mut self, color: Color) -> Self {
- self.style.color = color;
+ fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
+ let text_str: &str = self.as_ref();
+ state.paint(bounds, text_str, cx)
+ }
+}
+
+impl IntoElement for SharedString {
+ type Element = Self;
+
+ fn element_id(&self) -> Option<ElementId> {
+ None
+ }
+
+ fn into_element(self) -> Self::Element {
self
}
+}
+
+/// Renders text with runs of different styles.
+///
+/// Callers are responsible for setting the correct style for each run.
+/// For text with a uniform style, you can usually avoid calling this constructor
+/// and just pass text directly.
+pub struct StyledText {
+ text: SharedString,
+ runs: Option<Vec<TextRun>>,
+}
+
+impl StyledText {
+ pub fn new(text: impl Into<SharedString>) -> Self {
+ StyledText {
+ text: text.into(),
+ runs: None,
+ }
+ }
pub fn with_highlights(
mut self,
- runs: impl Into<Box<[(Range<usize>, HighlightStyle)]>>,
+ default_style: &TextStyle,
+ highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
) -> Self {
- self.highlights = Some(runs.into());
+ let mut runs = Vec::new();
+ let mut ix = 0;
+ for (range, highlight) in highlights {
+ if ix < range.start {
+ runs.push(default_style.clone().to_run(range.start - ix));
+ }
+ runs.push(
+ default_style
+ .clone()
+ .highlight(highlight)
+ .to_run(range.len()),
+ );
+ ix = range.end;
+ }
+ if ix < self.text.len() {
+ runs.push(default_style.to_run(self.text.len() - ix));
+ }
+ self.runs = Some(runs);
self
}
+}
- pub fn with_custom_runs(
- mut self,
- runs: impl Into<Box<[Range<usize>]>>,
- callback: impl 'static + FnMut(usize, RectF, &mut WindowContext),
- ) -> Self {
- self.custom_runs = Some((runs.into(), Box::new(callback)));
- self
+impl Element for StyledText {
+ type State = TextState;
+
+ fn request_layout(
+ &mut self,
+ _: Option<Self::State>,
+ cx: &mut WindowContext,
+ ) -> (LayoutId, Self::State) {
+ let mut state = TextState::default();
+ let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
+ (layout_id, state)
}
- pub fn with_soft_wrap(mut self, soft_wrap: bool) -> Self {
- self.soft_wrap = soft_wrap;
+ fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+ state.paint(bounds, &self.text, cx)
+ }
+}
+
+impl IntoElement for StyledText {
+ type Element = Self;
+
+ fn element_id(&self) -> Option<crate::ElementId> {
+ None
+ }
+
+ fn into_element(self) -> Self::Element {
self
}
}
-impl<V: 'static> Element<V> for Text {
- type LayoutState = LayoutState;
- type PaintState = ();
+#[derive(Default, Clone)]
+pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
+
+struct TextStateInner {
+ lines: SmallVec<[WrappedLine; 1]>,
+ line_height: Pixels,
+ wrap_width: Option<Pixels>,
+ size: Option<Size<Pixels>>,
+}
+
+impl TextState {
+ fn lock(&self) -> MutexGuard<Option<TextStateInner>> {
+ self.0.lock()
+ }
fn layout(
&mut self,
- constraint: SizeConstraint,
- _: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- // Convert the string and highlight ranges into an iterator of highlighted chunks.
-
- let mut offset = 0;
- let mut highlight_ranges = self
- .highlights
- .as_ref()
- .map_or(Default::default(), AsRef::as_ref)
- .iter()
- .peekable();
- let chunks = std::iter::from_fn(|| {
- let result;
- if let Some((range, highlight_style)) = highlight_ranges.peek() {
- if offset < range.start {
- result = Some((&self.text[offset..range.start], None));
- offset = range.start;
- } else if range.end <= self.text.len() {
- result = Some((&self.text[range.clone()], Some(*highlight_style)));
- highlight_ranges.next();
- offset = range.end;
+ text: SharedString,
+ runs: Option<Vec<TextRun>>,
+ cx: &mut WindowContext,
+ ) -> LayoutId {
+ let text_style = cx.text_style();
+ let font_size = text_style.font_size.to_pixels(cx.rem_size());
+ let line_height = text_style
+ .line_height
+ .to_pixels(font_size.into(), cx.rem_size());
+
+ let runs = if let Some(runs) = runs {
+ runs
+ } else {
+ vec![text_style.to_run(text.len())]
+ };
+
+ let layout_id = cx.request_measured_layout(Default::default(), {
+ let element_state = self.clone();
+
+ move |known_dimensions, available_space, cx| {
+ let wrap_width = if text_style.white_space == WhiteSpace::Normal {
+ known_dimensions.width.or(match available_space.width {
+ crate::AvailableSpace::Definite(x) => Some(x),
+ _ => None,
+ })
} else {
- warn!(
- "Highlight out of text range. Text len: {}, Highlight range: {}..{}",
- self.text.len(),
- range.start,
- range.end
- );
- result = None;
+ None
+ };
+
+ if let Some(text_state) = element_state.0.lock().as_ref() {
+ if text_state.size.is_some()
+ && (wrap_width.is_none() || wrap_width == text_state.wrap_width)
+ {
+ return text_state.size.unwrap();
+ }
}
- } else if offset < self.text.len() {
- result = Some((&self.text[offset..], None));
- offset = self.text.len();
- } else {
- result = None;
+
+ let Some(lines) = cx
+ .text_system()
+ .shape_text(
+ &text, font_size, &runs, wrap_width, // Wrap if we know the width.
+ )
+ .log_err()
+ else {
+ element_state.lock().replace(TextStateInner {
+ lines: Default::default(),
+ line_height,
+ wrap_width,
+ size: Some(Size::default()),
+ });
+ return Size::default();
+ };
+
+ let mut size: Size<Pixels> = Size::default();
+ for line in &lines {
+ let line_size = line.size(line_height);
+ size.height += line_size.height;
+ size.width = size.width.max(line_size.width).ceil();
+ }
+
+ element_state.lock().replace(TextStateInner {
+ lines,
+ line_height,
+ wrap_width,
+ size: Some(size),
+ });
+
+ size
}
- result
});
- // Perform shaping on these highlighted chunks
- let shaped_lines = layout_highlighted_chunks(
- chunks,
- &self.style,
- cx.text_layout_cache(),
- &cx.font_cache,
- usize::MAX,
- self.text.matches('\n').count() + 1,
- );
-
- // If line wrapping is enabled, wrap each of the shaped lines.
- let font_id = self.style.font_id;
- let mut line_count = 0;
- let mut max_line_width = 0_f32;
- let mut wrap_boundaries = Vec::new();
- let mut wrapper = cx.font_cache.line_wrapper(font_id, self.style.font_size);
- for (line, shaped_line) in self.text.split('\n').zip(&shaped_lines) {
- if self.soft_wrap {
- let boundaries = wrapper
- .wrap_shaped_line(line, shaped_line, constraint.max.x())
- .collect::<Vec<_>>();
- line_count += boundaries.len() + 1;
- wrap_boundaries.push(boundaries);
- } else {
- line_count += 1;
- }
- max_line_width = max_line_width.max(shaped_line.width());
- }
+ layout_id
+ }
- let line_height = cx.font_cache.line_height(self.style.font_size);
- let size = vec2f(
- max_line_width
- .ceil()
- .max(constraint.min.x())
- .min(constraint.max.x()),
- (line_height * line_count as f32).ceil(),
- );
- (
- size,
- LayoutState {
- shaped_lines,
- wrap_boundaries,
- line_height,
- },
- )
+ fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut WindowContext) {
+ let element_state = self.lock();
+ let element_state = element_state
+ .as_ref()
+ .ok_or_else(|| anyhow!("measurement has not been performed on {}", text))
+ .unwrap();
+
+ let line_height = element_state.line_height;
+ let mut line_origin = bounds.origin;
+ for line in &element_state.lines {
+ line.paint(line_origin, line_height, cx).log_err();
+ line_origin.y += line.size(line_height).height;
+ }
}
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- layout: &mut Self::LayoutState,
- _: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- let mut origin = bounds.origin();
- let empty = Vec::new();
- let mut callback = |_, _, _: &mut WindowContext| {};
-
- let mouse_runs;
- let custom_run_callback;
- if let Some((runs, build_region)) = &mut self.custom_runs {
- mouse_runs = runs.iter();
- custom_run_callback = build_region.as_mut();
- } else {
- mouse_runs = [].iter();
- custom_run_callback = &mut callback;
+ fn index_for_position(&self, bounds: Bounds<Pixels>, position: Point<Pixels>) -> Option<usize> {
+ if !bounds.contains(&position) {
+ return None;
}
- let mut custom_runs = mouse_runs.enumerate().peekable();
-
- let mut offset = 0;
- for (ix, line) in layout.shaped_lines.iter().enumerate() {
- let wrap_boundaries = layout.wrap_boundaries.get(ix).unwrap_or(&empty);
- let boundaries = RectF::new(
- origin,
- vec2f(
- bounds.width(),
- (wrap_boundaries.len() + 1) as f32 * layout.line_height,
- ),
- );
- if boundaries.intersects(visible_bounds) {
- if self.soft_wrap {
- line.paint_wrapped(
- origin,
- visible_bounds,
- layout.line_height,
- wrap_boundaries,
- cx,
- );
- } else {
- line.paint(origin, visible_bounds, layout.line_height, cx);
- }
+ let element_state = self.lock();
+ let element_state = element_state
+ .as_ref()
+ .expect("measurement has not been performed");
+
+ let line_height = element_state.line_height;
+ let mut line_origin = bounds.origin;
+ let mut line_start_ix = 0;
+ for line in &element_state.lines {
+ let line_bottom = line_origin.y + line.size(line_height).height;
+ if position.y > line_bottom {
+ line_origin.y = line_bottom;
+ line_start_ix += line.len() + 1;
+ } else {
+ let position_within_line = position - line_origin;
+ let index_within_line =
+ line.index_for_position(position_within_line, line_height)?;
+ return Some(line_start_ix + index_within_line);
}
+ }
- // Paint any custom runs that intersect this line.
- let end_offset = offset + line.len();
- if let Some((custom_run_ix, custom_run_range)) = custom_runs.peek().cloned() {
- if custom_run_range.start < end_offset {
- let mut current_custom_run = None;
- if custom_run_range.start <= offset {
- current_custom_run = Some((custom_run_ix, custom_run_range.end, origin));
- }
-
- let mut glyph_origin = origin;
- let mut prev_position = 0.;
- let mut wrap_boundaries = wrap_boundaries.iter().copied().peekable();
- for (run_ix, glyph_ix, glyph) in
- line.runs().iter().enumerate().flat_map(|(run_ix, run)| {
- run.glyphs()
- .iter()
- .enumerate()
- .map(move |(ix, glyph)| (run_ix, ix, glyph))
- })
- {
- glyph_origin.set_x(glyph_origin.x() + glyph.position.x() - prev_position);
- prev_position = glyph.position.x();
-
- // If we've reached a soft wrap position, move down one line. If there
- // is a custom run in-progress, paint it.
- if wrap_boundaries
- .peek()
- .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
- {
- if let Some((run_ix, _, run_origin)) = &mut current_custom_run {
- let bounds = RectF::from_points(
- *run_origin,
- glyph_origin + vec2f(0., layout.line_height),
- );
- custom_run_callback(*run_ix, bounds, cx);
- *run_origin =
- vec2f(origin.x(), glyph_origin.y() + layout.line_height);
- }
- wrap_boundaries.next();
- glyph_origin = vec2f(origin.x(), glyph_origin.y() + layout.line_height);
- }
+ None
+ }
+}
- // If we've reached the end of the current custom run, paint it.
- if let Some((run_ix, run_end_offset, run_origin)) = current_custom_run {
- if offset + glyph.index == run_end_offset {
- current_custom_run.take();
- let bounds = RectF::from_points(
- run_origin,
- glyph_origin + vec2f(0., layout.line_height),
- );
- custom_run_callback(run_ix, bounds, cx);
- custom_runs.next();
- }
-
- if let Some((_, run_range)) = custom_runs.peek() {
- if run_range.start >= end_offset {
- break;
- }
- if run_range.start == offset + glyph.index {
- current_custom_run =
- Some((run_ix, run_range.end, glyph_origin));
- }
- }
- }
+pub struct InteractiveText {
+ element_id: ElementId,
+ text: StyledText,
+ click_listener:
+ Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut WindowContext<'_>)>>,
+ clickable_ranges: Vec<Range<usize>>,
+}
- // If we've reached the start of a new custom run, start tracking it.
- if let Some((run_ix, run_range)) = custom_runs.peek() {
- if offset + glyph.index == run_range.start {
- current_custom_run = Some((*run_ix, run_range.end, glyph_origin));
- }
- }
- }
+struct InteractiveTextClickEvent {
+ mouse_down_index: usize,
+ mouse_up_index: usize,
+}
- // If a custom run extends beyond the end of the line, paint it.
- if let Some((run_ix, run_end_offset, run_origin)) = current_custom_run {
- let line_end = glyph_origin + vec2f(line.width() - prev_position, 0.);
- let bounds = RectF::from_points(
- run_origin,
- line_end + vec2f(0., layout.line_height),
- );
- custom_run_callback(run_ix, bounds, cx);
- if end_offset == run_end_offset {
- custom_runs.next();
- }
- }
- }
- }
+pub struct InteractiveTextState {
+ text_state: TextState,
+ mouse_down_index: Rc<Cell<Option<usize>>>,
+}
- offset = end_offset + 1;
- origin.set_y(boundaries.max_y());
+impl InteractiveText {
+ pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self {
+ Self {
+ element_id: id.into(),
+ text,
+ click_listener: None,
+ clickable_ranges: Vec::new(),
}
}
- fn rect_for_text_range(
- &self,
- _: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- _: &V,
- _: &ViewContext<V>,
- ) -> Option<RectF> {
- None
+ pub fn on_click(
+ mut self,
+ ranges: Vec<Range<usize>>,
+ listener: impl Fn(usize, &mut WindowContext<'_>) + 'static,
+ ) -> Self {
+ self.click_listener = Some(Box::new(move |ranges, event, cx| {
+ for (range_ix, range) in ranges.iter().enumerate() {
+ if range.contains(&event.mouse_down_index) && range.contains(&event.mouse_up_index)
+ {
+ listener(range_ix, cx);
+ }
+ }
+ }));
+ self.clickable_ranges = ranges;
+ self
}
+}
- fn debug(
- &self,
- bounds: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- _: &V,
- _: &ViewContext<V>,
- ) -> Value {
- json!({
- "type": "Text",
- "bounds": bounds.to_json(),
- "text": &self.text,
- "style": self.style.to_json(),
- })
+impl Element for InteractiveText {
+ type State = InteractiveTextState;
+
+ fn request_layout(
+ &mut self,
+ state: Option<Self::State>,
+ cx: &mut WindowContext,
+ ) -> (LayoutId, Self::State) {
+ if let Some(InteractiveTextState {
+ mouse_down_index, ..
+ }) = state
+ {
+ let (layout_id, text_state) = self.text.request_layout(None, cx);
+ let element_state = InteractiveTextState {
+ text_state,
+ mouse_down_index,
+ };
+ (layout_id, element_state)
+ } else {
+ let (layout_id, text_state) = self.text.request_layout(None, cx);
+ let element_state = InteractiveTextState {
+ text_state,
+ mouse_down_index: Rc::default(),
+ };
+ (layout_id, element_state)
+ }
}
-}
-/// Perform text layout on a series of highlighted chunks of text.
-pub fn layout_highlighted_chunks<'a>(
- chunks: impl Iterator<Item = (&'a str, Option<HighlightStyle>)>,
- text_style: &TextStyle,
- text_layout_cache: &TextLayoutCache,
- font_cache: &Arc<FontCache>,
- max_line_len: usize,
- max_line_count: usize,
-) -> Vec<Line> {
- let mut layouts = Vec::with_capacity(max_line_count);
- let mut line = String::new();
- let mut styles = Vec::new();
- let mut row = 0;
- let mut line_exceeded_max_len = false;
- for (chunk, highlight_style) in chunks.chain([("\n", Default::default())]) {
- for (ix, mut line_chunk) in chunk.split('\n').enumerate() {
- if ix > 0 {
- layouts.push(text_layout_cache.layout_str(&line, text_style.font_size, &styles));
- line.clear();
- styles.clear();
- row += 1;
- line_exceeded_max_len = false;
- if row == max_line_count {
- return layouts;
+ fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+ if let Some(click_listener) = self.click_listener.take() {
+ let mouse_position = cx.mouse_position();
+ if let Some(ix) = state.text_state.index_for_position(bounds, mouse_position) {
+ if self
+ .clickable_ranges
+ .iter()
+ .any(|range| range.contains(&ix))
+ && cx.was_top_layer(&mouse_position, cx.stacking_order())
+ {
+ cx.set_cursor_style(crate::CursorStyle::PointingHand)
}
}
- if !line_chunk.is_empty() && !line_exceeded_max_len {
- let text_style = if let Some(style) = highlight_style {
- text_style
- .clone()
- .highlight(style, font_cache)
- .map(Cow::Owned)
- .unwrap_or_else(|_| Cow::Borrowed(text_style))
- } else {
- Cow::Borrowed(text_style)
- };
+ let text_state = state.text_state.clone();
+ let mouse_down = state.mouse_down_index.clone();
+ if let Some(mouse_down_index) = mouse_down.get() {
+ let clickable_ranges = mem::take(&mut self.clickable_ranges);
+ cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
+ if phase == DispatchPhase::Bubble {
+ if let Some(mouse_up_index) =
+ text_state.index_for_position(bounds, event.position)
+ {
+ click_listener(
+ &clickable_ranges,
+ InteractiveTextClickEvent {
+ mouse_down_index,
+ mouse_up_index,
+ },
+ cx,
+ )
+ }
- if line.len() + line_chunk.len() > max_line_len {
- let mut chunk_len = max_line_len - line.len();
- while !line_chunk.is_char_boundary(chunk_len) {
- chunk_len -= 1;
+ mouse_down.take();
+ cx.notify();
}
- line_chunk = &line_chunk[..chunk_len];
- line_exceeded_max_len = true;
- }
-
- line.push_str(line_chunk);
- styles.push((
- line_chunk.len(),
- RunStyle {
- font_id: text_style.font_id,
- color: text_style.color,
- underline: text_style.underline,
- },
- ));
+ });
+ } else {
+ cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
+ if phase == DispatchPhase::Bubble {
+ if let Some(mouse_down_index) =
+ text_state.index_for_position(bounds, event.position)
+ {
+ mouse_down.set(Some(mouse_down_index));
+ cx.notify();
+ }
+ }
+ });
}
}
- }
- 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 (_, state) = text.layout(
- SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)),
- &mut view,
- cx,
- );
- assert_eq!(state.shaped_lines.len(), 2);
- assert_eq!(state.wrap_boundaries.len(), 2);
- });
- view
- });
+ self.text.paint(bounds, &mut state.text_state, cx)
}
+}
- struct TestView;
+impl IntoElement for InteractiveText {
+ type Element = Self;
- impl Entity for TestView {
- type Event = ();
+ fn element_id(&self) -> Option<ElementId> {
+ Some(self.element_id.clone())
}
- impl View for TestView {
- fn ui_name() -> &'static str {
- "TestView"
- }
-
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- Empty::new().into_any()
- }
+ fn into_element(self) -> Self::Element {
+ self
}
}
@@ -1,244 +0,0 @@
-use super::{
- AnyElement, ContainerStyle, Element, Flex, KeystrokeLabel, MouseEventHandler, Overlay,
- OverlayFitMode, ParentElement, Text,
-};
-use crate::{
- fonts::TextStyle,
- geometry::{rect::RectF, vector::Vector2F},
- json::json,
- Action, Axis, ElementStateHandle, SizeConstraint, Task, TypeTag, ViewContext,
-};
-use schemars::JsonSchema;
-use serde::Deserialize;
-use std::{
- borrow::Cow,
- cell::{Cell, RefCell},
- ops::Range,
- rc::Rc,
- time::Duration,
-};
-use util::ResultExt;
-
-const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(500);
-
-pub struct Tooltip<V> {
- child: AnyElement<V>,
- tooltip: Option<AnyElement<V>>,
- _state: ElementStateHandle<Rc<TooltipState>>,
-}
-
-#[derive(Default)]
-struct TooltipState {
- visible: Cell<bool>,
- position: Cell<Vector2F>,
- debounce: RefCell<Option<Task<()>>>,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct TooltipStyle {
- #[serde(flatten)]
- pub container: ContainerStyle,
- pub text: TextStyle,
- keystroke: KeystrokeStyle,
- pub max_text_width: Option<f32>,
-}
-
-#[derive(Clone, Deserialize, Default, JsonSchema)]
-pub struct KeystrokeStyle {
- #[serde(flatten)]
- container: ContainerStyle,
- #[serde(flatten)]
- text: TextStyle,
-}
-
-impl<V: 'static> Tooltip<V> {
- pub fn new<Tag: 'static>(
- id: usize,
- text: impl Into<Cow<'static, str>>,
- action: Option<Box<dyn Action>>,
- style: TooltipStyle,
- child: AnyElement<V>,
- cx: &mut ViewContext<V>,
- ) -> Self {
- Self::new_dynamic(TypeTag::new::<Tag>(), id, text, action, style, child, cx)
- }
-
- pub fn new_dynamic(
- mut tag: TypeTag,
- id: usize,
- text: impl Into<Cow<'static, str>>,
- action: Option<Box<dyn Action>>,
- style: TooltipStyle,
- child: AnyElement<V>,
- cx: &mut ViewContext<V>,
- ) -> Self {
- tag = tag.compose(TypeTag::new::<Self>());
-
- let focused_view_id = cx.focused_view_id();
-
- let state_handle = cx.default_element_state_dynamic::<Rc<TooltipState>>(tag, id);
- let state = state_handle.read(cx).clone();
- let text = text.into();
-
- let tooltip = if state.visible.get() {
- let mut collapsed_tooltip = Self::render_tooltip(
- focused_view_id,
- text.clone(),
- style.clone(),
- action.as_ref().map(|a| a.boxed_clone()),
- true,
- );
- Some(
- Overlay::new(
- Self::render_tooltip(focused_view_id, text, style, action, false)
- .constrained()
- .dynamically(move |constraint, view, cx| {
- SizeConstraint::strict_along(
- Axis::Vertical,
- collapsed_tooltip.layout(constraint, view, cx).0.y(),
- )
- }),
- )
- .with_fit_mode(OverlayFitMode::SwitchAnchor)
- .with_anchor_position(state.position.get())
- .into_any(),
- )
- } else {
- None
- };
- let child = MouseEventHandler::new_dynamic(tag, id, cx, |_, _| child)
- .on_hover(move |e, _, cx| {
- let position = e.position;
- if e.started {
- if !state.visible.get() {
- state.position.set(position);
-
- let mut debounce = state.debounce.borrow_mut();
- if debounce.is_none() {
- *debounce = Some(cx.spawn({
- let state = state.clone();
- |view, mut cx| async move {
- cx.background().timer(DEBOUNCE_TIMEOUT).await;
- state.visible.set(true);
- view.update(&mut cx, |_, cx| cx.notify()).log_err();
- }
- }));
- }
- }
- } else {
- state.visible.set(false);
- state.debounce.take();
- cx.notify();
- }
- })
- .into_any();
- Self {
- child,
- tooltip,
- _state: state_handle,
- }
- }
-
- pub fn render_tooltip(
- focused_view_id: Option<usize>,
- text: impl Into<Cow<'static, str>>,
- style: TooltipStyle,
- action: Option<Box<dyn Action>>,
- measure: bool,
- ) -> impl Element<V> {
- Flex::row()
- .with_child({
- let text = if let Some(max_text_width) = style.max_text_width {
- Text::new(text, style.text)
- .constrained()
- .with_max_width(max_text_width)
- } else {
- Text::new(text, style.text).constrained()
- };
-
- if measure {
- text.flex(1., false).into_any()
- } else {
- text.flex(1., false).aligned().into_any()
- }
- })
- .with_children(action.and_then(|action| {
- let keystroke_label = KeystrokeLabel::new(
- focused_view_id?,
- action,
- style.keystroke.container,
- style.keystroke.text,
- );
- if measure {
- Some(keystroke_label.into_any())
- } else {
- Some(keystroke_label.aligned().into_any())
- }
- }))
- .contained()
- .with_style(style.container)
- }
-}
-
-impl<V: 'static> Element<V> for Tooltip<V> {
- type LayoutState = ();
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- let size = self.child.layout(constraint, view, cx);
- if let Some(tooltip) = self.tooltip.as_mut() {
- tooltip.layout(
- SizeConstraint::new(Vector2F::zero(), cx.window_size()),
- view,
- cx,
- );
- }
- (size, ())
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- _: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) {
- self.child.paint(bounds.origin(), visible_bounds, view, cx);
- if let Some(tooltip) = self.tooltip.as_mut() {
- tooltip.paint(bounds.origin(), visible_bounds, view, cx);
- }
- }
-
- fn rect_for_text_range(
- &self,
- range: Range<usize>,
- _: RectF,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- self.child.rect_for_text_range(range, view, cx)
- }
-
- fn debug(
- &self,
- _: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> serde_json::Value {
- json!({
- "child": self.child.debug(view, cx),
- "tooltip": self.tooltip.as_ref().map(|t| t.debug(view, cx)),
- })
- }
-}
@@ -1,354 +1,316 @@
-use super::{Element, SizeConstraint};
use crate::{
- geometry::{
- rect::RectF,
- vector::{vec2f, Vector2F},
- },
- json::{self, json},
- platform::ScrollWheelEvent,
- AnyElement, MouseRegion, ViewContext,
+ point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element,
+ ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId,
+ Pixels, Point, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
};
-use json::ToJson;
+use smallvec::SmallVec;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
-
-#[derive(Clone, Default)]
-pub struct UniformListState(Rc<RefCell<StateInner>>);
-
-#[derive(Debug)]
-pub enum ScrollTarget {
- Show(usize),
- Center(usize),
-}
-
-impl UniformListState {
- pub fn scroll_to(&self, scroll_to: ScrollTarget) {
- self.0.borrow_mut().scroll_to = Some(scroll_to);
- }
-
- pub fn scroll_top(&self) -> f32 {
- self.0.borrow().scroll_top
+use taffy::style::Overflow;
+
+/// uniform_list provides lazy rendering for a set of items that are of uniform height.
+/// When rendered into a container with overflow-y: hidden and a fixed (or max) height,
+/// uniform_list will only render the visible subset of items.
+#[track_caller]
+pub fn uniform_list<I, R, V>(
+ view: View<V>,
+ id: I,
+ item_count: usize,
+ f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> Vec<R>,
+) -> UniformList
+where
+ I: Into<ElementId>,
+ R: IntoElement,
+ V: Render,
+{
+ let id = id.into();
+ let mut base_style = StyleRefinement::default();
+ base_style.overflow.y = Some(Overflow::Scroll);
+
+ let render_range = move |range, cx: &mut WindowContext| {
+ view.update(cx, |this, cx| {
+ f(this, range, cx)
+ .into_iter()
+ .map(|component| component.into_any_element())
+ .collect()
+ })
+ };
+
+ UniformList {
+ id: id.clone(),
+ item_count,
+ item_to_measure_index: 0,
+ render_items: Box::new(render_range),
+ interactivity: Interactivity {
+ element_id: Some(id),
+ base_style: Box::new(base_style),
+
+ #[cfg(debug_assertions)]
+ location: Some(*core::panic::Location::caller()),
+
+ ..Default::default()
+ },
+ scroll_handle: None,
}
}
-#[derive(Default)]
-struct StateInner {
- scroll_top: f32,
- scroll_to: Option<ScrollTarget>,
-}
-
-pub struct UniformListLayoutState<V> {
- scroll_max: f32,
- item_height: f32,
- items: Vec<AnyElement<V>>,
-}
-
-pub struct UniformList<V> {
- state: UniformListState,
+pub struct UniformList {
+ id: ElementId,
item_count: usize,
- #[allow(clippy::type_complexity)]
- append_items: Box<dyn Fn(&mut V, Range<usize>, &mut Vec<AnyElement<V>>, &mut ViewContext<V>)>,
- padding_top: f32,
- padding_bottom: f32,
- get_width_from_item: Option<usize>,
- view_id: usize,
+ item_to_measure_index: usize,
+ render_items:
+ Box<dyn for<'a> Fn(Range<usize>, &'a mut WindowContext) -> SmallVec<[AnyElement; 64]>>,
+ interactivity: Interactivity,
+ scroll_handle: Option<UniformListScrollHandle>,
}
-impl<V: 'static> UniformList<V> {
- pub fn new<F>(
- state: UniformListState,
- item_count: usize,
- cx: &mut ViewContext<V>,
- append_items: F,
- ) -> Self
- where
- F: 'static + Fn(&mut V, Range<usize>, &mut Vec<AnyElement<V>>, &mut ViewContext<V>),
- {
- Self {
- state,
- item_count,
- append_items: Box::new(append_items),
- padding_top: 0.,
- padding_bottom: 0.,
- get_width_from_item: None,
- view_id: cx.handle().id(),
- }
- }
-
- pub fn with_width_from_item(mut self, item_ix: Option<usize>) -> Self {
- self.get_width_from_item = item_ix;
- self
- }
-
- pub fn with_padding_top(mut self, padding: f32) -> Self {
- self.padding_top = padding;
- self
- }
-
- pub fn with_padding_bottom(mut self, padding: f32) -> Self {
- self.padding_bottom = padding;
- self
- }
-
- fn scroll(
- state: UniformListState,
- _: Vector2F,
- mut delta: Vector2F,
- precise: bool,
- scroll_max: f32,
- cx: &mut ViewContext<V>,
- ) -> bool {
- if !precise {
- delta *= 20.;
- }
+#[derive(Clone, Default)]
+pub struct UniformListScrollHandle(Rc<RefCell<Option<ScrollHandleState>>>);
- let mut state = state.0.borrow_mut();
- state.scroll_top = (state.scroll_top - delta.y()).max(0.0).min(scroll_max);
- cx.notify();
+#[derive(Clone, Debug)]
+struct ScrollHandleState {
+ item_height: Pixels,
+ list_height: Pixels,
+ scroll_offset: Rc<RefCell<Point<Pixels>>>,
+}
- true
+impl UniformListScrollHandle {
+ pub fn new() -> Self {
+ Self(Rc::new(RefCell::new(None)))
}
- fn autoscroll(&mut self, scroll_max: f32, list_height: f32, item_height: f32) {
- let mut state = self.state.0.borrow_mut();
-
- if let Some(scroll_to) = state.scroll_to.take() {
- let item_ix;
- let center;
- match scroll_to {
- ScrollTarget::Show(ix) => {
- item_ix = ix;
- center = false;
- }
- ScrollTarget::Center(ix) => {
- item_ix = ix;
- center = true;
- }
- }
-
- let item_top = self.padding_top + item_ix as f32 * item_height;
- let item_bottom = item_top + item_height;
- if center {
- let item_center = item_top + item_height / 2.;
- state.scroll_top = (item_center - list_height / 2.).max(0.);
- } else {
- let scroll_bottom = state.scroll_top + list_height;
- if item_top < state.scroll_top {
- state.scroll_top = item_top;
- } else if item_bottom > scroll_bottom {
- state.scroll_top = item_bottom - list_height;
- }
+ pub fn scroll_to_item(&self, ix: usize) {
+ if let Some(state) = &*self.0.borrow() {
+ let mut scroll_offset = state.scroll_offset.borrow_mut();
+ let item_top = state.item_height * ix;
+ let item_bottom = item_top + state.item_height;
+ let scroll_top = -scroll_offset.y;
+ if item_top < scroll_top {
+ scroll_offset.y = -item_top;
+ } else if item_bottom > scroll_top + state.list_height {
+ scroll_offset.y = -(item_bottom - state.list_height);
}
}
+ }
- if state.scroll_top > scroll_max {
- state.scroll_top = scroll_max;
+ pub fn scroll_top(&self) -> Pixels {
+ if let Some(state) = &*self.0.borrow() {
+ -state.scroll_offset.borrow().y
+ } else {
+ Pixels::ZERO
}
}
+}
- fn scroll_top(&self) -> f32 {
- self.state.0.borrow().scroll_top
+impl Styled for UniformList {
+ fn style(&mut self) -> &mut StyleRefinement {
+ &mut self.interactivity.base_style
}
}
-impl<V: 'static> Element<V> for UniformList<V> {
- type LayoutState = UniformListLayoutState<V>;
- type PaintState = ();
+#[derive(Default)]
+pub struct UniformListState {
+ interactive: InteractiveElementState,
+ item_size: Size<Pixels>,
+}
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> (Vector2F, Self::LayoutState) {
- if constraint.max.y().is_infinite() {
- unimplemented!(
- "UniformList does not support being rendered with an unconstrained height"
- );
- }
+impl Element for UniformList {
+ type State = UniformListState;
- let no_items = (
- constraint.min,
- UniformListLayoutState {
- item_height: 0.,
- scroll_max: 0.,
- items: Default::default(),
- },
- );
+ fn request_layout(
+ &mut self,
+ state: Option<Self::State>,
+ cx: &mut WindowContext,
+ ) -> (LayoutId, Self::State) {
+ let max_items = self.item_count;
+ let item_size = state
+ .as_ref()
+ .map(|s| s.item_size)
+ .unwrap_or_else(|| self.measure_item(None, cx));
+
+ let (layout_id, interactive) =
+ self.interactivity
+ .layout(state.map(|s| s.interactive), cx, |style, cx| {
+ cx.request_measured_layout(
+ style,
+ move |known_dimensions, available_space, _cx| {
+ let desired_height = item_size.height * max_items;
+ let width =
+ known_dimensions
+ .width
+ .unwrap_or(match available_space.width {
+ AvailableSpace::Definite(x) => x,
+ AvailableSpace::MinContent | AvailableSpace::MaxContent => {
+ item_size.width
+ }
+ });
+
+ let height = match available_space.height {
+ AvailableSpace::Definite(height) => desired_height.min(height),
+ AvailableSpace::MinContent | AvailableSpace::MaxContent => {
+ desired_height
+ }
+ };
+ size(width, height)
+ },
+ )
+ });
+
+ let element_state = UniformListState {
+ interactive,
+ item_size,
+ };
- if self.item_count == 0 {
- return no_items;
- }
+ (layout_id, element_state)
+ }
- let mut items = Vec::new();
- let mut size = constraint.max;
- let mut item_size;
- let sample_item_ix;
- let sample_item;
- if let Some(sample_ix) = self.get_width_from_item {
- (self.append_items)(view, sample_ix..sample_ix + 1, &mut items, cx);
- sample_item_ix = sample_ix;
-
- if let Some(mut item) = items.pop() {
- item_size = item.layout(constraint, view, cx);
- size.set_x(item_size.x());
- sample_item = item;
- } else {
- return no_items;
- }
- } else {
- (self.append_items)(view, 0..1, &mut items, cx);
- sample_item_ix = 0;
- if let Some(mut item) = items.pop() {
- item_size = item.layout(
- SizeConstraint::new(
- vec2f(constraint.max.x(), 0.0),
- vec2f(constraint.max.x(), f32::INFINITY),
- ),
- view,
- cx,
- );
- item_size.set_x(size.x());
- sample_item = item
- } else {
- return no_items;
- }
- }
+ fn paint(
+ &mut self,
+ bounds: Bounds<crate::Pixels>,
+ element_state: &mut Self::State,
+ cx: &mut WindowContext,
+ ) {
+ let style =
+ self.interactivity
+ .compute_style(Some(bounds), &mut element_state.interactive, cx);
+ let border = style.border_widths.to_pixels(cx.rem_size());
+ let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
+
+ let padded_bounds = Bounds::from_corners(
+ bounds.origin + point(border.left + padding.left, border.top + padding.top),
+ bounds.lower_right()
+ - point(border.right + padding.right, border.bottom + padding.bottom),
+ );
- let item_constraint = SizeConstraint {
- min: item_size,
- max: vec2f(constraint.max.x(), item_size.y()),
+ let item_size = element_state.item_size;
+ let content_size = Size {
+ width: padded_bounds.size.width,
+ height: item_size.height * self.item_count + padding.top + padding.bottom,
};
- let item_height = item_size.y();
- let scroll_height = self.item_count as f32 * item_height;
- if scroll_height < size.y() {
- size.set_y(size.y().min(scroll_height).max(constraint.min.y()));
- }
+ let shared_scroll_offset = element_state
+ .interactive
+ .scroll_offset
+ .get_or_insert_with(|| {
+ if let Some(scroll_handle) = self.scroll_handle.as_ref() {
+ if let Some(scroll_handle) = scroll_handle.0.borrow().as_ref() {
+ return scroll_handle.scroll_offset.clone();
+ }
+ }
- let scroll_height =
- item_height * self.item_count as f32 + self.padding_top + self.padding_bottom;
- let scroll_max = (scroll_height - size.y()).max(0.);
- self.autoscroll(scroll_max, size.y(), item_height);
+ Rc::default()
+ })
+ .clone();
- let start = cmp::min(
- ((self.scroll_top() - self.padding_top) / item_height.max(1.)) as usize,
- self.item_count,
- );
- let end = cmp::min(
- self.item_count,
- start + (size.y() / item_height.max(1.)).ceil() as usize + 1,
- );
+ let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height;
- if (start..end).contains(&sample_item_ix) {
- if sample_item_ix > start {
- (self.append_items)(view, start..sample_item_ix, &mut items, cx);
- }
+ self.interactivity.paint(
+ bounds,
+ content_size,
+ &mut element_state.interactive,
+ cx,
+ |style, mut scroll_offset, cx| {
+ let border = style.border_widths.to_pixels(cx.rem_size());
+ let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
- items.push(sample_item);
+ let padded_bounds = Bounds::from_corners(
+ bounds.origin + point(border.left + padding.left, border.top),
+ bounds.lower_right() - point(border.right + padding.right, border.bottom),
+ );
- if sample_item_ix < end {
- (self.append_items)(view, sample_item_ix + 1..end, &mut items, cx);
- }
- } else {
- (self.append_items)(view, start..end, &mut items, cx);
- }
+ if self.item_count > 0 {
+ let content_height =
+ item_height * self.item_count + padding.top + padding.bottom;
+ let min_scroll_offset = padded_bounds.size.height - content_height;
+ let is_scrolled = scroll_offset.y != px(0.);
- for item in &mut items {
- let item_size = item.layout(item_constraint, view, cx);
- if item_size.x() > size.x() {
- size.set_x(item_size.x());
- }
- }
+ if is_scrolled && scroll_offset.y < min_scroll_offset {
+ shared_scroll_offset.borrow_mut().y = min_scroll_offset;
+ scroll_offset.y = min_scroll_offset;
+ }
+
+ if let Some(scroll_handle) = self.scroll_handle.clone() {
+ scroll_handle.0.borrow_mut().replace(ScrollHandleState {
+ item_height,
+ list_height: padded_bounds.size.height,
+ scroll_offset: shared_scroll_offset,
+ });
+ }
- (
- size,
- UniformListLayoutState {
- item_height,
- scroll_max,
- items,
+ let first_visible_element_ix =
+ (-(scroll_offset.y + padding.top) / item_height).floor() as usize;
+ let last_visible_element_ix = ((-scroll_offset.y + padded_bounds.size.height)
+ / item_height)
+ .ceil() as usize;
+ let visible_range = first_visible_element_ix
+ ..cmp::min(last_visible_element_ix, self.item_count);
+
+ let mut items = (self.render_items)(visible_range.clone(), cx);
+ cx.with_z_index(1, |cx| {
+ let content_mask = ContentMask { bounds };
+ cx.with_content_mask(Some(content_mask), |cx| {
+ for (item, ix) in items.iter_mut().zip(visible_range) {
+ let item_origin = padded_bounds.origin
+ + point(
+ px(0.),
+ item_height * ix + scroll_offset.y + padding.top,
+ );
+ let available_space = size(
+ AvailableSpace::Definite(padded_bounds.size.width),
+ AvailableSpace::Definite(item_height),
+ );
+ item.draw(item_origin, available_space, cx);
+ }
+ });
+ });
+ }
},
)
}
+}
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- layout: &mut Self::LayoutState,
- view: &mut V,
- cx: &mut ViewContext<V>,
- ) -> Self::PaintState {
- let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
-
- cx.scene().push_layer(Some(visible_bounds));
-
- cx.scene().push_mouse_region(
- MouseRegion::new::<Self>(self.view_id, 0, visible_bounds).on_scroll({
- let scroll_max = layout.scroll_max;
- let state = self.state.clone();
- move |event, _, cx| {
- let ScrollWheelEvent {
- position, delta, ..
- } = event.platform_event;
- if !Self::scroll(
- state.clone(),
- position,
- *delta.raw(),
- delta.precise(),
- scroll_max,
- cx,
- ) {
- cx.propagate_event();
- }
- }
- }),
- );
+impl IntoElement for UniformList {
+ type Element = Self;
- let mut item_origin = bounds.origin()
- - vec2f(
- 0.,
- (self.state.scroll_top() - self.padding_top) % layout.item_height,
- );
+ fn element_id(&self) -> Option<crate::ElementId> {
+ Some(self.id.clone())
+ }
- for item in &mut layout.items {
- item.paint(item_origin, visible_bounds, view, cx);
- item_origin += vec2f(0.0, layout.item_height);
- }
+ fn into_element(self) -> Self::Element {
+ self
+ }
+}
- cx.scene().pop_layer();
+impl UniformList {
+ pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
+ self.item_to_measure_index = item_index.unwrap_or(0);
+ self
}
- fn rect_for_text_range(
- &self,
- range: Range<usize>,
- _: RectF,
- _: RectF,
- layout: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> Option<RectF> {
- layout
- .items
- .iter()
- .find_map(|child| child.rect_for_text_range(range.clone(), view, cx))
+ fn measure_item(&self, list_width: Option<Pixels>, cx: &mut WindowContext) -> Size<Pixels> {
+ if self.item_count == 0 {
+ return Size::default();
+ }
+
+ let item_ix = cmp::min(self.item_to_measure_index, self.item_count - 1);
+ let mut items = (self.render_items)(item_ix..item_ix + 1, cx);
+ let mut item_to_measure = items.pop().unwrap();
+ let available_space = size(
+ list_width.map_or(AvailableSpace::MinContent, |width| {
+ AvailableSpace::Definite(width)
+ }),
+ AvailableSpace::MinContent,
+ );
+ item_to_measure.measure(available_space, cx)
}
- fn debug(
- &self,
- bounds: RectF,
- layout: &Self::LayoutState,
- _: &Self::PaintState,
- view: &V,
- cx: &ViewContext<V>,
- ) -> json::Value {
- json!({
- "type": "UniformList",
- "bounds": bounds.to_json(),
- "scroll_max": layout.scroll_max,
- "item_height": layout.item_height,
- "items": layout.items.iter().map(|item| item.debug(view, cx)).collect::<Vec<json::Value>>()
+ pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
+ self.scroll_handle = Some(handle);
+ self
+ }
+}
- })
+impl InteractiveElement for UniformList {
+ fn interactivity(&mut self) -> &mut crate::Interactivity {
+ &mut self.interactivity
}
}
@@ -1,916 +1,372 @@
-use anyhow::{anyhow, Result};
-use async_task::Runnable;
-use futures::channel::mpsc;
-use smol::{channel, prelude::*, Executor};
+use crate::{AppContext, PlatformDispatcher};
+use futures::{channel::mpsc, pin_mut, FutureExt};
+use smol::prelude::*;
use std::{
- any::Any,
- fmt::{self, Display},
+ fmt::Debug,
marker::PhantomData,
mem,
- panic::Location,
+ num::NonZeroUsize,
pin::Pin,
rc::Rc,
- sync::Arc,
+ sync::{
+ atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
+ Arc,
+ },
task::{Context, Poll},
- thread,
time::Duration,
};
+use util::TryFutureExt;
+use waker_fn::waker_fn;
-use crate::{
- platform::{self, Dispatcher},
- util, AppContext,
-};
+#[cfg(any(test, feature = "test-support"))]
+use rand::rngs::StdRng;
-pub enum Foreground {
- Platform {
- dispatcher: Arc<dyn platform::Dispatcher>,
- _not_send_or_sync: PhantomData<Rc<()>>,
- },
- #[cfg(any(test, feature = "test-support"))]
- Deterministic {
- cx_id: usize,
- executor: Arc<Deterministic>,
- },
+#[derive(Clone)]
+pub struct BackgroundExecutor {
+ dispatcher: Arc<dyn PlatformDispatcher>,
}
-pub enum Background {
- #[cfg(any(test, feature = "test-support"))]
- Deterministic { executor: Arc<Deterministic> },
- Production {
- executor: Arc<smol::Executor<'static>>,
- _stop: channel::Sender<()>,
- },
+#[derive(Clone)]
+pub struct ForegroundExecutor {
+ dispatcher: Arc<dyn PlatformDispatcher>,
+ not_send: PhantomData<Rc<()>>,
}
-type AnyLocalFuture = Pin<Box<dyn 'static + Future<Output = Box<dyn Any + 'static>>>>;
-type AnyFuture = Pin<Box<dyn 'static + Send + Future<Output = Box<dyn Any + Send + 'static>>>>;
-type AnyTask = async_task::Task<Box<dyn Any + Send + 'static>>;
-type AnyLocalTask = async_task::Task<Box<dyn Any + 'static>>;
-
#[must_use]
+#[derive(Debug)]
pub enum Task<T> {
Ready(Option<T>),
- Local {
- any_task: AnyLocalTask,
- result_type: PhantomData<T>,
- },
- Send {
- any_task: AnyTask,
- result_type: PhantomData<T>,
- },
+ Spawned(async_task::Task<T>),
}
-unsafe impl<T: Send> Send for Task<T> {}
-
-#[cfg(any(test, feature = "test-support"))]
-struct DeterministicState {
- rng: rand::prelude::StdRng,
- seed: u64,
- scheduled_from_foreground: collections::HashMap<usize, Vec<ForegroundRunnable>>,
- scheduled_from_background: Vec<BackgroundRunnable>,
- forbid_parking: bool,
- block_on_ticks: std::ops::RangeInclusive<usize>,
- now: std::time::Instant,
- next_timer_id: usize,
- pending_timers: Vec<(usize, std::time::Instant, postage::barrier::Sender)>,
- waiting_backtrace: Option<backtrace::Backtrace>,
- next_runnable_id: usize,
- poll_history: Vec<ExecutorEvent>,
- previous_poll_history: Option<Vec<ExecutorEvent>>,
- enable_runnable_backtraces: bool,
- runnable_backtraces: collections::HashMap<usize, backtrace::Backtrace>,
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub enum ExecutorEvent {
- PollRunnable { id: usize },
- EnqueueRunnable { id: usize },
-}
-
-#[cfg(any(test, feature = "test-support"))]
-struct ForegroundRunnable {
- id: usize,
- runnable: Runnable,
- main: bool,
-}
+impl<T> Task<T> {
+ pub fn ready(val: T) -> Self {
+ Task::Ready(Some(val))
+ }
-#[cfg(any(test, feature = "test-support"))]
-struct BackgroundRunnable {
- id: usize,
- runnable: Runnable,
+ pub fn detach(self) {
+ match self {
+ Task::Ready(_) => {}
+ Task::Spawned(task) => task.detach(),
+ }
+ }
}
-#[cfg(any(test, feature = "test-support"))]
-pub struct Deterministic {
- state: Arc<parking_lot::Mutex<DeterministicState>>,
- parker: parking_lot::Mutex<parking::Parker>,
+impl<E, T> Task<Result<T, E>>
+where
+ T: 'static,
+ E: 'static + Debug,
+{
+ #[track_caller]
+ pub fn detach_and_log_err(self, cx: &mut AppContext) {
+ let location = core::panic::Location::caller();
+ cx.foreground_executor()
+ .spawn(self.log_tracked_err(*location))
+ .detach();
+ }
}
-#[must_use]
-pub enum Timer {
- Production(smol::Timer),
- #[cfg(any(test, feature = "test-support"))]
- Deterministic(DeterministicTimer),
-}
+impl<T> Future for Task<T> {
+ type Output = T;
-#[cfg(any(test, feature = "test-support"))]
-pub struct DeterministicTimer {
- rx: postage::barrier::Receiver,
- id: usize,
- state: Arc<parking_lot::Mutex<DeterministicState>>,
+ fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ match unsafe { self.get_unchecked_mut() } {
+ Task::Ready(val) => Poll::Ready(val.take().unwrap()),
+ Task::Spawned(task) => task.poll(cx),
+ }
+ }
}
-#[cfg(any(test, feature = "test-support"))]
-impl Deterministic {
- pub fn new(seed: u64) -> Arc<Self> {
- use rand::prelude::*;
+#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
+pub struct TaskLabel(NonZeroUsize);
- Arc::new(Self {
- state: Arc::new(parking_lot::Mutex::new(DeterministicState {
- rng: StdRng::seed_from_u64(seed),
- seed,
- scheduled_from_foreground: Default::default(),
- scheduled_from_background: Default::default(),
- forbid_parking: false,
- block_on_ticks: 0..=1000,
- now: std::time::Instant::now(),
- next_timer_id: Default::default(),
- pending_timers: Default::default(),
- waiting_backtrace: None,
- next_runnable_id: 0,
- poll_history: Default::default(),
- previous_poll_history: Default::default(),
- enable_runnable_backtraces: false,
- runnable_backtraces: Default::default(),
- })),
- parker: Default::default(),
- })
+impl Default for TaskLabel {
+ fn default() -> Self {
+ Self::new()
}
+}
- pub fn execution_history(&self) -> Vec<ExecutorEvent> {
- self.state.lock().poll_history.clone()
+impl TaskLabel {
+ pub fn new() -> Self {
+ static NEXT_TASK_LABEL: AtomicUsize = AtomicUsize::new(1);
+ Self(NEXT_TASK_LABEL.fetch_add(1, SeqCst).try_into().unwrap())
}
+}
- pub fn set_previous_execution_history(&self, history: Option<Vec<ExecutorEvent>>) {
- self.state.lock().previous_poll_history = history;
- }
+type AnyLocalFuture<R> = Pin<Box<dyn 'static + Future<Output = R>>>;
- pub fn enable_runnable_backtrace(&self) {
- self.state.lock().enable_runnable_backtraces = true;
- }
+type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
- pub fn runnable_backtrace(&self, runnable_id: usize) -> backtrace::Backtrace {
- let mut backtrace = self.state.lock().runnable_backtraces[&runnable_id].clone();
- backtrace.resolve();
- backtrace
+impl BackgroundExecutor {
+ pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
+ Self { dispatcher }
}
- pub fn build_background(self: &Arc<Self>) -> Arc<Background> {
- Arc::new(Background::Deterministic {
- executor: self.clone(),
- })
+ /// Enqueues the given future to be run to completion on a background thread.
+ pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
+ where
+ R: Send + 'static,
+ {
+ self.spawn_internal::<R>(Box::pin(future), None)
}
- pub fn build_foreground(self: &Arc<Self>, id: usize) -> Rc<Foreground> {
- Rc::new(Foreground::Deterministic {
- cx_id: id,
- executor: self.clone(),
- })
+ /// Enqueues the given future to be run to completion on a background thread.
+ /// The given label can be used to control the priority of the task in tests.
+ pub fn spawn_labeled<R>(
+ &self,
+ label: TaskLabel,
+ future: impl Future<Output = R> + Send + 'static,
+ ) -> Task<R>
+ where
+ R: Send + 'static,
+ {
+ self.spawn_internal::<R>(Box::pin(future), Some(label))
}
- fn spawn_from_foreground(
+ fn spawn_internal<R: Send + 'static>(
&self,
- cx_id: usize,
- future: AnyLocalFuture,
- main: bool,
- ) -> AnyLocalTask {
- let state = self.state.clone();
- let id;
- {
- let mut state = state.lock();
- id = util::post_inc(&mut state.next_runnable_id);
- if state.enable_runnable_backtraces {
- state
- .runnable_backtraces
- .insert(id, backtrace::Backtrace::new_unresolved());
- }
- }
-
- let unparker = self.parker.lock().unparker();
- let (runnable, task) = async_task::spawn_local(future, move |runnable| {
- let mut state = state.lock();
- state.push_to_history(ExecutorEvent::EnqueueRunnable { id });
- state
- .scheduled_from_foreground
- .entry(cx_id)
- .or_default()
- .push(ForegroundRunnable { id, runnable, main });
- unparker.unpark();
- });
+ future: AnyFuture<R>,
+ label: Option<TaskLabel>,
+ ) -> Task<R> {
+ let dispatcher = self.dispatcher.clone();
+ let (runnable, task) =
+ async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable, label));
runnable.schedule();
- task
+ Task::Spawned(task)
}
- fn spawn(&self, future: AnyFuture) -> AnyTask {
- let state = self.state.clone();
- let id;
- {
- let mut state = state.lock();
- id = util::post_inc(&mut state.next_runnable_id);
- if state.enable_runnable_backtraces {
- state
- .runnable_backtraces
- .insert(id, backtrace::Backtrace::new_unresolved());
- }
+ #[cfg(any(test, feature = "test-support"))]
+ #[track_caller]
+ pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
+ if let Ok(value) = self.block_internal(false, future, usize::MAX) {
+ value
+ } else {
+ unreachable!()
}
-
- let unparker = self.parker.lock().unparker();
- let (runnable, task) = async_task::spawn(future, move |runnable| {
- let mut state = state.lock();
- state
- .poll_history
- .push(ExecutorEvent::EnqueueRunnable { id });
- state
- .scheduled_from_background
- .push(BackgroundRunnable { id, runnable });
- unparker.unpark();
- });
- runnable.schedule();
- task
}
- fn run<'a>(
- &self,
- cx_id: usize,
- main_future: Pin<Box<dyn 'a + Future<Output = Box<dyn Any>>>>,
- ) -> Box<dyn Any> {
- use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
-
- let woken = Arc::new(AtomicBool::new(false));
-
- let state = self.state.clone();
- let id;
- {
- let mut state = state.lock();
- id = util::post_inc(&mut state.next_runnable_id);
- if state.enable_runnable_backtraces {
- state
- .runnable_backtraces
- .insert(id, backtrace::Backtrace::new_unresolved());
- }
- }
-
- let unparker = self.parker.lock().unparker();
- let (runnable, mut main_task) = unsafe {
- async_task::spawn_unchecked(main_future, move |runnable| {
- let state = &mut *state.lock();
- state
- .scheduled_from_foreground
- .entry(cx_id)
- .or_default()
- .push(ForegroundRunnable {
- id: util::post_inc(&mut state.next_runnable_id),
- runnable,
- main: true,
- });
- unparker.unpark();
- })
- };
- runnable.schedule();
-
- loop {
- if let Some(result) = self.run_internal(woken.clone(), Some(&mut main_task)) {
- return result;
- }
-
- if !woken.load(SeqCst) {
- self.state.lock().will_park();
- }
-
- woken.store(false, SeqCst);
- self.parker.lock().park();
+ pub fn block<R>(&self, future: impl Future<Output = R>) -> R {
+ if let Ok(value) = self.block_internal(true, future, usize::MAX) {
+ value
+ } else {
+ unreachable!()
}
}
- pub fn run_until_parked(&self) {
- use std::sync::atomic::AtomicBool;
- let woken = Arc::new(AtomicBool::new(false));
- self.run_internal(woken, None);
- }
-
- fn run_internal(
+ #[track_caller]
+ pub(crate) fn block_internal<R>(
&self,
- woken: Arc<std::sync::atomic::AtomicBool>,
- mut main_task: Option<&mut AnyLocalTask>,
- ) -> Option<Box<dyn Any>> {
- use rand::prelude::*;
- use std::sync::atomic::Ordering::SeqCst;
-
- let unparker = self.parker.lock().unparker();
- let waker = waker_fn::waker_fn(move || {
- woken.store(true, SeqCst);
- unparker.unpark();
+ background_only: bool,
+ future: impl Future<Output = R>,
+ mut max_ticks: usize,
+ ) -> Result<R, ()> {
+ pin_mut!(future);
+ let unparker = self.dispatcher.unparker();
+ let awoken = Arc::new(AtomicBool::new(false));
+
+ let waker = waker_fn({
+ let awoken = awoken.clone();
+ move || {
+ awoken.store(true, SeqCst);
+ unparker.unpark();
+ }
});
+ let mut cx = std::task::Context::from_waker(&waker);
- let mut cx = Context::from_waker(&waker);
loop {
- let mut state = self.state.lock();
-
- if state.scheduled_from_foreground.is_empty()
- && state.scheduled_from_background.is_empty()
- {
- if let Some(main_task) = main_task {
- if let Poll::Ready(result) = main_task.poll(&mut cx) {
- return Some(result);
+ match future.as_mut().poll(&mut cx) {
+ Poll::Ready(result) => return Ok(result),
+ Poll::Pending => {
+ if max_ticks == 0 {
+ return Err(());
}
- }
+ max_ticks -= 1;
- return None;
- }
-
- if !state.scheduled_from_background.is_empty() && state.rng.gen() {
- let background_len = state.scheduled_from_background.len();
- let ix = state.rng.gen_range(0..background_len);
- let background_runnable = state.scheduled_from_background.remove(ix);
- state.push_to_history(ExecutorEvent::PollRunnable {
- id: background_runnable.id,
- });
- drop(state);
- background_runnable.runnable.run();
- } else if !state.scheduled_from_foreground.is_empty() {
- let available_cx_ids = state
- .scheduled_from_foreground
- .keys()
- .copied()
- .collect::<Vec<_>>();
- let cx_id_to_run = *available_cx_ids.iter().choose(&mut state.rng).unwrap();
- let scheduled_from_cx = state
- .scheduled_from_foreground
- .get_mut(&cx_id_to_run)
- .unwrap();
- let foreground_runnable = scheduled_from_cx.remove(0);
- if scheduled_from_cx.is_empty() {
- state.scheduled_from_foreground.remove(&cx_id_to_run);
- }
- state.push_to_history(ExecutorEvent::PollRunnable {
- id: foreground_runnable.id,
- });
-
- drop(state);
-
- foreground_runnable.runnable.run();
- if let Some(main_task) = main_task.as_mut() {
- if foreground_runnable.main {
- if let Poll::Ready(result) = main_task.poll(&mut cx) {
- return Some(result);
+ if !self.dispatcher.tick(background_only) {
+ if awoken.swap(false, SeqCst) {
+ continue;
}
- }
- }
- }
- }
- }
- fn block<F, T>(&self, future: &mut F, max_ticks: usize) -> Option<T>
- where
- F: Unpin + Future<Output = T>,
- {
- use rand::prelude::*;
-
- let unparker = self.parker.lock().unparker();
- let waker = waker_fn::waker_fn(move || {
- unparker.unpark();
- });
-
- let mut cx = Context::from_waker(&waker);
- for _ in 0..max_ticks {
- let mut state = self.state.lock();
- let runnable_count = state.scheduled_from_background.len();
- let ix = state.rng.gen_range(0..=runnable_count);
- if ix < state.scheduled_from_background.len() {
- let background_runnable = state.scheduled_from_background.remove(ix);
- state.push_to_history(ExecutorEvent::PollRunnable {
- id: background_runnable.id,
- });
- drop(state);
- background_runnable.runnable.run();
- } else {
- drop(state);
- if let Poll::Ready(result) = future.poll(&mut cx) {
- return Some(result);
- }
- let mut state = self.state.lock();
- if state.scheduled_from_background.is_empty() {
- state.will_park();
- drop(state);
- self.parker.lock().park();
- }
-
- continue;
- }
- }
-
- None
- }
-
- pub fn timer(&self, duration: Duration) -> Timer {
- let (tx, rx) = postage::barrier::channel();
- let mut state = self.state.lock();
- let wakeup_at = state.now + duration;
- let id = util::post_inc(&mut state.next_timer_id);
- match state
- .pending_timers
- .binary_search_by_key(&wakeup_at, |e| e.1)
- {
- Ok(ix) | Err(ix) => state.pending_timers.insert(ix, (id, wakeup_at, tx)),
- }
- let state = self.state.clone();
- Timer::Deterministic(DeterministicTimer { rx, id, state })
- }
-
- pub fn now(&self) -> std::time::Instant {
- let state = self.state.lock();
- state.now
- }
-
- pub fn advance_clock(&self, duration: Duration) {
- let new_now = self.state.lock().now + duration;
- loop {
- self.run_until_parked();
- let mut state = self.state.lock();
+ #[cfg(any(test, feature = "test-support"))]
+ if let Some(test) = self.dispatcher.as_test() {
+ if !test.parking_allowed() {
+ let mut backtrace_message = String::new();
+ if let Some(backtrace) = test.waiting_backtrace() {
+ backtrace_message =
+ format!("\nbacktrace of waiting future:\n{:?}", backtrace);
+ }
+ panic!("parked with nothing left to run\n{:?}", backtrace_message)
+ }
+ }
- if let Some((_, wakeup_time, _)) = state.pending_timers.first() {
- let wakeup_time = *wakeup_time;
- if wakeup_time <= new_now {
- let timer_count = state
- .pending_timers
- .iter()
- .take_while(|(_, t, _)| *t == wakeup_time)
- .count();
- state.now = wakeup_time;
- let timers_to_wake = state
- .pending_timers
- .drain(0..timer_count)
- .collect::<Vec<_>>();
- drop(state);
- drop(timers_to_wake);
- continue;
+ self.dispatcher.park();
+ }
}
}
-
- break;
}
-
- self.state.lock().now = new_now;
}
- pub fn start_waiting(&self) {
- self.state.lock().waiting_backtrace = Some(backtrace::Backtrace::new_unresolved());
- }
-
- pub fn finish_waiting(&self) {
- self.state.lock().waiting_backtrace.take();
- }
-
- pub fn forbid_parking(&self) {
- use rand::prelude::*;
-
- let mut state = self.state.lock();
- state.forbid_parking = true;
- state.rng = StdRng::seed_from_u64(state.seed);
- }
-
- pub fn allow_parking(&self) {
- use rand::prelude::*;
-
- let mut state = self.state.lock();
- state.forbid_parking = false;
- state.rng = StdRng::seed_from_u64(state.seed);
- }
-
- pub async fn simulate_random_delay(&self) {
- use rand::prelude::*;
- use smol::future::yield_now;
- if self.state.lock().rng.gen_bool(0.2) {
- let yields = self.state.lock().rng.gen_range(1..=10);
- for _ in 0..yields {
- yield_now().await;
- }
- }
- }
-
- pub fn record_backtrace(&self) {
- let mut state = self.state.lock();
- if state.enable_runnable_backtraces {
- let current_id = state
- .poll_history
- .iter()
- .rev()
- .find_map(|event| match event {
- ExecutorEvent::PollRunnable { id } => Some(*id),
- _ => None,
- });
- if let Some(id) = current_id {
- state
- .runnable_backtraces
- .insert(id, backtrace::Backtrace::new_unresolved());
- }
+ pub fn block_with_timeout<R>(
+ &self,
+ duration: Duration,
+ future: impl Future<Output = R>,
+ ) -> Result<R, impl Future<Output = R>> {
+ let mut future = Box::pin(future.fuse());
+ if duration.is_zero() {
+ return Err(future);
}
- }
-}
-impl Drop for Timer {
- fn drop(&mut self) {
#[cfg(any(test, feature = "test-support"))]
- if let Timer::Deterministic(DeterministicTimer { state, id, .. }) = self {
- state
- .lock()
- .pending_timers
- .retain(|(timer_id, _, _)| timer_id != id)
- }
- }
-}
-
-impl Future for Timer {
- type Output = ();
-
- fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
- match &mut *self {
- #[cfg(any(test, feature = "test-support"))]
- Self::Deterministic(DeterministicTimer { rx, .. }) => {
- use postage::stream::{PollRecv, Stream as _};
- smol::pin!(rx);
- match rx.poll_recv(&mut postage::Context::from_waker(cx.waker())) {
- PollRecv::Ready(()) | PollRecv::Closed => Poll::Ready(()),
- PollRecv::Pending => Poll::Pending,
- }
+ let max_ticks = self
+ .dispatcher
+ .as_test()
+ .map_or(usize::MAX, |dispatcher| dispatcher.gen_block_on_ticks());
+ #[cfg(not(any(test, feature = "test-support")))]
+ let max_ticks = usize::MAX;
+
+ let mut timer = self.timer(duration).fuse();
+
+ let timeout = async {
+ futures::select_biased! {
+ value = future => Ok(value),
+ _ = timer => Err(()),
}
- Self::Production(timer) => {
- smol::pin!(timer);
- match timer.poll(cx) {
- Poll::Ready(_) => Poll::Ready(()),
- Poll::Pending => Poll::Pending,
- }
- }
- }
- }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl DeterministicState {
- fn push_to_history(&mut self, event: ExecutorEvent) {
- use std::fmt::Write as _;
-
- self.poll_history.push(event);
- if let Some(prev_history) = &self.previous_poll_history {
- let ix = self.poll_history.len() - 1;
- let prev_event = prev_history[ix];
- if event != prev_event {
- let mut message = String::new();
- writeln!(
- &mut message,
- "current runnable backtrace:\n{:?}",
- self.runnable_backtraces.get_mut(&event.id()).map(|trace| {
- trace.resolve();
- util::CwdBacktrace(trace)
- })
- )
- .unwrap();
- writeln!(
- &mut message,
- "previous runnable backtrace:\n{:?}",
- self.runnable_backtraces
- .get_mut(&prev_event.id())
- .map(|trace| {
- trace.resolve();
- util::CwdBacktrace(trace)
- })
- )
- .unwrap();
- panic!("detected non-determinism after {ix}. {message}");
- }
- }
- }
-
- fn will_park(&mut self) {
- if self.forbid_parking {
- let mut backtrace_message = String::new();
- #[cfg(any(test, feature = "test-support"))]
- if let Some(backtrace) = self.waiting_backtrace.as_mut() {
- backtrace.resolve();
- backtrace_message = format!(
- "\nbacktrace of waiting future:\n{:?}",
- util::CwdBacktrace(backtrace)
- );
- }
-
- panic!(
- "deterministic executor parked after a call to forbid_parking{}",
- backtrace_message
- );
- }
- }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl ExecutorEvent {
- pub fn id(&self) -> usize {
- match self {
- ExecutorEvent::PollRunnable { id } => *id,
- ExecutorEvent::EnqueueRunnable { id } => *id,
+ };
+ match self.block_internal(true, timeout, max_ticks) {
+ Ok(Ok(value)) => Ok(value),
+ _ => Err(future),
}
}
-}
-impl Foreground {
- pub fn platform(dispatcher: Arc<dyn platform::Dispatcher>) -> Result<Self> {
- if dispatcher.is_main_thread() {
- Ok(Self::Platform {
- dispatcher,
- _not_send_or_sync: PhantomData,
- })
- } else {
- Err(anyhow!("must be constructed on main thread"))
+ pub async fn scoped<'scope, F>(&self, scheduler: F)
+ where
+ F: FnOnce(&mut Scope<'scope>),
+ {
+ let mut scope = Scope::new(self.clone());
+ (scheduler)(&mut scope);
+ let spawned = mem::take(&mut scope.futures)
+ .into_iter()
+ .map(|f| self.spawn(f))
+ .collect::<Vec<_>>();
+ for task in spawned {
+ task.await;
}
}
- pub fn spawn<T: 'static>(&self, future: impl Future<Output = T> + 'static) -> Task<T> {
- let future = any_local_future(future);
- let any_task = match self {
- #[cfg(any(test, feature = "test-support"))]
- Self::Deterministic { cx_id, executor } => {
- executor.spawn_from_foreground(*cx_id, future, false)
- }
- Self::Platform { dispatcher, .. } => {
- fn spawn_inner(
- future: AnyLocalFuture,
- dispatcher: &Arc<dyn Dispatcher>,
- ) -> AnyLocalTask {
- let dispatcher = dispatcher.clone();
- let schedule =
- move |runnable: Runnable| dispatcher.run_on_main_thread(runnable);
- let (runnable, task) = async_task::spawn_local(future, schedule);
- runnable.schedule();
- task
- }
- spawn_inner(future, dispatcher)
- }
- };
- Task::local(any_task)
+ pub fn timer(&self, duration: Duration) -> Task<()> {
+ let (runnable, task) = async_task::spawn(async move {}, {
+ let dispatcher = self.dispatcher.clone();
+ move |runnable| dispatcher.dispatch_after(duration, runnable)
+ });
+ runnable.schedule();
+ Task::Spawned(task)
}
#[cfg(any(test, feature = "test-support"))]
- pub fn run<T: 'static>(&self, future: impl Future<Output = T>) -> T {
- let future = async move { Box::new(future.await) as Box<dyn Any> }.boxed_local();
- let result = match self {
- Self::Deterministic { cx_id, executor } => executor.run(*cx_id, future),
- Self::Platform { .. } => panic!("you can't call run on a platform foreground executor"),
- };
- *result.downcast().unwrap()
+ pub fn start_waiting(&self) {
+ self.dispatcher.as_test().unwrap().start_waiting();
}
#[cfg(any(test, feature = "test-support"))]
- pub fn run_until_parked(&self) {
- match self {
- Self::Deterministic { executor, .. } => executor.run_until_parked(),
- _ => panic!("this method can only be called on a deterministic executor"),
- }
+ pub fn finish_waiting(&self) {
+ self.dispatcher.as_test().unwrap().finish_waiting();
}
#[cfg(any(test, feature = "test-support"))]
- pub fn parking_forbidden(&self) -> bool {
- match self {
- Self::Deterministic { executor, .. } => executor.state.lock().forbid_parking,
- _ => panic!("this method can only be called on a deterministic executor"),
- }
+ pub fn simulate_random_delay(&self) -> impl Future<Output = ()> {
+ self.dispatcher.as_test().unwrap().simulate_random_delay()
}
#[cfg(any(test, feature = "test-support"))]
- pub fn start_waiting(&self) {
- match self {
- Self::Deterministic { executor, .. } => executor.start_waiting(),
- _ => panic!("this method can only be called on a deterministic executor"),
- }
+ pub fn deprioritize(&self, task_label: TaskLabel) {
+ self.dispatcher.as_test().unwrap().deprioritize(task_label)
}
#[cfg(any(test, feature = "test-support"))]
- pub fn finish_waiting(&self) {
- match self {
- Self::Deterministic { executor, .. } => executor.finish_waiting(),
- _ => panic!("this method can only be called on a deterministic executor"),
- }
+ pub fn advance_clock(&self, duration: Duration) {
+ self.dispatcher.as_test().unwrap().advance_clock(duration)
}
#[cfg(any(test, feature = "test-support"))]
- pub fn forbid_parking(&self) {
- match self {
- Self::Deterministic { executor, .. } => executor.forbid_parking(),
- _ => panic!("this method can only be called on a deterministic executor"),
- }
+ pub fn tick(&self) -> bool {
+ self.dispatcher.as_test().unwrap().tick(false)
}
#[cfg(any(test, feature = "test-support"))]
- pub fn allow_parking(&self) {
- match self {
- Self::Deterministic { executor, .. } => executor.allow_parking(),
- _ => panic!("this method can only be called on a deterministic executor"),
- }
+ pub fn run_until_parked(&self) {
+ self.dispatcher.as_test().unwrap().run_until_parked()
}
#[cfg(any(test, feature = "test-support"))]
- pub fn advance_clock(&self, duration: Duration) {
- match self {
- Self::Deterministic { executor, .. } => executor.advance_clock(duration),
- _ => panic!("this method can only be called on a deterministic executor"),
- }
+ pub fn allow_parking(&self) {
+ self.dispatcher.as_test().unwrap().allow_parking();
}
#[cfg(any(test, feature = "test-support"))]
- pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
- match self {
- Self::Deterministic { executor, .. } => executor.state.lock().block_on_ticks = range,
- _ => panic!("this method can only be called on a deterministic executor"),
- }
- }
-}
-
-impl Background {
- pub fn new() -> Self {
- let executor = Arc::new(Executor::new());
- let stop = channel::unbounded::<()>();
-
- for i in 0..2 * num_cpus::get() {
- let executor = executor.clone();
- let stop = stop.1.clone();
- thread::Builder::new()
- .name(format!("background-executor-{}", i))
- .spawn(move || smol::block_on(executor.run(stop.recv())))
- .unwrap();
- }
-
- Self::Production {
- executor,
- _stop: stop.0,
- }
+ pub fn rng(&self) -> StdRng {
+ self.dispatcher.as_test().unwrap().rng()
}
pub fn num_cpus(&self) -> usize {
num_cpus::get()
}
- pub fn spawn<T, F>(&self, future: F) -> Task<T>
- where
- T: 'static + Send,
- F: Send + Future<Output = T> + 'static,
- {
- let future = any_future(future);
- let any_task = match self {
- Self::Production { executor, .. } => executor.spawn(future),
- #[cfg(any(test, feature = "test-support"))]
- Self::Deterministic { executor } => executor.spawn(future),
- };
- Task::send(any_task)
+ pub fn is_main_thread(&self) -> bool {
+ self.dispatcher.is_main_thread()
}
- pub fn block<F, T>(&self, future: F) -> T
- where
- F: Future<Output = T>,
- {
- smol::pin!(future);
- match self {
- Self::Production { .. } => smol::block_on(&mut future),
- #[cfg(any(test, feature = "test-support"))]
- Self::Deterministic { executor, .. } => {
- executor.block(&mut future, usize::MAX).unwrap()
- }
- }
+ #[cfg(any(test, feature = "test-support"))]
+ pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
+ self.dispatcher.as_test().unwrap().set_block_on_ticks(range);
}
+}
- pub fn block_with_timeout<F, T>(
- &self,
- timeout: Duration,
- future: F,
- ) -> Result<T, impl Future<Output = T>>
- where
- T: 'static,
- F: 'static + Unpin + Future<Output = T>,
- {
- let mut future = any_local_future(future);
- if !timeout.is_zero() {
- let output = match self {
- Self::Production { .. } => smol::block_on(util::timeout(timeout, &mut future)).ok(),
- #[cfg(any(test, feature = "test-support"))]
- Self::Deterministic { executor, .. } => {
- use rand::prelude::*;
- let max_ticks = {
- let mut state = executor.state.lock();
- let range = state.block_on_ticks.clone();
- state.rng.gen_range(range)
- };
- executor.block(&mut future, max_ticks)
- }
- };
- if let Some(output) = output {
- return Ok(*output.downcast().unwrap());
- }
+impl ForegroundExecutor {
+ pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
+ Self {
+ dispatcher,
+ not_send: PhantomData,
}
- Err(async { *future.await.downcast().unwrap() })
}
- pub async fn scoped<'scope, F>(self: &Arc<Self>, scheduler: F)
+ /// Enqueues the given closure to be run on any thread. The closure returns
+ /// a future which will be run to completion on any available thread.
+ pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
where
- F: FnOnce(&mut Scope<'scope>),
+ R: 'static,
{
- let mut scope = Scope::new(self.clone());
- (scheduler)(&mut scope);
- let spawned = mem::take(&mut scope.futures)
- .into_iter()
- .map(|f| self.spawn(f))
- .collect::<Vec<_>>();
- for task in spawned {
- task.await;
- }
- }
-
- pub fn timer(&self, duration: Duration) -> Timer {
- match self {
- Background::Production { .. } => Timer::Production(smol::Timer::after(duration)),
- #[cfg(any(test, feature = "test-support"))]
- Background::Deterministic { executor } => executor.timer(duration),
- }
- }
-
- pub fn now(&self) -> std::time::Instant {
- match self {
- Background::Production { .. } => std::time::Instant::now(),
- #[cfg(any(test, feature = "test-support"))]
- Background::Deterministic { executor } => executor.now(),
- }
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn rng<'a>(&'a self) -> impl 'a + std::ops::DerefMut<Target = rand::prelude::StdRng> {
- match self {
- Self::Deterministic { executor, .. } => {
- parking_lot::lock_api::MutexGuard::map(executor.state.lock(), |s| &mut s.rng)
- }
- _ => panic!("this method can only be called on a deterministic executor"),
- }
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub async fn simulate_random_delay(&self) {
- match self {
- Self::Deterministic { executor, .. } => {
- executor.simulate_random_delay().await;
- }
- _ => {
- panic!("this method can only be called on a deterministic executor")
- }
- }
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn record_backtrace(&self) {
- match self {
- Self::Deterministic { executor, .. } => executor.record_backtrace(),
- _ => {
- panic!("this method can only be called on a deterministic executor")
- }
- }
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn start_waiting(&self) {
- match self {
- Self::Deterministic { executor, .. } => executor.start_waiting(),
- _ => panic!("this method can only be called on a deterministic executor"),
- }
- }
-}
-
-impl Default for Background {
- fn default() -> Self {
- Self::new()
+ let dispatcher = self.dispatcher.clone();
+ fn inner<R: 'static>(
+ dispatcher: Arc<dyn PlatformDispatcher>,
+ future: AnyLocalFuture<R>,
+ ) -> Task<R> {
+ let (runnable, task) = async_task::spawn_local(future, move |runnable| {
+ dispatcher.dispatch_on_main_thread(runnable)
+ });
+ runnable.schedule();
+ Task::Spawned(task)
+ }
+ inner::<R>(dispatcher, Box::pin(future))
}
}
pub struct Scope<'a> {
- executor: Arc<Background>,
+ executor: BackgroundExecutor,
futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
tx: Option<mpsc::Sender<()>>,
rx: mpsc::Receiver<()>,
- _phantom: PhantomData<&'a ()>,
+ lifetime: PhantomData<&'a ()>,
}
impl<'a> Scope<'a> {
- fn new(executor: Arc<Background>) -> Self {
+ fn new(executor: BackgroundExecutor) -> Self {
let (tx, rx) = mpsc::channel(1);
Self {
executor,
tx: Some(tx),
rx,
futures: Default::default(),
- _phantom: PhantomData,
+ lifetime: PhantomData,
}
}
@@ -1,330 +0,0 @@
-use crate::{
- fonts::{Features, FontId, Metrics, Properties},
- geometry::vector::{vec2f, Vector2F},
- platform,
- text_layout::LineWrapper,
-};
-use anyhow::{anyhow, Result};
-use ordered_float::OrderedFloat;
-use parking_lot::{RwLock, RwLockUpgradableReadGuard};
-use schemars::JsonSchema;
-use std::{
- collections::HashMap,
- ops::{Deref, DerefMut},
- sync::Arc,
-};
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, JsonSchema)]
-pub struct FamilyId(usize);
-
-struct Family {
- name: Arc<str>,
- font_features: Features,
- font_ids: Vec<FontId>,
-}
-
-pub struct FontCache(RwLock<FontCacheState>);
-
-pub struct FontCacheState {
- font_system: Arc<dyn platform::FontSystem>,
- families: Vec<Family>,
- default_family: Option<FamilyId>,
- font_selections: HashMap<FamilyId, HashMap<Properties, FontId>>,
- metrics: HashMap<FontId, Metrics>,
- wrapper_pool: HashMap<(FontId, OrderedFloat<f32>), Vec<LineWrapper>>,
-}
-
-pub struct LineWrapperHandle {
- wrapper: Option<LineWrapper>,
- font_cache: Arc<FontCache>,
-}
-
-unsafe impl Send for FontCache {}
-
-impl FontCache {
- pub fn new(fonts: Arc<dyn platform::FontSystem>) -> Self {
- Self(RwLock::new(FontCacheState {
- font_system: fonts,
- families: Default::default(),
- default_family: None,
- font_selections: Default::default(),
- metrics: Default::default(),
- wrapper_pool: Default::default(),
- }))
- }
-
- pub fn family_name(&self, family_id: FamilyId) -> Result<Arc<str>> {
- self.0
- .read()
- .families
- .get(family_id.0)
- .ok_or_else(|| anyhow!("invalid family id"))
- .map(|family| family.name.clone())
- }
-
- pub fn load_family(&self, names: &[&str], features: &Features) -> Result<FamilyId> {
- for name in names {
- let state = self.0.upgradable_read();
-
- if let Some(ix) = state
- .families
- .iter()
- .position(|f| f.name.as_ref() == *name && f.font_features == *features)
- {
- return Ok(FamilyId(ix));
- }
-
- let mut state = RwLockUpgradableReadGuard::upgrade(state);
-
- if let Ok(font_ids) = state.font_system.load_family(name, features) {
- if font_ids.is_empty() {
- continue;
- }
-
- let family_id = FamilyId(state.families.len());
- for font_id in &font_ids {
- if state.font_system.glyph_for_char(*font_id, 'm').is_none() {
- return Err(anyhow!("font must contain a glyph for the 'm' character"));
- }
- }
-
- state.families.push(Family {
- name: Arc::from(*name),
- font_features: features.clone(),
- font_ids,
- });
- return Ok(family_id);
- }
- }
-
- Err(anyhow!(
- "could not find a non-empty font family matching one of the given names: {}",
- names
- .iter()
- .map(|name| format!("`{name}`"))
- .collect::<Vec<_>>()
- .join(", ")
- ))
- }
-
- /// Returns an arbitrary font family that is available on the system.
- pub fn known_existing_family(&self) -> FamilyId {
- if let Some(family_id) = self.0.read().default_family {
- return family_id;
- }
-
- let default_family = self
- .load_family(
- &["Courier", "Helvetica", "Arial", "Verdana"],
- &Default::default(),
- )
- .unwrap_or_else(|_| {
- let all_family_names = self.0.read().font_system.all_families();
- let all_family_names: Vec<_> = all_family_names
- .iter()
- .map(|string| string.as_str())
- .collect();
- self.load_family(&all_family_names, &Default::default())
- .expect("could not load any default font family")
- });
-
- self.0.write().default_family = Some(default_family);
- default_family
- }
-
- pub fn default_font(&self, family_id: FamilyId) -> FontId {
- self.select_font(family_id, &Properties::default()).unwrap()
- }
-
- pub fn select_font(&self, family_id: FamilyId, properties: &Properties) -> Result<FontId> {
- let inner = self.0.upgradable_read();
- if let Some(font_id) = inner
- .font_selections
- .get(&family_id)
- .and_then(|f| f.get(properties))
- {
- Ok(*font_id)
- } else {
- let mut inner = RwLockUpgradableReadGuard::upgrade(inner);
- let family = &inner.families[family_id.0];
- let font_id = inner
- .font_system
- .select_font(&family.font_ids, properties)
- .unwrap_or(family.font_ids[0]);
-
- inner
- .font_selections
- .entry(family_id)
- .or_default()
- .insert(*properties, font_id);
- Ok(font_id)
- }
- }
-
- pub fn metric<F, T>(&self, font_id: FontId, f: F) -> T
- where
- F: FnOnce(&Metrics) -> T,
- T: 'static,
- {
- let state = self.0.upgradable_read();
- if let Some(metrics) = state.metrics.get(&font_id) {
- f(metrics)
- } else {
- let metrics = state.font_system.font_metrics(font_id);
- let metric = f(&metrics);
- let mut state = RwLockUpgradableReadGuard::upgrade(state);
- state.metrics.insert(font_id, metrics);
- metric
- }
- }
-
- pub fn bounding_box(&self, font_id: FontId, font_size: f32) -> Vector2F {
- let bounding_box = self.metric(font_id, |m| m.bounding_box);
- let width = bounding_box.width() * self.em_scale(font_id, font_size);
- let height = bounding_box.height() * self.em_scale(font_id, font_size);
- vec2f(width, height)
- }
-
- pub fn em_width(&self, font_id: FontId, font_size: f32) -> f32 {
- let glyph_id;
- let bounds;
- {
- let state = self.0.read();
- glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
- bounds = state
- .font_system
- .typographic_bounds(font_id, glyph_id)
- .unwrap();
- }
- bounds.width() * self.em_scale(font_id, font_size)
- }
-
- pub fn em_advance(&self, font_id: FontId, font_size: f32) -> f32 {
- let glyph_id;
- let advance;
- {
- let state = self.0.read();
- glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
- advance = state.font_system.advance(font_id, glyph_id).unwrap();
- }
- advance.x() * self.em_scale(font_id, font_size)
- }
-
- pub fn line_height(&self, font_size: f32) -> f32 {
- (font_size * 1.618).round()
- }
-
- pub fn cap_height(&self, font_id: FontId, font_size: f32) -> f32 {
- self.metric(font_id, |m| m.cap_height) * self.em_scale(font_id, font_size)
- }
-
- pub fn x_height(&self, font_id: FontId, font_size: f32) -> f32 {
- self.metric(font_id, |m| m.x_height) * self.em_scale(font_id, font_size)
- }
-
- pub fn ascent(&self, font_id: FontId, font_size: f32) -> f32 {
- self.metric(font_id, |m| m.ascent) * self.em_scale(font_id, font_size)
- }
-
- pub fn descent(&self, font_id: FontId, font_size: f32) -> f32 {
- self.metric(font_id, |m| -m.descent) * self.em_scale(font_id, font_size)
- }
-
- pub fn em_scale(&self, font_id: FontId, font_size: f32) -> f32 {
- font_size / self.metric(font_id, |m| m.units_per_em as f32)
- }
-
- pub fn baseline_offset(&self, font_id: FontId, font_size: f32) -> f32 {
- let line_height = self.line_height(font_size);
- let ascent = self.ascent(font_id, font_size);
- let descent = self.descent(font_id, font_size);
- let padding_top = (line_height - ascent - descent) / 2.;
- padding_top + ascent
- }
-
- pub fn line_wrapper(self: &Arc<Self>, font_id: FontId, font_size: f32) -> LineWrapperHandle {
- let mut state = self.0.write();
- let wrappers = state
- .wrapper_pool
- .entry((font_id, OrderedFloat(font_size)))
- .or_default();
- let wrapper = wrappers
- .pop()
- .unwrap_or_else(|| LineWrapper::new(font_id, font_size, state.font_system.clone()));
- LineWrapperHandle {
- wrapper: Some(wrapper),
- font_cache: self.clone(),
- }
- }
-}
-
-impl Drop for LineWrapperHandle {
- fn drop(&mut self) {
- let mut state = self.font_cache.0.write();
- let wrapper = self.wrapper.take().unwrap();
- state
- .wrapper_pool
- .get_mut(&(wrapper.font_id, OrderedFloat(wrapper.font_size)))
- .unwrap()
- .push(wrapper);
- }
-}
-
-impl Deref for LineWrapperHandle {
- type Target = LineWrapper;
-
- fn deref(&self) -> &Self::Target {
- self.wrapper.as_ref().unwrap()
- }
-}
-
-impl DerefMut for LineWrapperHandle {
- fn deref_mut(&mut self) -> &mut Self::Target {
- 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);
- }
-}
@@ -1,636 +0,0 @@
-use crate::{
- color::Color,
- font_cache::FamilyId,
- json::{json, ToJson},
- text_layout::RunStyle,
- FontCache,
-};
-use anyhow::{anyhow, Result};
-pub use font_kit::{
- metrics::Metrics,
- properties::{Properties, Stretch, Style, Weight},
-};
-use ordered_float::OrderedFloat;
-use refineable::Refineable;
-use schemars::JsonSchema;
-use serde::{de, Deserialize, Serialize};
-use serde_json::Value;
-use std::{cell::RefCell, sync::Arc};
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, JsonSchema)]
-pub struct FontId(pub usize);
-
-pub type GlyphId = u32;
-
-#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
-pub struct Features {
- pub calt: Option<bool>,
- pub case: Option<bool>,
- pub cpsp: Option<bool>,
- pub frac: Option<bool>,
- pub liga: Option<bool>,
- pub onum: Option<bool>,
- pub ordn: Option<bool>,
- pub pnum: Option<bool>,
- pub ss01: Option<bool>,
- pub ss02: Option<bool>,
- pub ss03: Option<bool>,
- pub ss04: Option<bool>,
- pub ss05: Option<bool>,
- pub ss06: Option<bool>,
- pub ss07: Option<bool>,
- pub ss08: Option<bool>,
- pub ss09: Option<bool>,
- pub ss10: Option<bool>,
- pub ss11: Option<bool>,
- pub ss12: Option<bool>,
- pub ss13: Option<bool>,
- pub ss14: Option<bool>,
- pub ss15: Option<bool>,
- pub ss16: Option<bool>,
- pub ss17: Option<bool>,
- pub ss18: Option<bool>,
- pub ss19: Option<bool>,
- pub ss20: Option<bool>,
- pub subs: Option<bool>,
- pub sups: Option<bool>,
- pub swsh: Option<bool>,
- pub titl: Option<bool>,
- pub tnum: Option<bool>,
- pub zero: Option<bool>,
-}
-
-#[derive(Clone, Debug, JsonSchema)]
-pub struct TextStyle {
- pub color: Color,
- pub font_family_name: Arc<str>,
- pub font_family_id: FamilyId,
- pub font_id: FontId,
- pub font_size: f32,
- #[schemars(with = "PropertiesDef")]
- pub font_properties: Properties,
- pub underline: Underline,
- pub soft_wrap: bool,
-}
-
-impl TextStyle {
- pub fn for_color(color: Color) -> Self {
- Self {
- color,
- ..Default::default()
- }
- }
-}
-
-impl TextStyle {
- pub fn refine(
- &mut self,
- refinement: &TextStyleRefinement,
- font_cache: &FontCache,
- ) -> Result<()> {
- if let Some(font_size) = refinement.font_size {
- self.font_size = font_size;
- }
- if let Some(color) = refinement.color {
- self.color = color;
- }
- if let Some(underline) = refinement.underline {
- self.underline = underline;
- }
-
- let mut update_font_id = false;
- if let Some(font_family) = refinement.font_family.clone() {
- self.font_family_id = font_cache.load_family(&[&font_family], &Default::default())?;
- self.font_family_name = font_family;
- update_font_id = true;
- }
- if let Some(font_weight) = refinement.font_weight {
- self.font_properties.weight = font_weight;
- update_font_id = true;
- }
- if let Some(font_style) = refinement.font_style {
- self.font_properties.style = font_style;
- update_font_id = true;
- }
-
- if update_font_id {
- self.font_id = font_cache.select_font(self.font_family_id, &self.font_properties)?;
- }
-
- Ok(())
- }
-}
-
-#[derive(Clone, Debug, Default)]
-pub struct TextStyleRefinement {
- pub color: Option<Color>,
- pub font_family: Option<Arc<str>>,
- pub font_size: Option<f32>,
- pub font_weight: Option<Weight>,
- pub font_style: Option<Style>,
- pub underline: Option<Underline>,
-}
-
-impl Refineable for TextStyleRefinement {
- type Refinement = Self;
-
- fn refine(&mut self, refinement: &Self::Refinement) {
- if refinement.color.is_some() {
- self.color = refinement.color;
- }
- if refinement.font_family.is_some() {
- self.font_family = refinement.font_family.clone();
- }
- if refinement.font_size.is_some() {
- self.font_size = refinement.font_size;
- }
- if refinement.font_weight.is_some() {
- self.font_weight = refinement.font_weight;
- }
- if refinement.font_style.is_some() {
- self.font_style = refinement.font_style;
- }
- if refinement.underline.is_some() {
- self.underline = refinement.underline;
- }
- }
-
- fn refined(mut self, refinement: Self::Refinement) -> Self {
- self.refine(&refinement);
- self
- }
-}
-
-#[derive(JsonSchema)]
-#[serde(remote = "Properties")]
-pub struct PropertiesDef {
- /// The font style, as defined in CSS.
- pub style: StyleDef,
- /// The font weight, as defined in CSS.
- pub weight: f32,
- /// The font stretchiness, as defined in CSS.
- pub stretch: f32,
-}
-
-#[derive(JsonSchema)]
-#[schemars(remote = "Style")]
-pub enum StyleDef {
- /// A face that is neither italic not obliqued.
- Normal,
- /// A form that is generally cursive in nature.
- Italic,
- /// A typically-sloped version of the regular face.
- Oblique,
-}
-
-#[derive(Copy, Clone, Debug, Default, PartialEq, JsonSchema)]
-pub struct HighlightStyle {
- pub color: Option<Color>,
- #[schemars(with = "Option::<f32>")]
- pub weight: Option<Weight>,
- pub italic: Option<bool>,
- pub underline: Option<Underline>,
- pub fade_out: Option<f32>,
-}
-
-impl Eq for HighlightStyle {}
-
-#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, JsonSchema)]
-pub struct Underline {
- pub color: Option<Color>,
- #[schemars(with = "f32")]
- pub thickness: OrderedFloat<f32>,
- pub squiggly: bool,
-}
-
-#[allow(non_camel_case_types)]
-#[derive(Deserialize)]
-enum WeightJson {
- thin,
- extra_light,
- light,
- normal,
- medium,
- semibold,
- bold,
- extra_bold,
- black,
-}
-
-thread_local! {
- static FONT_CACHE: RefCell<Option<Arc<FontCache>>> = Default::default();
-}
-
-#[derive(Deserialize)]
-struct TextStyleJson {
- color: Color,
- family: String,
- #[serde(default)]
- features: Features,
- weight: Option<WeightJson>,
- size: f32,
- #[serde(default)]
- italic: bool,
- #[serde(default)]
- underline: UnderlineStyleJson,
-}
-
-#[derive(Deserialize)]
-struct HighlightStyleJson {
- color: Option<Color>,
- weight: Option<WeightJson>,
- italic: Option<bool>,
- underline: Option<UnderlineStyleJson>,
- fade_out: Option<f32>,
-}
-
-#[derive(Deserialize)]
-#[serde(untagged)]
-enum UnderlineStyleJson {
- Underlined(bool),
- UnderlinedWithProperties {
- #[serde(default)]
- color: Option<Color>,
- #[serde(default)]
- thickness: Option<f32>,
- #[serde(default)]
- squiggly: bool,
- },
-}
-
-impl TextStyle {
- pub fn new(
- font_family_name: impl Into<Arc<str>>,
- font_size: f32,
- font_properties: Properties,
- font_features: Features,
- underline: Underline,
- color: Color,
- font_cache: &FontCache,
- ) -> Result<Self> {
- let font_family_name = font_family_name.into();
- let font_family_id = font_cache.load_family(&[&font_family_name], &font_features)?;
- let font_id = font_cache.select_font(font_family_id, &font_properties)?;
- Ok(Self {
- color,
- font_family_name,
- font_family_id,
- font_id,
- font_size,
- font_properties,
- underline,
- soft_wrap: false,
- })
- }
-
- pub fn default(font_cache: &FontCache) -> Self {
- let font_family_id = font_cache.known_existing_family();
- let font_id = font_cache
- .select_font(font_family_id, &Default::default())
- .expect("did not have any font in system-provided family");
- let font_family_name = font_cache
- .family_name(font_family_id)
- .expect("we loaded this family from the font cache, so this should work");
-
- Self {
- color: Color::default(),
- font_family_name,
- font_family_id,
- font_id,
- font_size: 14.,
- font_properties: Default::default(),
- underline: Default::default(),
- soft_wrap: true,
- }
- }
-
- pub fn with_font_size(mut self, font_size: f32) -> Self {
- self.font_size = font_size;
- self
- }
-
- pub fn highlight(mut self, style: HighlightStyle, font_cache: &FontCache) -> Result<Self> {
- let mut font_properties = self.font_properties;
- if let Some(weight) = style.weight {
- font_properties.weight(weight);
- }
- if let Some(italic) = style.italic {
- if italic {
- font_properties.style(Style::Italic);
- } else {
- font_properties.style(Style::Normal);
- }
- }
-
- if self.font_properties != font_properties {
- self.font_id = font_cache.select_font(self.font_family_id, &font_properties)?;
- }
- if let Some(color) = style.color {
- self.color = Color::blend(color, self.color);
- }
- if let Some(factor) = style.fade_out {
- self.color.fade_out(factor);
- }
- if let Some(underline) = style.underline {
- self.underline = underline;
- }
-
- Ok(self)
- }
-
- pub fn to_run(&self) -> RunStyle {
- RunStyle {
- font_id: self.font_id,
- color: self.color,
- underline: self.underline,
- }
- }
-
- fn from_json(json: TextStyleJson) -> Result<Self> {
- FONT_CACHE.with(|font_cache| {
- if let Some(font_cache) = font_cache.borrow().as_ref() {
- let font_properties = properties_from_json(json.weight, json.italic);
- Self::new(
- json.family,
- json.size,
- font_properties,
- json.features,
- underline_from_json(json.underline),
- json.color,
- font_cache,
- )
- } else {
- Err(anyhow!(
- "TextStyle can only be deserialized within a call to with_font_cache"
- ))
- }
- })
- }
-
- pub fn line_height(&self, font_cache: &FontCache) -> f32 {
- font_cache.line_height(self.font_size)
- }
-
- pub fn cap_height(&self, font_cache: &FontCache) -> f32 {
- font_cache.cap_height(self.font_id, self.font_size)
- }
-
- pub fn x_height(&self, font_cache: &FontCache) -> f32 {
- font_cache.x_height(self.font_id, self.font_size)
- }
-
- pub fn em_width(&self, font_cache: &FontCache) -> f32 {
- font_cache.em_width(self.font_id, self.font_size)
- }
-
- pub fn em_advance(&self, font_cache: &FontCache) -> f32 {
- font_cache.em_advance(self.font_id, self.font_size)
- }
-
- pub fn descent(&self, font_cache: &FontCache) -> f32 {
- font_cache.metric(self.font_id, |m| m.descent) * self.em_scale(font_cache)
- }
-
- pub fn baseline_offset(&self, font_cache: &FontCache) -> f32 {
- font_cache.baseline_offset(self.font_id, self.font_size)
- }
-
- fn em_scale(&self, font_cache: &FontCache) -> f32 {
- font_cache.em_scale(self.font_id, self.font_size)
- }
-}
-
-impl From<TextStyle> for HighlightStyle {
- fn from(other: TextStyle) -> Self {
- Self::from(&other)
- }
-}
-
-impl From<&TextStyle> for HighlightStyle {
- fn from(other: &TextStyle) -> Self {
- Self {
- color: Some(other.color),
- weight: Some(other.font_properties.weight),
- italic: Some(other.font_properties.style == Style::Italic),
- underline: Some(other.underline),
- fade_out: None,
- }
- }
-}
-
-impl Default for UnderlineStyleJson {
- fn default() -> Self {
- Self::Underlined(false)
- }
-}
-
-impl Default for TextStyle {
- fn default() -> Self {
- FONT_CACHE.with(|font_cache| {
- let font_cache = font_cache.borrow();
- let font_cache = font_cache
- .as_ref()
- .expect("TextStyle::default can only be called within a call to with_font_cache");
- Self::default(font_cache)
- })
- }
-}
-
-impl HighlightStyle {
- fn from_json(json: HighlightStyleJson) -> Self {
- Self {
- color: json.color,
- weight: json.weight.map(weight_from_json),
- italic: json.italic,
- underline: json.underline.map(underline_from_json),
- fade_out: json.fade_out,
- }
- }
-
- pub fn highlight(&mut self, other: HighlightStyle) {
- match (self.color, other.color) {
- (Some(self_color), Some(other_color)) => {
- self.color = Some(Color::blend(other_color, self_color));
- }
- (None, Some(other_color)) => {
- self.color = Some(other_color);
- }
- _ => {}
- }
-
- if other.weight.is_some() {
- self.weight = other.weight;
- }
-
- if other.italic.is_some() {
- self.italic = other.italic;
- }
-
- if other.underline.is_some() {
- self.underline = other.underline;
- }
-
- match (other.fade_out, self.fade_out) {
- (Some(source_fade), None) => self.fade_out = Some(source_fade),
- (Some(source_fade), Some(dest_fade)) => {
- let source_alpha = 1. - source_fade;
- let dest_alpha = 1. - dest_fade;
- let blended_alpha = source_alpha + (dest_alpha * source_fade);
- let blended_fade = 1. - blended_alpha;
- self.fade_out = Some(blended_fade);
- }
- _ => {}
- }
- }
-}
-
-impl From<Color> for HighlightStyle {
- fn from(color: Color) -> Self {
- Self {
- color: Some(color),
- ..Default::default()
- }
- }
-}
-
-impl<'de> Deserialize<'de> for TextStyle {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: serde::Deserializer<'de>,
- {
- Self::from_json(TextStyleJson::deserialize(deserializer)?).map_err(de::Error::custom)
- }
-}
-
-impl ToJson for TextStyle {
- fn to_json(&self) -> Value {
- json!({
- "color": self.color.to_json(),
- "font_family": self.font_family_name.as_ref(),
- "font_properties": self.font_properties.to_json(),
- })
- }
-}
-
-impl<'de> Deserialize<'de> for HighlightStyle {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: serde::Deserializer<'de>,
- {
- let json = serde_json::Value::deserialize(deserializer)?;
- if json.is_object() {
- Ok(Self::from_json(
- serde_json::from_value(json).map_err(de::Error::custom)?,
- ))
- } else {
- Ok(Self {
- color: serde_json::from_value(json).map_err(de::Error::custom)?,
- ..Default::default()
- })
- }
- }
-}
-
-fn underline_from_json(json: UnderlineStyleJson) -> Underline {
- match json {
- UnderlineStyleJson::Underlined(false) => Underline::default(),
- UnderlineStyleJson::Underlined(true) => Underline {
- color: None,
- thickness: 1.0.into(),
- squiggly: false,
- },
- UnderlineStyleJson::UnderlinedWithProperties {
- color,
- thickness,
- squiggly,
- } => Underline {
- color,
- thickness: thickness.unwrap_or(1.).into(),
- squiggly,
- },
- }
-}
-
-fn properties_from_json(weight: Option<WeightJson>, italic: bool) -> Properties {
- let weight = weight.map(weight_from_json).unwrap_or_default();
- let style = if italic { Style::Italic } else { Style::Normal };
- *Properties::new().weight(weight).style(style)
-}
-
-fn weight_from_json(weight: WeightJson) -> Weight {
- match weight {
- WeightJson::thin => Weight::THIN,
- WeightJson::extra_light => Weight::EXTRA_LIGHT,
- WeightJson::light => Weight::LIGHT,
- WeightJson::normal => Weight::NORMAL,
- WeightJson::medium => Weight::MEDIUM,
- WeightJson::semibold => Weight::SEMIBOLD,
- WeightJson::bold => Weight::BOLD,
- WeightJson::extra_bold => Weight::EXTRA_BOLD,
- WeightJson::black => Weight::BLACK,
- }
-}
-
-impl ToJson for Properties {
- fn to_json(&self) -> crate::json::Value {
- json!({
- "style": self.style.to_json(),
- "weight": self.weight.to_json(),
- "stretch": self.stretch.to_json(),
- })
- }
-}
-
-impl ToJson for Style {
- fn to_json(&self) -> crate::json::Value {
- match self {
- Style::Normal => json!("normal"),
- Style::Italic => json!("italic"),
- Style::Oblique => json!("oblique"),
- }
- }
-}
-
-impl ToJson for Weight {
- fn to_json(&self) -> crate::json::Value {
- if self.0 == Weight::THIN.0 {
- json!("thin")
- } else if self.0 == Weight::EXTRA_LIGHT.0 {
- json!("extra light")
- } else if self.0 == Weight::LIGHT.0 {
- json!("light")
- } else if self.0 == Weight::NORMAL.0 {
- json!("normal")
- } else if self.0 == Weight::MEDIUM.0 {
- json!("medium")
- } else if self.0 == Weight::SEMIBOLD.0 {
- json!("semibold")
- } else if self.0 == Weight::BOLD.0 {
- json!("bold")
- } else if self.0 == Weight::EXTRA_BOLD.0 {
- json!("extra bold")
- } else if self.0 == Weight::BLACK.0 {
- json!("black")
- } else {
- json!(self.0)
- }
- }
-}
-
-impl ToJson for Stretch {
- fn to_json(&self) -> serde_json::Value {
- json!(self.0)
- }
-}
-
-pub fn with_font_cache<F, T>(font_cache: Arc<FontCache>, callback: F) -> T
-where
- F: FnOnce() -> T,
-{
- FONT_CACHE.with(|cache| {
- *cache.borrow_mut() = Some(font_cache);
- let result = callback();
- cache.borrow_mut().take();
- result
- })
-}
@@ -1,407 +1,2484 @@
-use super::scene::{Path, PathVertex};
-use crate::{color::Color, json::ToJson};
-use derive_more::Neg;
-pub use pathfinder_geometry::*;
-use rect::RectF;
+use core::fmt::Debug;
+use derive_more::{Add, AddAssign, Div, DivAssign, Mul, Neg, Sub, SubAssign};
use refineable::Refineable;
-use serde::{Deserialize, Deserializer};
-use serde_json::json;
-use std::fmt::Debug;
-use vector::{vec2f, Vector2F};
+use serde_derive::{Deserialize, Serialize};
+use std::{
+ cmp::{self, PartialOrd},
+ fmt,
+ ops::{Add, Div, Mul, MulAssign, Sub},
+};
-pub struct PathBuilder {
- vertices: Vec<PathVertex>,
- start: Vector2F,
- current: Vector2F,
- contour_count: usize,
- bounds: RectF,
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum Axis {
+ Vertical,
+ Horizontal,
}
-enum PathVertexKind {
- Solid,
- Quadratic,
+impl Axis {
+ pub fn invert(&self) -> Self {
+ match self {
+ Axis::Vertical => Axis::Horizontal,
+ Axis::Horizontal => Axis::Vertical,
+ }
+ }
+}
+
+pub trait Along {
+ type Unit;
+
+ fn along(&self, axis: Axis) -> Self::Unit;
+
+ fn apply_along(&self, axis: Axis, f: impl FnOnce(Self::Unit) -> Self::Unit) -> Self;
+}
+
+impl sqlez::bindable::StaticColumnCount for Axis {}
+impl sqlez::bindable::Bind for Axis {
+ fn bind(
+ &self,
+ statement: &sqlez::statement::Statement,
+ start_index: i32,
+ ) -> anyhow::Result<i32> {
+ match self {
+ Axis::Horizontal => "Horizontal",
+ Axis::Vertical => "Vertical",
+ }
+ .bind(statement, start_index)
+ }
+}
+
+impl sqlez::bindable::Column for Axis {
+ fn column(
+ statement: &mut sqlez::statement::Statement,
+ start_index: i32,
+ ) -> anyhow::Result<(Self, i32)> {
+ String::column(statement, start_index).and_then(|(axis_text, next_index)| {
+ Ok((
+ match axis_text.as_str() {
+ "Horizontal" => Axis::Horizontal,
+ "Vertical" => Axis::Vertical,
+ _ => anyhow::bail!("Stored serialized item kind is incorrect"),
+ },
+ next_index,
+ ))
+ })
+ }
+}
+
+/// Describes a location in a 2D cartesian coordinate space.
+///
+/// It holds two public fields, `x` and `y`, which represent the coordinates in the space.
+/// The type `T` for the coordinates can be any type that implements `Default`, `Clone`, and `Debug`.
+///
+/// # Examples
+///
+/// ```
+/// # use zed::Point;
+/// let point = Point { x: 10, y: 20 };
+/// println!("{:?}", point); // Outputs: Point { x: 10, y: 20 }
+/// ```
+#[derive(Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash)]
+#[refineable(Debug)]
+#[repr(C)]
+pub struct Point<T: Default + Clone + Debug> {
+ pub x: T,
+ pub y: T,
+}
+
+/// Constructs a new `Point<T>` with the given x and y coordinates.
+///
+/// # Arguments
+///
+/// * `x` - The x coordinate of the point.
+/// * `y` - The y coordinate of the point.
+///
+/// # Returns
+///
+/// Returns a `Point<T>` with the specified coordinates.
+///
+/// # Examples
+///
+/// ```
+/// # use zed::Point;
+/// let p = point(10, 20);
+/// assert_eq!(p.x, 10);
+/// assert_eq!(p.y, 20);
+/// ```
+pub fn point<T: Clone + Debug + Default>(x: T, y: T) -> Point<T> {
+ Point { x, y }
+}
+
+impl<T: Clone + Debug + Default> Point<T> {
+ /// Creates a new `Point` with the specified `x` and `y` coordinates.
+ ///
+ /// # Arguments
+ ///
+ /// * `x` - The horizontal coordinate of the point.
+ /// * `y` - The vertical coordinate of the point.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// let p = Point::new(10, 20);
+ /// assert_eq!(p.x, 10);
+ /// assert_eq!(p.y, 20);
+ /// ```
+ pub const fn new(x: T, y: T) -> Self {
+ Self { x, y }
+ }
+
+ /// Transforms the point to a `Point<U>` by applying the given function to both coordinates.
+ ///
+ /// This method allows for converting a `Point<T>` to a `Point<U>` by specifying a closure
+ /// that defines how to convert between the two types. The closure is applied to both the `x`
+ /// and `y` coordinates, resulting in a new point of the desired type.
+ ///
+ /// # Arguments
+ ///
+ /// * `f` - A closure that takes a value of type `T` and returns a value of type `U`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::Point;
+ /// let p = Point { x: 3, y: 4 };
+ /// let p_float = p.map(|coord| coord as f32);
+ /// assert_eq!(p_float, Point { x: 3.0, y: 4.0 });
+ /// ```
+ pub fn map<U: Clone + Default + Debug>(&self, f: impl Fn(T) -> U) -> Point<U> {
+ Point {
+ x: f(self.x.clone()),
+ y: f(self.y.clone()),
+ }
+ }
+}
+
+impl<T: Clone + Debug + Default> Along for Point<T> {
+ type Unit = T;
+
+ fn along(&self, axis: Axis) -> T {
+ match axis {
+ Axis::Horizontal => self.x.clone(),
+ Axis::Vertical => self.y.clone(),
+ }
+ }
+
+ fn apply_along(&self, axis: Axis, f: impl FnOnce(T) -> T) -> Point<T> {
+ match axis {
+ Axis::Horizontal => Point {
+ x: f(self.x.clone()),
+ y: self.y.clone(),
+ },
+ Axis::Vertical => Point {
+ x: self.x.clone(),
+ y: f(self.y.clone()),
+ },
+ }
+ }
+}
+
+impl Point<Pixels> {
+ /// Scales the point by a given factor, which is typically derived from the resolution
+ /// of a target display to ensure proper sizing of UI elements.
+ ///
+ /// # Arguments
+ ///
+ /// * `factor` - The scaling factor to apply to both the x and y coordinates.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Point, Pixels, ScaledPixels};
+ /// let p = Point { x: Pixels(10.0), y: Pixels(20.0) };
+ /// let scaled_p = p.scale(1.5);
+ /// assert_eq!(scaled_p, Point { x: ScaledPixels(15.0), y: ScaledPixels(30.0) });
+ /// ```
+ pub fn scale(&self, factor: f32) -> Point<ScaledPixels> {
+ Point {
+ x: self.x.scale(factor),
+ y: self.y.scale(factor),
+ }
+ }
+
+ /// Calculates the Euclidean distance from the origin (0, 0) to this point.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::Point;
+ /// # use zed::Pixels;
+ /// let p = Point { x: Pixels(3.0), y: Pixels(4.0) };
+ /// assert_eq!(p.magnitude(), 5.0);
+ /// ```
+ pub fn magnitude(&self) -> f64 {
+ ((self.x.0.powi(2) + self.y.0.powi(2)) as f64).sqrt()
+ }
+}
+
+impl<T, Rhs> Mul<Rhs> for Point<T>
+where
+ T: Mul<Rhs, Output = T> + Clone + Default + Debug,
+ Rhs: Clone + Debug,
+{
+ type Output = Point<T>;
+
+ fn mul(self, rhs: Rhs) -> Self::Output {
+ Point {
+ x: self.x * rhs.clone(),
+ y: self.y * rhs,
+ }
+ }
+}
+
+impl<T, S> MulAssign<S> for Point<T>
+where
+ T: Clone + Mul<S, Output = T> + Default + Debug,
+ S: Clone,
+{
+ fn mul_assign(&mut self, rhs: S) {
+ self.x = self.x.clone() * rhs.clone();
+ self.y = self.y.clone() * rhs;
+ }
+}
+
+impl<T, S> Div<S> for Point<T>
+where
+ T: Div<S, Output = T> + Clone + Default + Debug,
+ S: Clone,
+{
+ type Output = Self;
+
+ fn div(self, rhs: S) -> Self::Output {
+ Self {
+ x: self.x / rhs.clone(),
+ y: self.y / rhs,
+ }
+ }
+}
+
+impl<T> Point<T>
+where
+ T: PartialOrd + Clone + Default + Debug,
+{
+ /// Returns a new point with the maximum values of each dimension from `self` and `other`.
+ ///
+ /// # Arguments
+ ///
+ /// * `other` - A reference to another `Point` to compare with `self`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::Point;
+ /// let p1 = Point { x: 3, y: 7 };
+ /// let p2 = Point { x: 5, y: 2 };
+ /// let max_point = p1.max(&p2);
+ /// assert_eq!(max_point, Point { x: 5, y: 7 });
+ /// ```
+ pub fn max(&self, other: &Self) -> Self {
+ Point {
+ x: if self.x > other.x {
+ self.x.clone()
+ } else {
+ other.x.clone()
+ },
+ y: if self.y > other.y {
+ self.y.clone()
+ } else {
+ other.y.clone()
+ },
+ }
+ }
+
+ /// Returns a new point with the minimum values of each dimension from `self` and `other`.
+ ///
+ /// # Arguments
+ ///
+ /// * `other` - A reference to another `Point` to compare with `self`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::Point;
+ /// let p1 = Point { x: 3, y: 7 };
+ /// let p2 = Point { x: 5, y: 2 };
+ /// let min_point = p1.min(&p2);
+ /// assert_eq!(min_point, Point { x: 3, y: 2 });
+ /// ```
+ pub fn min(&self, other: &Self) -> Self {
+ Point {
+ x: if self.x <= other.x {
+ self.x.clone()
+ } else {
+ other.x.clone()
+ },
+ y: if self.y <= other.y {
+ self.y.clone()
+ } else {
+ other.y.clone()
+ },
+ }
+ }
+
+ /// Clamps the point to a specified range.
+ ///
+ /// Given a minimum point and a maximum point, this method constrains the current point
+ /// such that its coordinates do not exceed the range defined by the minimum and maximum points.
+ /// If the current point's coordinates are less than the minimum, they are set to the minimum.
+ /// If they are greater than the maximum, they are set to the maximum.
+ ///
+ /// # Arguments
+ ///
+ /// * `min` - A reference to a `Point` representing the minimum allowable coordinates.
+ /// * `max` - A reference to a `Point` representing the maximum allowable coordinates.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::Point;
+ /// let p = Point { x: 10, y: 20 };
+ /// let min = Point { x: 0, y: 5 };
+ /// let max = Point { x: 15, y: 25 };
+ /// let clamped_p = p.clamp(&min, &max);
+ /// assert_eq!(clamped_p, Point { x: 10, y: 20 });
+ ///
+ /// let p_out_of_bounds = Point { x: -5, y: 30 };
+ /// let clamped_p_out_of_bounds = p_out_of_bounds.clamp(&min, &max);
+ /// assert_eq!(clamped_p_out_of_bounds, Point { x: 0, y: 25 });
+ /// ```
+ pub fn clamp(&self, min: &Self, max: &Self) -> Self {
+ self.max(min).min(max)
+ }
+}
+
+impl<T: Clone + Default + Debug> Clone for Point<T> {
+ fn clone(&self) -> Self {
+ Self {
+ x: self.x.clone(),
+ y: self.y.clone(),
+ }
+ }
+}
+
+/// A structure representing a two-dimensional size with width and height in a given unit.
+///
+/// This struct is generic over the type `T`, which can be any type that implements `Clone`, `Default`, and `Debug`.
+/// It is commonly used to specify dimensions for elements in a UI, such as a window or element.
+#[derive(Refineable, Default, Clone, Copy, PartialEq, Div, Hash, Serialize, Deserialize)]
+#[refineable(Debug)]
+#[repr(C)]
+pub struct Size<T: Clone + Default + Debug> {
+ pub width: T,
+ pub height: T,
+}
+
+/// Constructs a new `Size<T>` with the provided width and height.
+///
+/// # Arguments
+///
+/// * `width` - The width component of the `Size`.
+/// * `height` - The height component of the `Size`.
+///
+/// # Examples
+///
+/// ```
+/// # use zed::Size;
+/// let my_size = size(10, 20);
+/// assert_eq!(my_size.width, 10);
+/// assert_eq!(my_size.height, 20);
+/// ```
+pub fn size<T>(width: T, height: T) -> Size<T>
+where
+ T: Clone + Default + Debug,
+{
+ Size { width, height }
+}
+
+impl<T> Size<T>
+where
+ T: Clone + Default + Debug,
+{
+ /// Applies a function to the width and height of the size, producing a new `Size<U>`.
+ ///
+ /// This method allows for converting a `Size<T>` to a `Size<U>` by specifying a closure
+ /// that defines how to convert between the two types. The closure is applied to both the `width`
+ /// and `height`, resulting in a new size of the desired type.
+ ///
+ /// # Arguments
+ ///
+ /// * `f` - A closure that takes a value of type `T` and returns a value of type `U`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::Size;
+ /// let my_size = Size { width: 10, height: 20 };
+ /// let my_new_size = my_size.map(|dimension| dimension as f32 * 1.5);
+ /// assert_eq!(my_new_size, Size { width: 15.0, height: 30.0 });
+ /// ```
+ pub fn map<U>(&self, f: impl Fn(T) -> U) -> Size<U>
+ where
+ U: Clone + Default + Debug,
+ {
+ Size {
+ width: f(self.width.clone()),
+ height: f(self.height.clone()),
+ }
+ }
+}
+
+impl Size<Pixels> {
+ /// Scales the size by a given factor.
+ ///
+ /// This method multiplies both the width and height by the provided scaling factor,
+ /// resulting in a new `Size<ScaledPixels>` that is proportionally larger or smaller
+ /// depending on the factor.
+ ///
+ /// # Arguments
+ ///
+ /// * `factor` - The scaling factor to apply to the width and height.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Size, Pixels, ScaledPixels};
+ /// let size = Size { width: Pixels(100.0), height: Pixels(50.0) };
+ /// let scaled_size = size.scale(2.0);
+ /// assert_eq!(scaled_size, Size { width: ScaledPixels(200.0), height: ScaledPixels(100.0) });
+ /// ```
+ pub fn scale(&self, factor: f32) -> Size<ScaledPixels> {
+ Size {
+ width: self.width.scale(factor),
+ height: self.height.scale(factor),
+ }
+ }
+}
+
+impl<T> Along for Size<T>
+where
+ T: Clone + Default + Debug,
+{
+ type Unit = T;
+
+ fn along(&self, axis: Axis) -> T {
+ match axis {
+ Axis::Horizontal => self.width.clone(),
+ Axis::Vertical => self.height.clone(),
+ }
+ }
+
+ /// Returns the value of this size along the given axis.
+ fn apply_along(&self, axis: Axis, f: impl FnOnce(T) -> T) -> Self {
+ match axis {
+ Axis::Horizontal => Size {
+ width: f(self.width.clone()),
+ height: self.height.clone(),
+ },
+ Axis::Vertical => Size {
+ width: self.width.clone(),
+ height: f(self.height.clone()),
+ },
+ }
+ }
+}
+
+impl<T> Size<T>
+where
+ T: PartialOrd + Clone + Default + Debug,
+{
+ /// Returns a new `Size` with the maximum width and height from `self` and `other`.
+ ///
+ /// # Arguments
+ ///
+ /// * `other` - A reference to another `Size` to compare with `self`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::Size;
+ /// let size1 = Size { width: 30, height: 40 };
+ /// let size2 = Size { width: 50, height: 20 };
+ /// let max_size = size1.max(&size2);
+ /// assert_eq!(max_size, Size { width: 50, height: 40 });
+ /// ```
+ pub fn max(&self, other: &Self) -> Self {
+ Size {
+ width: if self.width >= other.width {
+ self.width.clone()
+ } else {
+ other.width.clone()
+ },
+ height: if self.height >= other.height {
+ self.height.clone()
+ } else {
+ other.height.clone()
+ },
+ }
+ }
+}
+
+impl<T> Sub for Size<T>
+where
+ T: Sub<Output = T> + Clone + Default + Debug,
+{
+ type Output = Size<T>;
+
+ fn sub(self, rhs: Self) -> Self::Output {
+ Size {
+ width: self.width - rhs.width,
+ height: self.height - rhs.height,
+ }
+ }
+}
+
+impl<T, Rhs> Mul<Rhs> for Size<T>
+where
+ T: Mul<Rhs, Output = Rhs> + Clone + Default + Debug,
+ Rhs: Clone + Default + Debug,
+{
+ type Output = Size<Rhs>;
+
+ fn mul(self, rhs: Rhs) -> Self::Output {
+ Size {
+ width: self.width * rhs.clone(),
+ height: self.height * rhs,
+ }
+ }
+}
+
+impl<T, S> MulAssign<S> for Size<T>
+where
+ T: Mul<S, Output = T> + Clone + Default + Debug,
+ S: Clone,
+{
+ fn mul_assign(&mut self, rhs: S) {
+ self.width = self.width.clone() * rhs.clone();
+ self.height = self.height.clone() * rhs;
+ }
+}
+
+impl<T> Eq for Size<T> where T: Eq + Default + Debug + Clone {}
+
+impl<T> Debug for Size<T>
+where
+ T: Clone + Default + Debug,
+{
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "Size {{ {:?} × {:?} }}", self.width, self.height)
+ }
+}
+
+impl<T: Clone + Default + Debug> From<Point<T>> for Size<T> {
+ fn from(point: Point<T>) -> Self {
+ Self {
+ width: point.x,
+ height: point.y,
+ }
+ }
+}
+
+impl From<Size<Pixels>> for Size<GlobalPixels> {
+ fn from(size: Size<Pixels>) -> Self {
+ Size {
+ width: GlobalPixels(size.width.0),
+ height: GlobalPixels(size.height.0),
+ }
+ }
+}
+
+impl From<Size<Pixels>> for Size<DefiniteLength> {
+ fn from(size: Size<Pixels>) -> Self {
+ Size {
+ width: size.width.into(),
+ height: size.height.into(),
+ }
+ }
+}
+
+impl From<Size<Pixels>> for Size<AbsoluteLength> {
+ fn from(size: Size<Pixels>) -> Self {
+ Size {
+ width: size.width.into(),
+ height: size.height.into(),
+ }
+ }
+}
+
+impl Size<Length> {
+ /// Returns a `Size` with both width and height set to fill the available space.
+ ///
+ /// This function creates a `Size` instance where both the width and height are set to `Length::Definite(DefiniteLength::Fraction(1.0))`,
+ /// which represents 100% of the available space in both dimensions.
+ ///
+ /// # Returns
+ ///
+ /// A `Size<Length>` that will fill the available space when used in a layout.
+ pub fn full() -> Self {
+ Self {
+ width: relative(1.).into(),
+ height: relative(1.).into(),
+ }
+ }
+}
+
+impl Size<Length> {
+ /// Returns a `Size` with both width and height set to `auto`, which allows the layout engine to determine the size.
+ ///
+ /// This function creates a `Size` instance where both the width and height are set to `Length::Auto`,
+ /// indicating that their size should be computed based on the layout context, such as the content size or
+ /// available space.
+ ///
+ /// # Returns
+ ///
+ /// A `Size<Length>` with width and height set to `Length::Auto`.
+ pub fn auto() -> Self {
+ Self {
+ width: Length::Auto,
+ height: Length::Auto,
+ }
+ }
+}
+
+/// Represents a rectangular area in a 2D space with an origin point and a size.
+///
+/// The `Bounds` struct is generic over a type `T` which represents the type of the coordinate system.
+/// The origin is represented as a `Point<T>` which defines the upper-left corner of the rectangle,
+/// and the size is represented as a `Size<T>` which defines the width and height of the rectangle.
+///
+/// # Examples
+///
+/// ```
+/// # use zed::{Bounds, Point, Size};
+/// let origin = Point { x: 0, y: 0 };
+/// let size = Size { width: 10, height: 20 };
+/// let bounds = Bounds::new(origin, size);
+///
+/// assert_eq!(bounds.origin, origin);
+/// assert_eq!(bounds.size, size);
+/// ```
+#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
+#[refineable(Debug)]
+#[repr(C)]
+pub struct Bounds<T: Clone + Default + Debug> {
+ pub origin: Point<T>,
+ pub size: Size<T>,
+}
+
+impl<T> Bounds<T>
+where
+ T: Clone + Debug + Sub<Output = T> + Default,
+{
+ /// Constructs a `Bounds` from two corner points: the upper-left and lower-right corners.
+ ///
+ /// This function calculates the origin and size of the `Bounds` based on the provided corner points.
+ /// The origin is set to the upper-left corner, and the size is determined by the difference between
+ /// the x and y coordinates of the lower-right and upper-left points.
+ ///
+ /// # Arguments
+ ///
+ /// * `upper_left` - A `Point<T>` representing the upper-left corner of the rectangle.
+ /// * `lower_right` - A `Point<T>` representing the lower-right corner of the rectangle.
+ ///
+ /// # Returns
+ ///
+ /// Returns a `Bounds<T>` that encompasses the area defined by the two corner points.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Bounds, Point};
+ /// let upper_left = Point { x: 0, y: 0 };
+ /// let lower_right = Point { x: 10, y: 10 };
+ /// let bounds = Bounds::from_corners(upper_left, lower_right);
+ ///
+ /// assert_eq!(bounds.origin, upper_left);
+ /// assert_eq!(bounds.size.width, 10);
+ /// assert_eq!(bounds.size.height, 10);
+ /// ```
+ pub fn from_corners(upper_left: Point<T>, lower_right: Point<T>) -> Self {
+ let origin = Point {
+ x: upper_left.x.clone(),
+ y: upper_left.y.clone(),
+ };
+ let size = Size {
+ width: lower_right.x - upper_left.x,
+ height: lower_right.y - upper_left.y,
+ };
+ Bounds { origin, size }
+ }
+
+ /// Creates a new `Bounds` with the specified origin and size.
+ ///
+ /// # Arguments
+ ///
+ /// * `origin` - A `Point<T>` representing the origin of the bounds.
+ /// * `size` - A `Size<T>` representing the size of the bounds.
+ ///
+ /// # Returns
+ ///
+ /// Returns a `Bounds<T>` that has the given origin and size.
+ pub fn new(origin: Point<T>, size: Size<T>) -> Self {
+ Bounds { origin, size }
+ }
+}
+
+impl<T> Bounds<T>
+where
+ T: Clone + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T> + Default + Half,
+{
+ /// Checks if this `Bounds` intersects with another `Bounds`.
+ ///
+ /// Two `Bounds` instances intersect if they overlap in the 2D space they occupy.
+ /// This method checks if there is any overlapping area between the two bounds.
+ ///
+ /// # Arguments
+ ///
+ /// * `other` - A reference to another `Bounds` to check for intersection with.
+ ///
+ /// # Returns
+ ///
+ /// Returns `true` if there is any intersection between the two bounds, `false` otherwise.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Bounds, Point, Size};
+ /// let bounds1 = Bounds {
+ /// origin: Point { x: 0, y: 0 },
+ /// size: Size { width: 10, height: 10 },
+ /// };
+ /// let bounds2 = Bounds {
+ /// origin: Point { x: 5, y: 5 },
+ /// size: Size { width: 10, height: 10 },
+ /// };
+ /// let bounds3 = Bounds {
+ /// origin: Point { x: 20, y: 20 },
+ /// size: Size { width: 10, height: 10 },
+ /// };
+ ///
+ /// assert_eq!(bounds1.intersects(&bounds2), true); // Overlapping bounds
+ /// assert_eq!(bounds1.intersects(&bounds3), false); // Non-overlapping bounds
+ /// ```
+ pub fn intersects(&self, other: &Bounds<T>) -> bool {
+ let my_lower_right = self.lower_right();
+ let their_lower_right = other.lower_right();
+
+ self.origin.x < their_lower_right.x
+ && my_lower_right.x > other.origin.x
+ && self.origin.y < their_lower_right.y
+ && my_lower_right.y > other.origin.y
+ }
+
+ /// Dilates the bounds by a specified amount in all directions.
+ ///
+ /// This method expands the bounds by the given `amount`, increasing the size
+ /// and adjusting the origin so that the bounds grow outwards equally in all directions.
+ /// The resulting bounds will have its width and height increased by twice the `amount`
+ /// (since it grows in both directions), and the origin will be moved by `-amount`
+ /// in both the x and y directions.
+ ///
+ /// # Arguments
+ ///
+ /// * `amount` - The amount by which to dilate the bounds.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Bounds, Point, Size};
+ /// let mut bounds = Bounds {
+ /// origin: Point { x: 10, y: 10 },
+ /// size: Size { width: 10, height: 10 },
+ /// };
+ /// bounds.dilate(5);
+ /// assert_eq!(bounds, Bounds {
+ /// origin: Point { x: 5, y: 5 },
+ /// size: Size { width: 20, height: 20 },
+ /// });
+ /// ```
+ pub fn dilate(&mut self, amount: T) {
+ self.origin.x = self.origin.x.clone() - amount.clone();
+ self.origin.y = self.origin.y.clone() - amount.clone();
+ let double_amount = amount.clone() + amount;
+ self.size.width = self.size.width.clone() + double_amount.clone();
+ self.size.height = self.size.height.clone() + double_amount;
+ }
+
+ /// Returns the center point of the bounds.
+ ///
+ /// Calculates the center by taking the origin's x and y coordinates and adding half the width and height
+ /// of the bounds, respectively. The center is represented as a `Point<T>` where `T` is the type of the
+ /// coordinate system.
+ ///
+ /// # Returns
+ ///
+ /// A `Point<T>` representing the center of the bounds.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Bounds, Point, Size};
+ /// let bounds = Bounds {
+ /// origin: Point { x: 0, y: 0 },
+ /// size: Size { width: 10, height: 20 },
+ /// };
+ /// let center = bounds.center();
+ /// assert_eq!(center, Point { x: 5, y: 10 });
+ /// ```
+ pub fn center(&self) -> Point<T> {
+ Point {
+ x: self.origin.x.clone() + self.size.width.clone().half(),
+ y: self.origin.y.clone() + self.size.height.clone().half(),
+ }
+ }
+}
+
+impl<T: Clone + Default + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
+ /// Calculates the intersection of two `Bounds` objects.
+ ///
+ /// This method computes the overlapping region of two `Bounds`. If the bounds do not intersect,
+ /// the resulting `Bounds` will have a size with width and height of zero.
+ ///
+ /// # Arguments
+ ///
+ /// * `other` - A reference to another `Bounds` to intersect with.
+ ///
+ /// # Returns
+ ///
+ /// Returns a `Bounds` representing the intersection area. If there is no intersection,
+ /// the returned `Bounds` will have a size with width and height of zero.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Bounds, Point, Size};
+ /// let bounds1 = Bounds {
+ /// origin: Point { x: 0, y: 0 },
+ /// size: Size { width: 10, height: 10 },
+ /// };
+ /// let bounds2 = Bounds {
+ /// origin: Point { x: 5, y: 5 },
+ /// size: Size { width: 10, height: 10 },
+ /// };
+ /// let intersection = bounds1.intersect(&bounds2);
+ ///
+ /// assert_eq!(intersection, Bounds {
+ /// origin: Point { x: 5, y: 5 },
+ /// size: Size { width: 5, height: 5 },
+ /// });
+ /// ```
+ pub fn intersect(&self, other: &Self) -> Self {
+ let upper_left = self.origin.max(&other.origin);
+ let lower_right = self.lower_right().min(&other.lower_right());
+ Self::from_corners(upper_left, lower_right)
+ }
+
+ /// Computes the union of two `Bounds`.
+ ///
+ /// This method calculates the smallest `Bounds` that contains both the current `Bounds` and the `other` `Bounds`.
+ /// The resulting `Bounds` will have an origin that is the minimum of the origins of the two `Bounds`,
+ /// and a size that encompasses the furthest extents of both `Bounds`.
+ ///
+ /// # Arguments
+ ///
+ /// * `other` - A reference to another `Bounds` to create a union with.
+ ///
+ /// # Returns
+ ///
+ /// Returns a `Bounds` representing the union of the two `Bounds`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Bounds, Point, Size};
+ /// let bounds1 = Bounds {
+ /// origin: Point { x: 0, y: 0 },
+ /// size: Size { width: 10, height: 10 },
+ /// };
+ /// let bounds2 = Bounds {
+ /// origin: Point { x: 5, y: 5 },
+ /// size: Size { width: 15, height: 15 },
+ /// };
+ /// let union_bounds = bounds1.union(&bounds2);
+ ///
+ /// assert_eq!(union_bounds, Bounds {
+ /// origin: Point { x: 0, y: 0 },
+ /// size: Size { width: 20, height: 20 },
+ /// });
+ /// ```
+ pub fn union(&self, other: &Self) -> Self {
+ let top_left = self.origin.min(&other.origin);
+ let bottom_right = self.lower_right().max(&other.lower_right());
+ Bounds::from_corners(top_left, bottom_right)
+ }
+}
+
+impl<T, Rhs> Mul<Rhs> for Bounds<T>
+where
+ T: Mul<Rhs, Output = Rhs> + Clone + Default + Debug,
+ Point<T>: Mul<Rhs, Output = Point<Rhs>>,
+ Rhs: Clone + Default + Debug,
+{
+ type Output = Bounds<Rhs>;
+
+ fn mul(self, rhs: Rhs) -> Self::Output {
+ Bounds {
+ origin: self.origin * rhs.clone(),
+ size: self.size * rhs,
+ }
+ }
+}
+
+impl<T, S> MulAssign<S> for Bounds<T>
+where
+ T: Mul<S, Output = T> + Clone + Default + Debug,
+ S: Clone,
+{
+ fn mul_assign(&mut self, rhs: S) {
+ self.origin *= rhs.clone();
+ self.size *= rhs;
+ }
+}
+
+impl<T, S> Div<S> for Bounds<T>
+where
+ Size<T>: Div<S, Output = Size<T>>,
+ T: Div<S, Output = T> + Default + Clone + Debug,
+ S: Clone,
+{
+ type Output = Self;
+
+ fn div(self, rhs: S) -> Self {
+ Self {
+ origin: self.origin / rhs.clone(),
+ size: self.size / rhs,
+ }
+ }
+}
+
+impl<T> Bounds<T>
+where
+ T: Add<T, Output = T> + Clone + Default + Debug,
+{
+ /// Returns the top edge of the bounds.
+ ///
+ /// # Returns
+ ///
+ /// A value of type `T` representing the y-coordinate of the top edge of the bounds.
+ pub fn top(&self) -> T {
+ self.origin.y.clone()
+ }
+
+ /// Returns the bottom edge of the bounds.
+ ///
+ /// # Returns
+ ///
+ /// A value of type `T` representing the y-coordinate of the bottom edge of the bounds.
+ pub fn bottom(&self) -> T {
+ self.origin.y.clone() + self.size.height.clone()
+ }
+
+ /// Returns the left edge of the bounds.
+ ///
+ /// # Returns
+ ///
+ /// A value of type `T` representing the x-coordinate of the left edge of the bounds.
+ pub fn left(&self) -> T {
+ self.origin.x.clone()
+ }
+
+ /// Returns the right edge of the bounds.
+ ///
+ /// # Returns
+ ///
+ /// A value of type `T` representing the x-coordinate of the right edge of the bounds.
+ pub fn right(&self) -> T {
+ self.origin.x.clone() + self.size.width.clone()
+ }
+
+ /// Returns the upper-right corner point of the bounds.
+ ///
+ /// # Returns
+ ///
+ /// A `Point<T>` representing the upper-right corner of the bounds.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Bounds, Point, Size};
+ /// let bounds = Bounds {
+ /// origin: Point { x: 0, y: 0 },
+ /// size: Size { width: 10, height: 20 },
+ /// };
+ /// let upper_right = bounds.upper_right();
+ /// assert_eq!(upper_right, Point { x: 10, y: 0 });
+ /// ```
+ pub fn upper_right(&self) -> Point<T> {
+ Point {
+ x: self.origin.x.clone() + self.size.width.clone(),
+ y: self.origin.y.clone(),
+ }
+ }
+
+ /// Returns the lower-right corner point of the bounds.
+ ///
+ /// # Returns
+ ///
+ /// A `Point<T>` representing the lower-right corner of the bounds.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Bounds, Point, Size};
+ /// let bounds = Bounds {
+ /// origin: Point { x: 0, y: 0 },
+ /// size: Size { width: 10, height: 20 },
+ /// };
+ /// let lower_right = bounds.lower_right();
+ /// assert_eq!(lower_right, Point { x: 10, y: 20 });
+ /// ```
+ pub fn lower_right(&self) -> Point<T> {
+ Point {
+ x: self.origin.x.clone() + self.size.width.clone(),
+ y: self.origin.y.clone() + self.size.height.clone(),
+ }
+ }
+
+ /// Returns the lower-left corner point of the bounds.
+ ///
+ /// # Returns
+ ///
+ /// A `Point<T>` representing the lower-left corner of the bounds.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Bounds, Point, Size};
+ /// let bounds = Bounds {
+ /// origin: Point { x: 0, y: 0 },
+ /// size: Size { width: 10, height: 20 },
+ /// };
+ /// let lower_left = bounds.lower_left();
+ /// assert_eq!(lower_left, Point { x: 0, y: 20 });
+ /// ```
+ pub fn lower_left(&self) -> Point<T> {
+ Point {
+ x: self.origin.x.clone(),
+ y: self.origin.y.clone() + self.size.height.clone(),
+ }
+ }
+}
+
+impl<T> Bounds<T>
+where
+ T: Add<T, Output = T> + PartialOrd + Clone + Default + Debug,
+{
+ /// Checks if the given point is within the bounds.
+ ///
+ /// This method determines whether a point lies inside the rectangle defined by the bounds,
+ /// including the edges. The point is considered inside if its x-coordinate is greater than
+ /// or equal to the left edge and less than or equal to the right edge, and its y-coordinate
+ /// is greater than or equal to the top edge and less than or equal to the bottom edge of the bounds.
+ ///
+ /// # Arguments
+ ///
+ /// * `point` - A reference to a `Point<T>` that represents the point to check.
+ ///
+ /// # Returns
+ ///
+ /// Returns `true` if the point is within the bounds, `false` otherwise.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Point, Bounds};
+ /// let bounds = Bounds {
+ /// origin: Point { x: 0, y: 0 },
+ /// size: Size { width: 10, height: 10 },
+ /// };
+ /// let inside_point = Point { x: 5, y: 5 };
+ /// let outside_point = Point { x: 15, y: 15 };
+ ///
+ /// assert!(bounds.contains_point(&inside_point));
+ /// assert!(!bounds.contains_point(&outside_point));
+ /// ```
+ pub fn contains(&self, point: &Point<T>) -> bool {
+ point.x >= self.origin.x
+ && point.x <= self.origin.x.clone() + self.size.width.clone()
+ && point.y >= self.origin.y
+ && point.y <= self.origin.y.clone() + self.size.height.clone()
+ }
+
+ /// Applies a function to the origin and size of the bounds, producing a new `Bounds<U>`.
+ ///
+ /// This method allows for converting a `Bounds<T>` to a `Bounds<U>` by specifying a closure
+ /// that defines how to convert between the two types. The closure is applied to the `origin` and
+ /// `size` fields, resulting in new bounds of the desired type.
+ ///
+ /// # Arguments
+ ///
+ /// * `f` - A closure that takes a value of type `T` and returns a value of type `U`.
+ ///
+ /// # Returns
+ ///
+ /// Returns a new `Bounds<U>` with the origin and size mapped by the provided function.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Bounds, Point, Size};
+ /// let bounds = Bounds {
+ /// origin: Point { x: 10.0, y: 10.0 },
+ /// size: Size { width: 10.0, height: 20.0 },
+ /// };
+ /// let new_bounds = bounds.map(|value| value as f64 * 1.5);
+ ///
+ /// assert_eq!(new_bounds, Bounds {
+ /// origin: Point { x: 15.0, y: 15.0 },
+ /// size: Size { width: 15.0, height: 30.0 },
+ /// });
+ pub fn map<U>(&self, f: impl Fn(T) -> U) -> Bounds<U>
+ where
+ U: Clone + Default + Debug,
+ {
+ Bounds {
+ origin: self.origin.map(&f),
+ size: self.size.map(f),
+ }
+ }
+}
+
+impl Bounds<Pixels> {
+ /// Scales the bounds by a given factor, typically used to adjust for display scaling.
+ ///
+ /// This method multiplies the origin and size of the bounds by the provided scaling factor,
+ /// resulting in a new `Bounds<ScaledPixels>` that is proportionally larger or smaller
+ /// depending on the scaling factor. This can be used to ensure that the bounds are properly
+ /// scaled for different display densities.
+ ///
+ /// # Arguments
+ ///
+ /// * `factor` - The scaling factor to apply to the origin and size, typically the display's scaling factor.
+ ///
+ /// # Returns
+ ///
+ /// Returns a new `Bounds<ScaledPixels>` that represents the scaled bounds.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Bounds, Point, Size, Pixels};
+ /// let bounds = Bounds {
+ /// origin: Point { x: Pixels(10.0), y: Pixels(20.0) },
+ /// size: Size { width: Pixels(30.0), height: Pixels(40.0) },
+ /// };
+ /// let display_scale_factor = 2.0;
+ /// let scaled_bounds = bounds.scale(display_scale_factor);
+ /// assert_eq!(scaled_bounds, Bounds {
+ /// origin: Point { x: ScaledPixels(20.0), y: ScaledPixels(40.0) },
+ /// size: Size { width: ScaledPixels(60.0), height: ScaledPixels(80.0) },
+ /// });
+ /// ```
+ pub fn scale(&self, factor: f32) -> Bounds<ScaledPixels> {
+ Bounds {
+ origin: self.origin.scale(factor),
+ size: self.size.scale(factor),
+ }
+ }
+}
+
+impl<T: Clone + Debug + Copy + Default> Copy for Bounds<T> {}
+
+/// Represents the edges of a box in a 2D space, such as padding or margin.
+///
+/// Each field represents the size of the edge on one side of the box: `top`, `right`, `bottom`, and `left`.
+///
+/// # Examples
+///
+/// ```
+/// # use zed::Edges;
+/// let edges = Edges {
+/// top: 10.0,
+/// right: 20.0,
+/// bottom: 30.0,
+/// left: 40.0,
+/// };
+///
+/// assert_eq!(edges.top, 10.0);
+/// assert_eq!(edges.right, 20.0);
+/// assert_eq!(edges.bottom, 30.0);
+/// assert_eq!(edges.left, 40.0);
+/// ```
+#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
+#[refineable(Debug)]
+#[repr(C)]
+pub struct Edges<T: Clone + Default + Debug> {
+ pub top: T,
+ pub right: T,
+ pub bottom: T,
+ pub left: T,
+}
+
+impl<T> Mul for Edges<T>
+where
+ T: Mul<Output = T> + Clone + Default + Debug,
+{
+ type Output = Self;
+
+ fn mul(self, rhs: Self) -> Self::Output {
+ Self {
+ top: self.top.clone() * rhs.top,
+ right: self.right.clone() * rhs.right,
+ bottom: self.bottom.clone() * rhs.bottom,
+ left: self.left.clone() * rhs.left,
+ }
+ }
+}
+
+impl<T, S> MulAssign<S> for Edges<T>
+where
+ T: Mul<S, Output = T> + Clone + Default + Debug,
+ S: Clone,
+{
+ fn mul_assign(&mut self, rhs: S) {
+ self.top = self.top.clone() * rhs.clone();
+ self.right = self.right.clone() * rhs.clone();
+ self.bottom = self.bottom.clone() * rhs.clone();
+ self.left = self.left.clone() * rhs;
+ }
+}
+
+impl<T: Clone + Default + Debug + Copy> Copy for Edges<T> {}
+
+impl<T: Clone + Default + Debug> Edges<T> {
+ /// Constructs `Edges` where all sides are set to the same specified value.
+ ///
+ /// This function creates an `Edges` instance with the `top`, `right`, `bottom`, and `left` fields all initialized
+ /// to the same value provided as an argument. This is useful when you want to have uniform edges around a box,
+ /// such as padding or margin with the same size on all sides.
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The value to set for all four sides of the edges.
+ ///
+ /// # Returns
+ ///
+ /// An `Edges` instance with all sides set to the given value.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::Edges;
+ /// let uniform_edges = Edges::all(10.0);
+ /// assert_eq!(uniform_edges.top, 10.0);
+ /// assert_eq!(uniform_edges.right, 10.0);
+ /// assert_eq!(uniform_edges.bottom, 10.0);
+ /// assert_eq!(uniform_edges.left, 10.0);
+ /// ```
+ pub fn all(value: T) -> Self {
+ Self {
+ top: value.clone(),
+ right: value.clone(),
+ bottom: value.clone(),
+ left: value,
+ }
+ }
+
+ /// Applies a function to each field of the `Edges`, producing a new `Edges<U>`.
+ ///
+ /// This method allows for converting an `Edges<T>` to an `Edges<U>` by specifying a closure
+ /// that defines how to convert between the two types. The closure is applied to each field
+ /// (`top`, `right`, `bottom`, `left`), resulting in new edges of the desired type.
+ ///
+ /// # Arguments
+ ///
+ /// * `f` - A closure that takes a reference to a value of type `T` and returns a value of type `U`.
+ ///
+ /// # Returns
+ ///
+ /// Returns a new `Edges<U>` with each field mapped by the provided function.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::Edges;
+ /// let edges = Edges { top: 10, right: 20, bottom: 30, left: 40 };
+ /// let edges_float = edges.map(|&value| value as f32 * 1.1);
+ /// assert_eq!(edges_float, Edges { top: 11.0, right: 22.0, bottom: 33.0, left: 44.0 });
+ /// ```
+ pub fn map<U>(&self, f: impl Fn(&T) -> U) -> Edges<U>
+ where
+ U: Clone + Default + Debug,
+ {
+ Edges {
+ top: f(&self.top),
+ right: f(&self.right),
+ bottom: f(&self.bottom),
+ left: f(&self.left),
+ }
+ }
+
+ /// Checks if any of the edges satisfy a given predicate.
+ ///
+ /// This method applies a predicate function to each field of the `Edges` and returns `true` if any field satisfies the predicate.
+ ///
+ /// # Arguments
+ ///
+ /// * `predicate` - A closure that takes a reference to a value of type `T` and returns a `bool`.
+ ///
+ /// # Returns
+ ///
+ /// Returns `true` if the predicate returns `true` for any of the edge values, `false` otherwise.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::Edges;
+ /// let edges = Edges {
+ /// top: 10,
+ /// right: 0,
+ /// bottom: 5,
+ /// left: 0,
+ /// };
+ ///
+ /// assert!(edges.any(|value| *value == 0));
+ /// assert!(edges.any(|value| *value > 0));
+ /// assert!(!edges.any(|value| *value > 10));
+ /// ```
+ pub fn any<F: Fn(&T) -> bool>(&self, predicate: F) -> bool {
+ predicate(&self.top)
+ || predicate(&self.right)
+ || predicate(&self.bottom)
+ || predicate(&self.left)
+ }
+}
+
+impl Edges<Length> {
+ /// Sets the edges of the `Edges` struct to `auto`, which is a special value that allows the layout engine to automatically determine the size of the edges.
+ ///
+ /// This is typically used in layout contexts where the exact size of the edges is not important, or when the size should be calculated based on the content or container.
+ ///
+ /// # Returns
+ ///
+ /// Returns an `Edges<Length>` with all edges set to `Length::Auto`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::Edges;
+ /// let auto_edges = Edges::auto();
+ /// assert_eq!(auto_edges.top, Length::Auto);
+ /// assert_eq!(auto_edges.right, Length::Auto);
+ /// assert_eq!(auto_edges.bottom, Length::Auto);
+ /// assert_eq!(auto_edges.left, Length::Auto);
+ /// ```
+ pub fn auto() -> Self {
+ Self {
+ top: Length::Auto,
+ right: Length::Auto,
+ bottom: Length::Auto,
+ left: Length::Auto,
+ }
+ }
+
+ /// Sets the edges of the `Edges` struct to zero, which means no size or thickness.
+ ///
+ /// This is typically used when you want to specify that a box (like a padding or margin area)
+ /// should have no edges, effectively making it non-existent or invisible in layout calculations.
+ ///
+ /// # Returns
+ ///
+ /// Returns an `Edges<Length>` with all edges set to zero length.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::Edges;
+ /// let no_edges = Edges::zero();
+ /// assert_eq!(no_edges.top, Length::Definite(DefiniteLength::from(Pixels(0.))));
+ /// assert_eq!(no_edges.right, Length::Definite(DefiniteLength::from(Pixels(0.))));
+ /// assert_eq!(no_edges.bottom, Length::Definite(DefiniteLength::from(Pixels(0.))));
+ /// assert_eq!(no_edges.left, Length::Definite(DefiniteLength::from(Pixels(0.))));
+ /// ```
+ pub fn zero() -> Self {
+ Self {
+ top: px(0.).into(),
+ right: px(0.).into(),
+ bottom: px(0.).into(),
+ left: px(0.).into(),
+ }
+ }
+}
+
+impl Edges<DefiniteLength> {
+ /// Sets the edges of the `Edges` struct to zero, which means no size or thickness.
+ ///
+ /// This is typically used when you want to specify that a box (like a padding or margin area)
+ /// should have no edges, effectively making it non-existent or invisible in layout calculations.
+ ///
+ /// # Returns
+ ///
+ /// Returns an `Edges<DefiniteLength>` with all edges set to zero length.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::Edges;
+ /// let no_edges = Edges::zero();
+ /// assert_eq!(no_edges.top, DefiniteLength::from(zed::px(0.)));
+ /// assert_eq!(no_edges.right, DefiniteLength::from(zed::px(0.)));
+ /// assert_eq!(no_edges.bottom, DefiniteLength::from(zed::px(0.)));
+ /// assert_eq!(no_edges.left, DefiniteLength::from(zed::px(0.)));
+ /// ```
+ pub fn zero() -> Self {
+ Self {
+ top: px(0.).into(),
+ right: px(0.).into(),
+ bottom: px(0.).into(),
+ left: px(0.).into(),
+ }
+ }
+
+ /// Converts the `DefiniteLength` to `Pixels` based on the parent size and the REM size.
+ ///
+ /// This method allows for a `DefiniteLength` value to be converted into pixels, taking into account
+ /// the size of the parent element (for percentage-based lengths) and the size of a rem unit (for rem-based lengths).
+ ///
+ /// # Arguments
+ ///
+ /// * `parent_size` - `Size<AbsoluteLength>` representing the size of the parent element.
+ /// * `rem_size` - `Pixels` representing the size of one REM unit.
+ ///
+ /// # Returns
+ ///
+ /// Returns an `Edges<Pixels>` representing the edges with lengths converted to pixels.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Edges, DefiniteLength, px, AbsoluteLength, Size};
+ /// let edges = Edges {
+ /// top: DefiniteLength::Absolute(AbsoluteLength::Pixels(px(10.0))),
+ /// right: DefiniteLength::Fraction(0.5),
+ /// bottom: DefiniteLength::Absolute(AbsoluteLength::Rems(rems(2.0))),
+ /// left: DefiniteLength::Fraction(0.25),
+ /// };
+ /// let parent_size = Size {
+ /// width: AbsoluteLength::Pixels(px(200.0)),
+ /// height: AbsoluteLength::Pixels(px(100.0)),
+ /// };
+ /// let rem_size = px(16.0);
+ /// let edges_in_pixels = edges.to_pixels(parent_size, rem_size);
+ ///
+ /// assert_eq!(edges_in_pixels.top, px(10.0)); // Absolute length in pixels
+ /// assert_eq!(edges_in_pixels.right, px(100.0)); // 50% of parent width
+ /// assert_eq!(edges_in_pixels.bottom, px(32.0)); // 2 rems
+ /// assert_eq!(edges_in_pixels.left, px(50.0)); // 25% of parent width
+ /// ```
+ pub fn to_pixels(&self, parent_size: Size<AbsoluteLength>, rem_size: Pixels) -> Edges<Pixels> {
+ Edges {
+ top: self.top.to_pixels(parent_size.height, rem_size),
+ right: self.right.to_pixels(parent_size.width, rem_size),
+ bottom: self.bottom.to_pixels(parent_size.height, rem_size),
+ left: self.left.to_pixels(parent_size.width, rem_size),
+ }
+ }
+}
+
+impl Edges<AbsoluteLength> {
+ /// Sets the edges of the `Edges` struct to zero, which means no size or thickness.
+ ///
+ /// This is typically used when you want to specify that a box (like a padding or margin area)
+ /// should have no edges, effectively making it non-existent or invisible in layout calculations.
+ ///
+ /// # Returns
+ ///
+ /// Returns an `Edges<AbsoluteLength>` with all edges set to zero length.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::Edges;
+ /// let no_edges = Edges::zero();
+ /// assert_eq!(no_edges.top, AbsoluteLength::Pixels(Pixels(0.0)));
+ /// assert_eq!(no_edges.right, AbsoluteLength::Pixels(Pixels(0.0)));
+ /// assert_eq!(no_edges.bottom, AbsoluteLength::Pixels(Pixels(0.0)));
+ /// assert_eq!(no_edges.left, AbsoluteLength::Pixels(Pixels(0.0)));
+ /// ```
+ pub fn zero() -> Self {
+ Self {
+ top: px(0.).into(),
+ right: px(0.).into(),
+ bottom: px(0.).into(),
+ left: px(0.).into(),
+ }
+ }
+
+ /// Converts the `AbsoluteLength` to `Pixels` based on the `rem_size`.
+ ///
+ /// If the `AbsoluteLength` is already in pixels, it simply returns the corresponding `Pixels` value.
+ /// If the `AbsoluteLength` is in rems, it multiplies the number of rems by the `rem_size` to convert it to pixels.
+ ///
+ /// # Arguments
+ ///
+ /// * `rem_size` - The size of one rem unit in pixels.
+ ///
+ /// # Returns
+ ///
+ /// Returns an `Edges<Pixels>` representing the edges with lengths converted to pixels.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Edges, AbsoluteLength, Pixels, px};
+ /// let edges = Edges {
+ /// top: AbsoluteLength::Pixels(px(10.0)),
+ /// right: AbsoluteLength::Rems(rems(1.0)),
+ /// bottom: AbsoluteLength::Pixels(px(20.0)),
+ /// left: AbsoluteLength::Rems(rems(2.0)),
+ /// };
+ /// let rem_size = px(16.0);
+ /// let edges_in_pixels = edges.to_pixels(rem_size);
+ ///
+ /// assert_eq!(edges_in_pixels.top, px(10.0)); // Already in pixels
+ /// assert_eq!(edges_in_pixels.right, px(16.0)); // 1 rem converted to pixels
+ /// assert_eq!(edges_in_pixels.bottom, px(20.0)); // Already in pixels
+ /// assert_eq!(edges_in_pixels.left, px(32.0)); // 2 rems converted to pixels
+ /// ```
+ pub fn to_pixels(&self, rem_size: Pixels) -> Edges<Pixels> {
+ Edges {
+ top: self.top.to_pixels(rem_size),
+ right: self.right.to_pixels(rem_size),
+ bottom: self.bottom.to_pixels(rem_size),
+ left: self.left.to_pixels(rem_size),
+ }
+ }
+}
+
+impl Edges<Pixels> {
+ /// Scales the `Edges<Pixels>` by a given factor, returning `Edges<ScaledPixels>`.
+ ///
+ /// This method is typically used for adjusting the edge sizes for different display densities or scaling factors.
+ ///
+ /// # Arguments
+ ///
+ /// * `factor` - The scaling factor to apply to each edge.
+ ///
+ /// # Returns
+ ///
+ /// Returns a new `Edges<ScaledPixels>` where each edge is the result of scaling the original edge by the given factor.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Edges, Pixels};
+ /// let edges = Edges {
+ /// top: Pixels(10.0),
+ /// right: Pixels(20.0),
+ /// bottom: Pixels(30.0),
+ /// left: Pixels(40.0),
+ /// };
+ /// let scaled_edges = edges.scale(2.0);
+ /// assert_eq!(scaled_edges.top, ScaledPixels(20.0));
+ /// assert_eq!(scaled_edges.right, ScaledPixels(40.0));
+ /// assert_eq!(scaled_edges.bottom, ScaledPixels(60.0));
+ /// assert_eq!(scaled_edges.left, ScaledPixels(80.0));
+ /// ```
+ pub fn scale(&self, factor: f32) -> Edges<ScaledPixels> {
+ Edges {
+ top: self.top.scale(factor),
+ right: self.right.scale(factor),
+ bottom: self.bottom.scale(factor),
+ left: self.left.scale(factor),
+ }
+ }
+
+ /// Returns the maximum value of any edge.
+ ///
+ /// # Returns
+ ///
+ /// The maximum `Pixels` value among all four edges.
+ pub fn max(&self) -> Pixels {
+ self.top.max(self.right).max(self.bottom).max(self.left)
+ }
+}
+
+impl From<f32> for Edges<Pixels> {
+ fn from(val: f32) -> Self {
+ Edges {
+ top: val.into(),
+ right: val.into(),
+ bottom: val.into(),
+ left: val.into(),
+ }
+ }
+}
+
+/// Represents the corners of a box in a 2D space, such as border radius.
+///
+/// Each field represents the size of the corner on one side of the box: `top_left`, `top_right`, `bottom_right`, and `bottom_left`.
+/// ```
+#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
+#[refineable(Debug)]
+#[repr(C)]
+pub struct Corners<T: Clone + Default + Debug> {
+ pub top_left: T,
+ pub top_right: T,
+ pub bottom_right: T,
+ pub bottom_left: T,
+}
+
+impl<T> Corners<T>
+where
+ T: Clone + Default + Debug,
+{
+ /// Constructs `Corners` where all sides are set to the same specified value.
+ ///
+ /// This function creates a `Corners` instance with the `top_left`, `top_right`, `bottom_right`, and `bottom_left` fields all initialized
+ /// to the same value provided as an argument. This is useful when you want to have uniform corners around a box,
+ /// such as a uniform border radius on a rectangle.
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The value to set for all four corners.
+ ///
+ /// # Returns
+ ///
+ /// An `Corners` instance with all corners set to the given value.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::Corners;
+ /// let uniform_corners = Corners::all(5.0);
+ /// assert_eq!(uniform_corners.top_left, 5.0);
+ /// assert_eq!(uniform_corners.top_right, 5.0);
+ /// assert_eq!(uniform_corners.bottom_right, 5.0);
+ /// assert_eq!(uniform_corners.bottom_left, 5.0);
+ /// ```
+ pub fn all(value: T) -> Self {
+ Self {
+ top_left: value.clone(),
+ top_right: value.clone(),
+ bottom_right: value.clone(),
+ bottom_left: value,
+ }
+ }
+}
+
+impl Corners<AbsoluteLength> {
+ /// Converts the `AbsoluteLength` to `Pixels` based on the provided size and rem size, ensuring the resulting
+ /// `Pixels` do not exceed half of the maximum of the provided size's width and height.
+ ///
+ /// This method is particularly useful when dealing with corner radii, where the radius in pixels should not
+ /// exceed half the size of the box it applies to, to avoid the corners overlapping.
+ ///
+ /// # Arguments
+ ///
+ /// * `size` - The `Size<Pixels>` against which the maximum allowable radius is determined.
+ /// * `rem_size` - The size of one REM unit in pixels, used for conversion if the `AbsoluteLength` is in REMs.
+ ///
+ /// # Returns
+ ///
+ /// Returns a `Corners<Pixels>` instance with each corner's length converted to pixels and clamped to the
+ /// maximum allowable radius based on the provided size.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Corners, AbsoluteLength, Pixels, Size};
+ /// let corners = Corners {
+ /// top_left: AbsoluteLength::Pixels(Pixels(15.0)),
+ /// top_right: AbsoluteLength::Rems(Rems(1.0)),
+ /// bottom_right: AbsoluteLength::Pixels(Pixels(20.0)),
+ /// bottom_left: AbsoluteLength::Rems(Rems(2.0)),
+ /// };
+ /// let size = Size { width: Pixels(100.0), height: Pixels(50.0) };
+ /// let rem_size = Pixels(16.0);
+ /// let corners_in_pixels = corners.to_pixels(size, rem_size);
+ ///
+ /// // The resulting corners should not exceed half the size of the smallest dimension (50.0 / 2.0 = 25.0).
+ /// assert_eq!(corners_in_pixels.top_left, Pixels(15.0));
+ /// assert_eq!(corners_in_pixels.top_right, Pixels(16.0)); // 1 rem converted to pixels
+ /// assert_eq!(corners_in_pixels.bottom_right, Pixels(20.0).min(Pixels(25.0))); // Clamped to 25.0
+ /// assert_eq!(corners_in_pixels.bottom_left, Pixels(32.0).min(Pixels(25.0))); // 2 rems converted to pixels and clamped
+ /// ```
+ pub fn to_pixels(&self, size: Size<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
+ let max = size.width.max(size.height) / 2.;
+ Corners {
+ top_left: self.top_left.to_pixels(rem_size).min(max),
+ top_right: self.top_right.to_pixels(rem_size).min(max),
+ bottom_right: self.bottom_right.to_pixels(rem_size).min(max),
+ bottom_left: self.bottom_left.to_pixels(rem_size).min(max),
+ }
+ }
+}
+
+impl Corners<Pixels> {
+ /// Scales the `Corners<Pixels>` by a given factor, returning `Corners<ScaledPixels>`.
+ ///
+ /// This method is typically used for adjusting the corner sizes for different display densities or scaling factors.
+ ///
+ /// # Arguments
+ ///
+ /// * `factor` - The scaling factor to apply to each corner.
+ ///
+ /// # Returns
+ ///
+ /// Returns a new `Corners<ScaledPixels>` where each corner is the result of scaling the original corner by the given factor.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Corners, Pixels};
+ /// let corners = Corners {
+ /// top_left: Pixels(10.0),
+ /// top_right: Pixels(20.0),
+ /// bottom_right: Pixels(30.0),
+ /// bottom_left: Pixels(40.0),
+ /// };
+ /// let scaled_corners = corners.scale(2.0);
+ /// assert_eq!(scaled_corners.top_left, ScaledPixels(20.0));
+ /// assert_eq!(scaled_corners.top_right, ScaledPixels(40.0));
+ /// assert_eq!(scaled_corners.bottom_right, ScaledPixels(60.0));
+ /// assert_eq!(scaled_corners.bottom_left, ScaledPixels(80.0));
+ /// ```
+ pub fn scale(&self, factor: f32) -> Corners<ScaledPixels> {
+ Corners {
+ top_left: self.top_left.scale(factor),
+ top_right: self.top_right.scale(factor),
+ bottom_right: self.bottom_right.scale(factor),
+ bottom_left: self.bottom_left.scale(factor),
+ }
+ }
+
+ /// Returns the maximum value of any corner.
+ ///
+ /// # Returns
+ ///
+ /// The maximum `Pixels` value among all four corners.
+ pub fn max(&self) -> Pixels {
+ self.top_left
+ .max(self.top_right)
+ .max(self.bottom_right)
+ .max(self.bottom_left)
+ }
+}
+
+impl<T: Clone + Default + Debug> Corners<T> {
+ /// Applies a function to each field of the `Corners`, producing a new `Corners<U>`.
+ ///
+ /// This method allows for converting a `Corners<T>` to a `Corners<U>` by specifying a closure
+ /// that defines how to convert between the two types. The closure is applied to each field
+ /// (`top_left`, `top_right`, `bottom_right`, `bottom_left`), resulting in new corners of the desired type.
+ ///
+ /// # Arguments
+ ///
+ /// * `f` - A closure that takes a reference to a value of type `T` and returns a value of type `U`.
+ ///
+ /// # Returns
+ ///
+ /// Returns a new `Corners<U>` with each field mapped by the provided function.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{Corners, Pixels};
+ /// let corners = Corners {
+ /// top_left: Pixels(10.0),
+ /// top_right: Pixels(20.0),
+ /// bottom_right: Pixels(30.0),
+ /// bottom_left: Pixels(40.0),
+ /// };
+ /// let corners_in_rems = corners.map(|&px| Rems(px.0 / 16.0));
+ /// assert_eq!(corners_in_rems, Corners {
+ /// top_left: Rems(0.625),
+ /// top_right: Rems(1.25),
+ /// bottom_right: Rems(1.875),
+ /// bottom_left: Rems(2.5),
+ /// });
+ /// ```
+ pub fn map<U>(&self, f: impl Fn(&T) -> U) -> Corners<U>
+ where
+ U: Clone + Default + Debug,
+ {
+ Corners {
+ top_left: f(&self.top_left),
+ top_right: f(&self.top_right),
+ bottom_right: f(&self.bottom_right),
+ bottom_left: f(&self.bottom_left),
+ }
+ }
+}
+
+impl<T> Mul for Corners<T>
+where
+ T: Mul<Output = T> + Clone + Default + Debug,
+{
+ type Output = Self;
+
+ fn mul(self, rhs: Self) -> Self::Output {
+ Self {
+ top_left: self.top_left.clone() * rhs.top_left,
+ top_right: self.top_right.clone() * rhs.top_right,
+ bottom_right: self.bottom_right.clone() * rhs.bottom_right,
+ bottom_left: self.bottom_left.clone() * rhs.bottom_left,
+ }
+ }
+}
+
+impl<T, S> MulAssign<S> for Corners<T>
+where
+ T: Mul<S, Output = T> + Clone + Default + Debug,
+ S: Clone,
+{
+ fn mul_assign(&mut self, rhs: S) {
+ self.top_left = self.top_left.clone() * rhs.clone();
+ self.top_right = self.top_right.clone() * rhs.clone();
+ self.bottom_right = self.bottom_right.clone() * rhs.clone();
+ self.bottom_left = self.bottom_left.clone() * rhs;
+ }
+}
+
+impl<T> Copy for Corners<T> where T: Copy + Clone + Default + Debug {}
+
+impl From<f32> for Corners<Pixels> {
+ fn from(val: f32) -> Self {
+ Corners {
+ top_left: val.into(),
+ top_right: val.into(),
+ bottom_right: val.into(),
+ bottom_left: val.into(),
+ }
+ }
+}
+
+impl From<Pixels> for Corners<Pixels> {
+ fn from(val: Pixels) -> Self {
+ Corners {
+ top_left: val,
+ top_right: val,
+ bottom_right: val,
+ bottom_left: val,
+ }
+ }
+}
+
+/// Represents a length in pixels, the base unit of measurement in the UI framework.
+///
+/// `Pixels` is a value type that represents an absolute length in pixels, which is used
+/// for specifying sizes, positions, and distances in the UI. It is the fundamental unit
+/// of measurement for all visual elements and layout calculations.
+///
+/// The inner value is an `f32`, allowing for sub-pixel precision which can be useful for
+/// anti-aliasing and animations. However, when applied to actual pixel grids, the value
+/// is typically rounded to the nearest integer.
+///
+/// # Examples
+///
+/// ```
+/// use zed::Pixels;
+///
+/// // Define a length of 10 pixels
+/// let length = Pixels(10.0);
+///
+/// // Define a length and scale it by a factor of 2
+/// let scaled_length = length.scale(2.0);
+/// assert_eq!(scaled_length, Pixels(20.0));
+/// ```
+#[derive(
+ Clone,
+ Copy,
+ Default,
+ Add,
+ AddAssign,
+ Sub,
+ SubAssign,
+ Neg,
+ Div,
+ DivAssign,
+ PartialEq,
+ Serialize,
+ Deserialize,
+)]
+#[repr(transparent)]
+pub struct Pixels(pub f32);
+
+impl std::ops::Div for Pixels {
+ type Output = f32;
+
+ fn div(self, rhs: Self) -> Self::Output {
+ self.0 / rhs.0
+ }
+}
+
+impl std::ops::DivAssign for Pixels {
+ fn div_assign(&mut self, rhs: Self) {
+ *self = Self(self.0 / rhs.0);
+ }
+}
+
+impl std::ops::RemAssign for Pixels {
+ fn rem_assign(&mut self, rhs: Self) {
+ self.0 %= rhs.0;
+ }
}
-impl Default for PathBuilder {
- fn default() -> Self {
- PathBuilder::new()
+impl std::ops::Rem for Pixels {
+ type Output = Self;
+
+ fn rem(self, rhs: Self) -> Self {
+ Self(self.0 % rhs.0)
}
}
-impl PathBuilder {
- pub fn new() -> Self {
- Self {
- vertices: Vec::new(),
- start: vec2f(0., 0.),
- current: vec2f(0., 0.),
- contour_count: 0,
- bounds: RectF::default(),
- }
+impl Mul<f32> for Pixels {
+ type Output = Pixels;
+
+ fn mul(self, other: f32) -> Pixels {
+ Pixels(self.0 * other)
}
+}
- pub fn reset(&mut self, point: Vector2F) {
- self.vertices.clear();
- self.start = point;
- self.current = point;
- self.contour_count = 0;
+impl Mul<usize> for Pixels {
+ type Output = Pixels;
+
+ fn mul(self, other: usize) -> Pixels {
+ Pixels(self.0 * other as f32)
}
+}
- pub fn line_to(&mut self, point: Vector2F) {
- self.contour_count += 1;
- if self.contour_count > 1 {
- self.push_triangle(self.start, self.current, point, PathVertexKind::Solid);
- }
+impl Mul<Pixels> for f32 {
+ type Output = Pixels;
- self.current = point;
+ fn mul(self, rhs: Pixels) -> Self::Output {
+ Pixels(self * rhs.0)
}
+}
- pub fn curve_to(&mut self, point: Vector2F, ctrl: Vector2F) {
- self.contour_count += 1;
- if self.contour_count > 1 {
- self.push_triangle(self.start, self.current, point, PathVertexKind::Solid);
- }
+impl MulAssign<f32> for Pixels {
+ fn mul_assign(&mut self, other: f32) {
+ self.0 *= other;
+ }
+}
+
+impl Pixels {
+ /// Represents zero pixels.
+ pub const ZERO: Pixels = Pixels(0.0);
+ /// The maximum value that can be represented by `Pixels`.
+ pub const MAX: Pixels = Pixels(f32::MAX);
- self.push_triangle(self.current, ctrl, point, PathVertexKind::Quadratic);
- self.current = point;
+ /// Floors the `Pixels` value to the nearest whole number.
+ ///
+ /// # Returns
+ ///
+ /// Returns a new `Pixels` instance with the floored value.
+ pub fn floor(&self) -> Self {
+ Self(self.0.floor())
}
- pub fn build(mut self, color: Color, clip_bounds: Option<RectF>) -> Path {
- if let Some(clip_bounds) = clip_bounds {
- self.bounds = self.bounds.intersection(clip_bounds).unwrap_or_default();
- }
- Path {
- bounds: self.bounds,
- color,
- vertices: self.vertices,
- }
+ /// Rounds the `Pixels` value to the nearest whole number.
+ ///
+ /// # Returns
+ ///
+ /// Returns a new `Pixels` instance with the rounded value.
+ pub fn round(&self) -> Self {
+ Self(self.0.round())
}
- fn push_triangle(&mut self, a: Vector2F, b: Vector2F, c: Vector2F, kind: PathVertexKind) {
- if self.vertices.is_empty() {
- self.bounds = RectF::new(a, Vector2F::zero());
- }
- self.bounds = self.bounds.union_point(a).union_point(b).union_point(c);
+ /// Returns the ceiling of the `Pixels` value to the nearest whole number.
+ ///
+ /// # Returns
+ ///
+ /// Returns a new `Pixels` instance with the ceiling value.
+ pub fn ceil(&self) -> Self {
+ Self(self.0.ceil())
+ }
- match kind {
- PathVertexKind::Solid => {
- self.vertices.push(PathVertex {
- xy_position: a,
- st_position: vec2f(0., 1.),
- });
- self.vertices.push(PathVertex {
- xy_position: b,
- st_position: vec2f(0., 1.),
- });
- self.vertices.push(PathVertex {
- xy_position: c,
- st_position: vec2f(0., 1.),
- });
- }
- PathVertexKind::Quadratic => {
- self.vertices.push(PathVertex {
- xy_position: a,
- st_position: vec2f(0., 0.),
- });
- self.vertices.push(PathVertex {
- xy_position: b,
- st_position: vec2f(0.5, 0.),
- });
- self.vertices.push(PathVertex {
- xy_position: c,
- st_position: vec2f(1., 1.),
- });
- }
- }
+ /// Scales the `Pixels` value by a given factor, producing `ScaledPixels`.
+ ///
+ /// This method is used when adjusting pixel values for display scaling factors,
+ /// such as high DPI (dots per inch) or Retina displays, where the pixel density is higher and
+ /// thus requires scaling to maintain visual consistency and readability.
+ ///
+ /// The resulting `ScaledPixels` represent the scaled value which can be used for rendering
+ /// calculations where display scaling is considered.
+ pub fn scale(&self, factor: f32) -> ScaledPixels {
+ ScaledPixels(self.0 * factor)
+ }
+
+ /// Raises the `Pixels` value to a given power.
+ ///
+ /// # Arguments
+ ///
+ /// * `exponent` - The exponent to raise the `Pixels` value by.
+ ///
+ /// # Returns
+ ///
+ /// Returns a new `Pixels` instance with the value raised to the given exponent.
+ pub fn pow(&self, exponent: f32) -> Self {
+ Self(self.0.powf(exponent))
+ }
+
+ /// Returns the absolute value of the `Pixels`.
+ ///
+ /// # Returns
+ ///
+ /// A new `Pixels` instance with the absolute value of the original `Pixels`.
+ pub fn abs(&self) -> Self {
+ Self(self.0.abs())
}
}
-pub fn deserialize_vec2f<'de, D>(deserializer: D) -> Result<Vector2F, D::Error>
-where
- D: Deserializer<'de>,
-{
- let [x, y]: [f32; 2] = Deserialize::deserialize(deserializer)?;
- Ok(vec2f(x, y))
+impl Mul<Pixels> for Pixels {
+ type Output = Pixels;
+
+ fn mul(self, rhs: Pixels) -> Self::Output {
+ Pixels(self.0 * rhs.0)
+ }
}
-impl ToJson for Vector2F {
- fn to_json(&self) -> serde_json::Value {
- json!([self.x(), self.y()])
+impl Eq for Pixels {}
+
+impl PartialOrd for Pixels {
+ fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
+ self.0.partial_cmp(&other.0)
}
}
-impl ToJson for RectF {
- fn to_json(&self) -> serde_json::Value {
- json!({"origin": self.origin().to_json(), "size": self.size().to_json()})
+impl Ord for Pixels {
+ fn cmp(&self, other: &Self) -> cmp::Ordering {
+ self.partial_cmp(other).unwrap()
}
}
-#[derive(Refineable, Debug)]
-#[refineable(Debug)]
-pub struct Point<T: Clone + Default + Debug> {
- pub x: T,
- pub y: T,
+impl std::hash::Hash for Pixels {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ self.0.to_bits().hash(state);
+ }
}
-impl<T: Clone + Default + Debug> Clone for Point<T> {
- fn clone(&self) -> Self {
- Self {
- x: self.x.clone(),
- y: self.y.clone(),
- }
+impl From<f64> for Pixels {
+ fn from(pixels: f64) -> Self {
+ Pixels(pixels as f32)
}
}
-impl<T: Clone + Default + Debug> Into<taffy::geometry::Point<T>> for Point<T> {
- fn into(self) -> taffy::geometry::Point<T> {
- taffy::geometry::Point {
- x: self.x,
- y: self.y,
- }
+impl From<f32> for Pixels {
+ fn from(pixels: f32) -> Self {
+ Pixels(pixels)
}
}
-#[derive(Refineable, Clone, Debug)]
-#[refineable(Debug)]
-pub struct Size<T: Clone + Default + Debug> {
- pub width: T,
- pub height: T,
+impl Debug for Pixels {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{} px", self.0)
+ }
}
-impl<S, T: Clone + Default + Debug> From<taffy::geometry::Size<S>> for Size<T>
-where
- S: Into<T>,
-{
- fn from(value: taffy::geometry::Size<S>) -> Self {
- Self {
- width: value.width.into(),
- height: value.height.into(),
- }
+impl From<Pixels> for f32 {
+ fn from(pixels: Pixels) -> Self {
+ pixels.0
}
}
-impl<S, T: Clone + Default + Debug> Into<taffy::geometry::Size<S>> for Size<T>
-where
- T: Into<S>,
-{
- fn into(self) -> taffy::geometry::Size<S> {
- taffy::geometry::Size {
- width: self.width.into(),
- height: self.height.into(),
- }
+impl From<&Pixels> for f32 {
+ fn from(pixels: &Pixels) -> Self {
+ pixels.0
}
}
-impl Size<DefiniteLength> {
- pub fn zero() -> Self {
- Self {
- width: pixels(0.).into(),
- height: pixels(0.).into(),
- }
+impl From<Pixels> for f64 {
+ fn from(pixels: Pixels) -> Self {
+ pixels.0 as f64
}
+}
- pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Size<taffy::style::LengthPercentage> {
- taffy::geometry::Size {
- width: self.width.to_taffy(rem_size),
- height: self.height.to_taffy(rem_size),
- }
+impl From<Pixels> for u32 {
+ fn from(pixels: Pixels) -> Self {
+ pixels.0 as u32
}
}
-impl Size<Length> {
- pub fn auto() -> Self {
- Self {
- width: Length::Auto,
- height: Length::Auto,
- }
+impl From<u32> for Pixels {
+ fn from(pixels: u32) -> Self {
+ Pixels(pixels as f32)
}
+}
- pub fn to_taffy<T: From<taffy::prelude::LengthPercentageAuto>>(
- &self,
- rem_size: f32,
- ) -> taffy::geometry::Size<T> {
- taffy::geometry::Size {
- width: self.width.to_taffy(rem_size).into(),
- height: self.height.to_taffy(rem_size).into(),
- }
+impl From<Pixels> for usize {
+ fn from(pixels: Pixels) -> Self {
+ pixels.0 as usize
}
}
-#[derive(Clone, Default, Refineable, Debug)]
-#[refineable(Debug)]
-pub struct Edges<T: Clone + Default + Debug> {
- pub top: T,
- pub right: T,
- pub bottom: T,
- pub left: T,
+impl From<usize> for Pixels {
+ fn from(pixels: usize) -> Self {
+ Pixels(pixels as f32)
+ }
}
-impl<T: Clone + Default + Debug> Edges<T> {
- pub fn uniform(value: T) -> Self {
- Self {
- top: value.clone(),
- right: value.clone(),
- bottom: value.clone(),
- left: value.clone(),
- }
+/// Represents physical pixels on the display.
+///
+/// `DevicePixels` is a unit of measurement that refers to the actual pixels on a device's screen.
+/// This type is used when precise pixel manipulation is required, such as rendering graphics or
+/// interfacing with hardware that operates on the pixel level. Unlike logical pixels that may be
+/// affected by the device's scale factor, `DevicePixels` always correspond to real pixels on the
+/// display.
+#[derive(
+ Add, AddAssign, Clone, Copy, Default, Div, Eq, Hash, Ord, PartialEq, PartialOrd, Sub, SubAssign,
+)]
+#[repr(transparent)]
+pub struct DevicePixels(pub(crate) i32);
+
+impl DevicePixels {
+ /// Converts the `DevicePixels` value to the number of bytes needed to represent it in memory.
+ ///
+ /// This function is useful when working with graphical data that needs to be stored in a buffer,
+ /// such as images or framebuffers, where each pixel may be represented by a specific number of bytes.
+ ///
+ /// # Arguments
+ ///
+ /// * `bytes_per_pixel` - The number of bytes used to represent a single pixel.
+ ///
+ /// # Returns
+ ///
+ /// The number of bytes required to represent the `DevicePixels` value in memory.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::DevicePixels;
+ /// let pixels = DevicePixels(10); // 10 device pixels
+ /// let bytes_per_pixel = 4; // Assume each pixel is represented by 4 bytes (e.g., RGBA)
+ /// let total_bytes = pixels.to_bytes(bytes_per_pixel);
+ /// assert_eq!(total_bytes, 40); // 10 pixels * 4 bytes/pixel = 40 bytes
+ /// ```
+ pub fn to_bytes(&self, bytes_per_pixel: u8) -> u32 {
+ self.0 as u32 * bytes_per_pixel as u32
}
}
-impl Edges<Length> {
- pub fn auto() -> Self {
- Self {
- top: Length::Auto,
- right: Length::Auto,
- bottom: Length::Auto,
- left: Length::Auto,
- }
+impl fmt::Debug for DevicePixels {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{} px (device)", self.0)
}
+}
- pub fn zero() -> Self {
- Self {
- top: pixels(0.).into(),
- right: pixels(0.).into(),
- bottom: pixels(0.).into(),
- left: pixels(0.).into(),
- }
+impl From<DevicePixels> for i32 {
+ fn from(device_pixels: DevicePixels) -> Self {
+ device_pixels.0
}
+}
- pub fn to_taffy(
- &self,
- rem_size: f32,
- ) -> taffy::geometry::Rect<taffy::style::LengthPercentageAuto> {
- taffy::geometry::Rect {
- top: self.top.to_taffy(rem_size),
- right: self.right.to_taffy(rem_size),
- bottom: self.bottom.to_taffy(rem_size),
- left: self.left.to_taffy(rem_size),
- }
+impl From<i32> for DevicePixels {
+ fn from(device_pixels: i32) -> Self {
+ DevicePixels(device_pixels)
}
}
-impl Edges<DefiniteLength> {
- pub fn zero() -> Self {
- Self {
- top: pixels(0.).into(),
- right: pixels(0.).into(),
- bottom: pixels(0.).into(),
- left: pixels(0.).into(),
- }
+impl From<u32> for DevicePixels {
+ fn from(device_pixels: u32) -> Self {
+ DevicePixels(device_pixels as i32)
}
+}
- pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
- taffy::geometry::Rect {
- top: self.top.to_taffy(rem_size),
- right: self.right.to_taffy(rem_size),
- bottom: self.bottom.to_taffy(rem_size),
- left: self.left.to_taffy(rem_size),
- }
+impl From<DevicePixels> for u32 {
+ fn from(device_pixels: DevicePixels) -> Self {
+ device_pixels.0 as u32
}
}
-impl Edges<AbsoluteLength> {
- pub fn zero() -> Self {
- Self {
- top: pixels(0.),
- right: pixels(0.),
- bottom: pixels(0.),
- left: pixels(0.),
- }
+impl From<DevicePixels> for u64 {
+ fn from(device_pixels: DevicePixels) -> Self {
+ device_pixels.0 as u64
}
+}
- pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
- taffy::geometry::Rect {
- top: self.top.to_taffy(rem_size),
- right: self.right.to_taffy(rem_size),
- bottom: self.bottom.to_taffy(rem_size),
- left: self.left.to_taffy(rem_size),
- }
+impl From<u64> for DevicePixels {
+ fn from(device_pixels: u64) -> Self {
+ DevicePixels(device_pixels as i32)
}
+}
- pub fn to_pixels(&self, rem_size: f32) -> Edges<f32> {
- Edges {
- top: self.top.to_pixels(rem_size),
- right: self.right.to_pixels(rem_size),
- bottom: self.bottom.to_pixels(rem_size),
- left: self.left.to_pixels(rem_size),
- }
+impl From<DevicePixels> for usize {
+ fn from(device_pixels: DevicePixels) -> Self {
+ device_pixels.0 as usize
}
}
-impl Edges<f32> {
- pub fn is_empty(&self) -> bool {
- self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0 && self.left == 0.0
+impl From<usize> for DevicePixels {
+ fn from(device_pixels: usize) -> Self {
+ DevicePixels(device_pixels as i32)
}
}
-#[derive(Clone, Copy, Neg)]
-pub enum AbsoluteLength {
- Pixels(f32),
- Rems(f32),
+/// Represents scaled pixels that take into account the device's scale factor.
+///
+/// `ScaledPixels` are used to ensure that UI elements appear at the correct size on devices
+/// with different pixel densities. When a device has a higher scale factor (such as Retina displays),
+/// a single logical pixel may correspond to multiple physical pixels. By using `ScaledPixels`,
+/// dimensions and positions can be specified in a way that scales appropriately across different
+/// display resolutions.
+#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
+#[repr(transparent)]
+pub struct ScaledPixels(pub(crate) f32);
+
+impl ScaledPixels {
+ /// Floors the `ScaledPixels` value to the nearest whole number.
+ ///
+ /// # Returns
+ ///
+ /// Returns a new `ScaledPixels` instance with the floored value.
+ pub fn floor(&self) -> Self {
+ Self(self.0.floor())
+ }
+
+ /// Rounds the `ScaledPixels` value to the nearest whole number.
+ ///
+ /// # Returns
+ ///
+ /// Returns a new `ScaledPixels` instance with the rounded value.
+ pub fn ceil(&self) -> Self {
+ Self(self.0.ceil())
+ }
}
-impl std::fmt::Debug for AbsoluteLength {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- AbsoluteLength::Pixels(pixels) => write!(f, "{}px", pixels),
- AbsoluteLength::Rems(rems) => write!(f, "{}rems", rems),
- }
+impl Eq for ScaledPixels {}
+
+impl Debug for ScaledPixels {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{} px (scaled)", self.0)
+ }
+}
+
+impl From<ScaledPixels> for DevicePixels {
+ fn from(scaled: ScaledPixels) -> Self {
+ DevicePixels(scaled.0.ceil() as i32)
+ }
+}
+
+impl From<DevicePixels> for ScaledPixels {
+ fn from(device: DevicePixels) -> Self {
+ ScaledPixels(device.0 as f32)
}
}
+impl From<ScaledPixels> for f64 {
+ fn from(scaled_pixels: ScaledPixels) -> Self {
+ scaled_pixels.0 as f64
+ }
+}
+
+/// Represents pixels in a global coordinate space, which can span across multiple displays.
+///
+/// `GlobalPixels` is used when dealing with a coordinate system that is not limited to a single
+/// display's boundaries. This type is particularly useful in multi-monitor setups where
+/// positioning and measurements need to be consistent and relative to a "global" origin point
+/// rather than being relative to any individual display.
+#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
+#[repr(transparent)]
+pub struct GlobalPixels(pub(crate) f32);
+
+impl Debug for GlobalPixels {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{} px (global coordinate space)", self.0)
+ }
+}
+
+impl From<GlobalPixels> for f64 {
+ fn from(global_pixels: GlobalPixels) -> Self {
+ global_pixels.0 as f64
+ }
+}
+
+impl From<f64> for GlobalPixels {
+ fn from(global_pixels: f64) -> Self {
+ GlobalPixels(global_pixels as f32)
+ }
+}
+
+impl sqlez::bindable::StaticColumnCount for GlobalPixels {}
+
+impl sqlez::bindable::Bind for GlobalPixels {
+ fn bind(
+ &self,
+ statement: &sqlez::statement::Statement,
+ start_index: i32,
+ ) -> anyhow::Result<i32> {
+ self.0.bind(statement, start_index)
+ }
+}
+
+/// Represents a length in rems, a unit based on the font-size of the window, which can be assigned with [WindowContext::set_rem_size].
+///
+/// Rems are used for defining lengths that are scalable and consistent across different UI elements.
+/// The value of `1rem` is typically equal to the font-size of the root element (often the `<html>` element in browsers),
+/// making it a flexible unit that adapts to the user's text size preferences. In this framework, `rems` serve a similar
+/// purpose, allowing for scalable and accessible design that can adjust to different display settings or user preferences.
+///
+/// For example, if the root element's font-size is `16px`, then `1rem` equals `16px`. A length of `2rems` would then be `32px`.
+#[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg)]
+pub struct Rems(pub f32);
+
+impl Mul<Pixels> for Rems {
+ type Output = Pixels;
+
+ fn mul(self, other: Pixels) -> Pixels {
+ Pixels(self.0 * other.0)
+ }
+}
+
+impl Debug for Rems {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{} rem", self.0)
+ }
+}
+
+/// Represents an absolute length in pixels or rems.
+///
+/// `AbsoluteLength` can be either a fixed number of pixels, which is an absolute measurement not
+/// affected by the current font size, or a number of rems, which is relative to the font size of
+/// the root element. It is used for specifying dimensions that are either independent of or
+/// related to the typographic scale.
+#[derive(Clone, Copy, Debug, Neg)]
+pub enum AbsoluteLength {
+ /// A length in pixels.
+ Pixels(Pixels),
+ /// A length in rems.
+ Rems(Rems),
+}
+
impl AbsoluteLength {
- pub fn to_pixels(&self, rem_size: f32) -> f32 {
+ /// Checks if the absolute length is zero.
+ pub fn is_zero(&self) -> bool {
match self {
- AbsoluteLength::Pixels(pixels) => *pixels,
- AbsoluteLength::Rems(rems) => rems * rem_size,
+ AbsoluteLength::Pixels(px) => px.0 == 0.0,
+ AbsoluteLength::Rems(rems) => rems.0 == 0.0,
}
}
+}
+
+impl From<Pixels> for AbsoluteLength {
+ fn from(pixels: Pixels) -> Self {
+ AbsoluteLength::Pixels(pixels)
+ }
+}
+
+impl From<Rems> for AbsoluteLength {
+ fn from(rems: Rems) -> Self {
+ AbsoluteLength::Rems(rems)
+ }
+}
- pub fn to_taffy(&self, rem_size: f32) -> taffy::style::LengthPercentage {
+impl AbsoluteLength {
+ /// Converts an `AbsoluteLength` to `Pixels` based on a given `rem_size`.
+ ///
+ /// # Arguments
+ ///
+ /// * `rem_size` - The size of one rem in pixels.
+ ///
+ /// # Returns
+ ///
+ /// Returns the `AbsoluteLength` as `Pixels`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{AbsoluteLength, Pixels};
+ /// let length_in_pixels = AbsoluteLength::Pixels(Pixels(42.0));
+ /// let length_in_rems = AbsoluteLength::Rems(Rems(2.0));
+ /// let rem_size = Pixels(16.0);
+ ///
+ /// assert_eq!(length_in_pixels.to_pixels(rem_size), Pixels(42.0));
+ /// assert_eq!(length_in_rems.to_pixels(rem_size), Pixels(32.0));
+ /// ```
+ pub fn to_pixels(&self, rem_size: Pixels) -> Pixels {
match self {
- AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(*pixels),
- AbsoluteLength::Rems(rems) => taffy::style::LengthPercentage::Length(rems * rem_size),
+ AbsoluteLength::Pixels(pixels) => *pixels,
+ AbsoluteLength::Rems(rems) => *rems * rem_size,
}
}
}
impl Default for AbsoluteLength {
fn default() -> Self {
- Self::Pixels(0.0)
+ px(0.).into()
}
}
/// A non-auto length that can be defined in pixels, rems, or percent of parent.
+///
+/// This enum represents lengths that have a specific value, as opposed to lengths that are automatically
+/// determined by the context. It includes absolute lengths in pixels or rems, and relative lengths as a
+/// fraction of the parent's size.
#[derive(Clone, Copy, Neg)]
pub enum DefiniteLength {
+ /// An absolute length specified in pixels or rems.
Absolute(AbsoluteLength),
- Relative(f32), // 0. to 1.
+ /// A relative length specified as a fraction of the parent's size, between 0 and 1.
+ Fraction(f32),
}
impl DefiniteLength {
- fn to_taffy(&self, rem_size: f32) -> taffy::style::LengthPercentage {
+ /// Converts the `DefiniteLength` to `Pixels` based on a given `base_size` and `rem_size`.
+ ///
+ /// If the `DefiniteLength` is an absolute length, it will be directly converted to `Pixels`.
+ /// If it is a fraction, the fraction will be multiplied by the `base_size` to get the length in pixels.
+ ///
+ /// # Arguments
+ ///
+ /// * `base_size` - The base size in `AbsoluteLength` to which the fraction will be applied.
+ /// * `rem_size` - The size of one rem in pixels, used to convert rems to pixels.
+ ///
+ /// # Returns
+ ///
+ /// Returns the `DefiniteLength` as `Pixels`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use zed::{DefiniteLength, AbsoluteLength, Pixels, px, rems};
+ /// let length_in_pixels = DefiniteLength::Absolute(AbsoluteLength::Pixels(px(42.0)));
+ /// let length_in_rems = DefiniteLength::Absolute(AbsoluteLength::Rems(rems(2.0)));
+ /// let length_as_fraction = DefiniteLength::Fraction(0.5);
+ /// let base_size = AbsoluteLength::Pixels(px(100.0));
+ /// let rem_size = px(16.0);
+ ///
+ /// assert_eq!(length_in_pixels.to_pixels(base_size, rem_size), Pixels(42.0));
+ /// assert_eq!(length_in_rems.to_pixels(base_size, rem_size), Pixels(32.0));
+ /// assert_eq!(length_as_fraction.to_pixels(base_size, rem_size), Pixels(50.0));
+ /// ```
+ pub fn to_pixels(&self, base_size: AbsoluteLength, rem_size: Pixels) -> Pixels {
match self {
- DefiniteLength::Absolute(length) => match length {
- AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(*pixels),
- AbsoluteLength::Rems(rems) => {
- taffy::style::LengthPercentage::Length(rems * rem_size)
- }
+ DefiniteLength::Absolute(size) => size.to_pixels(rem_size),
+ DefiniteLength::Fraction(fraction) => match base_size {
+ AbsoluteLength::Pixels(px) => px * *fraction,
+ AbsoluteLength::Rems(rems) => rems * rem_size * *fraction,
},
- DefiniteLength::Relative(fraction) => {
- taffy::style::LengthPercentage::Percent(*fraction)
- }
}
}
}
-impl std::fmt::Debug for DefiniteLength {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+impl Debug for DefiniteLength {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
- DefiniteLength::Absolute(length) => std::fmt::Debug::fmt(length, f),
- DefiniteLength::Relative(fract) => write!(f, "{}%", (fract * 100.0) as i32),
+ DefiniteLength::Absolute(length) => Debug::fmt(length, f),
+ DefiniteLength::Fraction(fract) => write!(f, "{}%", (fract * 100.0) as i32),
}
}
}
+impl From<Pixels> for DefiniteLength {
+ fn from(pixels: Pixels) -> Self {
+ Self::Absolute(pixels.into())
+ }
+}
+
+impl From<Rems> for DefiniteLength {
+ fn from(rems: Rems) -> Self {
+ Self::Absolute(rems.into())
+ }
+}
+
impl From<AbsoluteLength> for DefiniteLength {
fn from(length: AbsoluteLength) -> Self {
Self::Absolute(length)
@@ -1,40 +1,215 @@
+#[macro_use]
+mod action;
mod app;
-mod image_cache;
-pub use app::*;
+
+mod arena;
mod assets;
+mod color;
+mod element;
+mod elements;
+mod executor;
+mod geometry;
+mod image_cache;
+mod input;
+mod interactive;
+mod key_dispatch;
+mod keymap;
+mod platform;
+pub mod prelude;
+mod scene;
+mod shared_string;
+mod style;
+mod styled;
+mod subscription;
+mod svg_renderer;
+mod taffy;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
-pub use assets::*;
-pub mod elements;
-pub mod font_cache;
-mod image_data;
-pub use crate::image_data::ImageData;
-pub use taffy;
-pub mod views;
-pub use font_cache::FontCache;
-mod clipboard;
-pub use clipboard::ClipboardItem;
-pub mod fonts;
-pub mod geometry;
-pub mod scene;
-pub use scene::{Border, CursorRegion, MouseRegion, MouseRegionId, Quad, Scene, SceneBuilder};
-pub mod text_layout;
-pub use text_layout::TextLayoutCache;
+mod text_system;
mod util;
-pub use elements::{AnyElement, Element};
-pub mod executor;
-pub use executor::Task;
-pub mod color;
-pub mod json;
-pub mod keymap_matcher;
-pub mod platform;
-pub use gpui_macros::{test, Element};
-pub use usvg;
-pub use window::{
- Axis, Layout, LayoutEngine, LayoutId, RectFExt, SizeConstraint, Vector2FExt, WindowContext,
-};
+mod view;
+mod window;
+
+mod private {
+ /// A mechanism for restricting implementations of a trait to only those in GPUI.
+ /// See: https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/
+ pub trait Sealed {}
+}
-pub use anyhow;
+pub use action::*;
+pub use anyhow::Result;
+pub use app::*;
+pub(crate) use arena::*;
+pub use assets::*;
+pub use color::*;
+pub use ctor::ctor;
+pub use element::*;
+pub use elements::*;
+pub use executor::*;
+pub use geometry::*;
+pub use gpui2_macros::*;
+pub use image_cache::*;
+pub use input::*;
+pub use interactive::*;
+pub use key_dispatch::*;
+pub use keymap::*;
+pub use linkme;
+pub use platform::*;
+use private::Sealed;
+pub use refineable::*;
+pub use scene::*;
+pub use serde;
+pub use serde_derive;
pub use serde_json;
+pub use shared_string::*;
+pub use smallvec;
+pub use smol::Timer;
+pub use style::*;
+pub use styled::*;
+pub use subscription::*;
+pub use svg_renderer::*;
+pub use taffy::{AvailableSpace, LayoutId};
+#[cfg(any(test, feature = "test-support"))]
+pub use test::*;
+pub use text_system::*;
+pub use util::arc_cow::ArcCow;
+pub use view::*;
+pub use window::*;
+
+use std::{
+ any::{Any, TypeId},
+ borrow::BorrowMut,
+};
+use taffy::TaffyLayoutEngine;
+
+pub trait Context {
+ type Result<T>;
+
+ fn new_model<T: 'static>(
+ &mut self,
+ build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
+ ) -> Self::Result<Model<T>>;
+
+ fn update_model<T, R>(
+ &mut self,
+ handle: &Model<T>,
+ update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
+ ) -> Self::Result<R>
+ where
+ T: 'static;
+
+ fn read_model<T, R>(
+ &self,
+ handle: &Model<T>,
+ read: impl FnOnce(&T, &AppContext) -> R,
+ ) -> Self::Result<R>
+ where
+ T: 'static;
+
+ fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
+ where
+ F: FnOnce(AnyView, &mut WindowContext<'_>) -> T;
+
+ fn read_window<T, R>(
+ &self,
+ window: &WindowHandle<T>,
+ read: impl FnOnce(View<T>, &AppContext) -> R,
+ ) -> Result<R>
+ where
+ T: 'static;
+}
+
+pub trait VisualContext: Context {
+ fn new_view<V>(
+ &mut self,
+ build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
+ ) -> Self::Result<View<V>>
+ where
+ V: 'static + Render;
+
+ fn update_view<V: 'static, R>(
+ &mut self,
+ view: &View<V>,
+ update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
+ ) -> Self::Result<R>;
+
+ fn replace_root_view<V>(
+ &mut self,
+ build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
+ ) -> Self::Result<View<V>>
+ where
+ V: 'static + Render;
+
+ fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
+ where
+ V: FocusableView;
+
+ fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
+ where
+ V: ManagedView;
+}
+
+pub trait Entity<T>: Sealed {
+ type Weak: 'static;
+
+ fn entity_id(&self) -> EntityId;
+ fn downgrade(&self) -> Self::Weak;
+ fn upgrade_from(weak: &Self::Weak) -> Option<Self>
+ where
+ Self: Sized;
+}
+
+pub trait EventEmitter<E: Any>: 'static {}
+
+pub enum GlobalKey {
+ Numeric(usize),
+ View(EntityId),
+ Type(TypeId),
+}
+
+pub trait BorrowAppContext {
+ fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
+ where
+ F: FnOnce(&mut Self) -> R;
+
+ fn set_global<T: 'static>(&mut self, global: T);
+}
+
+impl<C> BorrowAppContext for C
+where
+ C: BorrowMut<AppContext>,
+{
+ fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
+ where
+ F: FnOnce(&mut Self) -> R,
+ {
+ if let Some(style) = style {
+ self.borrow_mut().push_text_style(style);
+ let result = f(self);
+ self.borrow_mut().pop_text_style();
+ result
+ } else {
+ f(self)
+ }
+ }
+
+ fn set_global<G: 'static>(&mut self, global: G) {
+ self.borrow_mut().set_global(global)
+ }
+}
+
+pub trait Flatten<T> {
+ fn flatten(self) -> Result<T>;
+}
+
+impl<T> Flatten<T> for Result<Result<T>> {
+ fn flatten(self) -> Result<T> {
+ self?
+ }
+}
-actions!(zed, [NoAction]);
+impl<T> Flatten<T> for Result<T> {
+ fn flatten(self) -> Result<T> {
+ self
+ }
+}
@@ -1,18 +1,19 @@
-use std::sync::Arc;
-
-use crate::ImageData;
+use crate::{ImageData, ImageId, SharedString};
use collections::HashMap;
use futures::{
future::{BoxFuture, Shared},
- AsyncReadExt, FutureExt,
+ AsyncReadExt, FutureExt, TryFutureExt,
};
use image::ImageError;
use parking_lot::Mutex;
+use std::sync::Arc;
use thiserror::Error;
-use util::{
- arc_cow::ArcCow,
- http::{self, HttpClient},
-};
+use util::http::{self, HttpClient};
+
+#[derive(PartialEq, Eq, Hash, Clone)]
+pub struct RenderImageParams {
+ pub(crate) image_id: ImageId,
+}
#[derive(Debug, Error, Clone)]
pub enum Error {
@@ -43,7 +44,7 @@ impl From<ImageError> for Error {
pub struct ImageCache {
client: Arc<dyn HttpClient>,
- images: Arc<Mutex<HashMap<ArcCow<'static, str>, FetchImageFuture>>>,
+ images: Arc<Mutex<HashMap<SharedString, FetchImageFuture>>>,
}
type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;
@@ -58,12 +59,12 @@ impl ImageCache {
pub fn get(
&self,
- uri: impl Into<ArcCow<'static, str>>,
+ uri: impl Into<SharedString>,
) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
let uri = uri.into();
let mut images = self.images.lock();
- match images.get(uri.as_ref()) {
+ match images.get(&uri) {
Some(future) => future.clone(),
None => {
let client = self.client.clone();
@@ -84,9 +85,17 @@ impl ImageCache {
let format = image::guess_format(&body)?;
let image =
image::load_from_memory_with_format(&body, format)?.into_bgra8();
- Ok(ImageData::new(image))
+ Ok(Arc::new(ImageData::new(image)))
}
}
+ .map_err({
+ let uri = uri.clone();
+
+ move |error| {
+ log::log!(log::Level::Error, "{:?} {:?}", &uri, &error);
+ error
+ }
+ })
.boxed()
.shared();
@@ -1,43 +0,0 @@
-use crate::geometry::vector::{vec2i, Vector2I};
-use image::{Bgra, ImageBuffer};
-use std::{
- fmt,
- sync::{
- atomic::{AtomicUsize, Ordering::SeqCst},
- Arc,
- },
-};
-
-pub struct ImageData {
- pub id: usize,
- data: ImageBuffer<Bgra<u8>, Vec<u8>>,
-}
-
-impl ImageData {
- pub fn new(data: ImageBuffer<Bgra<u8>, Vec<u8>>) -> Arc<Self> {
- static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
-
- Arc::new(Self {
- id: NEXT_ID.fetch_add(1, SeqCst),
- data,
- })
- }
-
- pub fn as_bytes(&self) -> &[u8] {
- &self.data
- }
-
- pub fn size(&self) -> Vector2I {
- let (width, height) = self.data.dimensions();
- vec2i(width as i32, height as i32)
- }
-}
-
-impl fmt::Debug for ImageData {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_struct("ImageData")
- .field("id", &self.id)
- .field("size", &self.data.dimensions())
- .finish()
- }
-}
@@ -1,15 +0,0 @@
-pub use serde_json::*;
-
-pub trait ToJson {
- fn to_json(&self) -> Value;
-}
-
-impl<T: ToJson> ToJson for Option<T> {
- fn to_json(&self) -> Value {
- if let Some(value) = self.as_ref() {
- value.to_json()
- } else {
- json!(null)
- }
- }
-}
@@ -1,587 +0,0 @@
-mod binding;
-mod keymap;
-mod keymap_context;
-mod keystroke;
-
-use std::{any::TypeId, fmt::Debug};
-
-use collections::HashMap;
-use smallvec::SmallVec;
-
-use crate::{Action, NoAction};
-
-pub use binding::{Binding, BindingMatchResult};
-pub use keymap::Keymap;
-pub use keymap_context::{KeymapContext, KeymapContextPredicate};
-pub use keystroke::Keystroke;
-
-pub struct KeymapMatcher {
- pub contexts: Vec<KeymapContext>,
- pending_views: HashMap<usize, KeymapContext>,
- pending_keystrokes: Vec<Keystroke>,
- keymap: Keymap,
-}
-
-impl KeymapMatcher {
- pub fn new(keymap: Keymap) -> Self {
- Self {
- contexts: Vec::new(),
- pending_views: Default::default(),
- pending_keystrokes: Vec::new(),
- keymap,
- }
- }
-
- pub fn set_keymap(&mut self, keymap: Keymap) {
- self.clear_pending();
- self.keymap = keymap;
- }
-
- pub fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
- self.clear_pending();
- self.keymap.add_bindings(bindings);
- }
-
- pub fn clear_bindings(&mut self) {
- self.clear_pending();
- self.keymap.clear();
- }
-
- pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &Binding> {
- self.keymap.bindings_for_action(action_id)
- }
-
- pub fn clear_pending(&mut self) {
- self.pending_keystrokes.clear();
- self.pending_views.clear();
- }
-
- pub fn has_pending_keystrokes(&self) -> bool {
- !self.pending_keystrokes.is_empty()
- }
-
- /// Pushes a keystroke onto the matcher.
- /// The result of the new keystroke is returned:
- /// MatchResult::None =>
- /// No match is valid for this key given any pending keystrokes.
- /// MatchResult::Pending =>
- /// There exist bindings which are still waiting for more keys.
- /// MatchResult::Complete(matches) =>
- /// 1 or more bindings have received the necessary key presses.
- /// The order of the matched actions is by position of the matching first,
- // and order in the keymap second.
- pub fn push_keystroke(
- &mut self,
- keystroke: Keystroke,
- mut dispatch_path: Vec<(usize, KeymapContext)>,
- ) -> MatchResult {
- // Collect matched bindings into an ordered list using the position in the matching binding first,
- // and then the order the binding matched in the view tree second.
- // The key is the reverse position of the binding in the bindings list so that later bindings
- // match before earlier ones in the user's config
- let mut matched_bindings: Vec<(usize, Box<dyn Action>)> = Default::default();
- let no_action_id = (NoAction {}).id();
-
- let first_keystroke = self.pending_keystrokes.is_empty();
- let mut pending_key = None;
- let mut previous_keystrokes = self.pending_keystrokes.clone();
-
- self.contexts.clear();
- self.contexts
- .extend(dispatch_path.iter_mut().map(|e| std::mem::take(&mut e.1)));
-
- // Find the bindings which map the pending keystrokes and current context
- for (i, (view_id, _)) in dispatch_path.iter().enumerate() {
- // Don't require pending view entry if there are no pending keystrokes
- if !first_keystroke && !self.pending_views.contains_key(view_id) {
- continue;
- }
-
- // If there is a previous view context, invalidate that view if it
- // has changed
- if let Some(previous_view_context) = self.pending_views.remove(view_id) {
- if previous_view_context != self.contexts[i] {
- continue;
- }
- }
-
- for binding in self.keymap.bindings().iter().rev() {
- for possibility in keystroke.match_possibilities() {
- previous_keystrokes.push(possibility.clone());
- match binding.match_keys_and_context(&previous_keystrokes, &self.contexts[i..])
- {
- BindingMatchResult::Complete(action) => {
- if action.id() != no_action_id {
- matched_bindings.push((*view_id, action));
- }
- }
- BindingMatchResult::Partial => {
- if pending_key == None || pending_key == Some(possibility.clone()) {
- self.pending_views
- .insert(*view_id, self.contexts[i].clone());
- pending_key = Some(possibility)
- }
- }
- _ => {}
- }
- previous_keystrokes.pop();
- }
- }
- }
-
- if pending_key.is_some() {
- self.pending_keystrokes.push(pending_key.unwrap());
- } else {
- self.clear_pending();
- }
-
- if !matched_bindings.is_empty() {
- // Collect the sorted matched bindings into the final vec for ease of use
- // Matched bindings are in order by precedence
- MatchResult::Matches(matched_bindings)
- } else if !self.pending_keystrokes.is_empty() {
- MatchResult::Pending
- } else {
- MatchResult::None
- }
- }
-
- pub fn keystrokes_for_action(
- &self,
- action: &dyn Action,
- contexts: &[KeymapContext],
- ) -> Option<SmallVec<[Keystroke; 2]>> {
- self.keymap
- .bindings()
- .iter()
- .rev()
- .find_map(|binding| binding.keystrokes_for_action(action, contexts))
- }
-}
-
-impl Default for KeymapMatcher {
- fn default() -> Self {
- Self::new(Keymap::default())
- }
-}
-
-pub enum MatchResult {
- None,
- Pending,
- Matches(Vec<(usize, Box<dyn Action>)>),
-}
-
-impl Debug for MatchResult {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- MatchResult::None => f.debug_struct("MatchResult::None").finish(),
- MatchResult::Pending => f.debug_struct("MatchResult::Pending").finish(),
- MatchResult::Matches(matches) => f
- .debug_list()
- .entries(
- matches
- .iter()
- .map(|(view_id, action)| format!("{view_id}, {}", action.name())),
- )
- .finish(),
- }
- }
-}
-
-impl PartialEq for MatchResult {
- fn eq(&self, other: &Self) -> bool {
- match (self, other) {
- (MatchResult::None, MatchResult::None) => true,
- (MatchResult::Pending, MatchResult::Pending) => true,
- (MatchResult::Matches(matches), MatchResult::Matches(other_matches)) => {
- matches.len() == other_matches.len()
- && matches.iter().zip(other_matches.iter()).all(
- |((view_id, action), (other_view_id, other_action))| {
- view_id == other_view_id && action.eq(other_action.as_ref())
- },
- )
- }
- _ => false,
- }
- }
-}
-
-impl Eq for MatchResult {}
-
-impl Clone for MatchResult {
- fn clone(&self) -> Self {
- match self {
- MatchResult::None => MatchResult::None,
- MatchResult::Pending => MatchResult::Pending,
- MatchResult::Matches(matches) => MatchResult::Matches(
- matches
- .iter()
- .map(|(view_id, action)| (*view_id, Action::boxed_clone(action.as_ref())))
- .collect(),
- ),
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use anyhow::Result;
- use serde::Deserialize;
-
- use crate::{actions, impl_actions, keymap_matcher::KeymapContext};
-
- use super::*;
-
- #[test]
- fn test_keymap_and_view_ordering() -> Result<()> {
- actions!(test, [EditorAction, ProjectPanelAction]);
-
- let mut editor = KeymapContext::default();
- editor.add_identifier("Editor");
-
- let mut project_panel = KeymapContext::default();
- project_panel.add_identifier("ProjectPanel");
-
- // Editor 'deeper' in than project panel
- let dispatch_path = vec![(2, editor), (1, project_panel)];
-
- // But editor actions 'higher' up in keymap
- let keymap = Keymap::new(vec![
- Binding::new("left", EditorAction, Some("Editor")),
- Binding::new("left", ProjectPanelAction, Some("ProjectPanel")),
- ]);
-
- let mut matcher = KeymapMatcher::new(keymap);
-
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("left")?, dispatch_path.clone()),
- MatchResult::Matches(vec![
- (2, Box::new(EditorAction)),
- (1, Box::new(ProjectPanelAction)),
- ]),
- );
-
- Ok(())
- }
-
- #[test]
- fn test_push_keystroke() -> Result<()> {
- actions!(test, [B, AB, C, D, DA, E, EF]);
-
- let mut context1 = KeymapContext::default();
- context1.add_identifier("1");
-
- let mut context2 = KeymapContext::default();
- context2.add_identifier("2");
-
- let dispatch_path = vec![(2, context2), (1, context1)];
-
- let keymap = Keymap::new(vec![
- Binding::new("a b", AB, Some("1")),
- Binding::new("b", B, Some("2")),
- Binding::new("c", C, Some("2")),
- Binding::new("d", D, Some("1")),
- Binding::new("d", D, Some("2")),
- Binding::new("d a", DA, Some("2")),
- ]);
-
- let mut matcher = KeymapMatcher::new(keymap);
-
- // Binding with pending prefix always takes precedence
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
- MatchResult::Pending,
- );
- // B alone doesn't match because a was pending, so AB is returned instead
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
- MatchResult::Matches(vec![(1, Box::new(AB))]),
- );
- assert!(!matcher.has_pending_keystrokes());
-
- // Without an a prefix, B is dispatched like expected
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
- MatchResult::Matches(vec![(2, Box::new(B))]),
- );
- assert!(!matcher.has_pending_keystrokes());
-
- // If a is prefixed, C will not be dispatched because there
- // was a pending binding for it
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
- MatchResult::Pending,
- );
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("c")?, dispatch_path.clone()),
- MatchResult::None,
- );
- assert!(!matcher.has_pending_keystrokes());
-
- // If a single keystroke matches multiple bindings in the tree
- // all of them are returned so that we can fallback if the action
- // handler decides to propagate the action
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("d")?, dispatch_path.clone()),
- MatchResult::Matches(vec![(2, Box::new(D)), (1, Box::new(D))]),
- );
-
- // If none of the d action handlers consume the binding, a pending
- // binding may then be used
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
- MatchResult::Matches(vec![(2, Box::new(DA))]),
- );
- assert!(!matcher.has_pending_keystrokes());
-
- Ok(())
- }
-
- #[test]
- fn test_keystroke_parsing() -> Result<()> {
- assert_eq!(
- Keystroke::parse("ctrl-p")?,
- Keystroke {
- key: "p".into(),
- ctrl: true,
- alt: false,
- shift: false,
- cmd: false,
- function: false,
- ime_key: None,
- }
- );
-
- assert_eq!(
- Keystroke::parse("alt-shift-down")?,
- Keystroke {
- key: "down".into(),
- ctrl: false,
- alt: true,
- shift: true,
- cmd: false,
- function: false,
- ime_key: None,
- }
- );
-
- assert_eq!(
- Keystroke::parse("shift-cmd--")?,
- Keystroke {
- key: "-".into(),
- ctrl: false,
- alt: false,
- shift: true,
- cmd: true,
- function: false,
- ime_key: None,
- }
- );
-
- Ok(())
- }
-
- #[test]
- fn test_context_predicate_parsing() -> Result<()> {
- use KeymapContextPredicate::*;
-
- assert_eq!(
- KeymapContextPredicate::parse("a && (b == c || d != e)")?,
- And(
- Box::new(Identifier("a".into())),
- Box::new(Or(
- Box::new(Equal("b".into(), "c".into())),
- Box::new(NotEqual("d".into(), "e".into())),
- ))
- )
- );
-
- assert_eq!(
- KeymapContextPredicate::parse("!a")?,
- Not(Box::new(Identifier("a".into())),)
- );
-
- Ok(())
- }
-
- #[test]
- fn test_context_predicate_eval() {
- let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap();
-
- let mut context = KeymapContext::default();
- context.add_identifier("a");
- assert!(!predicate.eval(&[context]));
-
- let mut context = KeymapContext::default();
- context.add_identifier("a");
- context.add_identifier("b");
- assert!(predicate.eval(&[context]));
-
- let mut context = KeymapContext::default();
- context.add_identifier("a");
- context.add_key("c", "x");
- assert!(!predicate.eval(&[context]));
-
- let mut context = KeymapContext::default();
- context.add_identifier("a");
- context.add_key("c", "d");
- assert!(predicate.eval(&[context]));
-
- let predicate = KeymapContextPredicate::parse("!a").unwrap();
- assert!(predicate.eval(&[KeymapContext::default()]));
- }
-
- #[test]
- fn test_context_child_predicate_eval() {
- let predicate = KeymapContextPredicate::parse("a && b > c").unwrap();
- let contexts = [
- context_set(&["e", "f"]),
- context_set(&["c", "d"]), // match this context
- context_set(&["a", "b"]),
- ];
-
- assert!(!predicate.eval(&contexts[0..]));
- assert!(predicate.eval(&contexts[1..]));
- assert!(!predicate.eval(&contexts[2..]));
-
- let predicate = KeymapContextPredicate::parse("a && b > c && !d > e").unwrap();
- let contexts = [
- context_set(&["f"]),
- context_set(&["e"]), // only match this context
- context_set(&["c"]),
- context_set(&["a", "b"]),
- context_set(&["e"]),
- context_set(&["c", "d"]),
- context_set(&["a", "b"]),
- ];
-
- assert!(!predicate.eval(&contexts[0..]));
- assert!(predicate.eval(&contexts[1..]));
- assert!(!predicate.eval(&contexts[2..]));
- assert!(!predicate.eval(&contexts[3..]));
- assert!(!predicate.eval(&contexts[4..]));
- assert!(!predicate.eval(&contexts[5..]));
- assert!(!predicate.eval(&contexts[6..]));
-
- fn context_set(names: &[&str]) -> KeymapContext {
- let mut keymap = KeymapContext::new();
- names
- .iter()
- .for_each(|name| keymap.add_identifier(name.to_string()));
- keymap
- }
- }
-
- #[test]
- fn test_matcher() -> Result<()> {
- #[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
- pub struct A(pub String);
- impl_actions!(test, [A]);
- actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]);
-
- #[derive(Clone, Debug, Eq, PartialEq)]
- struct ActionArg {
- a: &'static str,
- }
-
- let keymap = Keymap::new(vec![
- Binding::new("a", A("x".to_string()), Some("a")),
- Binding::new("b", B, Some("a")),
- Binding::new("a b", Ab, Some("a || b")),
- Binding::new("$", Dollar, Some("a")),
- Binding::new("\"", Quote, Some("a")),
- Binding::new("alt-s", Ess, Some("a")),
- Binding::new("ctrl-`", Backtick, Some("a")),
- ]);
-
- let mut context_a = KeymapContext::default();
- context_a.add_identifier("a");
-
- let mut context_b = KeymapContext::default();
- context_b.add_identifier("b");
-
- let mut matcher = KeymapMatcher::new(keymap);
-
- // Basic match
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
- MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))])
- );
- matcher.clear_pending();
-
- // Multi-keystroke match
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
- MatchResult::Pending
- );
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
- MatchResult::Matches(vec![(1, Box::new(Ab))])
- );
- matcher.clear_pending();
-
- // Failed matches don't interfere with matching subsequent keys
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("x")?, vec![(1, context_a.clone())]),
- MatchResult::None
- );
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
- MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))])
- );
- matcher.clear_pending();
-
- // Pending keystrokes are cleared when the context changes
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
- MatchResult::Pending
- );
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_a.clone())]),
- MatchResult::None
- );
- matcher.clear_pending();
-
- let mut context_c = KeymapContext::default();
- context_c.add_identifier("c");
-
- // Pending keystrokes are maintained per-view
- assert_eq!(
- matcher.push_keystroke(
- Keystroke::parse("a")?,
- vec![(1, context_b.clone()), (2, context_c.clone())]
- ),
- MatchResult::Pending
- );
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
- MatchResult::Matches(vec![(1, Box::new(Ab))])
- );
-
- // handle Czech $ (option + 4 key)
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("alt-ç->$")?, vec![(1, context_a.clone())]),
- MatchResult::Matches(vec![(1, Box::new(Dollar))])
- );
-
- // handle Brazillian quote (quote key then space key)
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("space->\"")?, vec![(1, context_a.clone())]),
- MatchResult::Matches(vec![(1, Box::new(Quote))])
- );
-
- // handle ctrl+` on a brazillian keyboard
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("ctrl-->`")?, vec![(1, context_a.clone())]),
- MatchResult::Matches(vec![(1, Box::new(Backtick))])
- );
-
- // handle alt-s on a US keyboard
- assert_eq!(
- matcher.push_keystroke(Keystroke::parse("alt-s->ß")?, vec![(1, context_a.clone())]),
- MatchResult::Matches(vec![(1, Box::new(Ess))])
- );
-
- Ok(())
- }
-}
@@ -1,111 +0,0 @@
-use anyhow::Result;
-use smallvec::SmallVec;
-
-use crate::Action;
-
-use super::{KeymapContext, KeymapContextPredicate, Keystroke};
-
-pub struct Binding {
- action: Box<dyn Action>,
- pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
- pub(super) context_predicate: Option<KeymapContextPredicate>,
-}
-
-impl std::fmt::Debug for Binding {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(
- f,
- "Binding {{ keystrokes: {:?}, action: {}::{}, context_predicate: {:?} }}",
- self.keystrokes,
- self.action.namespace(),
- self.action.name(),
- self.context_predicate
- )
- }
-}
-
-impl Clone for Binding {
- fn clone(&self) -> Self {
- Self {
- action: self.action.boxed_clone(),
- keystrokes: self.keystrokes.clone(),
- context_predicate: self.context_predicate.clone(),
- }
- }
-}
-
-impl Binding {
- pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
- Self::load(keystrokes, Box::new(action), context).unwrap()
- }
-
- pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
- let context = if let Some(context) = context {
- Some(KeymapContextPredicate::parse(context)?)
- } else {
- None
- };
-
- let keystrokes = keystrokes
- .split_whitespace()
- .map(Keystroke::parse)
- .collect::<Result<_>>()?;
-
- Ok(Self {
- keystrokes,
- action,
- context_predicate: context,
- })
- }
-
- pub fn match_context(&self, contexts: &[KeymapContext]) -> bool {
- self.context_predicate
- .as_ref()
- .map(|predicate| predicate.eval(contexts))
- .unwrap_or(true)
- }
-
- pub fn match_keys_and_context(
- &self,
- pending_keystrokes: &Vec<Keystroke>,
- contexts: &[KeymapContext],
- ) -> BindingMatchResult {
- if self.keystrokes.as_ref().starts_with(&pending_keystrokes) && self.match_context(contexts)
- {
- // If the binding is completed, push it onto the matches list
- if self.keystrokes.as_ref().len() == pending_keystrokes.len() {
- BindingMatchResult::Complete(self.action.boxed_clone())
- } else {
- BindingMatchResult::Partial
- }
- } else {
- BindingMatchResult::Fail
- }
- }
-
- pub fn keystrokes_for_action(
- &self,
- action: &dyn Action,
- contexts: &[KeymapContext],
- ) -> Option<SmallVec<[Keystroke; 2]>> {
- if self.action.eq(action) && self.match_context(contexts) {
- Some(self.keystrokes.clone())
- } else {
- None
- }
- }
-
- pub fn keystrokes(&self) -> &[Keystroke] {
- self.keystrokes.as_slice()
- }
-
- pub fn action(&self) -> &dyn Action {
- self.action.as_ref()
- }
-}
-
-pub enum BindingMatchResult {
- Complete(Box<dyn Action>),
- Partial,
- Fail,
-}
@@ -1,392 +0,0 @@
-use collections::HashSet;
-use smallvec::SmallVec;
-use std::{any::TypeId, collections::HashMap};
-
-use crate::{Action, NoAction};
-
-use super::{Binding, KeymapContextPredicate, Keystroke};
-
-#[derive(Default)]
-pub struct Keymap {
- bindings: Vec<Binding>,
- binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
- disabled_keystrokes: HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeymapContextPredicate>>>,
-}
-
-impl Keymap {
- #[cfg(test)]
- pub(super) fn new(bindings: Vec<Binding>) -> Self {
- let mut this = Self::default();
- this.add_bindings(bindings);
- this
- }
-
- pub(crate) fn bindings_for_action(
- &self,
- action_id: TypeId,
- ) -> impl Iterator<Item = &'_ Binding> {
- self.binding_indices_by_action_id
- .get(&action_id)
- .map(SmallVec::as_slice)
- .unwrap_or(&[])
- .iter()
- .map(|ix| &self.bindings[*ix])
- .filter(|binding| !self.binding_disabled(binding))
- }
-
- pub(crate) fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
- let no_action_id = (NoAction {}).id();
- let mut new_bindings = Vec::new();
- let mut has_new_disabled_keystrokes = false;
- for binding in bindings {
- if binding.action().id() == no_action_id {
- has_new_disabled_keystrokes |= self
- .disabled_keystrokes
- .entry(binding.keystrokes)
- .or_default()
- .insert(binding.context_predicate);
- } else {
- new_bindings.push(binding);
- }
- }
-
- if has_new_disabled_keystrokes {
- self.binding_indices_by_action_id.retain(|_, indices| {
- indices.retain(|ix| {
- let binding = &self.bindings[*ix];
- match self.disabled_keystrokes.get(&binding.keystrokes) {
- Some(disabled_predicates) => {
- !disabled_predicates.contains(&binding.context_predicate)
- }
- None => true,
- }
- });
- !indices.is_empty()
- });
- }
-
- for new_binding in new_bindings {
- if !self.binding_disabled(&new_binding) {
- self.binding_indices_by_action_id
- .entry(new_binding.action().id())
- .or_default()
- .push(self.bindings.len());
- self.bindings.push(new_binding);
- }
- }
- }
-
- pub(crate) fn clear(&mut self) {
- self.bindings.clear();
- self.binding_indices_by_action_id.clear();
- self.disabled_keystrokes.clear();
- }
-
- pub fn bindings(&self) -> Vec<&Binding> {
- self.bindings
- .iter()
- .filter(|binding| !self.binding_disabled(binding))
- .collect()
- }
-
- fn binding_disabled(&self, binding: &Binding) -> bool {
- match self.disabled_keystrokes.get(&binding.keystrokes) {
- Some(disabled_predicates) => disabled_predicates.contains(&binding.context_predicate),
- None => false,
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use crate::actions;
-
- use super::*;
-
- actions!(
- keymap_test,
- [Present1, Present2, Present3, Duplicate, Missing]
- );
-
- #[test]
- fn regular_keymap() {
- let present_1 = Binding::new("ctrl-q", Present1 {}, None);
- let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
- let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
- let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
- let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
- let missing = Binding::new("ctrl-r", Missing {}, None);
- let all_bindings = [
- &present_1,
- &present_2,
- &present_3,
- &keystroke_duplicate_to_1,
- &full_duplicate_to_2,
- &missing,
- ];
-
- let mut keymap = Keymap::default();
- assert_absent(&keymap, &all_bindings);
- assert!(keymap.bindings().is_empty());
-
- keymap.add_bindings([present_1.clone(), present_2.clone(), present_3.clone()]);
- assert_absent(&keymap, &[&keystroke_duplicate_to_1, &missing]);
- assert_present(
- &keymap,
- &[(&present_1, "q"), (&present_2, "w"), (&present_3, "e")],
- );
-
- keymap.add_bindings([
- keystroke_duplicate_to_1.clone(),
- full_duplicate_to_2.clone(),
- ]);
- assert_absent(&keymap, &[&missing]);
- assert!(
- !keymap.binding_disabled(&keystroke_duplicate_to_1),
- "Duplicate binding 1 was added and should not be disabled"
- );
- assert!(
- !keymap.binding_disabled(&full_duplicate_to_2),
- "Duplicate binding 2 was added and should not be disabled"
- );
-
- assert_eq!(
- keymap
- .bindings_for_action(keystroke_duplicate_to_1.action().id())
- .map(|binding| &binding.keystrokes)
- .flatten()
- .collect::<Vec<_>>(),
- vec![&Keystroke {
- ctrl: true,
- alt: false,
- shift: false,
- cmd: false,
- function: false,
- key: "q".to_string(),
- ime_key: None,
- }],
- "{keystroke_duplicate_to_1:?} should have the expected keystroke in the keymap"
- );
- assert_eq!(
- keymap
- .bindings_for_action(full_duplicate_to_2.action().id())
- .map(|binding| &binding.keystrokes)
- .flatten()
- .collect::<Vec<_>>(),
- vec![
- &Keystroke {
- ctrl: true,
- alt: false,
- shift: false,
- cmd: false,
- function: false,
- key: "w".to_string(),
- ime_key: None,
- },
- &Keystroke {
- ctrl: true,
- alt: false,
- shift: false,
- cmd: false,
- function: false,
- key: "w".to_string(),
- ime_key: None,
- }
- ],
- "{full_duplicate_to_2:?} should have a duplicated keystroke in the keymap"
- );
-
- let updated_bindings = keymap.bindings();
- let expected_updated_bindings = vec![
- &present_1,
- &present_2,
- &present_3,
- &keystroke_duplicate_to_1,
- &full_duplicate_to_2,
- ];
- assert_eq!(
- updated_bindings.len(),
- expected_updated_bindings.len(),
- "Unexpected updated keymap bindings {updated_bindings:?}"
- );
- for (i, expected) in expected_updated_bindings.iter().enumerate() {
- let keymap_binding = &updated_bindings[i];
- assert_eq!(
- keymap_binding.context_predicate, expected.context_predicate,
- "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
- );
- assert_eq!(
- keymap_binding.keystrokes, expected.keystrokes,
- "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
- );
- }
-
- keymap.clear();
- assert_absent(&keymap, &all_bindings);
- assert!(keymap.bindings().is_empty());
- }
-
- #[test]
- fn keymap_with_ignored() {
- let present_1 = Binding::new("ctrl-q", Present1 {}, None);
- let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
- let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
- let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
- let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
- let ignored_1 = Binding::new("ctrl-q", NoAction {}, None);
- let ignored_2 = Binding::new("ctrl-w", NoAction {}, Some("pane"));
- let ignored_3_with_other_context =
- Binding::new("ctrl-e", NoAction {}, Some("other_context"));
-
- let mut keymap = Keymap::default();
-
- keymap.add_bindings([
- ignored_1.clone(),
- ignored_2.clone(),
- ignored_3_with_other_context.clone(),
- ]);
- assert_absent(&keymap, &[&present_3]);
- assert_disabled(
- &keymap,
- &[
- &present_1,
- &present_2,
- &ignored_1,
- &ignored_2,
- &ignored_3_with_other_context,
- ],
- );
- assert!(keymap.bindings().is_empty());
- keymap.clear();
-
- keymap.add_bindings([
- present_1.clone(),
- present_2.clone(),
- present_3.clone(),
- ignored_1.clone(),
- ignored_2.clone(),
- ignored_3_with_other_context.clone(),
- ]);
- assert_present(&keymap, &[(&present_3, "e")]);
- assert_disabled(
- &keymap,
- &[
- &present_1,
- &present_2,
- &ignored_1,
- &ignored_2,
- &ignored_3_with_other_context,
- ],
- );
- keymap.clear();
-
- keymap.add_bindings([
- present_1.clone(),
- present_2.clone(),
- present_3.clone(),
- ignored_1.clone(),
- ]);
- assert_present(&keymap, &[(&present_2, "w"), (&present_3, "e")]);
- assert_disabled(&keymap, &[&present_1, &ignored_1]);
- assert_absent(&keymap, &[&ignored_2, &ignored_3_with_other_context]);
- keymap.clear();
-
- keymap.add_bindings([
- present_1.clone(),
- present_2.clone(),
- present_3.clone(),
- keystroke_duplicate_to_1.clone(),
- full_duplicate_to_2.clone(),
- ignored_1.clone(),
- ignored_2.clone(),
- ignored_3_with_other_context.clone(),
- ]);
- assert_present(&keymap, &[(&present_3, "e")]);
- assert_disabled(
- &keymap,
- &[
- &present_1,
- &present_2,
- &keystroke_duplicate_to_1,
- &full_duplicate_to_2,
- &ignored_1,
- &ignored_2,
- &ignored_3_with_other_context,
- ],
- );
- keymap.clear();
- }
-
- #[track_caller]
- fn assert_present(keymap: &Keymap, expected_bindings: &[(&Binding, &str)]) {
- let keymap_bindings = keymap.bindings();
- assert_eq!(
- expected_bindings.len(),
- keymap_bindings.len(),
- "Unexpected keymap bindings {keymap_bindings:?}"
- );
- for (i, (expected, expected_key)) in expected_bindings.iter().enumerate() {
- assert!(
- !keymap.binding_disabled(expected),
- "{expected:?} should not be disabled as it was added into keymap for element {i}"
- );
- assert_eq!(
- keymap
- .bindings_for_action(expected.action().id())
- .map(|binding| &binding.keystrokes)
- .flatten()
- .collect::<Vec<_>>(),
- vec![&Keystroke {
- ctrl: true,
- alt: false,
- shift: false,
- cmd: false,
- function: false,
- key: expected_key.to_string(),
- ime_key: None,
- }],
- "{expected:?} should have the expected keystroke with key '{expected_key}' in the keymap for element {i}"
- );
-
- let keymap_binding = &keymap_bindings[i];
- assert_eq!(
- keymap_binding.context_predicate, expected.context_predicate,
- "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
- );
- assert_eq!(
- keymap_binding.keystrokes, expected.keystrokes,
- "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
- );
- }
- }
-
- #[track_caller]
- fn assert_absent(keymap: &Keymap, bindings: &[&Binding]) {
- for binding in bindings.iter() {
- assert!(
- !keymap.binding_disabled(binding),
- "{binding:?} should not be disabled in the keymap where was not added"
- );
- assert_eq!(
- keymap.bindings_for_action(binding.action().id()).count(),
- 0,
- "{binding:?} should have no actions in the keymap where was not added"
- );
- }
- }
-
- #[track_caller]
- fn assert_disabled(keymap: &Keymap, bindings: &[&Binding]) {
- for binding in bindings.iter() {
- assert!(
- keymap.binding_disabled(binding),
- "{binding:?} should be disabled in the keymap"
- );
- assert_eq!(
- keymap.bindings_for_action(binding.action().id()).count(),
- 0,
- "{binding:?} should have no actions in the keymap where it was disabled"
- );
- }
- }
-}
@@ -1,326 +0,0 @@
-use std::borrow::Cow;
-
-use anyhow::{anyhow, Result};
-use collections::{HashMap, HashSet};
-
-#[derive(Clone, Debug, Default, Eq, PartialEq)]
-pub struct KeymapContext {
- set: HashSet<Cow<'static, str>>,
- map: HashMap<Cow<'static, str>, Cow<'static, str>>,
-}
-
-impl KeymapContext {
- pub fn new() -> Self {
- KeymapContext {
- set: HashSet::default(),
- map: HashMap::default(),
- }
- }
-
- pub fn clear(&mut self) {
- self.set.clear();
- self.map.clear();
- }
-
- pub fn extend(&mut self, other: &Self) {
- for v in &other.set {
- self.set.insert(v.clone());
- }
- for (k, v) in &other.map {
- self.map.insert(k.clone(), v.clone());
- }
- }
-
- pub fn add_identifier<I: Into<Cow<'static, str>>>(&mut self, identifier: I) {
- self.set.insert(identifier.into());
- }
-
- pub fn add_key<S1: Into<Cow<'static, str>>, S2: Into<Cow<'static, str>>>(
- &mut self,
- key: S1,
- value: S2,
- ) {
- self.map.insert(key.into(), value.into());
- }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq, Hash)]
-pub enum KeymapContextPredicate {
- Identifier(String),
- Equal(String, String),
- NotEqual(String, String),
- Child(Box<KeymapContextPredicate>, Box<KeymapContextPredicate>),
- Not(Box<KeymapContextPredicate>),
- And(Box<KeymapContextPredicate>, Box<KeymapContextPredicate>),
- Or(Box<KeymapContextPredicate>, Box<KeymapContextPredicate>),
-}
-
-impl KeymapContextPredicate {
- pub fn parse(source: &str) -> Result<Self> {
- let source = Self::skip_whitespace(source);
- let (predicate, rest) = Self::parse_expr(source, 0)?;
- if let Some(next) = rest.chars().next() {
- Err(anyhow!("unexpected character {next:?}"))
- } else {
- Ok(predicate)
- }
- }
-
- pub fn eval(&self, contexts: &[KeymapContext]) -> bool {
- let Some(context) = contexts.first() else {
- return false;
- };
- match self {
- Self::Identifier(name) => (&context.set).contains(name.as_str()),
- Self::Equal(left, right) => context
- .map
- .get(left.as_str())
- .map(|value| value == right)
- .unwrap_or(false),
- Self::NotEqual(left, right) => context
- .map
- .get(left.as_str())
- .map(|value| value != right)
- .unwrap_or(true),
- Self::Not(pred) => !pred.eval(contexts),
- Self::Child(parent, child) => parent.eval(&contexts[1..]) && child.eval(contexts),
- Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
- Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
- }
- }
-
- fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
- type Op =
- fn(KeymapContextPredicate, KeymapContextPredicate) -> Result<KeymapContextPredicate>;
-
- let (mut predicate, rest) = Self::parse_primary(source)?;
- source = rest;
-
- 'parse: loop {
- for (operator, precedence, constructor) in [
- (">", PRECEDENCE_CHILD, Self::new_child as Op),
- ("&&", PRECEDENCE_AND, Self::new_and as Op),
- ("||", PRECEDENCE_OR, Self::new_or as Op),
- ("==", PRECEDENCE_EQ, Self::new_eq as Op),
- ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
- ] {
- if source.starts_with(operator) && precedence >= min_precedence {
- source = Self::skip_whitespace(&source[operator.len()..]);
- let (right, rest) = Self::parse_expr(source, precedence + 1)?;
- predicate = constructor(predicate, right)?;
- source = rest;
- continue 'parse;
- }
- }
- break;
- }
-
- Ok((predicate, source))
- }
-
- fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
- let next = source
- .chars()
- .next()
- .ok_or_else(|| anyhow!("unexpected eof"))?;
- match next {
- '(' => {
- source = Self::skip_whitespace(&source[1..]);
- let (predicate, rest) = Self::parse_expr(source, 0)?;
- if rest.starts_with(')') {
- source = Self::skip_whitespace(&rest[1..]);
- Ok((predicate, source))
- } else {
- Err(anyhow!("expected a ')'"))
- }
- }
- '!' => {
- let source = Self::skip_whitespace(&source[1..]);
- let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
- Ok((KeymapContextPredicate::Not(Box::new(predicate)), source))
- }
- _ if next.is_alphanumeric() || next == '_' => {
- let len = source
- .find(|c: char| !(c.is_alphanumeric() || c == '_'))
- .unwrap_or(source.len());
- let (identifier, rest) = source.split_at(len);
- source = Self::skip_whitespace(rest);
- Ok((
- KeymapContextPredicate::Identifier(identifier.into()),
- source,
- ))
- }
- _ => Err(anyhow!("unexpected character {next:?}")),
- }
- }
-
- fn skip_whitespace(source: &str) -> &str {
- let len = source
- .find(|c: char| !c.is_whitespace())
- .unwrap_or(source.len());
- &source[len..]
- }
-
- fn new_or(self, other: Self) -> Result<Self> {
- Ok(Self::Or(Box::new(self), Box::new(other)))
- }
-
- fn new_and(self, other: Self) -> Result<Self> {
- Ok(Self::And(Box::new(self), Box::new(other)))
- }
-
- fn new_child(self, other: Self) -> Result<Self> {
- Ok(Self::Child(Box::new(self), Box::new(other)))
- }
-
- fn new_eq(self, other: Self) -> Result<Self> {
- if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
- Ok(Self::Equal(left, right))
- } else {
- Err(anyhow!("operands must be identifiers"))
- }
- }
-
- fn new_neq(self, other: Self) -> Result<Self> {
- if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
- Ok(Self::NotEqual(left, right))
- } else {
- Err(anyhow!("operands must be identifiers"))
- }
- }
-}
-
-const PRECEDENCE_CHILD: u32 = 1;
-const PRECEDENCE_OR: u32 = 2;
-const PRECEDENCE_AND: u32 = 3;
-const PRECEDENCE_EQ: u32 = 4;
-const PRECEDENCE_NOT: u32 = 5;
-
-#[cfg(test)]
-mod tests {
- use super::KeymapContextPredicate::{self, *};
-
- #[test]
- fn test_parse_identifiers() {
- // Identifiers
- assert_eq!(
- KeymapContextPredicate::parse("abc12").unwrap(),
- Identifier("abc12".into())
- );
- assert_eq!(
- KeymapContextPredicate::parse("_1a").unwrap(),
- Identifier("_1a".into())
- );
- }
-
- #[test]
- fn test_parse_negations() {
- assert_eq!(
- KeymapContextPredicate::parse("!abc").unwrap(),
- Not(Box::new(Identifier("abc".into())))
- );
- assert_eq!(
- KeymapContextPredicate::parse(" ! ! abc").unwrap(),
- Not(Box::new(Not(Box::new(Identifier("abc".into())))))
- );
- }
-
- #[test]
- fn test_parse_equality_operators() {
- assert_eq!(
- KeymapContextPredicate::parse("a == b").unwrap(),
- Equal("a".into(), "b".into())
- );
- assert_eq!(
- KeymapContextPredicate::parse("c!=d").unwrap(),
- NotEqual("c".into(), "d".into())
- );
- assert_eq!(
- KeymapContextPredicate::parse("c == !d")
- .unwrap_err()
- .to_string(),
- "operands must be identifiers"
- );
- }
-
- #[test]
- fn test_parse_boolean_operators() {
- assert_eq!(
- KeymapContextPredicate::parse("a || b").unwrap(),
- Or(
- Box::new(Identifier("a".into())),
- Box::new(Identifier("b".into()))
- )
- );
- assert_eq!(
- KeymapContextPredicate::parse("a || !b && c").unwrap(),
- Or(
- Box::new(Identifier("a".into())),
- Box::new(And(
- Box::new(Not(Box::new(Identifier("b".into())))),
- Box::new(Identifier("c".into()))
- ))
- )
- );
- assert_eq!(
- KeymapContextPredicate::parse("a && b || c&&d").unwrap(),
- Or(
- Box::new(And(
- Box::new(Identifier("a".into())),
- Box::new(Identifier("b".into()))
- )),
- Box::new(And(
- Box::new(Identifier("c".into())),
- Box::new(Identifier("d".into()))
- ))
- )
- );
- assert_eq!(
- KeymapContextPredicate::parse("a == b && c || d == e && f").unwrap(),
- Or(
- Box::new(And(
- Box::new(Equal("a".into(), "b".into())),
- Box::new(Identifier("c".into()))
- )),
- Box::new(And(
- Box::new(Equal("d".into(), "e".into())),
- Box::new(Identifier("f".into()))
- ))
- )
- );
- assert_eq!(
- KeymapContextPredicate::parse("a && b && c && d").unwrap(),
- And(
- Box::new(And(
- Box::new(And(
- Box::new(Identifier("a".into())),
- Box::new(Identifier("b".into()))
- )),
- Box::new(Identifier("c".into())),
- )),
- Box::new(Identifier("d".into()))
- ),
- );
- }
-
- #[test]
- fn test_parse_parenthesized_expressions() {
- assert_eq!(
- KeymapContextPredicate::parse("a && (b == c || d != e)").unwrap(),
- And(
- Box::new(Identifier("a".into())),
- Box::new(Or(
- Box::new(Equal("b".into(), "c".into())),
- Box::new(NotEqual("d".into(), "e".into())),
- )),
- ),
- );
- assert_eq!(
- KeymapContextPredicate::parse(" ( a || b ) ").unwrap(),
- Or(
- Box::new(Identifier("a".into())),
- Box::new(Identifier("b".into())),
- )
- );
- }
-}
@@ -1,141 +0,0 @@
-use std::fmt::Write;
-
-use anyhow::anyhow;
-use serde::Deserialize;
-use smallvec::SmallVec;
-
-#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
-pub struct Keystroke {
- pub ctrl: bool,
- pub alt: bool,
- pub shift: bool,
- pub cmd: bool,
- pub function: bool,
- /// key is the character printed on the key that was pressed
- /// e.g. for option-s, key is "s"
- pub key: String,
- /// ime_key is the character inserted by the IME engine when that key was pressed.
- /// e.g. for option-s, ime_key is "ß"
- pub ime_key: Option<String>,
-}
-
-impl Keystroke {
- // When matching a key we cannot know whether the user intended to type
- // the ime_key or the key. On some non-US keyboards keys we use in our
- // bindings are behind option (for example `$` is typed `alt-ç` on a Czech keyboard),
- // and on some keyboards the IME handler converts a sequence of keys into a
- // specific character (for example `"` is typed as `" space` on a brazillian keyboard).
- pub fn match_possibilities(&self) -> SmallVec<[Keystroke; 2]> {
- let mut possibilities = SmallVec::new();
- match self.ime_key.as_ref() {
- None => possibilities.push(self.clone()),
- Some(ime_key) => {
- possibilities.push(Keystroke {
- ctrl: self.ctrl,
- alt: false,
- shift: false,
- cmd: false,
- function: false,
- key: ime_key.to_string(),
- ime_key: None,
- });
- possibilities.push(Keystroke {
- ime_key: None,
- ..self.clone()
- });
- }
- }
- possibilities
- }
-
- /// key syntax is:
- /// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
- /// ime_key is only used for generating test events,
- /// when matching a key with an ime_key set will be matched without it.
- pub fn parse(source: &str) -> anyhow::Result<Self> {
- let mut ctrl = false;
- let mut alt = false;
- let mut shift = false;
- let mut cmd = false;
- let mut function = false;
- let mut key = None;
- let mut ime_key = None;
-
- let mut components = source.split('-').peekable();
- while let Some(component) = components.next() {
- match component {
- "ctrl" => ctrl = true,
- "alt" => alt = true,
- "shift" => shift = true,
- "cmd" => cmd = true,
- "fn" => function = true,
- _ => {
- if let Some(next) = components.peek() {
- if next.is_empty() && source.ends_with('-') {
- key = Some(String::from("-"));
- break;
- } else if next.len() > 1 && next.starts_with('>') {
- key = Some(String::from(component));
- ime_key = Some(String::from(&next[1..]));
- components.next();
- } else {
- return Err(anyhow!("Invalid keystroke `{}`", source));
- }
- } else {
- key = Some(String::from(component));
- }
- }
- }
- }
-
- let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
-
- Ok(Keystroke {
- ctrl,
- alt,
- shift,
- cmd,
- function,
- key,
- ime_key,
- })
- }
-
- pub fn modified(&self) -> bool {
- self.ctrl || self.alt || self.shift || self.cmd
- }
-}
-
-impl std::fmt::Display for Keystroke {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- if self.ctrl {
- f.write_char('^')?;
- }
- if self.alt {
- f.write_char('⌥')?;
- }
- if self.cmd {
- f.write_char('⌘')?;
- }
- if self.shift {
- f.write_char('⇧')?;
- }
- let key = match self.key.as_str() {
- "backspace" => '⌫',
- "up" => '↑',
- "down" => '↓',
- "left" => '←',
- "right" => '→',
- "tab" => '⇥',
- "escape" => '⎋',
- key => {
- if key.len() == 1 {
- key.chars().next().unwrap().to_ascii_uppercase()
- } else {
- return f.write_str(key);
- }
- }
- };
- f.write_char(key)
- }
-}
@@ -1,37 +1,27 @@
-mod event;
+mod app_menu;
+mod keystroke;
#[cfg(target_os = "macos")]
-pub mod mac;
-pub mod test;
-pub mod current {
- #[cfg(target_os = "macos")]
- pub use super::mac::*;
-}
+mod mac;
+#[cfg(any(test, feature = "test-support"))]
+mod test;
use crate::{
- executor,
- fonts::{
- Features as FontFeatures, FontId, GlyphId, Metrics as FontMetrics,
- Properties as FontProperties,
- },
- geometry::{
- rect::{RectF, RectI},
- vector::Vector2F,
- },
- keymap_matcher::KeymapMatcher,
- text_layout::{LineLayout, RunStyle},
- Action, AnyWindowHandle, ClipboardItem, Menu, Scene,
+ point, size, Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId,
+ FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, Keymap,
+ LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
+ Scene, SharedString, Size, TaskLabel,
};
-use anyhow::{anyhow, bail, Result};
+use anyhow::{anyhow, bail};
use async_task::Runnable;
-pub use event::*;
-use pathfinder_geometry::vector::vec2f;
-use postage::oneshot;
-use schemars::JsonSchema;
-use serde::Deserialize;
-use sqlez::{
- bindable::{Bind, Column, StaticColumnCount},
- statement::Statement,
-};
+use futures::channel::oneshot;
+use parking::Unparker;
+use seahash::SeaHasher;
+use serde::{Deserialize, Serialize};
+use sqlez::bindable::{Bind, Column, StaticColumnCount};
+use sqlez::statement::Statement;
+use std::borrow::Cow;
+use std::hash::{Hash, Hasher};
+use std::time::Duration;
use std::{
any::Any,
fmt::{self, Debug, Display},
@@ -41,115 +31,126 @@ use std::{
str::FromStr,
sync::Arc,
};
-use time::UtcOffset;
use uuid::Uuid;
-pub trait Platform: Send + Sync {
- fn dispatcher(&self) -> Arc<dyn Dispatcher>;
- fn fonts(&self) -> Arc<dyn FontSystem>;
+pub use app_menu::*;
+pub use keystroke::*;
+#[cfg(target_os = "macos")]
+pub use mac::*;
+#[cfg(any(test, feature = "test-support"))]
+pub use test::*;
+pub use time::UtcOffset;
+
+#[cfg(target_os = "macos")]
+pub(crate) fn current_platform() -> Rc<dyn Platform> {
+ Rc::new(MacPlatform::new())
+}
+
+pub type DrawWindow = Box<dyn FnMut() -> Result<Scene>>;
+
+pub(crate) trait Platform: 'static {
+ fn background_executor(&self) -> BackgroundExecutor;
+ fn foreground_executor(&self) -> ForegroundExecutor;
+ fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
+ fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
+ fn quit(&self);
+ fn restart(&self);
fn activate(&self, ignoring_other_apps: bool);
fn hide(&self);
fn hide_other_apps(&self);
fn unhide_other_apps(&self);
- fn quit(&self);
-
- fn screen_by_id(&self, id: Uuid) -> Option<Rc<dyn Screen>>;
- fn screens(&self) -> Vec<Rc<dyn Screen>>;
+ fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
+ fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
+ fn active_window(&self) -> Option<AnyWindowHandle>;
fn open_window(
&self,
handle: AnyWindowHandle,
options: WindowOptions,
- executor: Rc<executor::Foreground>,
- ) -> Box<dyn Window>;
- fn main_window(&self) -> Option<AnyWindowHandle>;
+ draw: DrawWindow,
+ ) -> Box<dyn PlatformWindow>;
- fn add_status_item(&self, handle: AnyWindowHandle) -> Box<dyn Window>;
+ fn set_display_link_output_callback(
+ &self,
+ display_id: DisplayId,
+ callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
+ );
+ fn start_display_link(&self, display_id: DisplayId);
+ fn stop_display_link(&self, display_id: DisplayId);
+ // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn PlatformWindow>;
- fn write_to_clipboard(&self, item: ClipboardItem);
- fn read_from_clipboard(&self) -> Option<ClipboardItem>;
fn open_url(&self, url: &str);
+ fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
+ fn prompt_for_paths(
+ &self,
+ options: PathPromptOptions,
+ ) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
+ fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
+ fn reveal_path(&self, path: &Path);
- fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
- fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
- fn delete_credentials(&self, url: &str) -> Result<()>;
+ fn on_become_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_reopen(&self, callback: Box<dyn FnMut()>);
+ fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
- fn set_cursor_style(&self, style: CursorStyle);
- fn should_auto_hide_scrollbars(&self) -> bool;
+ fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
+ fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
+ fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
+ fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
+ fn os_name(&self) -> &'static str;
+ fn os_version(&self) -> Result<SemanticVersion>;
+ fn app_version(&self) -> Result<SemanticVersion>;
+ fn app_path(&self) -> Result<PathBuf>;
fn local_timezone(&self) -> UtcOffset;
-
+ fn double_click_interval(&self) -> Duration;
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
- fn app_path(&self) -> Result<PathBuf>;
- fn app_version(&self) -> Result<AppVersion>;
- fn os_name(&self) -> &'static str;
- fn os_version(&self) -> Result<AppVersion>;
- fn restart(&self);
-}
-
-pub(crate) trait ForegroundPlatform {
- fn on_become_active(&self, callback: Box<dyn FnMut()>);
- fn on_resign_active(&self, callback: Box<dyn FnMut()>);
- fn on_quit(&self, callback: Box<dyn FnMut()>);
- /// Handle the application being re-activated with no windows open.
- fn on_reopen(&self, callback: Box<dyn FnMut()>);
+ fn set_cursor_style(&self, style: CursorStyle);
+ fn should_auto_hide_scrollbars(&self) -> bool;
- fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
- fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
- fn run(&self, on_finish_launching: Box<dyn FnOnce()>);
+ fn write_to_clipboard(&self, item: ClipboardItem);
+ fn read_from_clipboard(&self) -> Option<ClipboardItem>;
- fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>);
- fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
- fn on_will_open_menu(&self, callback: Box<dyn FnMut()>);
- fn set_menus(&self, menus: Vec<Menu>, matcher: &KeymapMatcher);
- fn prompt_for_paths(
- &self,
- options: PathPromptOptions,
- ) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
- fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
- fn reveal_path(&self, path: &Path);
+ fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
+ fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
+ fn delete_credentials(&self, url: &str) -> Result<()>;
}
-pub trait Dispatcher: Send + Sync {
- fn is_main_thread(&self) -> bool;
- fn run_on_main_thread(&self, task: Runnable);
+pub trait PlatformDisplay: Send + Sync + Debug {
+ fn id(&self) -> DisplayId;
+ /// Returns a stable identifier for this display that can be persisted and used
+ /// across system restarts.
+ fn uuid(&self) -> Result<Uuid>;
+ fn as_any(&self) -> &dyn Any;
+ fn bounds(&self) -> Bounds<GlobalPixels>;
}
-pub trait InputHandler {
- fn selected_text_range(&self) -> Option<Range<usize>>;
- fn marked_text_range(&self) -> Option<Range<usize>>;
- fn text_for_range(&self, range_utf16: Range<usize>) -> Option<String>;
- fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
- fn replace_and_mark_text_in_range(
- &mut self,
- range_utf16: Option<Range<usize>>,
- new_text: &str,
- new_selected_range: Option<Range<usize>>,
- );
- fn unmark_text(&mut self);
- fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF>;
-}
+#[derive(PartialEq, Eq, Hash, Copy, Clone)]
+pub struct DisplayId(pub(crate) u32);
-pub trait Screen: Debug {
- fn as_any(&self) -> &dyn Any;
- fn bounds(&self) -> RectF;
- fn content_bounds(&self) -> RectF;
- fn display_uuid(&self) -> Option<Uuid>;
+impl Debug for DisplayId {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "DisplayId({})", self.0)
+ }
}
-pub trait Window {
+unsafe impl Send for DisplayId {}
+
+pub trait PlatformWindow {
fn bounds(&self) -> WindowBounds;
- fn content_size(&self) -> Vector2F;
+ fn content_size(&self) -> Size<Pixels>;
fn scale_factor(&self) -> f32;
- fn titlebar_height(&self) -> f32;
- fn appearance(&self) -> Appearance;
- fn screen(&self) -> Rc<dyn Screen>;
- fn mouse_position(&self) -> Vector2F;
-
+ fn titlebar_height(&self) -> Pixels;
+ fn appearance(&self) -> WindowAppearance;
+ fn display(&self) -> Rc<dyn PlatformDisplay>;
+ fn mouse_position(&self) -> Point<Pixels>;
+ fn modifiers(&self) -> Modifiers;
fn as_any_mut(&mut self) -> &mut dyn Any;
- fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);
+ fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
+ fn clear_input_handler(&mut self);
fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
fn activate(&self);
fn set_title(&mut self, title: &str);
@@ -157,47 +158,214 @@ pub trait Window {
fn show_character_palette(&self);
fn minimize(&self);
fn zoom(&self);
- fn present_scene(&mut self, scene: Scene);
fn toggle_full_screen(&self);
+ fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
+ fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
+ fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
+ fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);
+ fn on_moved(&self, callback: Box<dyn FnMut()>);
+ fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
+ fn on_close(&self, callback: Box<dyn FnOnce()>);
+ fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
+ fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
+ fn invalidate(&self);
+
+ fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
+
+ #[cfg(any(test, feature = "test-support"))]
+ fn as_test(&mut self) -> Option<&mut TestWindow> {
+ None
+ }
+}
+
+pub trait PlatformDispatcher: Send + Sync {
+ fn is_main_thread(&self) -> bool;
+ fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
+ fn dispatch_on_main_thread(&self, runnable: Runnable);
+ fn dispatch_after(&self, duration: Duration, runnable: Runnable);
+ fn tick(&self, background_only: bool) -> bool;
+ fn park(&self);
+ fn unparker(&self) -> Unparker;
+
+ #[cfg(any(test, feature = "test-support"))]
+ fn as_test(&self) -> Option<&TestDispatcher> {
+ None
+ }
+}
- fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>);
- fn on_active_status_change(&mut self, callback: Box<dyn FnMut(bool)>);
- fn on_resize(&mut self, callback: Box<dyn FnMut()>);
- fn on_fullscreen(&mut self, callback: Box<dyn FnMut(bool)>);
- fn on_moved(&mut self, callback: Box<dyn FnMut()>);
- fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>);
- fn on_close(&mut self, callback: Box<dyn FnOnce()>);
- fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>);
- fn is_topmost_for_position(&self, position: Vector2F) -> bool;
+pub trait PlatformTextSystem: Send + Sync {
+ fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
+ fn all_font_families(&self) -> Vec<String>;
+ fn font_id(&self, descriptor: &Font) -> Result<FontId>;
+ fn font_metrics(&self, font_id: FontId) -> FontMetrics;
+ fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
+ fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
+ fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
+ fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
+ fn rasterize_glyph(
+ &self,
+ params: &RenderGlyphParams,
+ raster_bounds: Bounds<DevicePixels>,
+ ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
+ fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
+ fn wrap_line(
+ &self,
+ text: &str,
+ font_id: FontId,
+ font_size: Pixels,
+ width: Pixels,
+ ) -> Vec<usize>;
+}
+
+#[derive(Clone, Debug)]
+pub struct AppMetadata {
+ pub os_name: &'static str,
+ pub os_version: Option<SemanticVersion>,
+ pub app_version: Option<SemanticVersion>,
+}
+
+#[derive(PartialEq, Eq, Hash, Clone)]
+pub enum AtlasKey {
+ Glyph(RenderGlyphParams),
+ Svg(RenderSvgParams),
+ Image(RenderImageParams),
+}
+
+impl AtlasKey {
+ pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
+ match self {
+ AtlasKey::Glyph(params) => {
+ if params.is_emoji {
+ AtlasTextureKind::Polychrome
+ } else {
+ AtlasTextureKind::Monochrome
+ }
+ }
+ AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
+ AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
+ }
+ }
+}
+
+impl From<RenderGlyphParams> for AtlasKey {
+ fn from(params: RenderGlyphParams) -> Self {
+ Self::Glyph(params)
+ }
+}
+
+impl From<RenderSvgParams> for AtlasKey {
+ fn from(params: RenderSvgParams) -> Self {
+ Self::Svg(params)
+ }
+}
+
+impl From<RenderImageParams> for AtlasKey {
+ fn from(params: RenderImageParams) -> Self {
+ Self::Image(params)
+ }
+}
+
+pub trait PlatformAtlas: Send + Sync {
+ fn get_or_insert_with<'a>(
+ &self,
+ key: &AtlasKey,
+ build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
+ ) -> Result<AtlasTile>;
+
+ fn clear(&self);
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+#[repr(C)]
+pub struct AtlasTile {
+ pub(crate) texture_id: AtlasTextureId,
+ pub(crate) tile_id: TileId,
+ pub(crate) bounds: Bounds<DevicePixels>,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+#[repr(C)]
+pub(crate) struct AtlasTextureId {
+ // We use u32 instead of usize for Metal Shader Language compatibility
+ pub(crate) index: u32,
+ pub(crate) kind: AtlasTextureKind,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+#[repr(C)]
+pub(crate) enum AtlasTextureKind {
+ Monochrome = 0,
+ Polychrome = 1,
+ Path = 2,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+#[repr(C)]
+pub(crate) struct TileId(pub(crate) u32);
+
+impl From<etagere::AllocId> for TileId {
+ fn from(id: etagere::AllocId) -> Self {
+ Self(id.serialize())
+ }
+}
+
+impl From<TileId> for etagere::AllocId {
+ fn from(id: TileId) -> Self {
+ Self::deserialize(id.0)
+ }
+}
+
+pub trait PlatformInputHandler: 'static {
+ fn selected_text_range(&mut self) -> Option<Range<usize>>;
+ fn marked_text_range(&mut self) -> Option<Range<usize>>;
+ fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String>;
+ fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
+ fn replace_and_mark_text_in_range(
+ &mut self,
+ range_utf16: Option<Range<usize>>,
+ new_text: &str,
+ new_selected_range: Option<Range<usize>>,
+ );
+ fn unmark_text(&mut self);
+ fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
}
#[derive(Debug)]
-pub struct WindowOptions<'a> {
+pub struct WindowOptions {
pub bounds: WindowBounds,
- pub titlebar: Option<TitlebarOptions<'a>>,
+ pub titlebar: Option<TitlebarOptions>,
pub center: bool,
pub focus: bool,
pub show: bool,
pub kind: WindowKind,
pub is_movable: bool,
- pub screen: Option<Rc<dyn Screen>>,
+ pub display_id: Option<DisplayId>,
}
-impl<'a> WindowOptions<'a> {
- pub fn with_bounds(bounds: Vector2F) -> Self {
+impl Default for WindowOptions {
+ fn default() -> Self {
Self {
- bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), bounds)),
- center: true,
- ..Default::default()
+ bounds: WindowBounds::default(),
+ titlebar: Some(TitlebarOptions {
+ title: Default::default(),
+ appears_transparent: Default::default(),
+ traffic_light_position: Default::default(),
+ }),
+ center: false,
+ focus: true,
+ show: true,
+ kind: WindowKind::Normal,
+ is_movable: true,
+ display_id: None,
}
}
}
#[derive(Debug, Default)]
-pub struct TitlebarOptions<'a> {
- pub title: Option<&'a str>,
+pub struct TitlebarOptions {
+ pub title: Option<SharedString>,
pub appears_transparent: bool,
- pub traffic_light_position: Option<Vector2F>,
+ pub traffic_light_position: Option<Point<Pixels>>,
}
#[derive(Copy, Clone, Debug)]
@@ -220,11 +388,12 @@ pub enum WindowKind {
PopUp,
}
-#[derive(Copy, Clone, Debug, PartialEq)]
+#[derive(Copy, Clone, Debug, PartialEq, Default)]
pub enum WindowBounds {
Fullscreen,
+ #[default]
Maximized,
- Fixed(RectF),
+ Fixed(Bounds<GlobalPixels>),
}
impl StaticColumnCount for WindowBounds {
@@ -253,10 +422,10 @@ impl Bind for WindowBounds {
statement.bind(
®ion.map(|region| {
(
- region.min_x(),
- region.min_y(),
- region.width(),
- region.height(),
+ region.origin.x,
+ region.origin.y,
+ region.size.width,
+ region.size.height,
)
}),
next_index,
@@ -272,10 +441,14 @@ impl Column for WindowBounds {
"Maximized" => WindowBounds::Maximized,
"Fixed" => {
let ((x, y, width, height), _) = Column::column(statement, next_index)?;
- WindowBounds::Fixed(RectF::new(
- Vector2F::new(x, y),
- Vector2F::new(width, height),
- ))
+ let x: f64 = x;
+ let y: f64 = y;
+ let width: f64 = width;
+ let height: f64 = height;
+ WindowBounds::Fixed(Bounds {
+ origin: point(x.into(), y.into()),
+ size: size(width.into(), height.into()),
+ })
}
_ => bail!("Window State did not have a valid string"),
};
@@ -284,25 +457,55 @@ impl Column for WindowBounds {
}
}
+#[derive(Copy, Clone, Debug)]
+pub enum WindowAppearance {
+ Light,
+ VibrantLight,
+ Dark,
+ VibrantDark,
+}
+
+impl Default for WindowAppearance {
+ fn default() -> Self {
+ Self::Light
+ }
+}
+
+#[derive(Copy, Clone, Debug)]
pub struct PathPromptOptions {
pub files: bool,
pub directories: bool,
pub multiple: bool,
}
+#[derive(Copy, Clone, Debug)]
pub enum PromptLevel {
Info,
Warning,
Critical,
}
-#[derive(Copy, Clone, Debug, Deserialize, JsonSchema)]
+/// The style of the cursor (pointer)
+#[derive(Copy, Clone, Debug)]
pub enum CursorStyle {
Arrow,
+ IBeam,
+ Crosshair,
+ ClosedHand,
+ OpenHand,
+ PointingHand,
+ ResizeLeft,
+ ResizeRight,
ResizeLeftRight,
+ ResizeUp,
+ ResizeDown,
ResizeUpDown,
- PointingHand,
- IBeam,
+ DisappearingItem,
+ IBeamCursorForVerticalLayout,
+ OperationNotAllowed,
+ DragLink,
+ DragCopy,
+ ContextualMenu,
}
impl Default for CursorStyle {
@@ -311,14 +514,14 @@ impl Default for CursorStyle {
}
}
-#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub struct AppVersion {
+#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
+pub struct SemanticVersion {
major: usize,
minor: usize,
patch: usize,
}
-impl FromStr for AppVersion {
+impl FromStr for SemanticVersion {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
@@ -343,59 +546,47 @@ impl FromStr for AppVersion {
}
}
-impl Display for AppVersion {
+impl Display for SemanticVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
-#[derive(Copy, Clone, Debug)]
-pub enum RasterizationOptions {
- Alpha,
- Bgra,
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct ClipboardItem {
+ pub(crate) text: String,
+ pub(crate) metadata: Option<String>,
}
-pub trait FontSystem: Send + Sync {
- fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()>;
- fn all_families(&self) -> Vec<String>;
- fn load_family(&self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>>;
- fn select_font(
- &self,
- font_ids: &[FontId],
- properties: &FontProperties,
- ) -> anyhow::Result<FontId>;
- fn font_metrics(&self, font_id: FontId) -> FontMetrics;
- fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<RectF>;
- fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Vector2F>;
- fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
- fn rasterize_glyph(
- &self,
- font_id: FontId,
- font_size: f32,
- glyph_id: GlyphId,
- subpixel_shift: Vector2F,
- scale_factor: f32,
- options: RasterizationOptions,
- ) -> Option<(RectI, Vec<u8>)>;
- fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout;
- fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec<usize>;
-}
-
-impl<'a> Default for WindowOptions<'a> {
- fn default() -> Self {
+impl ClipboardItem {
+ pub fn new(text: String) -> Self {
Self {
- bounds: WindowBounds::Maximized,
- titlebar: Some(TitlebarOptions {
- title: Default::default(),
- appears_transparent: Default::default(),
- traffic_light_position: Default::default(),
- }),
- center: false,
- focus: true,
- show: true,
- kind: WindowKind::Normal,
- is_movable: true,
- screen: None,
+ text,
+ metadata: None,
}
}
+
+ pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
+ self.metadata = Some(serde_json::to_string(&metadata).unwrap());
+ self
+ }
+
+ pub fn text(&self) -> &String {
+ &self.text
+ }
+
+ pub fn metadata<T>(&self) -> Option<T>
+ where
+ T: for<'a> Deserialize<'a>,
+ {
+ self.metadata
+ .as_ref()
+ .and_then(|m| serde_json::from_str(m).ok())
+ }
+
+ pub(crate) fn text_hash(text: &str) -> u64 {
+ let mut hasher = SeaHasher::new();
+ text.hash(&mut hasher);
+ hasher.finish()
+ }
}
@@ -1,236 +0,0 @@
-use std::{any::Any, ops::Deref};
-
-use pathfinder_geometry::vector::vec2f;
-
-use crate::{geometry::vector::Vector2F, keymap_matcher::Keystroke};
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct KeyDownEvent {
- pub keystroke: Keystroke,
- pub is_held: bool,
-}
-
-#[derive(Clone, Debug)]
-pub struct KeyUpEvent {
- pub keystroke: Keystroke,
-}
-
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
-pub struct Modifiers {
- pub ctrl: bool,
- pub alt: bool,
- pub shift: bool,
- pub cmd: bool,
- pub fun: bool,
-}
-
-#[derive(Clone, Copy, Debug, Default)]
-pub struct ModifiersChangedEvent {
- pub modifiers: Modifiers,
-}
-
-impl Deref for ModifiersChangedEvent {
- type Target = Modifiers;
-
- fn deref(&self) -> &Self::Target {
- &self.modifiers
- }
-}
-
-/// The phase of a touch motion event.
-/// Based on the winit enum of the same name,
-#[derive(Clone, Copy, Debug)]
-pub enum TouchPhase {
- Started,
- Moved,
- Ended,
-}
-
-#[derive(Clone, Copy, Debug)]
-pub enum ScrollDelta {
- Pixels(Vector2F),
- Lines(Vector2F),
-}
-
-impl Default for ScrollDelta {
- fn default() -> Self {
- Self::Lines(Default::default())
- }
-}
-
-impl ScrollDelta {
- pub fn raw(&self) -> &Vector2F {
- match self {
- ScrollDelta::Pixels(v) => v,
- ScrollDelta::Lines(v) => v,
- }
- }
-
- pub fn precise(&self) -> bool {
- match self {
- ScrollDelta::Pixels(_) => true,
- ScrollDelta::Lines(_) => false,
- }
- }
-
- pub fn pixel_delta(&self, line_height: f32) -> Vector2F {
- match self {
- ScrollDelta::Pixels(delta) => *delta,
- ScrollDelta::Lines(delta) => vec2f(delta.x() * line_height, delta.y() * line_height),
- }
- }
-}
-
-#[derive(Clone, Copy, Debug, Default)]
-pub struct ScrollWheelEvent {
- pub position: Vector2F,
- pub delta: ScrollDelta,
- pub modifiers: Modifiers,
- /// If the platform supports returning the phase of a scroll wheel event, it will be stored here
- pub phase: Option<TouchPhase>,
-}
-
-impl Deref for ScrollWheelEvent {
- type Target = Modifiers;
-
- fn deref(&self) -> &Self::Target {
- &self.modifiers
- }
-}
-
-#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
-pub enum NavigationDirection {
- Back,
- Forward,
-}
-
-impl Default for NavigationDirection {
- fn default() -> Self {
- Self::Back
- }
-}
-
-#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
-pub enum MouseButton {
- Left,
- Right,
- Middle,
- Navigate(NavigationDirection),
-}
-
-impl MouseButton {
- pub fn all() -> Vec<Self> {
- vec![
- MouseButton::Left,
- MouseButton::Right,
- MouseButton::Middle,
- MouseButton::Navigate(NavigationDirection::Back),
- MouseButton::Navigate(NavigationDirection::Forward),
- ]
- }
-}
-
-impl Default for MouseButton {
- fn default() -> Self {
- Self::Left
- }
-}
-
-#[derive(Clone, Copy, Debug, Default)]
-pub struct MouseButtonEvent {
- pub button: MouseButton,
- pub position: Vector2F,
- pub modifiers: Modifiers,
- pub click_count: usize,
- pub is_down: bool,
-}
-
-impl Deref for MouseButtonEvent {
- type Target = Modifiers;
-
- fn deref(&self) -> &Self::Target {
- &self.modifiers
- }
-}
-
-#[derive(Clone, Copy, Debug, Default)]
-pub struct MouseMovedEvent {
- pub position: Vector2F,
- pub pressed_button: Option<MouseButton>,
- pub modifiers: Modifiers,
-}
-
-impl Deref for MouseMovedEvent {
- type Target = Modifiers;
-
- fn deref(&self) -> &Self::Target {
- &self.modifiers
- }
-}
-
-impl MouseMovedEvent {
- pub fn to_button_event(&self, button: MouseButton) -> MouseButtonEvent {
- MouseButtonEvent {
- position: self.position,
- button: self.pressed_button.unwrap_or(button),
- modifiers: self.modifiers,
- click_count: 0,
- is_down: self.pressed_button.is_some(),
- }
- }
-}
-
-#[derive(Clone, Copy, Debug, Default)]
-pub struct MouseExitedEvent {
- pub position: Vector2F,
- pub pressed_button: Option<MouseButton>,
- pub modifiers: Modifiers,
-}
-
-impl Deref for MouseExitedEvent {
- type Target = Modifiers;
-
- fn deref(&self) -> &Self::Target {
- &self.modifiers
- }
-}
-
-#[derive(Clone, Debug)]
-pub enum Event {
- KeyDown(KeyDownEvent),
- KeyUp(KeyUpEvent),
- ModifiersChanged(ModifiersChangedEvent),
- MouseDown(MouseButtonEvent),
- MouseUp(MouseButtonEvent),
- MouseMoved(MouseMovedEvent),
- MouseExited(MouseExitedEvent),
- ScrollWheel(ScrollWheelEvent),
-}
-
-impl Event {
- pub fn position(&self) -> Option<Vector2F> {
- match self {
- Event::KeyDown { .. } => None,
- Event::KeyUp { .. } => None,
- Event::ModifiersChanged { .. } => None,
- Event::MouseDown(event) => Some(event.position),
- Event::MouseUp(event) => Some(event.position),
- Event::MouseMoved(event) => Some(event.position),
- Event::MouseExited(event) => Some(event.position),
- Event::ScrollWheel(event) => Some(event.position),
- }
- }
-
- pub fn mouse_event<'a>(&'a self) -> Option<&'a dyn Any> {
- match self {
- Event::KeyDown { .. } => None,
- Event::KeyUp { .. } => None,
- Event::ModifiersChanged { .. } => None,
- Event::MouseDown(event) => Some(event),
- Event::MouseUp(event) => Some(event),
- Event::MouseMoved(event) => Some(event),
- Event::MouseExited(event) => Some(event),
- Event::ScrollWheel(event) => Some(event),
- }
- }
-}
@@ -1,39 +1,33 @@
-mod appearance;
-mod atlas;
+//! Macos screen have a y axis that goings up from the bottom of the screen and
+//! an origin at the bottom left of the main display.
mod dispatcher;
-mod event;
-mod fonts;
-mod geometry;
-mod image_cache;
+mod display;
+mod display_linker;
+mod events;
+mod metal_atlas;
+mod metal_renderer;
+mod open_type;
mod platform;
-mod renderer;
-mod screen;
-mod sprite_cache;
-mod status_item;
+mod text_system;
mod window;
+mod window_appearence;
+use crate::{px, size, GlobalPixels, Pixels, Size};
use cocoa::{
- base::{id, nil, BOOL, NO, YES},
- foundation::{NSAutoreleasePool, NSNotFound, NSString, NSUInteger},
+ base::{id, nil},
+ foundation::{NSAutoreleasePool, NSNotFound, NSRect, NSSize, NSString, NSUInteger},
};
-pub use dispatcher::Dispatcher;
-pub use fonts::FontSystem;
-use platform::{MacForegroundPlatform, MacPlatform};
-pub use renderer::Surface;
-use std::{ops::Range, rc::Rc, sync::Arc};
-use window::MacWindow;
+use metal_renderer::*;
+use objc::runtime::{BOOL, NO, YES};
+use std::ops::Range;
-use crate::executor;
-
-pub fn platform() -> Arc<dyn super::Platform> {
- Arc::new(MacPlatform::new())
-}
-
-pub(crate) fn foreground_platform(
- foreground: Rc<executor::Foreground>,
-) -> Rc<dyn super::ForegroundPlatform> {
- Rc::new(MacForegroundPlatform::new(foreground))
-}
+pub use dispatcher::*;
+pub use display::*;
+pub use display_linker::*;
+pub use metal_atlas::*;
+pub use platform::*;
+pub use text_system::*;
+pub use window::*;
trait BoolExt {
fn to_objc(self) -> BOOL;
@@ -102,3 +96,44 @@ unsafe impl objc::Encode for NSRange {
unsafe fn ns_string(string: &str) -> id {
NSString::alloc(nil).init_str(string).autorelease()
}
+
+impl From<NSSize> for Size<Pixels> {
+ fn from(value: NSSize) -> Self {
+ Size {
+ width: px(value.width as f32),
+ height: px(value.height as f32),
+ }
+ }
+}
+
+pub trait NSRectExt {
+ fn size(&self) -> Size<Pixels>;
+ fn intersects(&self, other: Self) -> bool;
+}
+
+impl From<NSRect> for Size<Pixels> {
+ fn from(rect: NSRect) -> Self {
+ let NSSize { width, height } = rect.size;
+ size(width.into(), height.into())
+ }
+}
+
+impl From<NSRect> for Size<GlobalPixels> {
+ fn from(rect: NSRect) -> Self {
+ let NSSize { width, height } = rect.size;
+ size(width.into(), height.into())
+ }
+}
+
+// impl NSRectExt for NSRect {
+// fn intersects(&self, other: Self) -> bool {
+// self.size.width > 0.
+// && self.size.height > 0.
+// && other.size.width > 0.
+// && other.size.height > 0.
+// && self.origin.x <= other.origin.x + other.size.width
+// && self.origin.x + self.size.width >= other.origin.x
+// && self.origin.y <= other.origin.y + other.size.height
+// && self.origin.y + self.size.height >= other.origin.y
+// }
+// }
@@ -1,36 +0,0 @@
-use std::ffi::CStr;
-
-use crate::platform::Appearance;
-use cocoa::{
- appkit::{NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight},
- base::id,
- foundation::NSString,
-};
-use objc::{msg_send, sel, sel_impl};
-
-impl Appearance {
- pub unsafe fn from_native(appearance: id) -> Self {
- let name: id = msg_send![appearance, name];
- if name == NSAppearanceNameVibrantLight {
- Self::VibrantLight
- } else if name == NSAppearanceNameVibrantDark {
- Self::VibrantDark
- } else if name == NSAppearanceNameAqua {
- Self::Light
- } else if name == NSAppearanceNameDarkAqua {
- Self::Dark
- } else {
- println!(
- "unknown appearance: {:?}",
- CStr::from_ptr(name.UTF8String())
- );
- Self::Light
- }
- }
-}
-
-#[link(name = "AppKit", kind = "framework")]
-extern "C" {
- pub static NSAppearanceNameAqua: id;
- pub static NSAppearanceNameDarkAqua: id;
-}
@@ -1,173 +0,0 @@
-use crate::geometry::{
- rect::RectI,
- vector::{vec2i, Vector2I},
-};
-use etagere::BucketedAtlasAllocator;
-use foreign_types::ForeignType;
-use log::warn;
-use metal::{Device, TextureDescriptor};
-use objc::{msg_send, sel, sel_impl};
-
-pub struct AtlasAllocator {
- device: Device,
- texture_descriptor: TextureDescriptor,
- atlases: Vec<Atlas>,
- last_used_atlas_id: usize,
-}
-
-#[derive(Copy, Clone, Debug)]
-pub struct AllocId {
- pub atlas_id: usize,
- alloc_id: etagere::AllocId,
-}
-
-impl AtlasAllocator {
- pub fn new(device: Device, texture_descriptor: TextureDescriptor) -> Self {
- let mut this = Self {
- device,
- texture_descriptor,
- atlases: vec![],
- last_used_atlas_id: 0,
- };
- let atlas = this.new_atlas(Vector2I::zero());
- this.atlases.push(atlas);
- this
- }
-
- pub fn default_atlas_size(&self) -> Vector2I {
- vec2i(
- self.texture_descriptor.width() as i32,
- self.texture_descriptor.height() as i32,
- )
- }
-
- pub fn allocate(&mut self, requested_size: Vector2I) -> Option<(AllocId, Vector2I)> {
- let atlas_id = self.last_used_atlas_id;
- if let Some((alloc_id, origin)) = self.atlases[atlas_id].allocate(requested_size) {
- return Some((AllocId { atlas_id, alloc_id }, origin));
- }
-
- for (atlas_id, atlas) in self.atlases.iter_mut().enumerate() {
- if atlas_id == self.last_used_atlas_id {
- continue;
- }
- if let Some((alloc_id, origin)) = atlas.allocate(requested_size) {
- self.last_used_atlas_id = atlas_id;
- return Some((AllocId { atlas_id, alloc_id }, origin));
- }
- }
-
- let atlas_id = self.atlases.len();
- let mut atlas = self.new_atlas(requested_size);
- let allocation = atlas
- .allocate(requested_size)
- .map(|(alloc_id, origin)| (AllocId { atlas_id, alloc_id }, origin));
- self.atlases.push(atlas);
-
- if allocation.is_none() {
- warn!(
- "allocation of size {:?} could not be created",
- requested_size,
- );
- }
-
- allocation
- }
-
- pub fn upload(&mut self, size: Vector2I, bytes: &[u8]) -> Option<(AllocId, RectI)> {
- let (alloc_id, origin) = self.allocate(size)?;
- let bounds = RectI::new(origin, size);
- self.atlases[alloc_id.atlas_id].upload(bounds, bytes);
- Some((alloc_id, bounds))
- }
-
- pub fn deallocate(&mut self, id: AllocId) {
- if let Some(atlas) = self.atlases.get_mut(id.atlas_id) {
- atlas.deallocate(id.alloc_id);
- }
- }
-
- pub fn clear(&mut self) {
- for atlas in &mut self.atlases {
- atlas.clear();
- }
- }
-
- pub fn texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
- self.atlases.get(atlas_id).map(|a| a.texture.as_ref())
- }
-
- fn new_atlas(&mut self, required_size: Vector2I) -> Atlas {
- let size = self.default_atlas_size().max(required_size);
- let texture = if size.x() as u64 > self.texture_descriptor.width()
- || size.y() as u64 > self.texture_descriptor.height()
- {
- let descriptor = unsafe {
- let descriptor_ptr: *mut metal::MTLTextureDescriptor =
- msg_send![self.texture_descriptor, copy];
- metal::TextureDescriptor::from_ptr(descriptor_ptr)
- };
- descriptor.set_width(size.x() as u64);
- descriptor.set_height(size.y() as u64);
-
- self.device.new_texture(&descriptor)
- } else {
- self.device.new_texture(&self.texture_descriptor)
- };
- Atlas::new(size, texture)
- }
-}
-
-struct Atlas {
- allocator: BucketedAtlasAllocator,
- texture: metal::Texture,
-}
-
-impl Atlas {
- fn new(size: Vector2I, texture: metal::Texture) -> Self {
- Self {
- allocator: BucketedAtlasAllocator::new(etagere::Size::new(size.x(), size.y())),
- texture,
- }
- }
-
- fn allocate(&mut self, size: Vector2I) -> Option<(etagere::AllocId, Vector2I)> {
- let alloc = self
- .allocator
- .allocate(etagere::Size::new(size.x(), size.y()))?;
- let origin = alloc.rectangle.min;
- Some((alloc.id, vec2i(origin.x, origin.y)))
- }
-
- fn upload(&mut self, bounds: RectI, bytes: &[u8]) {
- let region = metal::MTLRegion::new_2d(
- bounds.origin().x() as u64,
- bounds.origin().y() as u64,
- bounds.size().x() as u64,
- bounds.size().y() as u64,
- );
- self.texture.replace_region(
- region,
- 0,
- bytes.as_ptr() as *const _,
- (bounds.size().x() * self.bytes_per_pixel() as i32) as u64,
- );
- }
-
- fn bytes_per_pixel(&self) -> u8 {
- use metal::MTLPixelFormat::*;
- match self.texture.pixel_format() {
- A8Unorm | R8Unorm => 1,
- RGBA8Unorm | BGRA8Unorm => 4,
- _ => unimplemented!(),
- }
- }
-
- fn deallocate(&mut self, id: etagere::AllocId) {
- self.allocator.deallocate(id);
- }
-
- fn clear(&mut self) {
- self.allocator.clear();
- }
-}
@@ -2,15 +2,16 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
+use crate::{PlatformDispatcher, TaskLabel};
use async_task::Runnable;
use objc::{
class, msg_send,
runtime::{BOOL, YES},
sel, sel_impl,
};
-use std::ffi::c_void;
-
-use crate::platform;
+use parking::{Parker, Unparker};
+use parking_lot::Mutex;
+use std::{ffi::c_void, sync::Arc, time::Duration};
include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
@@ -18,15 +19,41 @@ pub fn dispatch_get_main_queue() -> dispatch_queue_t {
unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
}
-pub struct Dispatcher;
+pub struct MacDispatcher {
+ parker: Arc<Mutex<Parker>>,
+}
+
+impl Default for MacDispatcher {
+ fn default() -> Self {
+ Self::new()
+ }
+}
-impl platform::Dispatcher for Dispatcher {
+impl MacDispatcher {
+ pub fn new() -> Self {
+ MacDispatcher {
+ parker: Arc::new(Mutex::new(Parker::new())),
+ }
+ }
+}
+
+impl PlatformDispatcher for MacDispatcher {
fn is_main_thread(&self) -> bool {
let is_main_thread: BOOL = unsafe { msg_send![class!(NSThread), isMainThread] };
is_main_thread == YES
}
- fn run_on_main_thread(&self, runnable: Runnable) {
+ fn dispatch(&self, runnable: Runnable, _: Option<TaskLabel>) {
+ unsafe {
+ dispatch_async_f(
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0),
+ runnable.into_raw() as *mut c_void,
+ Some(trampoline),
+ );
+ }
+ }
+
+ fn dispatch_on_main_thread(&self, runnable: Runnable) {
unsafe {
dispatch_async_f(
dispatch_get_main_queue(),
@@ -34,10 +61,36 @@ impl platform::Dispatcher for Dispatcher {
Some(trampoline),
);
}
+ }
- extern "C" fn trampoline(runnable: *mut c_void) {
- let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
- task.run();
+ fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
+ unsafe {
+ let queue =
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0);
+ let when = dispatch_time(DISPATCH_TIME_NOW as u64, duration.as_nanos() as i64);
+ dispatch_after_f(
+ when,
+ queue,
+ runnable.into_raw() as *mut c_void,
+ Some(trampoline),
+ );
}
}
+
+ fn tick(&self, _background_only: bool) -> bool {
+ false
+ }
+
+ fn park(&self) {
+ self.parker.lock().park()
+ }
+
+ fn unparker(&self) -> Unparker {
+ self.parker.lock().unparker()
+ }
+}
+
+extern "C" fn trampoline(runnable: *mut c_void) {
+ let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
+ task.run();
}
@@ -1,359 +0,0 @@
-use crate::{
- geometry::vector::vec2f,
- keymap_matcher::Keystroke,
- platform::{
- Event, KeyDownEvent, KeyUpEvent, Modifiers, ModifiersChangedEvent, MouseButton,
- MouseButtonEvent, MouseExitedEvent, MouseMovedEvent, NavigationDirection, ScrollDelta,
- ScrollWheelEvent, TouchPhase,
- },
-};
-use cocoa::{
- appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
- base::{id, YES},
- foundation::NSString as _,
-};
-use core_graphics::{
- event::{CGEvent, CGEventFlags, CGKeyCode},
- event_source::{CGEventSource, CGEventSourceStateID},
-};
-use ctor::ctor;
-use foreign_types::ForeignType;
-use objc::{class, msg_send, sel, sel_impl};
-use std::{borrow::Cow, ffi::CStr, mem, os::raw::c_char, ptr};
-
-const BACKSPACE_KEY: u16 = 0x7f;
-const SPACE_KEY: u16 = b' ' as u16;
-const ENTER_KEY: u16 = 0x0d;
-const NUMPAD_ENTER_KEY: u16 = 0x03;
-const ESCAPE_KEY: u16 = 0x1b;
-const TAB_KEY: u16 = 0x09;
-const SHIFT_TAB_KEY: u16 = 0x19;
-
-static mut EVENT_SOURCE: core_graphics::sys::CGEventSourceRef = ptr::null_mut();
-
-#[ctor]
-unsafe fn build_event_source() {
- let source = CGEventSource::new(CGEventSourceStateID::Private).unwrap();
- EVENT_SOURCE = source.as_ptr();
- mem::forget(source);
-}
-
-pub fn key_to_native(key: &str) -> Cow<str> {
- use cocoa::appkit::*;
- let code = match key {
- "space" => SPACE_KEY,
- "backspace" => BACKSPACE_KEY,
- "up" => NSUpArrowFunctionKey,
- "down" => NSDownArrowFunctionKey,
- "left" => NSLeftArrowFunctionKey,
- "right" => NSRightArrowFunctionKey,
- "pageup" => NSPageUpFunctionKey,
- "pagedown" => NSPageDownFunctionKey,
- "home" => NSHomeFunctionKey,
- "end" => NSEndFunctionKey,
- "delete" => NSDeleteFunctionKey,
- "f1" => NSF1FunctionKey,
- "f2" => NSF2FunctionKey,
- "f3" => NSF3FunctionKey,
- "f4" => NSF4FunctionKey,
- "f5" => NSF5FunctionKey,
- "f6" => NSF6FunctionKey,
- "f7" => NSF7FunctionKey,
- "f8" => NSF8FunctionKey,
- "f9" => NSF9FunctionKey,
- "f10" => NSF10FunctionKey,
- "f11" => NSF11FunctionKey,
- "f12" => NSF12FunctionKey,
- _ => return Cow::Borrowed(key),
- };
- Cow::Owned(String::from_utf16(&[code]).unwrap())
-}
-
-unsafe fn read_modifiers(native_event: id) -> Modifiers {
- let modifiers = native_event.modifierFlags();
- let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
- let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
- let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
- let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
- let fun = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
-
- Modifiers {
- ctrl,
- alt,
- shift,
- cmd,
- fun,
- }
-}
-
-impl Event {
- pub unsafe fn from_native(native_event: id, window_height: Option<f32>) -> Option<Self> {
- let event_type = native_event.eventType();
-
- // Filter out event types that aren't in the NSEventType enum.
- // See https://github.com/servo/cocoa-rs/issues/155#issuecomment-323482792 for details.
- match event_type as u64 {
- 0 | 21 | 32 | 33 | 35 | 36 | 37 => {
- return None;
- }
- _ => {}
- }
-
- match event_type {
- NSEventType::NSFlagsChanged => Some(Self::ModifiersChanged(ModifiersChangedEvent {
- modifiers: read_modifiers(native_event),
- })),
- NSEventType::NSKeyDown => Some(Self::KeyDown(KeyDownEvent {
- keystroke: parse_keystroke(native_event),
- is_held: native_event.isARepeat() == YES,
- })),
- NSEventType::NSKeyUp => Some(Self::KeyUp(KeyUpEvent {
- keystroke: parse_keystroke(native_event),
- })),
- NSEventType::NSLeftMouseDown
- | NSEventType::NSRightMouseDown
- | NSEventType::NSOtherMouseDown => {
- let button = match native_event.buttonNumber() {
- 0 => MouseButton::Left,
- 1 => MouseButton::Right,
- 2 => MouseButton::Middle,
- 3 => MouseButton::Navigate(NavigationDirection::Back),
- 4 => MouseButton::Navigate(NavigationDirection::Forward),
- // Other mouse buttons aren't tracked currently
- _ => return None,
- };
- window_height.map(|window_height| {
- Self::MouseDown(MouseButtonEvent {
- button,
- position: vec2f(
- native_event.locationInWindow().x as f32,
- // MacOS screen coordinates are relative to bottom left
- window_height - native_event.locationInWindow().y as f32,
- ),
- modifiers: read_modifiers(native_event),
- click_count: native_event.clickCount() as usize,
- is_down: true,
- })
- })
- }
- NSEventType::NSLeftMouseUp
- | NSEventType::NSRightMouseUp
- | NSEventType::NSOtherMouseUp => {
- let button = match native_event.buttonNumber() {
- 0 => MouseButton::Left,
- 1 => MouseButton::Right,
- 2 => MouseButton::Middle,
- 3 => MouseButton::Navigate(NavigationDirection::Back),
- 4 => MouseButton::Navigate(NavigationDirection::Forward),
- // Other mouse buttons aren't tracked currently
- _ => return None,
- };
-
- window_height.map(|window_height| {
- Self::MouseUp(MouseButtonEvent {
- button,
- position: vec2f(
- native_event.locationInWindow().x as f32,
- // MacOS view coordinates are relative to bottom left
- window_height - native_event.locationInWindow().y as f32,
- ),
- modifiers: read_modifiers(native_event),
- click_count: native_event.clickCount() as usize,
- is_down: false,
- })
- })
- }
- NSEventType::NSScrollWheel => window_height.map(|window_height| {
- let phase = match native_event.phase() {
- NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
- Some(TouchPhase::Started)
- }
- NSEventPhase::NSEventPhaseEnded => Some(TouchPhase::Ended),
- _ => Some(TouchPhase::Moved),
- };
-
- let raw_data = vec2f(
- native_event.scrollingDeltaX() as f32,
- native_event.scrollingDeltaY() as f32,
- );
-
- let delta = if native_event.hasPreciseScrollingDeltas() == YES {
- ScrollDelta::Pixels(raw_data)
- } else {
- ScrollDelta::Lines(raw_data)
- };
-
- Self::ScrollWheel(ScrollWheelEvent {
- position: vec2f(
- native_event.locationInWindow().x as f32,
- window_height - native_event.locationInWindow().y as f32,
- ),
- delta,
- phase,
- modifiers: read_modifiers(native_event),
- })
- }),
- NSEventType::NSLeftMouseDragged
- | NSEventType::NSRightMouseDragged
- | NSEventType::NSOtherMouseDragged => {
- let pressed_button = match native_event.buttonNumber() {
- 0 => MouseButton::Left,
- 1 => MouseButton::Right,
- 2 => MouseButton::Middle,
- 3 => MouseButton::Navigate(NavigationDirection::Back),
- 4 => MouseButton::Navigate(NavigationDirection::Forward),
- // Other mouse buttons aren't tracked currently
- _ => return None,
- };
-
- window_height.map(|window_height| {
- Self::MouseMoved(MouseMovedEvent {
- pressed_button: Some(pressed_button),
- position: vec2f(
- native_event.locationInWindow().x as f32,
- window_height - native_event.locationInWindow().y as f32,
- ),
- modifiers: read_modifiers(native_event),
- })
- })
- }
- NSEventType::NSMouseMoved => window_height.map(|window_height| {
- Self::MouseMoved(MouseMovedEvent {
- position: vec2f(
- native_event.locationInWindow().x as f32,
- window_height - native_event.locationInWindow().y as f32,
- ),
- pressed_button: None,
- modifiers: read_modifiers(native_event),
- })
- }),
- NSEventType::NSMouseExited => window_height.map(|window_height| {
- Self::MouseExited(MouseExitedEvent {
- position: vec2f(
- native_event.locationInWindow().x as f32,
- window_height - native_event.locationInWindow().y as f32,
- ),
- pressed_button: None,
- modifiers: read_modifiers(native_event),
- })
- }),
- _ => None,
- }
- }
-}
-
-unsafe fn parse_keystroke(native_event: id) -> Keystroke {
- use cocoa::appkit::*;
-
- let mut chars_ignoring_modifiers =
- CStr::from_ptr(native_event.charactersIgnoringModifiers().UTF8String() as *mut c_char)
- .to_str()
- .unwrap()
- .to_string();
- let first_char = chars_ignoring_modifiers.chars().next().map(|ch| ch as u16);
- let modifiers = native_event.modifierFlags();
-
- let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
- let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
- let mut shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
- let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
- let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask)
- && first_char.map_or(true, |ch| {
- !(NSUpArrowFunctionKey..=NSModeSwitchFunctionKey).contains(&ch)
- });
-
- #[allow(non_upper_case_globals)]
- let key = match first_char {
- Some(SPACE_KEY) => "space".to_string(),
- Some(BACKSPACE_KEY) => "backspace".to_string(),
- Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => "enter".to_string(),
- Some(ESCAPE_KEY) => "escape".to_string(),
- Some(TAB_KEY) => "tab".to_string(),
- Some(SHIFT_TAB_KEY) => "tab".to_string(),
- Some(NSUpArrowFunctionKey) => "up".to_string(),
- Some(NSDownArrowFunctionKey) => "down".to_string(),
- Some(NSLeftArrowFunctionKey) => "left".to_string(),
- Some(NSRightArrowFunctionKey) => "right".to_string(),
- Some(NSPageUpFunctionKey) => "pageup".to_string(),
- Some(NSPageDownFunctionKey) => "pagedown".to_string(),
- Some(NSHomeFunctionKey) => "home".to_string(),
- Some(NSEndFunctionKey) => "end".to_string(),
- Some(NSDeleteFunctionKey) => "delete".to_string(),
- Some(NSF1FunctionKey) => "f1".to_string(),
- Some(NSF2FunctionKey) => "f2".to_string(),
- Some(NSF3FunctionKey) => "f3".to_string(),
- Some(NSF4FunctionKey) => "f4".to_string(),
- Some(NSF5FunctionKey) => "f5".to_string(),
- Some(NSF6FunctionKey) => "f6".to_string(),
- Some(NSF7FunctionKey) => "f7".to_string(),
- Some(NSF8FunctionKey) => "f8".to_string(),
- Some(NSF9FunctionKey) => "f9".to_string(),
- Some(NSF10FunctionKey) => "f10".to_string(),
- Some(NSF11FunctionKey) => "f11".to_string(),
- Some(NSF12FunctionKey) => "f12".to_string(),
- _ => {
- let mut chars_ignoring_modifiers_and_shift =
- chars_for_modified_key(native_event.keyCode(), false, false);
-
- // Honor ⌘ when Dvorak-QWERTY is used.
- let chars_with_cmd = chars_for_modified_key(native_event.keyCode(), true, false);
- if cmd && chars_ignoring_modifiers_and_shift != chars_with_cmd {
- chars_ignoring_modifiers =
- chars_for_modified_key(native_event.keyCode(), true, shift);
- chars_ignoring_modifiers_and_shift = chars_with_cmd;
- }
-
- if shift {
- if chars_ignoring_modifiers_and_shift
- == chars_ignoring_modifiers.to_ascii_lowercase()
- {
- chars_ignoring_modifiers_and_shift
- } else if chars_ignoring_modifiers_and_shift != chars_ignoring_modifiers {
- shift = false;
- chars_ignoring_modifiers
- } else {
- chars_ignoring_modifiers
- }
- } else {
- chars_ignoring_modifiers
- }
- }
- };
-
- Keystroke {
- ctrl,
- alt,
- shift,
- cmd,
- function,
- key,
- ime_key: None,
- }
-}
-
-fn chars_for_modified_key(code: CGKeyCode, cmd: bool, shift: bool) -> String {
- // Ideally, we would use `[NSEvent charactersByApplyingModifiers]` but that
- // always returns an empty string with certain keyboards, e.g. Japanese. Synthesizing
- // an event with the given flags instead lets us access `characters`, which always
- // returns a valid string.
- let source = unsafe { core_graphics::event_source::CGEventSource::from_ptr(EVENT_SOURCE) };
- let event = CGEvent::new_keyboard_event(source.clone(), code, true).unwrap();
- mem::forget(source);
-
- let mut flags = CGEventFlags::empty();
- if cmd {
- flags |= CGEventFlags::CGEventFlagCommand;
- }
- if shift {
- flags |= CGEventFlags::CGEventFlagShift;
- }
- event.set_flags(flags);
-
- unsafe {
- let event: id = msg_send![class!(NSEvent), eventWithCGEvent: &*event];
- CStr::from_ptr(event.characters().UTF8String())
- .to_str()
- .unwrap()
- .to_string()
- }
-}
@@ -1,671 +0,0 @@
-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 collections::HashMap;
-use core_foundation::{
- array::CFIndex,
- attributed_string::{CFAttributedStringRef, CFMutableAttributedString},
- base::{CFRange, TCFType},
- string::CFString,
-};
-use core_graphics::{
- base::{kCGImageAlphaPremultipliedLast, CGGlyph},
- color_space::CGColorSpace,
- context::CGContext,
-};
-use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
-use font_kit::{
- handle::Handle, hinting::HintingOptions, source::SystemSource, sources::mem::MemSource,
-};
-use parking_lot::RwLock;
-use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
-
-#[allow(non_upper_case_globals)]
-const kCGImageAlphaOnly: u32 = 7;
-
-pub struct FontSystem(RwLock<FontSystemState>);
-
-struct FontSystemState {
- memory_source: MemSource,
- system_source: SystemSource,
- fonts: Vec<font_kit::font::Font>,
- font_ids_by_postscript_name: HashMap<String, FontId>,
- postscript_names_by_font_id: HashMap<FontId, String>,
-}
-
-impl FontSystem {
- pub fn new() -> Self {
- Self(RwLock::new(FontSystemState {
- memory_source: MemSource::empty(),
- system_source: SystemSource::new(),
- fonts: Vec::new(),
- font_ids_by_postscript_name: Default::default(),
- postscript_names_by_font_id: Default::default(),
- }))
- }
-}
-
-impl Default for FontSystem {
- fn default() -> Self {
- Self::new()
- }
-}
-
-impl platform::FontSystem for FontSystem {
- fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()> {
- self.0.write().add_fonts(fonts)
- }
-
- fn all_families(&self) -> Vec<String> {
- self.0
- .read()
- .system_source
- .all_families()
- .expect("core text should never return an error")
- }
-
- fn load_family(&self, name: &str, features: &Features) -> anyhow::Result<Vec<FontId>> {
- self.0.write().load_family(name, features)
- }
-
- fn select_font(&self, font_ids: &[FontId], properties: &Properties) -> anyhow::Result<FontId> {
- self.0.read().select_font(font_ids, properties)
- }
-
- fn font_metrics(&self, font_id: FontId) -> Metrics {
- self.0.read().font_metrics(font_id)
- }
-
- fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<RectF> {
- self.0.read().typographic_bounds(font_id, glyph_id)
- }
-
- fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Vector2F> {
- self.0.read().advance(font_id, glyph_id)
- }
-
- fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
- self.0.read().glyph_for_char(font_id, ch)
- }
-
- fn rasterize_glyph(
- &self,
- font_id: FontId,
- font_size: f32,
- glyph_id: GlyphId,
- subpixel_shift: Vector2F,
- scale_factor: f32,
- options: RasterizationOptions,
- ) -> Option<(RectI, Vec<u8>)> {
- self.0.read().rasterize_glyph(
- font_id,
- font_size,
- glyph_id,
- subpixel_shift,
- scale_factor,
- options,
- )
- }
-
- fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout {
- self.0.write().layout_line(text, font_size, runs)
- }
-
- fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec<usize> {
- self.0.read().wrap_line(text, font_id, font_size, width)
- }
-}
-
-impl FontSystemState {
- fn add_fonts(&mut self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()> {
- self.memory_source.add_fonts(
- fonts
- .iter()
- .map(|bytes| Handle::from_memory(bytes.clone(), 0)),
- )?;
- Ok(())
- }
-
- fn load_family(&mut self, name: &str, features: &Features) -> anyhow::Result<Vec<FontId>> {
- let mut font_ids = Vec::new();
-
- let family = self
- .memory_source
- .select_family_by_name(name)
- .or_else(|_| self.system_source.select_family_by_name(name))?;
- for font in family.fonts() {
- let mut font = font.load()?;
- open_type::apply_features(&mut font, features);
- let font_id = FontId(self.fonts.len());
- font_ids.push(font_id);
- let postscript_name = font.postscript_name().unwrap();
- self.font_ids_by_postscript_name
- .insert(postscript_name.clone(), font_id);
- self.postscript_names_by_font_id
- .insert(font_id, postscript_name);
- self.fonts.push(font);
- }
- Ok(font_ids)
- }
-
- fn select_font(&self, font_ids: &[FontId], properties: &Properties) -> anyhow::Result<FontId> {
- let candidates = font_ids
- .iter()
- .map(|font_id| self.fonts[font_id.0].properties())
- .collect::<Vec<_>>();
- let idx = font_kit::matching::find_best_match(&candidates, properties)?;
- Ok(font_ids[idx])
- }
-
- fn font_metrics(&self, font_id: FontId) -> Metrics {
- self.fonts[font_id.0].metrics()
- }
-
- fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<RectF> {
- Ok(self.fonts[font_id.0].typographic_bounds(glyph_id)?)
- }
-
- fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Vector2F> {
- Ok(self.fonts[font_id.0].advance(glyph_id)?)
- }
-
- fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
- self.fonts[font_id.0].glyph_for_char(ch)
- }
-
- fn id_for_native_font(&mut self, requested_font: CTFont) -> FontId {
- let postscript_name = requested_font.postscript_name();
- if let Some(font_id) = self.font_ids_by_postscript_name.get(&postscript_name) {
- *font_id
- } else {
- let font_id = FontId(self.fonts.len());
- self.font_ids_by_postscript_name
- .insert(postscript_name.clone(), font_id);
- self.postscript_names_by_font_id
- .insert(font_id, postscript_name);
- self.fonts
- .push(font_kit::font::Font::from_core_graphics_font(
- requested_font.copy_to_CGFont(),
- ));
- font_id
- }
- }
-
- fn is_emoji(&self, font_id: FontId) -> bool {
- self.postscript_names_by_font_id
- .get(&font_id)
- .map_or(false, |postscript_name| {
- postscript_name == "AppleColorEmoji"
- })
- }
-
- fn rasterize_glyph(
- &self,
- font_id: FontId,
- font_size: f32,
- glyph_id: GlyphId,
- subpixel_shift: Vector2F,
- scale_factor: f32,
- options: RasterizationOptions,
- ) -> Option<(RectI, Vec<u8>)> {
- let font = &self.fonts[font_id.0];
- let scale = Transform2F::from_scale(scale_factor);
- let glyph_bounds = font
- .raster_bounds(
- glyph_id,
- font_size,
- scale,
- HintingOptions::None,
- font_kit::canvas::RasterizationOptions::GrayscaleAa,
- )
- .ok()?;
-
- if glyph_bounds.width() == 0 || glyph_bounds.height() == 0 {
- None
- } else {
- // Make room for subpixel variants.
- let subpixel_padding = subpixel_shift.ceil().to_i32();
- let cx_bounds = RectI::new(
- glyph_bounds.origin(),
- glyph_bounds.size() + subpixel_padding,
- );
-
- let mut bytes;
- let cx;
- match options {
- RasterizationOptions::Alpha => {
- bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize];
- cx = CGContext::create_bitmap_context(
- Some(bytes.as_mut_ptr() as *mut _),
- cx_bounds.width() as usize,
- cx_bounds.height() as usize,
- 8,
- cx_bounds.width() as usize,
- &CGColorSpace::create_device_gray(),
- kCGImageAlphaOnly,
- );
- }
- RasterizationOptions::Bgra => {
- bytes = vec![0; cx_bounds.width() as usize * 4 * cx_bounds.height() as usize];
- cx = CGContext::create_bitmap_context(
- Some(bytes.as_mut_ptr() as *mut _),
- cx_bounds.width() as usize,
- cx_bounds.height() as usize,
- 8,
- cx_bounds.width() as usize * 4,
- &CGColorSpace::create_device_rgb(),
- kCGImageAlphaPremultipliedLast,
- );
- }
- }
-
- // Move the origin to bottom left and account for scaling, this
- // makes drawing text consistent with the font-kit's raster_bounds.
- cx.translate(
- -glyph_bounds.origin_x() as CGFloat,
- (glyph_bounds.origin_y() + glyph_bounds.height()) as CGFloat,
- );
- cx.scale(scale_factor as CGFloat, scale_factor as CGFloat);
-
- cx.set_allows_font_subpixel_positioning(true);
- cx.set_should_subpixel_position_fonts(true);
- cx.set_allows_font_subpixel_quantization(false);
- cx.set_should_subpixel_quantize_fonts(false);
- font.native_font()
- .clone_with_font_size(font_size as CGFloat)
- .draw_glyphs(
- &[glyph_id as CGGlyph],
- &[CGPoint::new(
- (subpixel_shift.x() / scale_factor) as CGFloat,
- (subpixel_shift.y() / scale_factor) as CGFloat,
- )],
- cx,
- );
-
- if let RasterizationOptions::Bgra = options {
- // Convert from RGBA with premultiplied alpha to BGRA with straight alpha.
- for pixel in bytes.chunks_exact_mut(4) {
- pixel.swap(0, 2);
- let a = pixel[3] as f32 / 255.;
- pixel[0] = (pixel[0] as f32 / a) as u8;
- pixel[1] = (pixel[1] as f32 / a) as u8;
- pixel[2] = (pixel[2] as f32 / a) as u8;
- }
- }
-
- Some((cx_bounds, bytes))
- }
- }
-
- fn layout_line(
- &mut self,
- text: &str,
- font_size: f32,
- runs: &[(usize, RunStyle)],
- ) -> LineLayout {
- // Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
- let mut string = CFMutableAttributedString::new();
- {
- string.replace_str(&CFString::new(text), CFRange::init(0, 0));
- let utf16_line_len = string.char_len() as usize;
-
- let last_run: RefCell<Option<(usize, FontId)>> = Default::default();
- let font_runs = runs
- .iter()
- .filter_map(|(len, style)| {
- let mut last_run = last_run.borrow_mut();
- if let Some((last_len, last_font_id)) = last_run.as_mut() {
- if style.font_id == *last_font_id {
- *last_len += *len;
- None
- } else {
- let result = (*last_len, *last_font_id);
- *last_len = *len;
- *last_font_id = style.font_id;
- Some(result)
- }
- } else {
- *last_run = Some((*len, style.font_id));
- None
- }
- })
- .chain(std::iter::from_fn(|| last_run.borrow_mut().take()));
-
- let mut ix_converter = StringIndexConverter::new(text);
- for (run_len, font_id) in font_runs {
- let utf8_end = ix_converter.utf8_ix + run_len;
- let utf16_start = ix_converter.utf16_ix;
-
- if utf16_start >= utf16_line_len {
- break;
- }
-
- ix_converter.advance_to_utf8_ix(utf8_end);
- let utf16_end = cmp::min(ix_converter.utf16_ix, utf16_line_len);
-
- let cf_range =
- CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
- let font = &self.fonts[font_id.0];
- unsafe {
- string.set_attribute(
- cf_range,
- kCTFontAttributeName,
- &font.native_font().clone_with_font_size(font_size as f64),
- );
- }
-
- if utf16_end == utf16_line_len {
- break;
- }
- }
- }
-
- // Retrieve the glyphs from the shaped line, converting UTF16 offsets to UTF8 offsets.
- let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());
-
- let mut runs = Vec::new();
- for run in line.glyph_runs().into_iter() {
- let attributes = run.attributes().unwrap();
- let font = unsafe {
- attributes
- .get(kCTFontAttributeName)
- .downcast::<CTFont>()
- .unwrap()
- };
- let font_id = self.id_for_native_font(font);
-
- let mut ix_converter = StringIndexConverter::new(text);
- let mut glyphs = Vec::new();
- for ((glyph_id, position), glyph_utf16_ix) in run
- .glyphs()
- .iter()
- .zip(run.positions().iter())
- .zip(run.string_indices().iter())
- {
- let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
- ix_converter.advance_to_utf16_ix(glyph_utf16_ix);
- glyphs.push(Glyph {
- id: *glyph_id as GlyphId,
- position: vec2f(position.x as f32, position.y as f32),
- index: ix_converter.utf8_ix,
- is_emoji: self.is_emoji(font_id),
- });
- }
-
- runs.push(Run { font_id, glyphs })
- }
-
- let typographic_bounds = line.get_typographic_bounds();
- LineLayout {
- width: typographic_bounds.width as f32,
- ascent: typographic_bounds.ascent as f32,
- descent: typographic_bounds.descent as f32,
- runs,
- font_size,
- len: text.len(),
- }
- }
-
- fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec<usize> {
- let mut string = CFMutableAttributedString::new();
- string.replace_str(&CFString::new(text), CFRange::init(0, 0));
- let cf_range = CFRange::init(0, text.encode_utf16().count() as isize);
- let font = &self.fonts[font_id.0];
- unsafe {
- string.set_attribute(
- cf_range,
- kCTFontAttributeName,
- &font.native_font().clone_with_font_size(font_size as f64),
- );
-
- let typesetter = CTTypesetterCreateWithAttributedString(string.as_concrete_TypeRef());
- let mut ix_converter = StringIndexConverter::new(text);
- let mut break_indices = Vec::new();
- while ix_converter.utf8_ix < text.len() {
- let utf16_len = CTTypesetterSuggestLineBreak(
- typesetter,
- ix_converter.utf16_ix as isize,
- width as f64,
- ) as usize;
- ix_converter.advance_to_utf16_ix(ix_converter.utf16_ix + utf16_len);
- if ix_converter.utf8_ix >= text.len() {
- break;
- }
- break_indices.push(ix_converter.utf8_ix as usize);
- }
- break_indices
- }
- }
-}
-
-#[derive(Clone)]
-struct StringIndexConverter<'a> {
- text: &'a str,
- utf8_ix: usize,
- utf16_ix: usize,
-}
-
-impl<'a> StringIndexConverter<'a> {
- fn new(text: &'a str) -> Self {
- Self {
- text,
- utf8_ix: 0,
- utf16_ix: 0,
- }
- }
-
- fn advance_to_utf8_ix(&mut self, utf8_target: usize) {
- for (ix, c) in self.text[self.utf8_ix..].char_indices() {
- if self.utf8_ix + ix >= utf8_target {
- self.utf8_ix += ix;
- return;
- }
- self.utf16_ix += c.len_utf16();
- }
- self.utf8_ix = self.text.len();
- }
-
- fn advance_to_utf16_ix(&mut self, utf16_target: usize) {
- for (ix, c) in self.text[self.utf8_ix..].char_indices() {
- if self.utf16_ix >= utf16_target {
- self.utf8_ix += ix;
- return;
- }
- self.utf16_ix += c.len_utf16();
- }
- self.utf8_ix = self.text.len();
- }
-}
-
-#[repr(C)]
-pub struct __CFTypesetter(c_void);
-
-pub type CTTypesetterRef = *const __CFTypesetter;
-
-#[link(name = "CoreText", kind = "framework")]
-extern "C" {
- fn CTTypesetterCreateWithAttributedString(string: CFAttributedStringRef) -> CTTypesetterRef;
-
- fn CTTypesetterSuggestLineBreak(
- typesetter: CTTypesetterRef,
- start_index: CFIndex,
- width: f64,
- ) -> CFIndex;
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::AppContext;
- use font_kit::properties::{Style, Weight};
- use platform::FontSystem as _;
-
- #[crate::test(self, retries = 5)]
- fn test_layout_str(_: &mut AppContext) {
- // This is failing intermittently on CI and we don't have time to figure it out
- let fonts = FontSystem::new();
- let menlo = fonts.load_family("Menlo", &Default::default()).unwrap();
- let menlo_regular = RunStyle {
- font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(),
- color: Default::default(),
- underline: Default::default(),
- };
- let menlo_italic = RunStyle {
- font_id: fonts
- .select_font(&menlo, Properties::new().style(Style::Italic))
- .unwrap(),
- color: Default::default(),
- underline: Default::default(),
- };
- let menlo_bold = RunStyle {
- font_id: fonts
- .select_font(&menlo, Properties::new().weight(Weight::BOLD))
- .unwrap(),
- color: Default::default(),
- underline: Default::default(),
- };
- assert_ne!(menlo_regular, menlo_italic);
- assert_ne!(menlo_regular, menlo_bold);
- assert_ne!(menlo_italic, menlo_bold);
-
- let line = fonts.layout_line(
- "hello world",
- 16.0,
- &[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)],
- );
- assert_eq!(line.runs.len(), 3);
- assert_eq!(line.runs[0].font_id, menlo_bold.font_id);
- assert_eq!(line.runs[0].glyphs.len(), 2);
- assert_eq!(line.runs[1].font_id, menlo_italic.font_id);
- assert_eq!(line.runs[1].glyphs.len(), 4);
- assert_eq!(line.runs[2].font_id, menlo_regular.font_id);
- assert_eq!(line.runs[2].glyphs.len(), 5);
- }
-
- #[test]
- fn test_glyph_offsets() -> anyhow::Result<()> {
- let fonts = FontSystem::new();
- let zapfino = fonts.load_family("Zapfino", &Default::default())?;
- let zapfino_regular = RunStyle {
- font_id: fonts.select_font(&zapfino, &Properties::new())?,
- color: Default::default(),
- underline: Default::default(),
- };
- let menlo = fonts.load_family("Menlo", &Default::default())?;
- let menlo_regular = RunStyle {
- font_id: fonts.select_font(&menlo, &Properties::new())?,
- color: Default::default(),
- underline: Default::default(),
- };
-
- let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈";
- let line = fonts.layout_line(
- text,
- 16.0,
- &[
- (9, zapfino_regular),
- (13, menlo_regular),
- (text.len() - 22, zapfino_regular),
- ],
- );
- assert_eq!(
- line.runs
- .iter()
- .flat_map(|r| r.glyphs.iter())
- .map(|g| g.index)
- .collect::<Vec<_>>(),
- vec![0, 2, 4, 5, 7, 8, 9, 10, 14, 15, 16, 17, 21, 22, 23, 24, 26, 27, 28, 29, 36, 37],
- );
- Ok(())
- }
-
- #[test]
- #[ignore]
- fn test_rasterize_glyph() {
- use std::{fs::File, io::BufWriter, path::Path};
-
- let fonts = FontSystem::new();
- let font_ids = fonts.load_family("Fira Code", &Default::default()).unwrap();
- let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
- let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap();
-
- const VARIANTS: usize = 1;
- for i in 0..VARIANTS {
- let variant = i as f32 / VARIANTS as f32;
- let (bounds, bytes) = fonts
- .rasterize_glyph(
- font_id,
- 16.0,
- glyph_id,
- vec2f(variant, variant),
- 2.,
- RasterizationOptions::Alpha,
- )
- .unwrap();
-
- let name = format!("/Users/as-cii/Desktop/twog-{}.png", i);
- let path = Path::new(&name);
- let file = File::create(path).unwrap();
- let w = &mut BufWriter::new(file);
-
- let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
- encoder.set_color(png::ColorType::Grayscale);
- encoder.set_depth(png::BitDepth::Eight);
- let mut writer = encoder.write_header().unwrap();
- writer.write_image_data(&bytes).unwrap();
- }
- }
-
- #[test]
- fn test_wrap_line() {
- let fonts = FontSystem::new();
- let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
- let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
-
- let line = "one two three four five\n";
- let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
- assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]);
-
- let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n";
- let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
- assert_eq!(
- wrap_boundaries,
- &["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),]
- );
- }
-
- #[test]
- fn test_layout_line_bom_char() {
- let fonts = FontSystem::new();
- let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
- let style = RunStyle {
- font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(),
- color: Default::default(),
- underline: Default::default(),
- };
-
- let line = "\u{feff}";
- let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
- assert_eq!(layout.len, line.len());
- assert!(layout.runs.is_empty());
-
- let line = "a\u{feff}b";
- let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
- assert_eq!(layout.len, line.len());
- assert_eq!(layout.runs.len(), 1);
- assert_eq!(layout.runs[0].glyphs.len(), 2);
- assert_eq!(layout.runs[0].glyphs[0].id, 68); // a
- // There's no glyph for \u{feff}
- assert_eq!(layout.runs[0].glyphs[1].id, 69); // b
- }
-}
@@ -1,395 +0,0 @@
-#![allow(unused, non_upper_case_globals)]
-
-use std::ptr;
-
-use crate::fonts::Features;
-use cocoa::appkit::CGFloat;
-use core_foundation::{base::TCFType, number::CFNumber};
-use core_graphics::geometry::CGAffineTransform;
-use core_text::{
- font::{CTFont, CTFontRef},
- font_descriptor::{
- CTFontDescriptor, CTFontDescriptorCreateCopyWithFeature, CTFontDescriptorRef,
- },
-};
-use font_kit::font::Font;
-
-const kCaseSensitiveLayoutOffSelector: i32 = 1;
-const kCaseSensitiveLayoutOnSelector: i32 = 0;
-const kCaseSensitiveLayoutType: i32 = 33;
-const kCaseSensitiveSpacingOffSelector: i32 = 3;
-const kCaseSensitiveSpacingOnSelector: i32 = 2;
-const kCharacterAlternativesType: i32 = 17;
-const kCommonLigaturesOffSelector: i32 = 3;
-const kCommonLigaturesOnSelector: i32 = 2;
-const kContextualAlternatesOffSelector: i32 = 1;
-const kContextualAlternatesOnSelector: i32 = 0;
-const kContextualAlternatesType: i32 = 36;
-const kContextualLigaturesOffSelector: i32 = 19;
-const kContextualLigaturesOnSelector: i32 = 18;
-const kContextualSwashAlternatesOffSelector: i32 = 5;
-const kContextualSwashAlternatesOnSelector: i32 = 4;
-const kDefaultLowerCaseSelector: i32 = 0;
-const kDefaultUpperCaseSelector: i32 = 0;
-const kDiagonalFractionsSelector: i32 = 2;
-const kFractionsType: i32 = 11;
-const kHistoricalLigaturesOffSelector: i32 = 21;
-const kHistoricalLigaturesOnSelector: i32 = 20;
-const kHojoCharactersSelector: i32 = 12;
-const kInferiorsSelector: i32 = 2;
-const kJIS2004CharactersSelector: i32 = 11;
-const kLigaturesType: i32 = 1;
-const kLowerCasePetiteCapsSelector: i32 = 2;
-const kLowerCaseSmallCapsSelector: i32 = 1;
-const kLowerCaseType: i32 = 37;
-const kLowerCaseNumbersSelector: i32 = 0;
-const kMathematicalGreekOffSelector: i32 = 11;
-const kMathematicalGreekOnSelector: i32 = 10;
-const kMonospacedNumbersSelector: i32 = 0;
-const kNLCCharactersSelector: i32 = 13;
-const kNoFractionsSelector: i32 = 0;
-const kNormalPositionSelector: i32 = 0;
-const kNoStyleOptionsSelector: i32 = 0;
-const kNumberCaseType: i32 = 21;
-const kNumberSpacingType: i32 = 6;
-const kOrdinalsSelector: i32 = 3;
-const kProportionalNumbersSelector: i32 = 1;
-const kQuarterWidthTextSelector: i32 = 4;
-const kScientificInferiorsSelector: i32 = 4;
-const kSlashedZeroOffSelector: i32 = 5;
-const kSlashedZeroOnSelector: i32 = 4;
-const kStyleOptionsType: i32 = 19;
-const kStylisticAltEighteenOffSelector: i32 = 37;
-const kStylisticAltEighteenOnSelector: i32 = 36;
-const kStylisticAltEightOffSelector: i32 = 17;
-const kStylisticAltEightOnSelector: i32 = 16;
-const kStylisticAltElevenOffSelector: i32 = 23;
-const kStylisticAltElevenOnSelector: i32 = 22;
-const kStylisticAlternativesType: i32 = 35;
-const kStylisticAltFifteenOffSelector: i32 = 31;
-const kStylisticAltFifteenOnSelector: i32 = 30;
-const kStylisticAltFiveOffSelector: i32 = 11;
-const kStylisticAltFiveOnSelector: i32 = 10;
-const kStylisticAltFourOffSelector: i32 = 9;
-const kStylisticAltFourOnSelector: i32 = 8;
-const kStylisticAltFourteenOffSelector: i32 = 29;
-const kStylisticAltFourteenOnSelector: i32 = 28;
-const kStylisticAltNineOffSelector: i32 = 19;
-const kStylisticAltNineOnSelector: i32 = 18;
-const kStylisticAltNineteenOffSelector: i32 = 39;
-const kStylisticAltNineteenOnSelector: i32 = 38;
-const kStylisticAltOneOffSelector: i32 = 3;
-const kStylisticAltOneOnSelector: i32 = 2;
-const kStylisticAltSevenOffSelector: i32 = 15;
-const kStylisticAltSevenOnSelector: i32 = 14;
-const kStylisticAltSeventeenOffSelector: i32 = 35;
-const kStylisticAltSeventeenOnSelector: i32 = 34;
-const kStylisticAltSixOffSelector: i32 = 13;
-const kStylisticAltSixOnSelector: i32 = 12;
-const kStylisticAltSixteenOffSelector: i32 = 33;
-const kStylisticAltSixteenOnSelector: i32 = 32;
-const kStylisticAltTenOffSelector: i32 = 21;
-const kStylisticAltTenOnSelector: i32 = 20;
-const kStylisticAltThirteenOffSelector: i32 = 27;
-const kStylisticAltThirteenOnSelector: i32 = 26;
-const kStylisticAltThreeOffSelector: i32 = 7;
-const kStylisticAltThreeOnSelector: i32 = 6;
-const kStylisticAltTwelveOffSelector: i32 = 25;
-const kStylisticAltTwelveOnSelector: i32 = 24;
-const kStylisticAltTwentyOffSelector: i32 = 41;
-const kStylisticAltTwentyOnSelector: i32 = 40;
-const kStylisticAltTwoOffSelector: i32 = 5;
-const kStylisticAltTwoOnSelector: i32 = 4;
-const kSuperiorsSelector: i32 = 1;
-const kSwashAlternatesOffSelector: i32 = 3;
-const kSwashAlternatesOnSelector: i32 = 2;
-const kTitlingCapsSelector: i32 = 4;
-const kTypographicExtrasType: i32 = 14;
-const kVerticalFractionsSelector: i32 = 1;
-const kVerticalPositionType: i32 = 10;
-
-pub fn apply_features(font: &mut Font, features: &Features) {
- // See https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/third_party/harfbuzz-ng/src/hb-coretext.cc
- // for a reference implementation.
- toggle_open_type_feature(
- font,
- features.calt,
- kContextualAlternatesType,
- kContextualAlternatesOnSelector,
- kContextualAlternatesOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.case,
- kCaseSensitiveLayoutType,
- kCaseSensitiveLayoutOnSelector,
- kCaseSensitiveLayoutOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.cpsp,
- kCaseSensitiveLayoutType,
- kCaseSensitiveSpacingOnSelector,
- kCaseSensitiveSpacingOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.frac,
- kFractionsType,
- kDiagonalFractionsSelector,
- kNoFractionsSelector,
- );
- toggle_open_type_feature(
- font,
- features.liga,
- kLigaturesType,
- kCommonLigaturesOnSelector,
- kCommonLigaturesOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.onum,
- kNumberCaseType,
- kLowerCaseNumbersSelector,
- 2,
- );
- toggle_open_type_feature(
- font,
- features.ordn,
- kVerticalPositionType,
- kOrdinalsSelector,
- kNormalPositionSelector,
- );
- toggle_open_type_feature(
- font,
- features.pnum,
- kNumberSpacingType,
- kProportionalNumbersSelector,
- 4,
- );
- toggle_open_type_feature(
- font,
- features.ss01,
- kStylisticAlternativesType,
- kStylisticAltOneOnSelector,
- kStylisticAltOneOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss02,
- kStylisticAlternativesType,
- kStylisticAltTwoOnSelector,
- kStylisticAltTwoOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss03,
- kStylisticAlternativesType,
- kStylisticAltThreeOnSelector,
- kStylisticAltThreeOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss04,
- kStylisticAlternativesType,
- kStylisticAltFourOnSelector,
- kStylisticAltFourOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss05,
- kStylisticAlternativesType,
- kStylisticAltFiveOnSelector,
- kStylisticAltFiveOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss06,
- kStylisticAlternativesType,
- kStylisticAltSixOnSelector,
- kStylisticAltSixOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss07,
- kStylisticAlternativesType,
- kStylisticAltSevenOnSelector,
- kStylisticAltSevenOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss08,
- kStylisticAlternativesType,
- kStylisticAltEightOnSelector,
- kStylisticAltEightOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss09,
- kStylisticAlternativesType,
- kStylisticAltNineOnSelector,
- kStylisticAltNineOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss10,
- kStylisticAlternativesType,
- kStylisticAltTenOnSelector,
- kStylisticAltTenOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss11,
- kStylisticAlternativesType,
- kStylisticAltElevenOnSelector,
- kStylisticAltElevenOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss12,
- kStylisticAlternativesType,
- kStylisticAltTwelveOnSelector,
- kStylisticAltTwelveOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss13,
- kStylisticAlternativesType,
- kStylisticAltThirteenOnSelector,
- kStylisticAltThirteenOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss14,
- kStylisticAlternativesType,
- kStylisticAltFourteenOnSelector,
- kStylisticAltFourteenOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss15,
- kStylisticAlternativesType,
- kStylisticAltFifteenOnSelector,
- kStylisticAltFifteenOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss16,
- kStylisticAlternativesType,
- kStylisticAltSixteenOnSelector,
- kStylisticAltSixteenOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss17,
- kStylisticAlternativesType,
- kStylisticAltSeventeenOnSelector,
- kStylisticAltSeventeenOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss18,
- kStylisticAlternativesType,
- kStylisticAltEighteenOnSelector,
- kStylisticAltEighteenOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss19,
- kStylisticAlternativesType,
- kStylisticAltNineteenOnSelector,
- kStylisticAltNineteenOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.ss20,
- kStylisticAlternativesType,
- kStylisticAltTwentyOnSelector,
- kStylisticAltTwentyOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.subs,
- kVerticalPositionType,
- kInferiorsSelector,
- kNormalPositionSelector,
- );
- toggle_open_type_feature(
- font,
- features.sups,
- kVerticalPositionType,
- kSuperiorsSelector,
- kNormalPositionSelector,
- );
- toggle_open_type_feature(
- font,
- features.swsh,
- kContextualAlternatesType,
- kSwashAlternatesOnSelector,
- kSwashAlternatesOffSelector,
- );
- toggle_open_type_feature(
- font,
- features.titl,
- kStyleOptionsType,
- kTitlingCapsSelector,
- kNoStyleOptionsSelector,
- );
- toggle_open_type_feature(
- font,
- features.tnum,
- kNumberSpacingType,
- kMonospacedNumbersSelector,
- 4,
- );
- toggle_open_type_feature(
- font,
- features.zero,
- kTypographicExtrasType,
- kSlashedZeroOnSelector,
- kSlashedZeroOffSelector,
- );
-}
-
-fn toggle_open_type_feature(
- font: &mut Font,
- enabled: Option<bool>,
- type_identifier: i32,
- on_selector_identifier: i32,
- off_selector_identifier: i32,
-) {
- if let Some(enabled) = enabled {
- let native_font = font.native_font();
- unsafe {
- let selector_identifier = if enabled {
- on_selector_identifier
- } else {
- off_selector_identifier
- };
- let new_descriptor = CTFontDescriptorCreateCopyWithFeature(
- native_font.copy_descriptor().as_concrete_TypeRef(),
- CFNumber::from(type_identifier).as_concrete_TypeRef(),
- CFNumber::from(selector_identifier).as_concrete_TypeRef(),
- );
- let new_descriptor = CTFontDescriptor::wrap_under_create_rule(new_descriptor);
- let new_font = CTFontCreateCopyWithAttributes(
- font.native_font().as_concrete_TypeRef(),
- 0.0,
- ptr::null(),
- new_descriptor.as_concrete_TypeRef(),
- );
- let new_font = CTFont::wrap_under_create_rule(new_font);
- *font = Font::from_native_font(new_font);
- }
- }
-}
-
-#[link(name = "CoreText", kind = "framework")]
-extern "C" {
- fn CTFontCreateCopyWithAttributes(
- font: CTFontRef,
- size: CGFloat,
- matrix: *const CGAffineTransform,
- attributes: CTFontDescriptorRef,
- ) -> CTFontRef;
-}
@@ -1,45 +0,0 @@
-use cocoa::{
- base::id,
- foundation::{NSPoint, NSRect},
-};
-use objc::{msg_send, sel, sel_impl};
-use pathfinder_geometry::vector::{vec2f, Vector2F};
-
-///! Macos screen have a y axis that goings up from the bottom of the screen and
-///! an origin at the bottom left of the main display.
-
-pub trait Vector2FExt {
- /// Converts self to an NSPoint with y axis pointing up.
- fn to_screen_ns_point(&self, native_window: id, window_height: f64) -> NSPoint;
-}
-
-impl Vector2FExt for Vector2F {
- fn to_screen_ns_point(&self, native_window: id, window_height: f64) -> NSPoint {
- unsafe {
- let point = NSPoint::new(self.x() as f64, window_height - self.y() as f64);
- msg_send![native_window, convertPointToScreen: point]
- }
- }
-}
-
-pub trait NSRectExt {
- fn size_vec(&self) -> Vector2F;
- fn intersects(&self, other: Self) -> bool;
-}
-
-impl NSRectExt for NSRect {
- fn size_vec(&self) -> Vector2F {
- vec2f(self.size.width as f32, self.size.height as f32)
- }
-
- fn intersects(&self, other: Self) -> bool {
- self.size.width > 0.
- && self.size.height > 0.
- && other.size.width > 0.
- && other.size.height > 0.
- && self.origin.x <= other.origin.x + other.size.width
- && self.origin.x + self.size.width >= other.origin.x
- && self.origin.y <= other.origin.y + other.size.height
- && self.origin.y + self.size.height >= other.origin.y
- }
-}
@@ -1,115 +0,0 @@
-use super::atlas::{AllocId, AtlasAllocator};
-use crate::{
- fonts::{FontId, GlyphId},
- geometry::{rect::RectI, vector::Vector2I},
- platform::{FontSystem, RasterizationOptions},
- scene::ImageGlyph,
- ImageData,
-};
-use anyhow::anyhow;
-use metal::{MTLPixelFormat, TextureDescriptor, TextureRef};
-use ordered_float::OrderedFloat;
-use std::{collections::HashMap, mem, sync::Arc};
-
-#[derive(Hash, Eq, PartialEq)]
-struct GlyphDescriptor {
- font_id: FontId,
- font_size: OrderedFloat<f32>,
- glyph_id: GlyphId,
-}
-
-pub struct ImageCache {
- prev_frame: HashMap<usize, (AllocId, RectI)>,
- curr_frame: HashMap<usize, (AllocId, RectI)>,
- image_glyphs: HashMap<GlyphDescriptor, Option<(AllocId, RectI, Vector2I)>>,
- atlases: AtlasAllocator,
- scale_factor: f32,
- fonts: Arc<dyn FontSystem>,
-}
-
-impl ImageCache {
- pub fn new(
- device: metal::Device,
- size: Vector2I,
- scale_factor: f32,
- fonts: Arc<dyn FontSystem>,
- ) -> Self {
- let descriptor = TextureDescriptor::new();
- descriptor.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
- descriptor.set_width(size.x() as u64);
- descriptor.set_height(size.y() as u64);
- Self {
- prev_frame: Default::default(),
- curr_frame: Default::default(),
- image_glyphs: Default::default(),
- atlases: AtlasAllocator::new(device, descriptor),
- scale_factor,
- fonts,
- }
- }
-
- pub fn set_scale_factor(&mut self, scale_factor: f32) {
- if scale_factor != self.scale_factor {
- self.scale_factor = scale_factor;
- for (_, glyph) in self.image_glyphs.drain() {
- if let Some((alloc_id, _, _)) = glyph {
- self.atlases.deallocate(alloc_id);
- }
- }
- }
- }
-
- pub fn render(&mut self, image: &ImageData) -> (AllocId, RectI) {
- let (alloc_id, atlas_bounds) = self
- .prev_frame
- .remove(&image.id)
- .or_else(|| self.curr_frame.get(&image.id).copied())
- .or_else(|| self.atlases.upload(image.size(), image.as_bytes()))
- .ok_or_else(|| anyhow!("could not upload image of size {:?}", image.size()))
- .unwrap();
- self.curr_frame.insert(image.id, (alloc_id, atlas_bounds));
- (alloc_id, atlas_bounds)
- }
-
- pub fn render_glyph(&mut self, image_glyph: &ImageGlyph) -> Option<(AllocId, RectI, Vector2I)> {
- *self
- .image_glyphs
- .entry(GlyphDescriptor {
- font_id: image_glyph.font_id,
- font_size: OrderedFloat(image_glyph.font_size),
- glyph_id: image_glyph.id,
- })
- .or_insert_with(|| {
- let (glyph_bounds, bytes) = self.fonts.rasterize_glyph(
- image_glyph.font_id,
- image_glyph.font_size,
- image_glyph.id,
- Default::default(),
- self.scale_factor,
- RasterizationOptions::Bgra,
- )?;
- let (alloc_id, atlas_bounds) = self
- .atlases
- .upload(glyph_bounds.size(), &bytes)
- .ok_or_else(|| {
- anyhow!(
- "could not upload image glyph of size {:?}",
- glyph_bounds.size()
- )
- })
- .unwrap();
- Some((alloc_id, atlas_bounds, glyph_bounds.origin()))
- })
- }
-
- pub fn finish_frame(&mut self) {
- mem::swap(&mut self.prev_frame, &mut self.curr_frame);
- for (_, (id, _)) in self.curr_frame.drain() {
- self.atlases.deallocate(id);
- }
- }
-
- pub fn atlas_texture(&self, atlas_id: usize) -> Option<&TextureRef> {
- self.atlases.texture(atlas_id)
- }
-}
@@ -1,14 +1,12 @@
-use super::{
- event::key_to_native, screen::Screen, status_item::StatusItem, BoolExt as _, Dispatcher,
- FontSystem, MacWindow,
-};
+use super::{events::key_to_native, BoolExt};
use crate::{
- executor,
- keymap_matcher::KeymapMatcher,
- platform::{self, AppVersion, CursorStyle, Event},
- Action, AnyWindowHandle, ClipboardItem, Menu, MenuItem,
+ Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
+ ForegroundExecutor, InputEvent, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker,
+ MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay,
+ PlatformTextSystem, PlatformWindow, Result, Scene, SemanticVersion, VideoTimestamp,
+ WindowOptions,
};
-use anyhow::{anyhow, Result};
+use anyhow::anyhow;
use block::ConcreteBlock;
use cocoa::{
appkit::{
@@ -30,6 +28,7 @@ use core_foundation::{
string::{CFString, CFStringRef},
};
use ctor::ctor;
+use futures::channel::oneshot;
use objc::{
class,
declare::ClassDecl,
@@ -37,11 +36,10 @@ use objc::{
runtime::{Class, Object, Sel},
sel, sel_impl,
};
-
-use postage::oneshot;
+use parking_lot::Mutex;
use ptr::null_mut;
use std::{
- cell::{Cell, RefCell},
+ cell::Cell,
convert::TryInto,
ffi::{c_void, CStr, OsStr},
os::{raw::c_char, unix::ffi::OsStrExt},
@@ -51,6 +49,7 @@ use std::{
rc::Rc,
slice, str,
sync::Arc,
+ time::Duration,
};
use time::UtcOffset;
@@ -140,34 +139,50 @@ unsafe fn build_classes() {
sel!(application:openURLs:),
open_urls as extern "C" fn(&mut Object, Sel, id, id),
);
- decl.add_method(
- sel!(application:continueUserActivity:restorationHandler:),
- continue_user_activity as extern "C" fn(&mut Object, Sel, id, id, id),
- );
decl.register()
}
}
-pub struct MacForegroundPlatform(RefCell<MacForegroundPlatformState>);
+pub struct MacPlatform(Mutex<MacPlatformState>);
-pub struct MacForegroundPlatformState {
+pub struct MacPlatformState {
+ background_executor: BackgroundExecutor,
+ foreground_executor: ForegroundExecutor,
+ text_system: Arc<MacTextSystem>,
+ display_linker: MacDisplayLinker,
+ pasteboard: id,
+ text_hash_pasteboard_type: id,
+ metadata_pasteboard_type: id,
become_active: Option<Box<dyn FnMut()>>,
resign_active: Option<Box<dyn FnMut()>>,
reopen: Option<Box<dyn FnMut()>>,
quit: Option<Box<dyn FnMut()>>,
- event: Option<Box<dyn FnMut(platform::Event) -> bool>>,
+ event: Option<Box<dyn FnMut(InputEvent) -> bool>>,
menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
will_open_menu: Option<Box<dyn FnMut()>>,
+ menu_actions: Vec<Box<dyn Action>>,
open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
finish_launching: Option<Box<dyn FnOnce()>>,
- menu_actions: Vec<Box<dyn Action>>,
- foreground: Rc<executor::Foreground>,
}
-impl MacForegroundPlatform {
- pub fn new(foreground: Rc<executor::Foreground>) -> Self {
- Self(RefCell::new(MacForegroundPlatformState {
+impl Default for MacPlatform {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl MacPlatform {
+ pub fn new() -> Self {
+ let dispatcher = Arc::new(MacDispatcher::new());
+ Self(Mutex::new(MacPlatformState {
+ background_executor: BackgroundExecutor::new(dispatcher.clone()),
+ foreground_executor: ForegroundExecutor::new(dispatcher),
+ text_system: Arc::new(MacTextSystem::new()),
+ display_linker: MacDisplayLinker::new(),
+ pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
+ text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
+ metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
become_active: None,
resign_active: None,
reopen: None,
@@ -176,19 +191,30 @@ impl MacForegroundPlatform {
menu_command: None,
validate_menu_command: None,
will_open_menu: None,
+ menu_actions: Default::default(),
open_urls: None,
finish_launching: None,
- menu_actions: Default::default(),
- foreground,
}))
}
+ unsafe fn read_from_pasteboard(&self, pasteboard: *mut Object, kind: id) -> Option<&[u8]> {
+ let data = pasteboard.dataForType(kind);
+ if data == nil {
+ None
+ } else {
+ Some(slice::from_raw_parts(
+ data.bytes() as *mut u8,
+ data.length() as usize,
+ ))
+ }
+ }
+
unsafe fn create_menu_bar(
&self,
menus: Vec<Menu>,
delegate: id,
actions: &mut Vec<Box<dyn Action>>,
- keystroke_matcher: &KeymapMatcher,
+ keymap: &Keymap,
) -> id {
let application_menu = NSMenu::new(nil).autorelease();
application_menu.setDelegate_(delegate);
@@ -199,11 +225,11 @@ impl MacForegroundPlatform {
menu.setDelegate_(delegate);
for item_config in menu_config.items {
- menu.addItem_(self.create_menu_item(
+ menu.addItem_(Self::create_menu_item(
item_config,
delegate,
actions,
- keystroke_matcher,
+ keymap,
));
}
@@ -221,11 +247,10 @@ impl MacForegroundPlatform {
}
unsafe fn create_menu_item(
- &self,
item: MenuItem,
delegate: id,
actions: &mut Vec<Box<dyn Action>>,
- keystroke_matcher: &KeymapMatcher,
+ keymap: &Keymap,
) -> id {
match item {
MenuItem::Separator => NSMenuItem::separatorItem(nil),
@@ -234,11 +259,11 @@ impl MacForegroundPlatform {
action,
os_action,
} => {
- // TODO
- let keystrokes = keystroke_matcher
- .bindings_for_action(action.id())
- .find(|binding| binding.action().eq(action.as_ref()))
+ let keystrokes = keymap
+ .bindings_for_action(action.type_id())
+ .find(|binding| binding.action().partial_eq(action.as_ref()))
.map(|binding| binding.keystrokes());
+
let selector = match os_action {
Some(crate::OsAction::Cut) => selector("cut:"),
Some(crate::OsAction::Copy) => selector("copy:"),
@@ -255,10 +280,22 @@ impl MacForegroundPlatform {
let keystroke = &keystrokes[0];
let mut mask = NSEventModifierFlags::empty();
for (modifier, flag) in &[
- (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
- (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),
- (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),
- (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask),
+ (
+ keystroke.modifiers.command,
+ NSEventModifierFlags::NSCommandKeyMask,
+ ),
+ (
+ keystroke.modifiers.control,
+ NSEventModifierFlags::NSControlKeyMask,
+ ),
+ (
+ keystroke.modifiers.alt,
+ NSEventModifierFlags::NSAlternateKeyMask,
+ ),
+ (
+ keystroke.modifiers.shift,
+ NSEventModifierFlags::NSShiftKeyMask,
+ ),
] {
if *modifier {
mask |= *flag;
@@ -315,12 +352,7 @@ impl MacForegroundPlatform {
let submenu = NSMenu::new(nil).autorelease();
submenu.setDelegate_(delegate);
for item in items {
- submenu.addItem_(self.create_menu_item(
- item,
- delegate,
- actions,
- keystroke_matcher,
- ));
+ submenu.addItem_(Self::create_menu_item(item, delegate, actions, keymap));
}
item.setSubmenu_(submenu);
item.setTitle_(ns_string(name));
@@ -330,33 +362,21 @@ impl MacForegroundPlatform {
}
}
-impl platform::ForegroundPlatform for MacForegroundPlatform {
- fn on_become_active(&self, callback: Box<dyn FnMut()>) {
- self.0.borrow_mut().become_active = Some(callback);
- }
-
- fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
- self.0.borrow_mut().resign_active = Some(callback);
+impl Platform for MacPlatform {
+ fn background_executor(&self) -> BackgroundExecutor {
+ self.0.lock().background_executor.clone()
}
- fn on_quit(&self, callback: Box<dyn FnMut()>) {
- self.0.borrow_mut().quit = Some(callback);
+ fn foreground_executor(&self) -> crate::ForegroundExecutor {
+ self.0.lock().foreground_executor.clone()
}
- fn on_reopen(&self, callback: Box<dyn FnMut()>) {
- self.0.borrow_mut().reopen = Some(callback);
- }
-
- fn on_event(&self, callback: Box<dyn FnMut(platform::Event) -> bool>) {
- self.0.borrow_mut().event = Some(callback);
- }
-
- fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
- self.0.borrow_mut().open_urls = Some(callback);
+ fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
+ self.0.lock().text_system.clone()
}
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
- self.0.borrow_mut().finish_launching = Some(on_finish_launching);
+ self.0.lock().finish_launching = Some(on_finish_launching);
unsafe {
let app: id = msg_send![APP_CLASS, sharedApplication];
@@ -376,35 +396,157 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
}
}
- fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
- self.0.borrow_mut().menu_command = Some(callback);
+ fn quit(&self) {
+ // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks
+ // synchronously before this method terminates. If we call `Platform::quit` while holding a
+ // borrow of the app state (which most of the time we will do), we will end up
+ // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve
+ // this, we make quitting the application asynchronous so that we aren't holding borrows to
+ // the app state on the stack when we actually terminate the app.
+
+ use super::dispatcher::{dispatch_async_f, dispatch_get_main_queue};
+
+ unsafe {
+ dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));
+ }
+
+ unsafe extern "C" fn quit(_: *mut c_void) {
+ let app = NSApplication::sharedApplication(nil);
+ let _: () = msg_send![app, terminate: nil];
+ }
+ }
+
+ fn restart(&self) {
+ use std::os::unix::process::CommandExt as _;
+
+ let app_pid = std::process::id().to_string();
+ let app_path = self
+ .app_path()
+ .ok()
+ // When the app is not bundled, `app_path` returns the
+ // directory containing the executable. Disregard this
+ // and get the path to the executable itself.
+ .and_then(|path| (path.extension()?.to_str()? == "app").then_some(path))
+ .unwrap_or_else(|| std::env::current_exe().unwrap());
+
+ // Wait until this process has exited and then re-open this path.
+ let script = r#"
+ while kill -0 $0 2> /dev/null; do
+ sleep 0.1
+ done
+ open "$1"
+ "#;
+
+ let restart_process = Command::new("/bin/bash")
+ .arg("-c")
+ .arg(script)
+ .arg(app_pid)
+ .arg(app_path)
+ .process_group(0)
+ .spawn();
+
+ match restart_process {
+ Ok(_) => self.quit(),
+ Err(e) => log::error!("failed to spawn restart script: {:?}", e),
+ }
}
- fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
- self.0.borrow_mut().will_open_menu = Some(callback);
+ fn activate(&self, ignoring_other_apps: bool) {
+ unsafe {
+ let app = NSApplication::sharedApplication(nil);
+ app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
+ }
}
- fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
- self.0.borrow_mut().validate_menu_command = Some(callback);
+ fn hide(&self) {
+ unsafe {
+ let app = NSApplication::sharedApplication(nil);
+ let _: () = msg_send![app, hide: nil];
+ }
}
- fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
+ fn hide_other_apps(&self) {
unsafe {
- let app: id = msg_send![APP_CLASS, sharedApplication];
- let mut state = self.0.borrow_mut();
- let actions = &mut state.menu_actions;
- app.setMainMenu_(self.create_menu_bar(
- menus,
- app.delegate(),
- actions,
- keystroke_matcher,
- ));
+ let app = NSApplication::sharedApplication(nil);
+ let _: () = msg_send![app, hideOtherApplications: nil];
+ }
+ }
+
+ fn unhide_other_apps(&self) {
+ unsafe {
+ let app = NSApplication::sharedApplication(nil);
+ let _: () = msg_send![app, unhideAllApplications: nil];
+ }
+ }
+
+ // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
+ // Box::new(StatusItem::add(self.fonts()))
+ // }
+
+ fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
+ MacDisplay::all()
+ .map(|screen| Rc::new(screen) as Rc<_>)
+ .collect()
+ }
+
+ fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
+ MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
+ }
+
+ fn active_window(&self) -> Option<AnyWindowHandle> {
+ MacWindow::active_window()
+ }
+
+ fn open_window(
+ &self,
+ handle: AnyWindowHandle,
+ options: WindowOptions,
+ draw: Box<dyn FnMut() -> Result<Scene>>,
+ ) -> Box<dyn PlatformWindow> {
+ Box::new(MacWindow::open(
+ handle,
+ options,
+ draw,
+ self.foreground_executor(),
+ ))
+ }
+
+ fn set_display_link_output_callback(
+ &self,
+ display_id: DisplayId,
+ callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
+ ) {
+ self.0
+ .lock()
+ .display_linker
+ .set_output_callback(display_id, callback);
+ }
+
+ fn start_display_link(&self, display_id: DisplayId) {
+ self.0.lock().display_linker.start(display_id);
+ }
+
+ fn stop_display_link(&self, display_id: DisplayId) {
+ self.0.lock().display_linker.stop(display_id);
+ }
+
+ fn open_url(&self, url: &str) {
+ unsafe {
+ let url = NSURL::alloc(nil)
+ .initWithString_(ns_string(url))
+ .autorelease();
+ let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
+ msg_send![workspace, openURL: url]
}
}
+ fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
+ self.0.lock().open_urls = Some(callback);
+ }
+
fn prompt_for_paths(
&self,
- options: platform::PathPromptOptions,
+ options: PathPromptOptions,
) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
unsafe {
let panel = NSOpenPanel::openPanel(nil);
@@ -431,8 +573,8 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
None
};
- if let Some(mut done_tx) = done_tx.take() {
- let _ = postage::sink::Sink::try_send(&mut done_tx, result);
+ if let Some(done_tx) = done_tx.take() {
+ let _ = done_tx.send(result);
}
});
let block = block.copy();
@@ -459,8 +601,8 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
}
}
- if let Some(mut done_tx) = done_tx.take() {
- let _ = postage::sink::Sink::try_send(&mut done_tx, result);
+ if let Some(done_tx) = done_tx.take() {
+ let _ = done_tx.send(result);
}
});
let block = block.copy();
@@ -473,8 +615,8 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
unsafe {
let path = path.to_path_buf();
self.0
- .borrow()
- .foreground
+ .lock()
+ .background_executor
.spawn(async move {
let full_path = ns_string(path.to_str().unwrap_or(""));
let root_full_path = ns_string("");
@@ -488,138 +630,182 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
.detach();
}
}
-}
-pub struct MacPlatform {
- dispatcher: Arc<Dispatcher>,
- fonts: Arc<FontSystem>,
- pasteboard: id,
- text_hash_pasteboard_type: id,
- metadata_pasteboard_type: id,
-}
+ fn on_become_active(&self, callback: Box<dyn FnMut()>) {
+ self.0.lock().become_active = Some(callback);
+ }
-impl MacPlatform {
- pub fn new() -> Self {
- Self {
- dispatcher: Arc::new(Dispatcher),
- fonts: Arc::new(FontSystem::new()),
- pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
- text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
- metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
- }
+ fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
+ self.0.lock().resign_active = Some(callback);
}
- unsafe fn read_from_pasteboard(&self, kind: id) -> Option<&[u8]> {
- let data = self.pasteboard.dataForType(kind);
- if data == nil {
- None
- } else {
- Some(slice::from_raw_parts(
- data.bytes() as *mut u8,
- data.length() as usize,
- ))
- }
+ fn on_quit(&self, callback: Box<dyn FnMut()>) {
+ self.0.lock().quit = Some(callback);
}
-}
-unsafe impl Send for MacPlatform {}
-unsafe impl Sync for MacPlatform {}
+ fn on_reopen(&self, callback: Box<dyn FnMut()>) {
+ self.0.lock().reopen = Some(callback);
+ }
-impl platform::Platform for MacPlatform {
- fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {
- self.dispatcher.clone()
+ fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>) {
+ self.0.lock().event = Some(callback);
}
- fn fonts(&self) -> Arc<dyn platform::FontSystem> {
- self.fonts.clone()
+ fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
+ self.0.lock().menu_command = Some(callback);
}
- fn activate(&self, ignoring_other_apps: bool) {
+ fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
+ self.0.lock().will_open_menu = Some(callback);
+ }
+
+ fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
+ self.0.lock().validate_menu_command = Some(callback);
+ }
+
+ fn os_name(&self) -> &'static str {
+ "macOS"
+ }
+
+ fn double_click_interval(&self) -> Duration {
unsafe {
- let app = NSApplication::sharedApplication(nil);
- app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
+ let double_click_interval: f64 = msg_send![class!(NSEvent), doubleClickInterval];
+ Duration::from_secs_f64(double_click_interval)
}
}
- fn hide(&self) {
+ fn os_version(&self) -> Result<SemanticVersion> {
unsafe {
- let app = NSApplication::sharedApplication(nil);
- let _: () = msg_send![app, hide: nil];
+ let process_info = NSProcessInfo::processInfo(nil);
+ let version = process_info.operatingSystemVersion();
+ Ok(SemanticVersion {
+ major: version.majorVersion as usize,
+ minor: version.minorVersion as usize,
+ patch: version.patchVersion as usize,
+ })
}
}
- fn hide_other_apps(&self) {
+ fn app_version(&self) -> Result<SemanticVersion> {
unsafe {
- let app = NSApplication::sharedApplication(nil);
- let _: () = msg_send![app, hideOtherApplications: nil];
+ let bundle: id = NSBundle::mainBundle();
+ if bundle.is_null() {
+ Err(anyhow!("app is not running inside a bundle"))
+ } else {
+ let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")];
+ let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
+ let bytes = version.UTF8String() as *const u8;
+ let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
+ version.parse()
+ }
}
}
- fn unhide_other_apps(&self) {
+ fn app_path(&self) -> Result<PathBuf> {
unsafe {
- let app = NSApplication::sharedApplication(nil);
- let _: () = msg_send![app, unhideAllApplications: nil];
+ let bundle: id = NSBundle::mainBundle();
+ if bundle.is_null() {
+ Err(anyhow!("app is not running inside a bundle"))
+ } else {
+ Ok(path_from_objc(msg_send![bundle, bundlePath]))
+ }
}
}
- fn quit(&self) {
- // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks
- // synchronously before this method terminates. If we call `Platform::quit` while holding a
- // borrow of the app state (which most of the time we will do), we will end up
- // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve
- // this, we make quitting the application asynchronous so that we aren't holding borrows to
- // the app state on the stack when we actually terminate the app.
-
- use super::dispatcher::{dispatch_async_f, dispatch_get_main_queue};
-
+ fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {
unsafe {
- dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));
+ let app: id = msg_send![APP_CLASS, sharedApplication];
+ let mut state = self.0.lock();
+ let actions = &mut state.menu_actions;
+ app.setMainMenu_(self.create_menu_bar(menus, app.delegate(), actions, keymap));
}
+ }
- unsafe extern "C" fn quit(_: *mut c_void) {
- let app = NSApplication::sharedApplication(nil);
- let _: () = msg_send![app, terminate: nil];
+ fn local_timezone(&self) -> UtcOffset {
+ unsafe {
+ let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
+ let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT];
+ UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap()
}
}
- fn screen_by_id(&self, id: uuid::Uuid) -> Option<Rc<dyn platform::Screen>> {
- Screen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
+ fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
+ unsafe {
+ let bundle: id = NSBundle::mainBundle();
+ if bundle.is_null() {
+ Err(anyhow!("app is not running inside a bundle"))
+ } else {
+ let name = ns_string(name);
+ let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];
+ if url.is_null() {
+ Err(anyhow!("resource not found"))
+ } else {
+ ns_url_to_path(url)
+ }
+ }
+ }
}
- fn screens(&self) -> Vec<Rc<dyn platform::Screen>> {
- Screen::all()
- .into_iter()
- .map(|screen| Rc::new(screen) as Rc<_>)
- .collect()
- }
+ /// Match cursor style to one of the styles available
+ /// in macOS's [NSCursor](https://developer.apple.com/documentation/appkit/nscursor).
+ fn set_cursor_style(&self, style: CursorStyle) {
+ unsafe {
+ let new_cursor: id = match style {
+ CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
+ CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
+ CursorStyle::Crosshair => msg_send![class!(NSCursor), crosshairCursor],
+ CursorStyle::ClosedHand => msg_send![class!(NSCursor), closedHandCursor],
+ CursorStyle::OpenHand => msg_send![class!(NSCursor), openHandCursor],
+ CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
+ CursorStyle::ResizeLeft => msg_send![class!(NSCursor), resizeLeftCursor],
+ CursorStyle::ResizeRight => msg_send![class!(NSCursor), resizeRightCursor],
+ CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor],
+ CursorStyle::ResizeUp => msg_send![class!(NSCursor), resizeUpCursor],
+ CursorStyle::ResizeDown => msg_send![class!(NSCursor), resizeDownCursor],
+ CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
+ CursorStyle::DisappearingItem => {
+ msg_send![class!(NSCursor), disappearingItemCursor]
+ }
+ CursorStyle::IBeamCursorForVerticalLayout => {
+ msg_send![class!(NSCursor), IBeamCursorForVerticalLayout]
+ }
+ CursorStyle::OperationNotAllowed => {
+ msg_send![class!(NSCursor), operationNotAllowedCursor]
+ }
+ CursorStyle::DragLink => msg_send![class!(NSCursor), dragLinkCursor],
+ CursorStyle::DragCopy => msg_send![class!(NSCursor), dragCopyCursor],
+ CursorStyle::ContextualMenu => msg_send![class!(NSCursor), contextualMenuCursor],
+ };
- fn open_window(
- &self,
- handle: AnyWindowHandle,
- options: platform::WindowOptions,
- executor: Rc<executor::Foreground>,
- ) -> Box<dyn platform::Window> {
- Box::new(MacWindow::open(handle, options, executor, self.fonts()))
+ let old_cursor: id = msg_send![class!(NSCursor), currentCursor];
+ if new_cursor != old_cursor {
+ let _: () = msg_send![new_cursor, set];
+ }
+ }
}
- fn main_window(&self) -> Option<AnyWindowHandle> {
- MacWindow::main_window()
- }
+ fn should_auto_hide_scrollbars(&self) -> bool {
+ #[allow(non_upper_case_globals)]
+ const NSScrollerStyleOverlay: NSInteger = 1;
- fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
- Box::new(StatusItem::add(self.fonts()))
+ unsafe {
+ let style: NSInteger = msg_send![class!(NSScroller), preferredScrollerStyle];
+ style == NSScrollerStyleOverlay
+ }
}
fn write_to_clipboard(&self, item: ClipboardItem) {
+ let state = self.0.lock();
unsafe {
- self.pasteboard.clearContents();
+ state.pasteboard.clearContents();
let text_bytes = NSData::dataWithBytes_length_(
nil,
item.text.as_ptr() as *const c_void,
item.text.len() as u64,
);
- self.pasteboard
+ state
+ .pasteboard
.setData_forType(text_bytes, NSPasteboardTypeString);
if let Some(metadata) = item.metadata.as_ref() {
@@ -629,30 +815,35 @@ impl platform::Platform for MacPlatform {
hash_bytes.as_ptr() as *const c_void,
hash_bytes.len() as u64,
);
- self.pasteboard
- .setData_forType(hash_bytes, self.text_hash_pasteboard_type);
+ state
+ .pasteboard
+ .setData_forType(hash_bytes, state.text_hash_pasteboard_type);
let metadata_bytes = NSData::dataWithBytes_length_(
nil,
metadata.as_ptr() as *const c_void,
metadata.len() as u64,
);
- self.pasteboard
- .setData_forType(metadata_bytes, self.metadata_pasteboard_type);
+ state
+ .pasteboard
+ .setData_forType(metadata_bytes, state.metadata_pasteboard_type);
}
}
}
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
+ let state = self.0.lock();
unsafe {
- if let Some(text_bytes) = self.read_from_pasteboard(NSPasteboardTypeString) {
+ if let Some(text_bytes) =
+ self.read_from_pasteboard(state.pasteboard, NSPasteboardTypeString)
+ {
let text = String::from_utf8_lossy(text_bytes).to_string();
let hash_bytes = self
- .read_from_pasteboard(self.text_hash_pasteboard_type)
+ .read_from_pasteboard(state.pasteboard, state.text_hash_pasteboard_type)
.and_then(|bytes| bytes.try_into().ok())
.map(u64::from_be_bytes);
let metadata_bytes = self
- .read_from_pasteboard(self.metadata_pasteboard_type)
+ .read_from_pasteboard(state.pasteboard, state.metadata_pasteboard_type)
.and_then(|bytes| String::from_utf8(bytes.to_vec()).ok());
if let Some((hash, metadata)) = hash_bytes.zip(metadata_bytes) {
@@ -679,16 +870,6 @@ impl platform::Platform for MacPlatform {
}
}
- fn open_url(&self, url: &str) {
- unsafe {
- let url = NSURL::alloc(nil)
- .initWithString_(ns_string(url))
- .autorelease();
- let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
- msg_send![workspace, openURL: url]
- }
- }
-
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
let url = CFString::from(url);
let username = CFString::from(username);
@@ -788,137 +969,6 @@ impl platform::Platform for MacPlatform {
}
Ok(())
}
-
- fn set_cursor_style(&self, style: CursorStyle) {
- unsafe {
- let new_cursor: id = match style {
- CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
- CursorStyle::ResizeLeftRight => {
- msg_send![class!(NSCursor), resizeLeftRightCursor]
- }
- CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
- CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
- CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
- };
-
- let old_cursor: id = msg_send![class!(NSCursor), currentCursor];
- if new_cursor != old_cursor {
- let _: () = msg_send![new_cursor, set];
- }
- }
- }
-
- fn should_auto_hide_scrollbars(&self) -> bool {
- #[allow(non_upper_case_globals)]
- const NSScrollerStyleOverlay: NSInteger = 1;
-
- unsafe {
- let style: NSInteger = msg_send![class!(NSScroller), preferredScrollerStyle];
- style == NSScrollerStyleOverlay
- }
- }
-
- fn local_timezone(&self) -> UtcOffset {
- unsafe {
- let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
- let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT];
- UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap()
- }
- }
-
- fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
- unsafe {
- let bundle: id = NSBundle::mainBundle();
- if bundle.is_null() {
- Err(anyhow!("app is not running inside a bundle"))
- } else {
- let name = ns_string(name);
- let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];
- if url.is_null() {
- Err(anyhow!("resource not found"))
- } else {
- ns_url_to_path(url)
- }
- }
- }
- }
-
- fn app_path(&self) -> Result<PathBuf> {
- unsafe {
- let bundle: id = NSBundle::mainBundle();
- if bundle.is_null() {
- Err(anyhow!("app is not running inside a bundle"))
- } else {
- Ok(path_from_objc(msg_send![bundle, bundlePath]))
- }
- }
- }
-
- fn app_version(&self) -> Result<platform::AppVersion> {
- unsafe {
- let bundle: id = NSBundle::mainBundle();
- if bundle.is_null() {
- Err(anyhow!("app is not running inside a bundle"))
- } else {
- let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")];
- let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
- let bytes = version.UTF8String() as *const u8;
- let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
- version.parse()
- }
- }
- }
-
- fn os_name(&self) -> &'static str {
- "macOS"
- }
-
- fn os_version(&self) -> Result<crate::platform::AppVersion> {
- unsafe {
- let process_info = NSProcessInfo::processInfo(nil);
- let version = process_info.operatingSystemVersion();
- Ok(AppVersion {
- major: version.majorVersion as usize,
- minor: version.minorVersion as usize,
- patch: version.patchVersion as usize,
- })
- }
- }
-
- fn restart(&self) {
- use std::os::unix::process::CommandExt as _;
-
- let app_pid = std::process::id().to_string();
- let app_path = self
- .app_path()
- .ok()
- // When the app is not bundled, `app_path` returns the
- // directory containing the executable. Disregard this
- // and get the path to the executable itself.
- .and_then(|path| (path.extension()?.to_str()? == "app").then_some(path))
- .unwrap_or_else(|| std::env::current_exe().unwrap());
-
- // Wait until this process has exited and then re-open this path.
- let script = r#"
- while kill -0 $0 2> /dev/null; do
- sleep 0.1
- done
- open "$1"
- "#;
-
- let restart_process = Command::new("/bin/bash")
- .arg("-c")
- .arg(script)
- .arg(app_pid)
- .arg(app_path)
- .process_group(0)
- .spawn();
-
- match restart_process {
- Ok(_) => self.quit(),
- Err(e) => log::error!("failed to spawn restart script: {:?}", e),
- }
- }
}
unsafe fn path_from_objc(path: id) -> PathBuf {
@@ -928,18 +978,18 @@ unsafe fn path_from_objc(path: id) -> PathBuf {
PathBuf::from(path)
}
-unsafe fn get_foreground_platform(object: &mut Object) -> &MacForegroundPlatform {
+unsafe fn get_mac_platform(object: &mut Object) -> &MacPlatform {
let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
assert!(!platform_ptr.is_null());
- &*(platform_ptr as *const MacForegroundPlatform)
+ &*(platform_ptr as *const MacPlatform)
}
extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
unsafe {
- if let Some(event) = Event::from_native(native_event, None) {
- let platform = get_foreground_platform(this);
- if let Some(callback) = platform.0.borrow_mut().event.as_mut() {
- if callback(event) {
+ if let Some(event) = InputEvent::from_native(native_event, None) {
+ let platform = get_mac_platform(this);
+ if let Some(callback) = platform.0.lock().event.as_mut() {
+ if !callback(event) {
return;
}
}
@@ -953,8 +1003,8 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
let app: id = msg_send![APP_CLASS, sharedApplication];
app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
- let platform = get_foreground_platform(this);
- let callback = platform.0.borrow_mut().finish_launching.take();
+ let platform = get_mac_platform(this);
+ let callback = platform.0.lock().finish_launching.take();
if let Some(callback) = callback {
callback();
}
@@ -963,30 +1013,30 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) {
if !has_open_windows {
- let platform = unsafe { get_foreground_platform(this) };
- if let Some(callback) = platform.0.borrow_mut().reopen.as_mut() {
+ let platform = unsafe { get_mac_platform(this) };
+ if let Some(callback) = platform.0.lock().reopen.as_mut() {
callback();
}
}
}
extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
- let platform = unsafe { get_foreground_platform(this) };
- if let Some(callback) = platform.0.borrow_mut().become_active.as_mut() {
+ let platform = unsafe { get_mac_platform(this) };
+ if let Some(callback) = platform.0.lock().become_active.as_mut() {
callback();
}
}
extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
- let platform = unsafe { get_foreground_platform(this) };
- if let Some(callback) = platform.0.borrow_mut().resign_active.as_mut() {
+ let platform = unsafe { get_mac_platform(this) };
+ if let Some(callback) = platform.0.lock().resign_active.as_mut() {
callback();
}
}
extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) {
- let platform = unsafe { get_foreground_platform(this) };
- if let Some(callback) = platform.0.borrow_mut().quit.as_mut() {
+ let platform = unsafe { get_mac_platform(this) };
+ if let Some(callback) = platform.0.lock().quit.as_mut() {
callback();
}
}
@@ -1,1277 +0,0 @@
-use super::{atlas::AtlasAllocator, image_cache::ImageCache, sprite_cache::SpriteCache};
-use crate::{
- color::Color,
- geometry::{
- rect::RectF,
- vector::{vec2f, vec2i, Vector2F},
- },
- platform,
- 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 media::core_video::{self, CVMetalTextureCache};
-use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
-use objc::{self, msg_send, sel, sel_impl};
-use shaders::ToFloat2 as _;
-use std::{collections::HashMap, ffi::c_void, iter::Peekable, mem, ptr, sync::Arc, vec};
-
-const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
-const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
-
-pub struct Renderer {
- layer: metal::MetalLayer,
- command_queue: CommandQueue,
- sprite_cache: SpriteCache,
- image_cache: ImageCache,
- path_atlases: AtlasAllocator,
- quad_pipeline_state: metal::RenderPipelineState,
- shadow_pipeline_state: metal::RenderPipelineState,
- sprite_pipeline_state: metal::RenderPipelineState,
- image_pipeline_state: metal::RenderPipelineState,
- surface_pipeline_state: metal::RenderPipelineState,
- path_atlas_pipeline_state: metal::RenderPipelineState,
- underline_pipeline_state: metal::RenderPipelineState,
- unit_vertices: metal::Buffer,
- instances: metal::Buffer,
- cv_texture_cache: core_video::CVMetalTextureCache,
-}
-
-struct PathSprite {
- layer_id: usize,
- atlas_id: usize,
- shader_data: shaders::GPUISprite,
-}
-
-pub struct Surface {
- pub bounds: RectF,
- pub image_buffer: core_video::CVImageBuffer,
-}
-
-impl Renderer {
- pub fn new(is_opaque: bool, fonts: Arc<dyn platform::FontSystem>) -> Self {
- const PIXEL_FORMAT: MTLPixelFormat = MTLPixelFormat::BGRA8Unorm;
-
- let device: metal::Device = if let Some(device) = metal::Device::system_default() {
- device
- } else {
- log::error!("unable to access a compatible graphics device");
- std::process::exit(1);
- };
-
- let layer = metal::MetalLayer::new();
- layer.set_device(&device);
- layer.set_pixel_format(PIXEL_FORMAT);
- layer.set_presents_with_transaction(true);
- layer.set_opaque(is_opaque);
- unsafe {
- let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
- let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
- let _: () = msg_send![
- &*layer,
- setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE
- | AutoresizingMask::HEIGHT_SIZABLE
- ];
- }
-
- let library = device
- .new_library_with_data(SHADERS_METALLIB)
- .expect("error building metal library");
-
- let unit_vertices = [
- (0., 0.).to_float2(),
- (1., 0.).to_float2(),
- (0., 1.).to_float2(),
- (0., 1.).to_float2(),
- (1., 0.).to_float2(),
- (1., 1.).to_float2(),
- ];
- let unit_vertices = device.new_buffer_with_data(
- unit_vertices.as_ptr() as *const c_void,
- (unit_vertices.len() * mem::size_of::<shaders::vector_float2>()) as u64,
- MTLResourceOptions::StorageModeManaged,
- );
- let instances = device.new_buffer(
- INSTANCE_BUFFER_SIZE as u64,
- MTLResourceOptions::StorageModeManaged,
- );
-
- let sprite_cache = SpriteCache::new(device.clone(), vec2i(1024, 768), 1., fonts.clone());
- let image_cache = ImageCache::new(device.clone(), vec2i(1024, 768), 1., fonts);
- let path_atlases =
- AtlasAllocator::new(device.clone(), build_path_atlas_texture_descriptor());
- let quad_pipeline_state = build_pipeline_state(
- &device,
- &library,
- "quad",
- "quad_vertex",
- "quad_fragment",
- PIXEL_FORMAT,
- );
- let shadow_pipeline_state = build_pipeline_state(
- &device,
- &library,
- "shadow",
- "shadow_vertex",
- "shadow_fragment",
- PIXEL_FORMAT,
- );
- let sprite_pipeline_state = build_pipeline_state(
- &device,
- &library,
- "sprite",
- "sprite_vertex",
- "sprite_fragment",
- PIXEL_FORMAT,
- );
- let image_pipeline_state = build_pipeline_state(
- &device,
- &library,
- "image",
- "image_vertex",
- "image_fragment",
- PIXEL_FORMAT,
- );
- let surface_pipeline_state = build_pipeline_state(
- &device,
- &library,
- "surface",
- "surface_vertex",
- "surface_fragment",
- PIXEL_FORMAT,
- );
- let path_atlas_pipeline_state = build_path_atlas_pipeline_state(
- &device,
- &library,
- "path_atlas",
- "path_atlas_vertex",
- "path_atlas_fragment",
- MTLPixelFormat::R16Float,
- );
- let underline_pipeline_state = build_pipeline_state(
- &device,
- &library,
- "underline",
- "underline_vertex",
- "underline_fragment",
- PIXEL_FORMAT,
- );
- let cv_texture_cache = unsafe { CVMetalTextureCache::new(device.as_ptr()).unwrap() };
- Self {
- layer,
- command_queue: device.new_command_queue(),
- sprite_cache,
- image_cache,
- path_atlases,
- quad_pipeline_state,
- shadow_pipeline_state,
- sprite_pipeline_state,
- image_pipeline_state,
- surface_pipeline_state,
- path_atlas_pipeline_state,
- underline_pipeline_state,
- unit_vertices,
- instances,
- cv_texture_cache,
- }
- }
-
- pub fn layer(&self) -> &metal::MetalLayerRef {
- &*self.layer
- }
-
- pub fn render(&mut self, scene: &Scene) {
- let layer = self.layer.clone();
- let drawable_size = layer.drawable_size();
- let drawable = if let Some(drawable) = layer.next_drawable() {
- drawable
- } else {
- log::error!(
- "failed to retrieve next drawable, drawable size: {:?}",
- drawable_size
- );
- return;
- };
- let command_queue = self.command_queue.clone();
- let command_buffer = command_queue.new_command_buffer();
-
- self.sprite_cache.set_scale_factor(scene.scale_factor());
- self.image_cache.set_scale_factor(scene.scale_factor());
-
- let mut offset = 0;
-
- let path_sprites = self.render_path_atlases(scene, &mut offset, command_buffer);
- self.render_layers(
- scene,
- path_sprites,
- &mut offset,
- vec2f(drawable_size.width as f32, drawable_size.height as f32),
- command_buffer,
- drawable.texture(),
- );
- self.instances.did_modify_range(NSRange {
- location: 0,
- length: offset as NSUInteger,
- });
- self.image_cache.finish_frame();
-
- command_buffer.commit();
- command_buffer.wait_until_completed();
- drawable.present();
- }
-
- fn render_path_atlases(
- &mut self,
- scene: &Scene,
- offset: &mut usize,
- command_buffer: &metal::CommandBufferRef,
- ) -> Vec<PathSprite> {
- self.path_atlases.clear();
- let mut sprites = Vec::new();
- let mut vertices = Vec::<shaders::GPUIPathVertex>::new();
- let mut current_atlas_id = None;
- for (layer_id, layer) in scene.layers().enumerate() {
- for path in layer.paths() {
- let origin = path.bounds.origin() * scene.scale_factor();
- let size = (path.bounds.size() * scene.scale_factor()).ceil();
-
- let path_allocation = self.path_atlases.allocate(size.to_i32());
- if path_allocation.is_none() {
- // Path size was likely zero.
- warn!("could not allocate path texture of size {:?}", size);
- continue;
- }
- let (alloc_id, atlas_origin) = path_allocation.unwrap();
- let atlas_origin = atlas_origin.to_f32();
- sprites.push(PathSprite {
- layer_id,
- atlas_id: alloc_id.atlas_id,
- shader_data: shaders::GPUISprite {
- origin: origin.floor().to_float2(),
- target_size: size.to_float2(),
- source_size: size.to_float2(),
- atlas_origin: atlas_origin.to_float2(),
- color: path.color.to_uchar4(),
- compute_winding: 1,
- },
- });
-
- if let Some(current_atlas_id) = current_atlas_id {
- if alloc_id.atlas_id != current_atlas_id {
- self.render_paths_to_atlas(
- offset,
- &vertices,
- current_atlas_id,
- command_buffer,
- );
- vertices.clear();
- }
- }
-
- current_atlas_id = Some(alloc_id.atlas_id);
-
- for vertex in &path.vertices {
- let xy_position =
- (vertex.xy_position - path.bounds.origin()) * scene.scale_factor();
- vertices.push(shaders::GPUIPathVertex {
- xy_position: (atlas_origin + xy_position).to_float2(),
- st_position: vertex.st_position.to_float2(),
- clip_rect_origin: atlas_origin.to_float2(),
- clip_rect_size: size.to_float2(),
- });
- }
- }
- }
-
- if let Some(atlas_id) = current_atlas_id {
- self.render_paths_to_atlas(offset, &vertices, atlas_id, command_buffer);
- }
-
- sprites
- }
-
- fn render_paths_to_atlas(
- &mut self,
- offset: &mut usize,
- vertices: &[shaders::GPUIPathVertex],
- atlas_id: usize,
- command_buffer: &metal::CommandBufferRef,
- ) {
- align_offset(offset);
- let next_offset = *offset + vertices.len() * mem::size_of::<shaders::GPUIPathVertex>();
- assert!(
- next_offset <= INSTANCE_BUFFER_SIZE,
- "instance buffer exhausted"
- );
-
- let render_pass_descriptor = metal::RenderPassDescriptor::new();
- let color_attachment = render_pass_descriptor
- .color_attachments()
- .object_at(0)
- .unwrap();
- let texture = self.path_atlases.texture(atlas_id).unwrap();
- color_attachment.set_texture(Some(texture));
- color_attachment.set_load_action(metal::MTLLoadAction::Clear);
- color_attachment.set_store_action(metal::MTLStoreAction::Store);
- color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
-
- let path_atlas_command_encoder =
- command_buffer.new_render_command_encoder(render_pass_descriptor);
- path_atlas_command_encoder.set_render_pipeline_state(&self.path_atlas_pipeline_state);
- path_atlas_command_encoder.set_vertex_buffer(
- shaders::GPUIPathAtlasVertexInputIndex_GPUIPathAtlasVertexInputIndexVertices as u64,
- Some(&self.instances),
- *offset as u64,
- );
- path_atlas_command_encoder.set_vertex_bytes(
- shaders::GPUIPathAtlasVertexInputIndex_GPUIPathAtlasVertexInputIndexAtlasSize as u64,
- mem::size_of::<shaders::vector_float2>() as u64,
- [vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr()
- as *const c_void,
- );
-
- let buffer_contents = unsafe {
- (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIPathVertex
- };
-
- for (ix, vertex) in vertices.iter().enumerate() {
- unsafe {
- *buffer_contents.add(ix) = *vertex;
- }
- }
-
- path_atlas_command_encoder.draw_primitives(
- metal::MTLPrimitiveType::Triangle,
- 0,
- vertices.len() as u64,
- );
- path_atlas_command_encoder.end_encoding();
- *offset = next_offset;
- }
-
- fn render_layers(
- &mut self,
- scene: &Scene,
- path_sprites: Vec<PathSprite>,
- offset: &mut usize,
- drawable_size: Vector2F,
- command_buffer: &metal::CommandBufferRef,
- output: &metal::TextureRef,
- ) {
- let render_pass_descriptor = metal::RenderPassDescriptor::new();
- let color_attachment = render_pass_descriptor
- .color_attachments()
- .object_at(0)
- .unwrap();
- color_attachment.set_texture(Some(output));
- color_attachment.set_load_action(metal::MTLLoadAction::Clear);
- color_attachment.set_store_action(metal::MTLStoreAction::Store);
- let alpha = if self.layer.is_opaque() { 1. } else { 0. };
- color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
- let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
-
- command_encoder.set_viewport(metal::MTLViewport {
- originX: 0.0,
- originY: 0.0,
- width: drawable_size.x() as f64,
- height: drawable_size.y() as f64,
- znear: 0.0,
- zfar: 1.0,
- });
-
- let scale_factor = scene.scale_factor();
- let mut path_sprites = path_sprites.into_iter().peekable();
- for (layer_id, layer) in scene.layers().enumerate() {
- self.clip(scene, layer, drawable_size, command_encoder);
- self.render_shadows(
- layer.shadows(),
- scale_factor,
- offset,
- drawable_size,
- command_encoder,
- );
- self.render_quads(
- layer.quads(),
- scale_factor,
- offset,
- drawable_size,
- command_encoder,
- );
- self.render_path_sprites(
- layer_id,
- &mut path_sprites,
- offset,
- drawable_size,
- command_encoder,
- );
- self.render_underlines(
- layer.underlines(),
- scale_factor,
- offset,
- drawable_size,
- command_encoder,
- );
- self.render_sprites(
- layer.glyphs(),
- layer.icons(),
- scale_factor,
- offset,
- drawable_size,
- command_encoder,
- );
- self.render_images(
- layer.images(),
- layer.image_glyphs(),
- scale_factor,
- offset,
- drawable_size,
- command_encoder,
- );
- self.render_surfaces(
- layer.surfaces(),
- scale_factor,
- offset,
- drawable_size,
- command_encoder,
- );
- }
-
- command_encoder.end_encoding();
- }
-
- fn clip(
- &mut self,
- scene: &Scene,
- layer: &Layer,
- drawable_size: Vector2F,
- command_encoder: &metal::RenderCommandEncoderRef,
- ) {
- let clip_bounds = (layer
- .clip_bounds()
- .unwrap_or_else(|| RectF::new(vec2f(0., 0.), drawable_size / scene.scale_factor()))
- * scene.scale_factor())
- .round();
- command_encoder.set_scissor_rect(metal::MTLScissorRect {
- x: clip_bounds.origin_x() as NSUInteger,
- y: clip_bounds.origin_y() as NSUInteger,
- width: clip_bounds.width() as NSUInteger,
- height: clip_bounds.height() as NSUInteger,
- });
- }
-
- fn render_shadows(
- &mut self,
- shadows: &[Shadow],
- scale_factor: f32,
- offset: &mut usize,
- drawable_size: Vector2F,
- command_encoder: &metal::RenderCommandEncoderRef,
- ) {
- if shadows.is_empty() {
- return;
- }
-
- align_offset(offset);
- let next_offset = *offset + shadows.len() * mem::size_of::<shaders::GPUIShadow>();
- assert!(
- next_offset <= INSTANCE_BUFFER_SIZE,
- "instance buffer exhausted"
- );
-
- command_encoder.set_render_pipeline_state(&self.shadow_pipeline_state);
- command_encoder.set_vertex_buffer(
- shaders::GPUIShadowInputIndex_GPUIShadowInputIndexVertices as u64,
- Some(&self.unit_vertices),
- 0,
- );
- command_encoder.set_vertex_buffer(
- shaders::GPUIShadowInputIndex_GPUIShadowInputIndexShadows as u64,
- Some(&self.instances),
- *offset as u64,
- );
- command_encoder.set_vertex_bytes(
- shaders::GPUIShadowInputIndex_GPUIShadowInputIndexUniforms as u64,
- mem::size_of::<shaders::GPUIUniforms>() as u64,
- [shaders::GPUIUniforms {
- viewport_size: drawable_size.to_float2(),
- }]
- .as_ptr() as *const c_void,
- );
-
- let buffer_contents = unsafe {
- (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIShadow
- };
- for (ix, shadow) in shadows.iter().enumerate() {
- let shape_bounds = shadow.bounds * scale_factor;
- let corner_radii = shadow.corner_radii * scale_factor;
- let shader_shadow = shaders::GPUIShadow {
- origin: shape_bounds.origin().to_float2(),
- size: shape_bounds.size().to_float2(),
- corner_radius_top_left: corner_radii.top_left,
- corner_radius_top_right: corner_radii.top_right,
- corner_radius_bottom_right: corner_radii.bottom_right,
- corner_radius_bottom_left: corner_radii.bottom_left,
- sigma: shadow.sigma,
- color: shadow.color.to_uchar4(),
- };
- unsafe {
- *(buffer_contents.add(ix)) = shader_shadow;
- }
- }
-
- command_encoder.draw_primitives_instanced(
- metal::MTLPrimitiveType::Triangle,
- 0,
- 6,
- shadows.len() as u64,
- );
- *offset = next_offset;
- }
-
- fn render_quads(
- &mut self,
- quads: &[Quad],
- scale_factor: f32,
- offset: &mut usize,
- drawable_size: Vector2F,
- command_encoder: &metal::RenderCommandEncoderRef,
- ) {
- if quads.is_empty() {
- return;
- }
- align_offset(offset);
- let next_offset = *offset + quads.len() * mem::size_of::<shaders::GPUIQuad>();
- assert!(
- next_offset <= INSTANCE_BUFFER_SIZE,
- "instance buffer exhausted"
- );
-
- command_encoder.set_render_pipeline_state(&self.quad_pipeline_state);
- command_encoder.set_vertex_buffer(
- shaders::GPUIQuadInputIndex_GPUIQuadInputIndexVertices as u64,
- Some(&self.unit_vertices),
- 0,
- );
- command_encoder.set_vertex_buffer(
- shaders::GPUIQuadInputIndex_GPUIQuadInputIndexQuads as u64,
- Some(&self.instances),
- *offset as u64,
- );
- command_encoder.set_vertex_bytes(
- shaders::GPUIQuadInputIndex_GPUIQuadInputIndexUniforms as u64,
- mem::size_of::<shaders::GPUIUniforms>() as u64,
- [shaders::GPUIUniforms {
- viewport_size: drawable_size.to_float2(),
- }]
- .as_ptr() as *const c_void,
- );
-
- let buffer_contents = unsafe {
- (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIQuad
- };
- for (ix, quad) in quads.iter().enumerate() {
- let bounds = quad.bounds * scale_factor;
- let shader_quad = shaders::GPUIQuad {
- origin: bounds.origin().round().to_float2(),
- size: bounds.size().round().to_float2(),
- background_color: quad
- .background
- .unwrap_or_else(Color::transparent_black)
- .to_uchar4(),
- border_top: quad.border.top * scale_factor,
- border_right: quad.border.right * scale_factor,
- border_bottom: quad.border.bottom * scale_factor,
- border_left: quad.border.left * scale_factor,
- border_color: quad.border.color.to_uchar4(),
- corner_radius_top_left: quad.corner_radii.top_left * scale_factor,
- corner_radius_top_right: quad.corner_radii.top_right * scale_factor,
- corner_radius_bottom_right: quad.corner_radii.bottom_right * scale_factor,
- corner_radius_bottom_left: quad.corner_radii.bottom_left * scale_factor,
- };
- unsafe {
- *(buffer_contents.add(ix)) = shader_quad;
- }
- }
-
- command_encoder.draw_primitives_instanced(
- metal::MTLPrimitiveType::Triangle,
- 0,
- 6,
- quads.len() as u64,
- );
- *offset = next_offset;
- }
-
- fn render_sprites(
- &mut self,
- glyphs: &[Glyph],
- icons: &[Icon],
- scale_factor: f32,
- offset: &mut usize,
- drawable_size: Vector2F,
- command_encoder: &metal::RenderCommandEncoderRef,
- ) {
- if glyphs.is_empty() && icons.is_empty() {
- return;
- }
-
- let mut sprites_by_atlas = HashMap::new();
-
- for glyph in glyphs {
- if let Some(sprite) = self.sprite_cache.render_glyph(
- glyph.font_id,
- glyph.font_size,
- glyph.id,
- glyph.origin,
- ) {
- // Snap sprite to pixel grid.
- let origin = (glyph.origin * scale_factor).floor() + sprite.offset.to_f32();
-
- sprites_by_atlas
- .entry(sprite.atlas_id)
- .or_insert_with(Vec::new)
- .push(shaders::GPUISprite {
- origin: origin.to_float2(),
- target_size: sprite.size.to_float2(),
- source_size: sprite.size.to_float2(),
- atlas_origin: sprite.atlas_origin.to_float2(),
- color: glyph.color.to_uchar4(),
- compute_winding: 0,
- });
- }
- }
-
- for icon in icons {
- // Snap sprite to pixel grid.
- let origin = (icon.bounds.origin() * scale_factor).floor();
- let target_size = (icon.bounds.size() * scale_factor).ceil();
- let source_size = (target_size * 2.).to_i32();
-
- let sprite =
- self.sprite_cache
- .render_icon(source_size, icon.path.clone(), icon.svg.clone());
- if sprite.is_none() {
- continue;
- }
- let sprite = sprite.unwrap();
-
- sprites_by_atlas
- .entry(sprite.atlas_id)
- .or_insert_with(Vec::new)
- .push(shaders::GPUISprite {
- origin: origin.to_float2(),
- target_size: target_size.to_float2(),
- source_size: sprite.size.to_float2(),
- atlas_origin: sprite.atlas_origin.to_float2(),
- color: icon.color.to_uchar4(),
- compute_winding: 0,
- });
- }
-
- command_encoder.set_render_pipeline_state(&self.sprite_pipeline_state);
- command_encoder.set_vertex_buffer(
- shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexVertices as u64,
- Some(&self.unit_vertices),
- 0,
- );
- command_encoder.set_vertex_bytes(
- shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexViewportSize as u64,
- mem::size_of::<shaders::vector_float2>() as u64,
- [drawable_size.to_float2()].as_ptr() as *const c_void,
- );
-
- for (atlas_id, sprites) in sprites_by_atlas {
- align_offset(offset);
- let next_offset = *offset + sprites.len() * mem::size_of::<shaders::GPUISprite>();
- assert!(
- next_offset <= INSTANCE_BUFFER_SIZE,
- "instance buffer exhausted"
- );
-
- let texture = self.sprite_cache.atlas_texture(atlas_id).unwrap();
- command_encoder.set_vertex_buffer(
- shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexSprites as u64,
- Some(&self.instances),
- *offset as u64,
- );
- command_encoder.set_vertex_bytes(
- shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexAtlasSize as u64,
- mem::size_of::<shaders::vector_float2>() as u64,
- [vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr()
- as *const c_void,
- );
-
- command_encoder.set_fragment_texture(
- shaders::GPUISpriteFragmentInputIndex_GPUISpriteFragmentInputIndexAtlas as u64,
- Some(texture),
- );
-
- unsafe {
- let buffer_contents =
- (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUISprite;
- std::ptr::copy_nonoverlapping(sprites.as_ptr(), buffer_contents, sprites.len());
- }
-
- command_encoder.draw_primitives_instanced(
- metal::MTLPrimitiveType::Triangle,
- 0,
- 6,
- sprites.len() as u64,
- );
- *offset = next_offset;
- }
- }
-
- fn render_images(
- &mut self,
- images: &[Image],
- image_glyphs: &[ImageGlyph],
- scale_factor: f32,
- offset: &mut usize,
- drawable_size: Vector2F,
- command_encoder: &metal::RenderCommandEncoderRef,
- ) {
- if images.is_empty() && image_glyphs.is_empty() {
- return;
- }
-
- let mut images_by_atlas = HashMap::new();
- for image in images {
- let origin = image.bounds.origin() * scale_factor;
- let target_size = image.bounds.size() * scale_factor;
- let corner_radii = image.corner_radii * scale_factor;
- let (alloc_id, atlas_bounds) = self.image_cache.render(&image.data);
- images_by_atlas
- .entry(alloc_id.atlas_id)
- .or_insert_with(Vec::new)
- .push(shaders::GPUIImage {
- origin: origin.to_float2(),
- target_size: target_size.to_float2(),
- source_size: atlas_bounds.size().to_float2(),
- atlas_origin: atlas_bounds.origin().to_float2(),
- border_top: image.border.top * scale_factor,
- border_right: image.border.right * scale_factor,
- border_bottom: image.border.bottom * scale_factor,
- border_left: image.border.left * scale_factor,
- border_color: image.border.color.to_uchar4(),
- corner_radius_top_left: corner_radii.top_left,
- corner_radius_top_right: corner_radii.top_right,
- corner_radius_bottom_right: corner_radii.bottom_right,
- corner_radius_bottom_left: corner_radii.bottom_left,
- grayscale: image.grayscale as u8,
- });
- }
-
- for image_glyph in image_glyphs {
- let origin = (image_glyph.origin * scale_factor).floor();
- if let Some((alloc_id, atlas_bounds, glyph_origin)) =
- self.image_cache.render_glyph(image_glyph)
- {
- images_by_atlas
- .entry(alloc_id.atlas_id)
- .or_insert_with(Vec::new)
- .push(shaders::GPUIImage {
- origin: (origin + glyph_origin.to_f32()).to_float2(),
- target_size: atlas_bounds.size().to_float2(),
- source_size: atlas_bounds.size().to_float2(),
- atlas_origin: atlas_bounds.origin().to_float2(),
- border_top: 0.,
- border_right: 0.,
- border_bottom: 0.,
- border_left: 0.,
- border_color: Default::default(),
- corner_radius_top_left: 0.,
- corner_radius_top_right: 0.,
- corner_radius_bottom_right: 0.,
- corner_radius_bottom_left: 0.,
- grayscale: false as u8,
- });
- } else {
- log::warn!("could not render glyph with id {}", image_glyph.id);
- }
- }
-
- command_encoder.set_render_pipeline_state(&self.image_pipeline_state);
- command_encoder.set_vertex_buffer(
- shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexVertices as u64,
- Some(&self.unit_vertices),
- 0,
- );
- command_encoder.set_vertex_bytes(
- shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexViewportSize as u64,
- mem::size_of::<shaders::vector_float2>() as u64,
- [drawable_size.to_float2()].as_ptr() as *const c_void,
- );
-
- for (atlas_id, images) in images_by_atlas {
- align_offset(offset);
- let next_offset = *offset + images.len() * mem::size_of::<shaders::GPUIImage>();
- assert!(
- next_offset <= INSTANCE_BUFFER_SIZE,
- "instance buffer exhausted"
- );
-
- let texture = self.image_cache.atlas_texture(atlas_id).unwrap();
- command_encoder.set_vertex_buffer(
- shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexImages as u64,
- Some(&self.instances),
- *offset as u64,
- );
- command_encoder.set_vertex_bytes(
- shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexAtlasSize as u64,
- mem::size_of::<shaders::vector_float2>() as u64,
- [vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr()
- as *const c_void,
- );
- command_encoder.set_fragment_texture(
- shaders::GPUIImageFragmentInputIndex_GPUIImageFragmentInputIndexAtlas as u64,
- Some(texture),
- );
-
- unsafe {
- let buffer_contents =
- (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIImage;
- std::ptr::copy_nonoverlapping(images.as_ptr(), buffer_contents, images.len());
- }
-
- command_encoder.draw_primitives_instanced(
- metal::MTLPrimitiveType::Triangle,
- 0,
- 6,
- images.len() as u64,
- );
- *offset = next_offset;
- }
- }
-
- fn render_surfaces(
- &mut self,
- surfaces: &[Surface],
- scale_factor: f32,
- offset: &mut usize,
- drawable_size: Vector2F,
- command_encoder: &metal::RenderCommandEncoderRef,
- ) {
- if surfaces.is_empty() {
- return;
- }
-
- command_encoder.set_render_pipeline_state(&self.surface_pipeline_state);
- command_encoder.set_vertex_buffer(
- shaders::GPUISurfaceVertexInputIndex_GPUISurfaceVertexInputIndexVertices as u64,
- Some(&self.unit_vertices),
- 0,
- );
- command_encoder.set_vertex_bytes(
- shaders::GPUISurfaceVertexInputIndex_GPUISurfaceVertexInputIndexViewportSize as u64,
- mem::size_of::<shaders::vector_float2>() as u64,
- [drawable_size.to_float2()].as_ptr() as *const c_void,
- );
-
- for surface in surfaces {
- let origin = surface.bounds.origin() * scale_factor;
- let source_size = vec2i(
- surface.image_buffer.width() as i32,
- surface.image_buffer.height() as i32,
- );
- let target_size = surface.bounds.size() * scale_factor;
-
- assert_eq!(
- surface.image_buffer.pixel_format_type(),
- core_video::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
- );
-
- let y_texture = unsafe {
- self.cv_texture_cache
- .create_texture_from_image(
- surface.image_buffer.as_concrete_TypeRef(),
- ptr::null(),
- MTLPixelFormat::R8Unorm,
- surface.image_buffer.plane_width(0),
- surface.image_buffer.plane_height(0),
- 0,
- )
- .unwrap()
- };
- let cb_cr_texture = unsafe {
- self.cv_texture_cache
- .create_texture_from_image(
- surface.image_buffer.as_concrete_TypeRef(),
- ptr::null(),
- MTLPixelFormat::RG8Unorm,
- surface.image_buffer.plane_width(1),
- surface.image_buffer.plane_height(1),
- 1,
- )
- .unwrap()
- };
-
- align_offset(offset);
- let next_offset = *offset + mem::size_of::<shaders::GPUISurface>();
- assert!(
- next_offset <= INSTANCE_BUFFER_SIZE,
- "instance buffer exhausted"
- );
-
- command_encoder.set_vertex_buffer(
- shaders::GPUISurfaceVertexInputIndex_GPUISurfaceVertexInputIndexSurfaces as u64,
- Some(&self.instances),
- *offset as u64,
- );
- command_encoder.set_vertex_bytes(
- shaders::GPUISurfaceVertexInputIndex_GPUISurfaceVertexInputIndexAtlasSize as u64,
- mem::size_of::<shaders::vector_float2>() as u64,
- [source_size.to_float2()].as_ptr() as *const c_void,
- );
- command_encoder.set_fragment_texture(
- shaders::GPUISurfaceFragmentInputIndex_GPUISurfaceFragmentInputIndexYAtlas as u64,
- Some(y_texture.as_texture_ref()),
- );
- command_encoder.set_fragment_texture(
- shaders::GPUISurfaceFragmentInputIndex_GPUISurfaceFragmentInputIndexCbCrAtlas
- as u64,
- Some(cb_cr_texture.as_texture_ref()),
- );
-
- unsafe {
- let buffer_contents = (self.instances.contents() as *mut u8).add(*offset)
- as *mut shaders::GPUISurface;
- std::ptr::write(
- buffer_contents,
- shaders::GPUISurface {
- origin: origin.to_float2(),
- target_size: target_size.to_float2(),
- source_size: source_size.to_float2(),
- },
- );
- }
-
- command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6);
- *offset = next_offset;
- }
- }
-
- fn render_path_sprites(
- &mut self,
- layer_id: usize,
- sprites: &mut Peekable<vec::IntoIter<PathSprite>>,
- offset: &mut usize,
- drawable_size: Vector2F,
- command_encoder: &metal::RenderCommandEncoderRef,
- ) {
- command_encoder.set_render_pipeline_state(&self.sprite_pipeline_state);
- command_encoder.set_vertex_buffer(
- shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexVertices as u64,
- Some(&self.unit_vertices),
- 0,
- );
- command_encoder.set_vertex_bytes(
- shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexViewportSize as u64,
- mem::size_of::<shaders::vector_float2>() as u64,
- [drawable_size.to_float2()].as_ptr() as *const c_void,
- );
-
- let mut atlas_id = None;
- let mut atlas_sprite_count = 0;
- align_offset(offset);
-
- while let Some(sprite) = sprites.peek() {
- if sprite.layer_id != layer_id {
- break;
- }
-
- let sprite = sprites.next().unwrap();
- if let Some(atlas_id) = atlas_id.as_mut() {
- if sprite.atlas_id != *atlas_id {
- self.render_path_sprites_for_atlas(
- offset,
- *atlas_id,
- atlas_sprite_count,
- command_encoder,
- );
-
- *atlas_id = sprite.atlas_id;
- atlas_sprite_count = 0;
- align_offset(offset);
- }
- } else {
- atlas_id = Some(sprite.atlas_id);
- }
-
- unsafe {
- let buffer_contents =
- (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUISprite;
- *buffer_contents.add(atlas_sprite_count) = sprite.shader_data;
- }
-
- atlas_sprite_count += 1;
- }
-
- if let Some(atlas_id) = atlas_id {
- self.render_path_sprites_for_atlas(
- offset,
- atlas_id,
- atlas_sprite_count,
- command_encoder,
- );
- }
- }
-
- fn render_path_sprites_for_atlas(
- &mut self,
- offset: &mut usize,
- atlas_id: usize,
- sprite_count: usize,
- command_encoder: &metal::RenderCommandEncoderRef,
- ) {
- let next_offset = *offset + sprite_count * mem::size_of::<shaders::GPUISprite>();
- assert!(
- next_offset <= INSTANCE_BUFFER_SIZE,
- "instance buffer exhausted"
- );
- command_encoder.set_vertex_buffer(
- shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexSprites as u64,
- Some(&self.instances),
- *offset as u64,
- );
- let texture = self.path_atlases.texture(atlas_id).unwrap();
- command_encoder.set_fragment_texture(
- shaders::GPUISpriteFragmentInputIndex_GPUISpriteFragmentInputIndexAtlas as u64,
- Some(texture),
- );
- command_encoder.set_vertex_bytes(
- shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexAtlasSize as u64,
- mem::size_of::<shaders::vector_float2>() as u64,
- [vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr()
- as *const c_void,
- );
-
- command_encoder.draw_primitives_instanced(
- metal::MTLPrimitiveType::Triangle,
- 0,
- 6,
- sprite_count as u64,
- );
- *offset = next_offset;
- }
-
- fn render_underlines(
- &mut self,
- underlines: &[Underline],
- scale_factor: f32,
- offset: &mut usize,
- drawable_size: Vector2F,
- command_encoder: &metal::RenderCommandEncoderRef,
- ) {
- if underlines.is_empty() {
- return;
- }
- align_offset(offset);
- let next_offset = *offset + underlines.len() * mem::size_of::<shaders::GPUIUnderline>();
- assert!(
- next_offset <= INSTANCE_BUFFER_SIZE,
- "instance buffer exhausted"
- );
-
- command_encoder.set_render_pipeline_state(&self.underline_pipeline_state);
- command_encoder.set_vertex_buffer(
- shaders::GPUIUnderlineInputIndex_GPUIUnderlineInputIndexVertices as u64,
- Some(&self.unit_vertices),
- 0,
- );
- command_encoder.set_vertex_buffer(
- shaders::GPUIUnderlineInputIndex_GPUIUnderlineInputIndexUnderlines as u64,
- Some(&self.instances),
- *offset as u64,
- );
- command_encoder.set_vertex_bytes(
- shaders::GPUIUnderlineInputIndex_GPUIUnderlineInputIndexUniforms as u64,
- mem::size_of::<shaders::GPUIUniforms>() as u64,
- [shaders::GPUIUniforms {
- viewport_size: drawable_size.to_float2(),
- }]
- .as_ptr() as *const c_void,
- );
-
- let buffer_contents = unsafe {
- (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIUnderline
- };
- for (ix, underline) in underlines.iter().enumerate() {
- let origin = underline.origin * scale_factor;
- let mut height = underline.thickness;
- if underline.squiggly {
- height *= 3.;
- }
- let size = vec2f(underline.width, height) * scale_factor;
- let shader_underline = shaders::GPUIUnderline {
- origin: origin.round().to_float2(),
- size: size.round().to_float2(),
- thickness: underline.thickness * scale_factor,
- color: underline.color.to_uchar4(),
- squiggly: underline.squiggly as u8,
- };
- unsafe {
- *(buffer_contents.add(ix)) = shader_underline;
- }
- }
-
- command_encoder.draw_primitives_instanced(
- metal::MTLPrimitiveType::Triangle,
- 0,
- 6,
- underlines.len() as u64,
- );
- *offset = next_offset;
- }
-}
-
-fn build_path_atlas_texture_descriptor() -> metal::TextureDescriptor {
- let texture_descriptor = metal::TextureDescriptor::new();
- texture_descriptor.set_width(2048);
- texture_descriptor.set_height(2048);
- texture_descriptor.set_pixel_format(MTLPixelFormat::R16Float);
- texture_descriptor
- .set_usage(metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead);
- texture_descriptor.set_storage_mode(metal::MTLStorageMode::Private);
- texture_descriptor
-}
-
-fn align_offset(offset: &mut usize) {
- let r = *offset % 256;
- if r > 0 {
- *offset += 256 - r; // Align to a multiple of 256 to make Metal happy
- }
-}
-
-fn build_pipeline_state(
- device: &metal::DeviceRef,
- library: &metal::LibraryRef,
- label: &str,
- vertex_fn_name: &str,
- fragment_fn_name: &str,
- pixel_format: metal::MTLPixelFormat,
-) -> metal::RenderPipelineState {
- let vertex_fn = library
- .get_function(vertex_fn_name, None)
- .expect("error locating vertex function");
- let fragment_fn = library
- .get_function(fragment_fn_name, None)
- .expect("error locating fragment function");
-
- let descriptor = metal::RenderPipelineDescriptor::new();
- descriptor.set_label(label);
- descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
- descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
- let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
- color_attachment.set_pixel_format(pixel_format);
- color_attachment.set_blending_enabled(true);
- color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
- color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
- color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
- color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
- color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
- color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
-
- device
- .new_render_pipeline_state(&descriptor)
- .expect("could not create render pipeline state")
-}
-
-fn build_path_atlas_pipeline_state(
- device: &metal::DeviceRef,
- library: &metal::LibraryRef,
- label: &str,
- vertex_fn_name: &str,
- fragment_fn_name: &str,
- pixel_format: metal::MTLPixelFormat,
-) -> metal::RenderPipelineState {
- let vertex_fn = library
- .get_function(vertex_fn_name, None)
- .expect("error locating vertex function");
- let fragment_fn = library
- .get_function(fragment_fn_name, None)
- .expect("error locating fragment function");
-
- let descriptor = metal::RenderPipelineDescriptor::new();
- descriptor.set_label(label);
- descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
- descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
- let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
- color_attachment.set_pixel_format(pixel_format);
- color_attachment.set_blending_enabled(true);
- color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
- color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
- color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::One);
- color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
- color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::One);
- color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
-
- device
- .new_render_pipeline_state(&descriptor)
- .expect("could not create render pipeline state")
-}
-
-mod shaders {
- #![allow(non_upper_case_globals)]
- #![allow(non_camel_case_types)]
- #![allow(non_snake_case)]
-
- use crate::{
- color::Color,
- geometry::vector::{Vector2F, Vector2I},
- };
- use std::mem;
-
- include!(concat!(env!("OUT_DIR"), "/shaders.rs"));
-
- pub trait ToFloat2 {
- fn to_float2(&self) -> vector_float2;
- }
-
- impl ToFloat2 for (f32, f32) {
- fn to_float2(&self) -> vector_float2 {
- unsafe {
- let mut output = mem::transmute::<_, u32>(self.1.to_bits()) as vector_float2;
- output <<= 32;
- output |= mem::transmute::<_, u32>(self.0.to_bits()) as vector_float2;
- output
- }
- }
- }
-
- impl ToFloat2 for Vector2F {
- fn to_float2(&self) -> vector_float2 {
- unsafe {
- let mut output = mem::transmute::<_, u32>(self.y().to_bits()) as vector_float2;
- output <<= 32;
- output |= mem::transmute::<_, u32>(self.x().to_bits()) as vector_float2;
- output
- }
- }
- }
-
- impl ToFloat2 for Vector2I {
- fn to_float2(&self) -> vector_float2 {
- self.to_f32().to_float2()
- }
- }
-
- impl Color {
- pub fn to_uchar4(&self) -> vector_uchar4 {
- let mut vec = self.a as vector_uchar4;
- vec <<= 8;
- vec |= self.b as vector_uchar4;
- vec <<= 8;
- vec |= self.g as vector_uchar4;
- vec <<= 8;
- vec |= self.r as vector_uchar4;
- vec
- }
- }
-}
@@ -1,144 +0,0 @@
-use super::ns_string;
-use crate::platform;
-use cocoa::{
- appkit::NSScreen,
- base::{id, nil},
- foundation::{NSArray, NSDictionary, NSPoint, NSRect, NSSize},
-};
-use core_foundation::{
- number::{kCFNumberIntType, CFNumberGetValue, CFNumberRef},
- uuid::{CFUUIDGetUUIDBytes, CFUUIDRef},
-};
-use core_graphics::display::CGDirectDisplayID;
-use pathfinder_geometry::{rect::RectF, vector::vec2f};
-use std::{any::Any, ffi::c_void};
-use uuid::Uuid;
-
-#[link(name = "ApplicationServices", kind = "framework")]
-extern "C" {
- pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
-}
-
-#[derive(Debug)]
-pub struct Screen {
- pub(crate) native_screen: id,
-}
-
-impl Screen {
- /// Get the screen with the given UUID.
- pub fn find_by_id(uuid: Uuid) -> Option<Self> {
- Self::all().find(|screen| platform::Screen::display_uuid(screen) == Some(uuid))
- }
-
- /// Get the primary screen - the one with the menu bar, and whose bottom left
- /// corner is at the origin of the AppKit coordinate system.
- fn primary() -> Self {
- Self::all().next().unwrap()
- }
-
- pub fn all() -> impl Iterator<Item = Self> {
- unsafe {
- let native_screens = NSScreen::screens(nil);
- (0..NSArray::count(native_screens)).map(move |ix| Screen {
- native_screen: native_screens.objectAtIndex(ix),
- })
- }
- }
-
- /// Convert the given rectangle in screen coordinates from GPUI's
- /// coordinate system to the AppKit coordinate system.
- ///
- /// In GPUI's coordinates, the origin is at the top left of the primary screen, with
- /// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
- /// bottom left of the primary screen, with the Y axis pointing upward.
- pub(crate) fn screen_rect_to_native(rect: RectF) -> NSRect {
- let primary_screen_height = unsafe { Self::primary().native_screen.frame().size.height };
- NSRect::new(
- NSPoint::new(
- rect.origin_x() as f64,
- primary_screen_height - rect.origin_y() as f64 - rect.height() as f64,
- ),
- NSSize::new(rect.width() as f64, rect.height() as f64),
- )
- }
-
- /// Convert the given rectangle in screen coordinates from the AppKit
- /// coordinate system to GPUI's coordinate system.
- ///
- /// In GPUI's coordinates, the origin is at the top left of the primary screen, with
- /// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
- /// bottom left of the primary screen, with the Y axis pointing upward.
- pub(crate) fn screen_rect_from_native(rect: NSRect) -> RectF {
- let primary_screen_height = unsafe { Self::primary().native_screen.frame().size.height };
- RectF::new(
- vec2f(
- rect.origin.x as f32,
- (primary_screen_height - rect.origin.y - rect.size.height) as f32,
- ),
- vec2f(rect.size.width as f32, rect.size.height as f32),
- )
- }
-}
-
-impl platform::Screen for Screen {
- fn as_any(&self) -> &dyn Any {
- self
- }
-
- fn display_uuid(&self) -> Option<uuid::Uuid> {
- unsafe {
- // Screen ids are not stable. Further, the default device id is also unstable across restarts.
- // CGDisplayCreateUUIDFromDisplayID is stable but not exposed in the bindings we use.
- // This approach is similar to that which winit takes
- // https://github.com/rust-windowing/winit/blob/402cbd55f932e95dbfb4e8b5e8551c49e56ff9ac/src/platform_impl/macos/monitor.rs#L99
- let device_description = self.native_screen.deviceDescription();
-
- let key = ns_string("NSScreenNumber");
- let device_id_obj = device_description.objectForKey_(key);
- if device_id_obj.is_null() {
- // Under some circumstances, especially display re-arrangements or display locking, we seem to get a null pointer
- // to the device id. See: https://linear.app/zed-industries/issue/Z-257/lock-screen-crash-with-multiple-monitors
- return None;
- }
-
- let mut device_id: u32 = 0;
- CFNumberGetValue(
- device_id_obj as CFNumberRef,
- kCFNumberIntType,
- (&mut device_id) as *mut _ as *mut c_void,
- );
- let cfuuid = CGDisplayCreateUUIDFromDisplayID(device_id as CGDirectDisplayID);
- if cfuuid.is_null() {
- return None;
- }
-
- let bytes = CFUUIDGetUUIDBytes(cfuuid);
- Some(Uuid::from_bytes([
- bytes.byte0,
- bytes.byte1,
- bytes.byte2,
- bytes.byte3,
- bytes.byte4,
- bytes.byte5,
- bytes.byte6,
- bytes.byte7,
- bytes.byte8,
- bytes.byte9,
- bytes.byte10,
- bytes.byte11,
- bytes.byte12,
- bytes.byte13,
- bytes.byte14,
- bytes.byte15,
- ]))
- }
- }
-
- fn bounds(&self) -> RectF {
- unsafe { Self::screen_rect_from_native(self.native_screen.frame()) }
- }
-
- fn content_bounds(&self) -> RectF {
- unsafe { Self::screen_rect_from_native(self.native_screen.visibleFrame()) }
- }
-}
@@ -1,135 +0,0 @@
-#include <simd/simd.h>
-
-typedef struct {
- vector_float2 viewport_size;
-} GPUIUniforms;
-
-typedef enum {
- GPUIQuadInputIndexVertices = 0,
- GPUIQuadInputIndexQuads = 1,
- GPUIQuadInputIndexUniforms = 2,
-} GPUIQuadInputIndex;
-
-typedef struct {
- vector_float2 origin;
- vector_float2 size;
- vector_uchar4 background_color;
- float border_top;
- float border_right;
- float border_bottom;
- float border_left;
- vector_uchar4 border_color;
- float corner_radius_top_left;
- float corner_radius_top_right;
- float corner_radius_bottom_right;
- float corner_radius_bottom_left;
-} GPUIQuad;
-
-typedef enum {
- GPUIShadowInputIndexVertices = 0,
- GPUIShadowInputIndexShadows = 1,
- GPUIShadowInputIndexUniforms = 2,
-} GPUIShadowInputIndex;
-
-typedef struct {
- vector_float2 origin;
- vector_float2 size;
- float corner_radius_top_left;
- float corner_radius_top_right;
- float corner_radius_bottom_right;
- float corner_radius_bottom_left;
- float sigma;
- vector_uchar4 color;
-} GPUIShadow;
-
-typedef enum {
- GPUISpriteVertexInputIndexVertices = 0,
- GPUISpriteVertexInputIndexSprites = 1,
- GPUISpriteVertexInputIndexViewportSize = 2,
- GPUISpriteVertexInputIndexAtlasSize = 3,
-} GPUISpriteVertexInputIndex;
-
-typedef enum {
- GPUISpriteFragmentInputIndexAtlas = 0,
-} GPUISpriteFragmentInputIndex;
-
-typedef struct {
- vector_float2 origin;
- vector_float2 target_size;
- vector_float2 source_size;
- vector_float2 atlas_origin;
- vector_uchar4 color;
- uint8_t compute_winding;
-} GPUISprite;
-
-typedef enum {
- GPUIPathAtlasVertexInputIndexVertices = 0,
- GPUIPathAtlasVertexInputIndexAtlasSize = 1,
-} GPUIPathAtlasVertexInputIndex;
-
-typedef struct {
- vector_float2 xy_position;
- vector_float2 st_position;
- vector_float2 clip_rect_origin;
- vector_float2 clip_rect_size;
-} GPUIPathVertex;
-
-typedef enum {
- GPUIImageVertexInputIndexVertices = 0,
- GPUIImageVertexInputIndexImages = 1,
- GPUIImageVertexInputIndexViewportSize = 2,
- GPUIImageVertexInputIndexAtlasSize = 3,
-} GPUIImageVertexInputIndex;
-
-typedef enum {
- GPUIImageFragmentInputIndexAtlas = 0,
-} GPUIImageFragmentInputIndex;
-
-typedef struct {
- vector_float2 origin;
- vector_float2 target_size;
- vector_float2 source_size;
- vector_float2 atlas_origin;
- float border_top;
- float border_right;
- float border_bottom;
- float border_left;
- vector_uchar4 border_color;
- float corner_radius_top_left;
- float corner_radius_top_right;
- float corner_radius_bottom_right;
- float corner_radius_bottom_left;
- uint8_t grayscale;
-} GPUIImage;
-
-typedef enum {
- GPUISurfaceVertexInputIndexVertices = 0,
- GPUISurfaceVertexInputIndexSurfaces = 1,
- GPUISurfaceVertexInputIndexViewportSize = 2,
- GPUISurfaceVertexInputIndexAtlasSize = 3,
-} GPUISurfaceVertexInputIndex;
-
-typedef enum {
- GPUISurfaceFragmentInputIndexYAtlas = 0,
- GPUISurfaceFragmentInputIndexCbCrAtlas = 1,
-} GPUISurfaceFragmentInputIndex;
-
-typedef struct {
- vector_float2 origin;
- vector_float2 target_size;
- vector_float2 source_size;
-} GPUISurface;
-
-typedef enum {
- GPUIUnderlineInputIndexVertices = 0,
- GPUIUnderlineInputIndexUnderlines = 1,
- GPUIUnderlineInputIndexUniforms = 2,
-} GPUIUnderlineInputIndex;
-
-typedef struct {
- vector_float2 origin;
- vector_float2 size;
- float thickness;
- vector_uchar4 color;
- uint8_t squiggly;
-} GPUIUnderline;
@@ -1,464 +0,0 @@
-#include <metal_stdlib>
-#include "shaders.h"
-
-using namespace metal;
-
-float4 coloru_to_colorf(uchar4 coloru) {
- return float4(coloru) / float4(0xff, 0xff, 0xff, 0xff);
-}
-
-float4 to_device_position(float2 pixel_position, float2 viewport_size) {
- return float4(pixel_position / viewport_size * float2(2., -2.) + float2(-1., 1.), 0., 1.);
-}
-
-// A standard gaussian function, used for weighting samples
-float gaussian(float x, float sigma) {
- return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
-}
-
-// This approximates the error function, needed for the gaussian integral
-float2 erf(float2 x) {
- float2 s = sign(x);
- float2 a = abs(x);
- x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
- x *= x;
- return s - s / (x * x);
-}
-
-float blur_along_x(float x, float y, float sigma, float corner, float2 halfSize) {
- float delta = min(halfSize.y - corner - abs(y), 0.);
- float curved = halfSize.x - corner + sqrt(max(0., corner * corner - delta * delta));
- float2 integral = 0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
- return integral.y - integral.x;
-}
-
-struct QuadFragmentInput {
- float4 position [[position]];
- float2 atlas_position; // only used in the image shader
- float2 origin;
- float2 size;
- float4 background_color;
- float border_top;
- float border_right;
- float border_bottom;
- float border_left;
- float4 border_color;
- float corner_radius_top_left;
- float corner_radius_top_right;
- float corner_radius_bottom_right;
- float corner_radius_bottom_left;
- uchar grayscale; // only used in image shader
-};
-
-float4 quad_sdf(QuadFragmentInput input) {
- float2 half_size = input.size / 2.;
- float2 center = input.origin + half_size;
- float2 center_to_point = input.position.xy - center;
- float corner_radius;
- if (center_to_point.x < 0.) {
- if (center_to_point.y < 0.) {
- corner_radius = input.corner_radius_top_left;
- } else {
- corner_radius = input.corner_radius_bottom_left;
- }
- } else {
- if (center_to_point.y < 0.) {
- corner_radius = input.corner_radius_top_right;
- } else {
- corner_radius = input.corner_radius_bottom_right;
- }
- }
-
- float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
- float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - corner_radius;
-
- float vertical_border = center_to_point.x <= 0. ? input.border_left : input.border_right;
- float horizontal_border = center_to_point.y <= 0. ? input.border_top : input.border_bottom;
- float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
- float2 point_to_inset_corner = abs(center_to_point) - inset_size;
- float border_width;
- if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
- border_width = 0.;
- } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
- border_width = horizontal_border;
- } else {
- border_width = vertical_border;
- }
-
- float4 color;
- if (border_width == 0.) {
- color = input.background_color;
- } else {
- float inset_distance = distance + border_width;
-
- // Decrease border's opacity as we move inside the background.
- input.border_color.a *= 1. - saturate(0.5 - inset_distance);
-
- // Alpha-blend the border and the background.
- float output_alpha = input.border_color.a + input.background_color.a * (1. - input.border_color.a);
- float3 premultiplied_border_rgb = input.border_color.rgb * input.border_color.a;
- float3 premultiplied_background_rgb = input.background_color.rgb * input.background_color.a;
- float3 premultiplied_output_rgb = premultiplied_border_rgb + premultiplied_background_rgb * (1. - input.border_color.a);
- color = float4(premultiplied_output_rgb / output_alpha, output_alpha);
- }
-
- return color * float4(1., 1., 1., saturate(0.5 - distance));
-}
-
-vertex QuadFragmentInput quad_vertex(
- uint unit_vertex_id [[vertex_id]],
- uint quad_id [[instance_id]],
- constant float2 *unit_vertices [[buffer(GPUIQuadInputIndexVertices)]],
- constant GPUIQuad *quads [[buffer(GPUIQuadInputIndexQuads)]],
- constant GPUIUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
-) {
- float2 unit_vertex = unit_vertices[unit_vertex_id];
- GPUIQuad quad = quads[quad_id];
- float2 position = unit_vertex * quad.size + quad.origin;
- float4 device_position = to_device_position(position, uniforms->viewport_size);
-
- return QuadFragmentInput {
- device_position,
- float2(0., 0.),
- quad.origin,
- quad.size,
- coloru_to_colorf(quad.background_color),
- quad.border_top,
- quad.border_right,
- quad.border_bottom,
- quad.border_left,
- coloru_to_colorf(quad.border_color),
- quad.corner_radius_top_left,
- quad.corner_radius_top_right,
- quad.corner_radius_bottom_right,
- quad.corner_radius_bottom_left,
- 0,
- };
-}
-
-fragment float4 quad_fragment(
- QuadFragmentInput input [[stage_in]]
-) {
- return quad_sdf(input);
-}
-
-struct ShadowFragmentInput {
- float4 position [[position]];
- vector_float2 origin;
- vector_float2 size;
- float corner_radius_top_left;
- float corner_radius_top_right;
- float corner_radius_bottom_right;
- float corner_radius_bottom_left;
- float sigma;
- vector_uchar4 color;
-};
-
-vertex ShadowFragmentInput shadow_vertex(
- uint unit_vertex_id [[vertex_id]],
- uint shadow_id [[instance_id]],
- constant float2 *unit_vertices [[buffer(GPUIShadowInputIndexVertices)]],
- constant GPUIShadow *shadows [[buffer(GPUIShadowInputIndexShadows)]],
- constant GPUIUniforms *uniforms [[buffer(GPUIShadowInputIndexUniforms)]]
-) {
- float2 unit_vertex = unit_vertices[unit_vertex_id];
- GPUIShadow shadow = shadows[shadow_id];
-
- float margin = 3. * shadow.sigma;
- float2 position = unit_vertex * (shadow.size + 2. * margin) + shadow.origin - margin;
- float4 device_position = to_device_position(position, uniforms->viewport_size);
-
- return ShadowFragmentInput {
- device_position,
- shadow.origin,
- shadow.size,
- shadow.corner_radius_top_left,
- shadow.corner_radius_top_right,
- shadow.corner_radius_bottom_right,
- shadow.corner_radius_bottom_left,
- shadow.sigma,
- shadow.color,
- };
-}
-
-fragment float4 shadow_fragment(
- ShadowFragmentInput input [[stage_in]]
-) {
- float sigma = input.sigma;
- float2 half_size = input.size / 2.;
- float2 center = input.origin + half_size;
- float2 point = input.position.xy - center;
- float2 center_to_point = input.position.xy - center;
- float corner_radius;
- if (center_to_point.x < 0.) {
- if (center_to_point.y < 0.) {
- corner_radius = input.corner_radius_top_left;
- } else {
- corner_radius = input.corner_radius_bottom_left;
- }
- } else {
- if (center_to_point.y < 0.) {
- corner_radius = input.corner_radius_top_right;
- } else {
- corner_radius = input.corner_radius_bottom_right;
- }
- }
-
- // The signal is only non-zero in a limited range, so don't waste samples
- float low = point.y - half_size.y;
- float high = point.y + half_size.y;
- float start = clamp(-3. * sigma, low, high);
- float end = clamp(3. * sigma, low, high);
-
- // Accumulate samples (we can get away with surprisingly few samples)
- float step = (end - start) / 4.;
- float y = start + step * 0.5;
- float alpha = 0.;
- for (int i = 0; i < 4; i++) {
- alpha += blur_along_x(point.x, point.y - y, sigma, corner_radius, half_size) * gaussian(y, sigma) * step;
- y += step;
- }
-
- return float4(1., 1., 1., alpha) * coloru_to_colorf(input.color);
-}
-
-struct SpriteFragmentInput {
- float4 position [[position]];
- float2 atlas_position;
- float4 color [[flat]];
- uchar compute_winding [[flat]];
-};
-
-vertex SpriteFragmentInput sprite_vertex(
- uint unit_vertex_id [[vertex_id]],
- uint sprite_id [[instance_id]],
- constant float2 *unit_vertices [[buffer(GPUISpriteVertexInputIndexVertices)]],
- constant GPUISprite *sprites [[buffer(GPUISpriteVertexInputIndexSprites)]],
- constant float2 *viewport_size [[buffer(GPUISpriteVertexInputIndexViewportSize)]],
- constant float2 *atlas_size [[buffer(GPUISpriteVertexInputIndexAtlasSize)]]
-) {
- float2 unit_vertex = unit_vertices[unit_vertex_id];
- GPUISprite sprite = sprites[sprite_id];
- float2 position = unit_vertex * sprite.target_size + sprite.origin;
- float4 device_position = to_device_position(position, *viewport_size);
- float2 atlas_position = (unit_vertex * sprite.source_size + sprite.atlas_origin) / *atlas_size;
-
- return SpriteFragmentInput {
- device_position,
- atlas_position,
- coloru_to_colorf(sprite.color),
- sprite.compute_winding
- };
-}
-
-fragment float4 sprite_fragment(
- SpriteFragmentInput input [[stage_in]],
- texture2d<float> atlas [[ texture(GPUISpriteFragmentInputIndexAtlas) ]]
-) {
- constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
- float4 color = input.color;
- float4 sample = atlas.sample(atlas_sampler, input.atlas_position);
- float mask;
- if (input.compute_winding) {
- mask = 1. - abs(1. - fmod(sample.r, 2.));
- } else {
- mask = sample.a;
- }
- color.a *= mask;
- return color;
-}
-
-vertex QuadFragmentInput image_vertex(
- uint unit_vertex_id [[vertex_id]],
- uint image_id [[instance_id]],
- constant float2 *unit_vertices [[buffer(GPUIImageVertexInputIndexVertices)]],
- constant GPUIImage *images [[buffer(GPUIImageVertexInputIndexImages)]],
- constant float2 *viewport_size [[buffer(GPUIImageVertexInputIndexViewportSize)]],
- constant float2 *atlas_size [[buffer(GPUIImageVertexInputIndexAtlasSize)]]
-) {
- float2 unit_vertex = unit_vertices[unit_vertex_id];
- GPUIImage image = images[image_id];
- float2 position = unit_vertex * image.target_size + image.origin;
- float4 device_position = to_device_position(position, *viewport_size);
- float2 atlas_position = (unit_vertex * image.source_size + image.atlas_origin) / *atlas_size;
-
- return QuadFragmentInput {
- device_position,
- atlas_position,
- image.origin,
- image.target_size,
- float4(0.),
- image.border_top,
- image.border_right,
- image.border_bottom,
- image.border_left,
- coloru_to_colorf(image.border_color),
- image.corner_radius_top_left,
- image.corner_radius_top_right,
- image.corner_radius_bottom_right,
- image.corner_radius_bottom_left,
- image.grayscale,
- };
-}
-
-fragment float4 image_fragment(
- QuadFragmentInput input [[stage_in]],
- texture2d<float> atlas [[ texture(GPUIImageFragmentInputIndexAtlas) ]]
-) {
- constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
- input.background_color = atlas.sample(atlas_sampler, input.atlas_position);
- if (input.grayscale) {
- float grayscale =
- 0.2126 * input.background_color.r +
- 0.7152 * input.background_color.g +
- 0.0722 * input.background_color.b;
- input.background_color = float4(grayscale, grayscale, grayscale, input.background_color.a);
- }
- return quad_sdf(input);
-}
-
-vertex QuadFragmentInput surface_vertex(
- uint unit_vertex_id [[vertex_id]],
- uint image_id [[instance_id]],
- constant float2 *unit_vertices [[buffer(GPUISurfaceVertexInputIndexVertices)]],
- constant GPUISurface *images [[buffer(GPUISurfaceVertexInputIndexSurfaces)]],
- constant float2 *viewport_size [[buffer(GPUISurfaceVertexInputIndexViewportSize)]],
- constant float2 *atlas_size [[buffer(GPUISurfaceVertexInputIndexAtlasSize)]]
-) {
- float2 unit_vertex = unit_vertices[unit_vertex_id];
- GPUISurface image = images[image_id];
- float2 position = unit_vertex * image.target_size + image.origin;
- float4 device_position = to_device_position(position, *viewport_size);
- float2 atlas_position = (unit_vertex * image.source_size) / *atlas_size;
-
- return QuadFragmentInput {
- device_position,
- atlas_position,
- image.origin,
- image.target_size,
- float4(0.),
- 0.,
- 0.,
- 0.,
- 0.,
- float4(0.),
- 0.,
- 0,
- };
-}
-
-fragment float4 surface_fragment(
- QuadFragmentInput input [[stage_in]],
- texture2d<float> y_atlas [[ texture(GPUISurfaceFragmentInputIndexYAtlas) ]],
- texture2d<float> cb_cr_atlas [[ texture(GPUISurfaceFragmentInputIndexCbCrAtlas) ]]
-) {
- constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
- const float4x4 ycbcrToRGBTransform = float4x4(
- float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f),
- float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f),
- float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f),
- float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f)
- );
- float4 ycbcr = float4(y_atlas.sample(atlas_sampler, input.atlas_position).r,
- cb_cr_atlas.sample(atlas_sampler, input.atlas_position).rg, 1.0);
-
- input.background_color = ycbcrToRGBTransform * ycbcr;
- return quad_sdf(input);
-}
-
-struct PathAtlasVertexOutput {
- float4 position [[position]];
- float2 st_position;
- float clip_rect_distance [[clip_distance]] [4];
-};
-
-struct PathAtlasFragmentInput {
- float4 position [[position]];
- float2 st_position;
-};
-
-vertex PathAtlasVertexOutput path_atlas_vertex(
- uint vertex_id [[vertex_id]],
- constant GPUIPathVertex *vertices [[buffer(GPUIPathAtlasVertexInputIndexVertices)]],
- constant float2 *atlas_size [[buffer(GPUIPathAtlasVertexInputIndexAtlasSize)]]
-) {
- GPUIPathVertex v = vertices[vertex_id];
- float4 device_position = to_device_position(v.xy_position, *atlas_size);
- return PathAtlasVertexOutput {
- device_position,
- v.st_position,
- {
- v.xy_position.x - v.clip_rect_origin.x,
- v.clip_rect_origin.x + v.clip_rect_size.x - v.xy_position.x,
- v.xy_position.y - v.clip_rect_origin.y,
- v.clip_rect_origin.y + v.clip_rect_size.y - v.xy_position.y
- }
- };
-}
-
-fragment float4 path_atlas_fragment(
- PathAtlasFragmentInput input [[stage_in]]
-) {
- float2 dx = dfdx(input.st_position);
- float2 dy = dfdy(input.st_position);
- float2 gradient = float2(
- (2. * input.st_position.x) * dx.x - dx.y,
- (2. * input.st_position.x) * dy.x - dy.y
- );
- float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
- float distance = f / length(gradient);
- float alpha = saturate(0.5 - distance);
- return float4(alpha, 0., 0., 1.);
-}
-
-struct UnderlineFragmentInput {
- float4 position [[position]];
- float2 origin;
- float2 size;
- float thickness;
- float4 color;
- bool squiggly;
-};
-
-vertex UnderlineFragmentInput underline_vertex(
- uint unit_vertex_id [[vertex_id]],
- uint underline_id [[instance_id]],
- constant float2 *unit_vertices [[buffer(GPUIUnderlineInputIndexVertices)]],
- constant GPUIUnderline *underlines [[buffer(GPUIUnderlineInputIndexUnderlines)]],
- constant GPUIUniforms *uniforms [[buffer(GPUIUnderlineInputIndexUniforms)]]
-) {
- float2 unit_vertex = unit_vertices[unit_vertex_id];
- GPUIUnderline underline = underlines[underline_id];
- float2 position = unit_vertex * underline.size + underline.origin;
- float4 device_position = to_device_position(position, uniforms->viewport_size);
-
- return UnderlineFragmentInput {
- device_position,
- underline.origin,
- underline.size,
- underline.thickness,
- coloru_to_colorf(underline.color),
- underline.squiggly != 0,
- };
-}
-
-fragment float4 underline_fragment(
- UnderlineFragmentInput input [[stage_in]]
-) {
- if (input.squiggly) {
- float half_thickness = input.thickness * 0.5;
- float2 st = ((input.position.xy - input.origin) / input.size.y) - float2(0., 0.5);
- float frequency = (M_PI_F * (3. * input.thickness)) / 8.;
- float amplitude = 1. / (2. * input.thickness);
- float sine = sin(st.x * frequency) * amplitude;
- float dSine = cos(st.x * frequency) * amplitude * frequency;
- float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
- float distance_in_pixels = distance * input.size.y;
- float distance_from_top_border = distance_in_pixels - half_thickness;
- float distance_from_bottom_border = distance_in_pixels + half_thickness;
- float alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border));
- return input.color * float4(1., 1., 1., alpha);
- } else {
- return input.color;
- }
-}
@@ -1,164 +0,0 @@
-use super::atlas::AtlasAllocator;
-use crate::{
- fonts::{FontId, GlyphId},
- geometry::vector::{vec2f, Vector2F, Vector2I},
- platform::{self, RasterizationOptions},
-};
-use collections::hash_map::Entry;
-use metal::{MTLPixelFormat, TextureDescriptor};
-use ordered_float::OrderedFloat;
-use std::{borrow::Cow, collections::HashMap, sync::Arc};
-
-#[derive(Hash, Eq, PartialEq)]
-struct GlyphDescriptor {
- font_id: FontId,
- font_size: OrderedFloat<f32>,
- glyph_id: GlyphId,
- subpixel_variant: (u8, u8),
-}
-
-#[derive(Clone)]
-pub struct GlyphSprite {
- pub atlas_id: usize,
- pub atlas_origin: Vector2I,
- pub offset: Vector2I,
- pub size: Vector2I,
-}
-
-#[derive(Hash, Eq, PartialEq)]
-struct IconDescriptor {
- path: Cow<'static, str>,
- width: i32,
- height: i32,
-}
-
-#[derive(Clone)]
-pub struct IconSprite {
- pub atlas_id: usize,
- pub atlas_origin: Vector2I,
- pub size: Vector2I,
-}
-
-pub struct SpriteCache {
- fonts: Arc<dyn platform::FontSystem>,
- atlases: AtlasAllocator,
- glyphs: HashMap<GlyphDescriptor, Option<GlyphSprite>>,
- icons: HashMap<IconDescriptor, IconSprite>,
- scale_factor: f32,
-}
-
-impl SpriteCache {
- pub fn new(
- device: metal::Device,
- size: Vector2I,
- scale_factor: f32,
- fonts: Arc<dyn platform::FontSystem>,
- ) -> Self {
- let descriptor = TextureDescriptor::new();
- descriptor.set_pixel_format(MTLPixelFormat::A8Unorm);
- descriptor.set_width(size.x() as u64);
- descriptor.set_height(size.y() as u64);
- Self {
- fonts,
- atlases: AtlasAllocator::new(device, descriptor),
- glyphs: Default::default(),
- icons: Default::default(),
- scale_factor,
- }
- }
-
- pub fn set_scale_factor(&mut self, scale_factor: f32) {
- if scale_factor != self.scale_factor {
- self.icons.clear();
- self.glyphs.clear();
- self.atlases.clear();
- }
- self.scale_factor = scale_factor;
- }
-
- pub fn render_glyph(
- &mut self,
- font_id: FontId,
- font_size: f32,
- glyph_id: GlyphId,
- target_position: Vector2F,
- ) -> Option<GlyphSprite> {
- const SUBPIXEL_VARIANTS: u8 = 4;
-
- let target_position = target_position * self.scale_factor;
- let subpixel_variant = (
- (target_position.x().fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
- (target_position.y().fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
- );
-
- self.glyphs
- .entry(GlyphDescriptor {
- font_id,
- font_size: OrderedFloat(font_size),
- glyph_id,
- subpixel_variant,
- })
- .or_insert_with(|| {
- let subpixel_shift = vec2f(
- subpixel_variant.0 as f32 / SUBPIXEL_VARIANTS as f32,
- subpixel_variant.1 as f32 / SUBPIXEL_VARIANTS as f32,
- );
- let (glyph_bounds, mask) = self.fonts.rasterize_glyph(
- font_id,
- font_size,
- glyph_id,
- subpixel_shift,
- self.scale_factor,
- RasterizationOptions::Alpha,
- )?;
-
- let (alloc_id, atlas_bounds) = self
- .atlases
- .upload(glyph_bounds.size(), &mask)
- .expect("could not upload glyph");
- Some(GlyphSprite {
- atlas_id: alloc_id.atlas_id,
- atlas_origin: atlas_bounds.origin(),
- offset: glyph_bounds.origin(),
- size: glyph_bounds.size(),
- })
- })
- .clone()
- }
-
- pub fn render_icon(
- &mut self,
- size: Vector2I,
- path: Cow<'static, str>,
- svg: usvg::Tree,
- ) -> Option<IconSprite> {
- let atlases = &mut self.atlases;
- match self.icons.entry(IconDescriptor {
- path,
- width: size.x(),
- height: size.y(),
- }) {
- Entry::Occupied(entry) => Some(entry.get().clone()),
- Entry::Vacant(entry) => {
- let mut pixmap = tiny_skia::Pixmap::new(size.x() as u32, size.y() as u32)?;
- resvg::render(&svg, usvg::FitTo::Width(size.x() as u32), pixmap.as_mut());
- let mask = pixmap
- .pixels()
- .iter()
- .map(|a| a.alpha())
- .collect::<Vec<_>>();
- let (alloc_id, atlas_bounds) = atlases.upload(size, &mask)?;
- let icon_sprite = IconSprite {
- atlas_id: alloc_id.atlas_id,
- atlas_origin: atlas_bounds.origin(),
- size,
- };
- Some(entry.insert(icon_sprite).clone())
- }
- }
- }
-
- pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
- self.atlases.texture(atlas_id)
- }
-}
@@ -1,33 +1,29 @@
+use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange};
use crate::{
- executor,
- 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,
+ display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, DrawWindow, ExternalPaths,
+ FileDropEvent, ForegroundExecutor, GlobalPixels, InputEvent, KeyDownEvent, Keystroke,
+ Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
+ Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
+ PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
};
use block::ConcreteBlock;
use cocoa::{
appkit::{
- CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable,
+ CGPoint, NSApplication, NSBackingStoreBuffered, NSEventModifierFlags,
+ NSFilenamesPboardType, NSPasteboard, NSScreen, NSView, NSViewHeightSizable,
NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior,
NSWindowStyleMask, NSWindowTitleVisibility,
},
base::{id, nil},
- foundation::{NSAutoreleasePool, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger},
+ foundation::{
+ NSArray, NSAutoreleasePool, NSDictionary, NSFastEnumeration, NSInteger, NSPoint, NSRect,
+ NSSize, NSString, NSUInteger,
+ },
};
use core_graphics::display::CGRect;
use ctor::ctor;
use foreign_types::ForeignTypeRef;
+use futures::channel::oneshot;
use objc::{
class,
declare::ClassDecl,
@@ -35,26 +31,22 @@ use objc::{
runtime::{Class, Object, Protocol, Sel, BOOL, NO, YES},
sel, sel_impl,
};
-use postage::oneshot;
-use smol::Timer;
+use parking_lot::Mutex;
+use smallvec::SmallVec;
use std::{
any::Any,
cell::{Cell, RefCell},
- convert::TryInto,
ffi::{c_void, CStr},
mem,
ops::Range,
os::raw::c_char,
+ path::PathBuf,
ptr,
- rc::{Rc, Weak},
- sync::Arc,
+ rc::Rc,
+ sync::{Arc, Weak},
time::Duration,
};
-
-use super::{
- geometry::{NSRectExt, Vector2FExt},
- ns_string, NSRange,
-};
+use util::ResultExt;
const WINDOW_STATE_IVAR: &str = "windowState";
@@ -79,10 +71,17 @@ const NSTrackingActiveAlways: NSUInteger = 0x80;
const NSTrackingInVisibleRect: NSUInteger = 0x200;
#[allow(non_upper_case_globals)]
const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4;
+#[allow(non_upper_case_globals)]
+const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
+// https://developer.apple.com/documentation/appkit/nsdragoperation
+type NSDragOperation = NSUInteger;
+#[allow(non_upper_case_globals)]
+const NSDragOperationNone: NSDragOperation = 0;
+#[allow(non_upper_case_globals)]
+const NSDragOperationCopy: NSDragOperation = 1;
#[ctor]
unsafe fn build_classes() {
- ::util::gpui1_loaded();
WINDOW_CLASS = build_window_class("GPUIWindow", class!(NSWindow));
PANEL_CLASS = build_window_class("GPUIPanel", class!(NSPanel));
VIEW_CLASS = {
@@ -222,11 +221,11 @@ unsafe fn build_classes() {
};
}
-pub fn convert_mouse_position(position: NSPoint, window_height: f32) -> Vector2F {
- vec2f(
- position.x as f32,
+pub fn convert_mouse_position(position: NSPoint, window_height: Pixels) -> Point<Pixels> {
+ point(
+ px(position.x as f32),
// MacOS screen coordinates are relative to bottom left
- window_height - position.y as f32,
+ window_height - px(position.y as f32),
)
}
@@ -271,6 +270,28 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C
window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL,
);
decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
+
+ decl.add_method(
+ sel!(draggingEntered:),
+ dragging_entered as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
+ );
+ decl.add_method(
+ sel!(draggingUpdated:),
+ dragging_updated as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
+ );
+ decl.add_method(
+ sel!(draggingExited:),
+ dragging_exited as extern "C" fn(&Object, Sel, id),
+ );
+ decl.add_method(
+ sel!(performDragOperation:),
+ perform_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL,
+ );
+ decl.add_method(
+ sel!(concludeDragOperation:),
+ conclude_drag_operation as extern "C" fn(&Object, Sel, id),
+ );
+
decl.register()
}
@@ -286,41 +307,40 @@ enum ImeState {
None,
}
-#[derive(Debug)]
struct InsertText {
replacement_range: Option<Range<usize>>,
text: String,
}
-struct WindowState {
+struct MacWindowState {
handle: AnyWindowHandle,
+ executor: ForegroundExecutor,
native_window: id,
+ renderer: MetalRenderer,
+ draw: Option<DrawWindow>,
kind: WindowKind,
- event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
+ event_callback: Option<Box<dyn FnMut(InputEvent) -> bool>>,
activate_callback: Option<Box<dyn FnMut(bool)>>,
- resize_callback: Option<Box<dyn FnMut()>>,
+ resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
moved_callback: Option<Box<dyn FnMut()>>,
should_close_callback: Option<Box<dyn FnMut() -> bool>>,
close_callback: Option<Box<dyn FnOnce()>>,
appearance_changed_callback: Option<Box<dyn FnMut()>>,
- input_handler: Option<Box<dyn InputHandler>>,
+ input_handler: Option<Box<dyn PlatformInputHandler>>,
pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
last_key_equivalent: Option<KeyDownEvent>,
synthetic_drag_counter: usize,
- executor: Rc<executor::Foreground>,
- scene_to_render: Option<Scene>,
- renderer: Renderer,
last_fresh_keydown: Option<Keystroke>,
- traffic_light_position: Option<Vector2F>,
- previous_modifiers_changed_event: Option<Event>,
- //State tracking what the IME did after the last request
+ traffic_light_position: Option<Point<Pixels>>,
+ previous_modifiers_changed_event: Option<InputEvent>,
+ // State tracking what the IME did after the last request
ime_state: ImeState,
- //Retains the last IME Text
+ // Retains the last IME Text
ime_text: Option<String>,
}
-impl WindowState {
+impl MacWindowState {
fn move_traffic_light(&self) {
if let Some(traffic_light_position) = self.traffic_light_position {
let titlebar_height = self.titlebar_height();
@@ -342,25 +362,26 @@ impl WindowState {
let mut close_button_frame: CGRect = msg_send![close_button, frame];
let mut min_button_frame: CGRect = msg_send![min_button, frame];
let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame];
- let mut origin = vec2f(
- traffic_light_position.x(),
+ let mut origin = point(
+ traffic_light_position.x,
titlebar_height
- - traffic_light_position.y()
- - close_button_frame.size.height as f32,
+ - traffic_light_position.y
+ - px(close_button_frame.size.height as f32),
);
let button_spacing =
- (min_button_frame.origin.x - close_button_frame.origin.x) as f32;
+ px((min_button_frame.origin.x - close_button_frame.origin.x) as f32);
- close_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
+ close_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
let _: () = msg_send![close_button, setFrame: close_button_frame];
- origin.set_x(origin.x() + button_spacing);
+ origin.x += button_spacing;
- min_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
+ min_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
let _: () = msg_send![min_button, setFrame: min_button_frame];
- origin.set_x(origin.x() + button_spacing);
+ origin.x += button_spacing;
- zoom_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
+ zoom_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
let _: () = msg_send![zoom_button, setFrame: zoom_button_frame];
+ origin.x += button_spacing;
}
}
}
@@ -379,8 +400,8 @@ impl WindowState {
}
let frame = self.frame();
- let screen_size = self.native_window.screen().visibleFrame().size_vec();
- if frame.size() == screen_size {
+ let screen_size = self.native_window.screen().visibleFrame().into();
+ if frame.size == screen_size {
WindowBounds::Maximized
} else {
WindowBounds::Fixed(frame)
@@ -388,47 +409,52 @@ impl WindowState {
}
}
- fn frame(&self) -> RectF {
+ fn frame(&self) -> Bounds<GlobalPixels> {
unsafe {
let frame = NSWindow::frame(self.native_window);
- Screen::screen_rect_from_native(frame)
+ display_bounds_from_native(mem::transmute::<NSRect, CGRect>(frame))
}
}
- fn content_size(&self) -> Vector2F {
+ fn content_size(&self) -> Size<Pixels> {
let NSSize { width, height, .. } =
unsafe { NSView::frame(self.native_window.contentView()) }.size;
- vec2f(width as f32, height as f32)
+ size(px(width as f32), px(height as f32))
}
fn scale_factor(&self) -> f32 {
get_scale_factor(self.native_window)
}
- fn titlebar_height(&self) -> f32 {
+ fn titlebar_height(&self) -> Pixels {
unsafe {
let frame = NSWindow::frame(self.native_window);
let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect];
- (frame.size.height - content_layout_rect.size.height) as f32
+ px((frame.size.height - content_layout_rect.size.height) as f32)
}
}
- fn present_scene(&mut self, scene: Scene) {
- self.scene_to_render = Some(scene);
+ fn to_screen_ns_point(&self, point: Point<Pixels>) -> NSPoint {
unsafe {
- let _: () = msg_send![self.native_window.contentView(), setNeedsDisplay: YES];
+ let point = NSPoint::new(
+ point.x.into(),
+ (self.content_size().height - point.y).into(),
+ );
+ msg_send![self.native_window, convertPointToScreen: point]
}
}
}
-pub struct MacWindow(Rc<RefCell<WindowState>>);
+unsafe impl Send for MacWindowState {}
+
+pub struct MacWindow(Arc<Mutex<MacWindowState>>);
impl MacWindow {
pub fn open(
handle: AnyWindowHandle,
- options: platform::WindowOptions,
- executor: Rc<executor::Foreground>,
- fonts: Arc<dyn platform::FontSystem>,
+ options: WindowOptions,
+ draw: DrawWindow,
+ executor: ForegroundExecutor,
) -> Self {
unsafe {
let pool = NSAutoreleasePool::new(nil);
@@ -455,19 +481,40 @@ impl MacWindow {
msg_send![PANEL_CLASS, alloc]
}
};
+
+ let display = options
+ .display_id
+ .and_then(|display_id| MacDisplay::all().find(|display| display.id() == display_id))
+ .unwrap_or_else(MacDisplay::primary);
+
+ let mut target_screen = nil;
+ let screens = NSScreen::screens(nil);
+ let count: u64 = cocoa::foundation::NSArray::count(screens);
+ for i in 0..count {
+ let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
+ let device_description = NSScreen::deviceDescription(screen);
+ let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
+ let screen_number = device_description.objectForKey_(screen_number_key);
+ let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
+ if screen_number as u32 == display.id().0 {
+ target_screen = screen;
+ break;
+ }
+ }
+
let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)),
style_mask,
NSBackingStoreBuffered,
NO,
- options
- .screen
- .and_then(|screen| {
- Some(screen.as_any().downcast_ref::<Screen>()?.native_screen)
- })
- .unwrap_or(nil),
+ target_screen,
);
assert!(!native_window.is_null());
+ let () = msg_send![
+ native_window,
+ registerForDraggedTypes:
+ NSArray::arrayWithObject(nil, NSFilenamesPboardType)
+ ];
let screen = native_window.screen();
match options.bounds {
@@ -477,14 +524,14 @@ impl MacWindow {
WindowBounds::Maximized => {
native_window.setFrame_display_(screen.visibleFrame(), YES);
}
- WindowBounds::Fixed(rect) => {
- let bounds = Screen::screen_rect_to_native(rect);
- let screen_bounds = screen.visibleFrame();
- if bounds.intersects(screen_bounds) {
- native_window.setFrame_display_(bounds, YES);
+ WindowBounds::Fixed(bounds) => {
+ let display_bounds = display.bounds();
+ let frame = if bounds.intersects(&display_bounds) {
+ display_bounds_to_native(bounds)
} else {
- native_window.setFrame_display_(screen_bounds, YES);
- }
+ display_bounds_to_native(display_bounds)
+ };
+ native_window.setFrame_display_(mem::transmute::<CGRect, NSRect>(frame), YES);
}
}
@@ -493,25 +540,25 @@ impl MacWindow {
assert!(!native_view.is_null());
- let window = Self(Rc::new(RefCell::new(WindowState {
+ let window = Self(Arc::new(Mutex::new(MacWindowState {
handle,
+ executor,
native_window,
+ renderer: MetalRenderer::new(true),
+ draw: Some(draw),
kind: options.kind,
event_callback: None,
- resize_callback: None,
- should_close_callback: None,
- close_callback: None,
activate_callback: None,
+ resize_callback: None,
fullscreen_callback: None,
moved_callback: None,
+ should_close_callback: None,
+ close_callback: None,
appearance_changed_callback: None,
input_handler: None,
pending_key_down: None,
last_key_equivalent: None,
synthetic_drag_counter: 0,
- executor,
- scene_to_render: Default::default(),
- renderer: Renderer::new(true, fonts),
last_fresh_keydown: None,
traffic_light_position: options
.titlebar
@@ -524,15 +571,19 @@ impl MacWindow {
(*native_window).set_ivar(
WINDOW_STATE_IVAR,
- Rc::into_raw(window.0.clone()) as *const c_void,
+ Arc::into_raw(window.0.clone()) as *const c_void,
);
native_window.setDelegate_(native_window);
(*native_view).set_ivar(
WINDOW_STATE_IVAR,
- Rc::into_raw(window.0.clone()) as *const c_void,
+ Arc::into_raw(window.0.clone()) as *const c_void,
);
- if let Some(title) = options.titlebar.as_ref().and_then(|t| t.title) {
+ if let Some(title) = options
+ .titlebar
+ .as_ref()
+ .and_then(|t| t.title.as_ref().map(AsRef::as_ref))
+ {
native_window.setTitle_(NSString::alloc(nil).init_str(title));
}
@@ -604,19 +655,19 @@ impl MacWindow {
native_window.orderFront_(nil);
}
- window.0.borrow().move_traffic_light();
+ window.0.lock().move_traffic_light();
pool.drain();
window
}
}
- pub fn main_window() -> Option<AnyWindowHandle> {
+ pub fn active_window() -> Option<AnyWindowHandle> {
unsafe {
let app = NSApplication::sharedApplication(nil);
let main_window: id = msg_send![app, mainWindow];
if msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
- let handle = get_window_state(&*main_window).borrow().handle;
+ let handle = get_window_state(&*main_window).lock().handle;
Some(handle)
} else {
None
@@ -627,11 +678,14 @@ impl MacWindow {
impl Drop for MacWindow {
fn drop(&mut self) {
- let this = self.0.borrow();
+ let this = self.0.lock();
let window = this.native_window;
this.executor
.spawn(async move {
unsafe {
+ // todo!() this panic()s when you click the red close button
+ // unless should_close returns false.
+ // (luckliy in zed it always returns false)
window.close();
}
})
@@ -639,62 +693,88 @@ impl Drop for MacWindow {
}
}
-impl platform::Window for MacWindow {
+impl PlatformWindow for MacWindow {
fn bounds(&self) -> WindowBounds {
- self.0.as_ref().borrow().bounds()
+ self.0.as_ref().lock().bounds()
}
- fn content_size(&self) -> Vector2F {
- self.0.as_ref().borrow().content_size()
+ fn content_size(&self) -> Size<Pixels> {
+ self.0.as_ref().lock().content_size()
}
fn scale_factor(&self) -> f32 {
- self.0.as_ref().borrow().scale_factor()
+ self.0.as_ref().lock().scale_factor()
}
- fn titlebar_height(&self) -> f32 {
- self.0.as_ref().borrow().titlebar_height()
+ fn titlebar_height(&self) -> Pixels {
+ self.0.as_ref().lock().titlebar_height()
}
- fn appearance(&self) -> platform::Appearance {
+ fn appearance(&self) -> WindowAppearance {
unsafe {
- let appearance: id = msg_send![self.0.borrow().native_window, effectiveAppearance];
- platform::Appearance::from_native(appearance)
+ let appearance: id = msg_send![self.0.lock().native_window, effectiveAppearance];
+ WindowAppearance::from_native(appearance)
}
}
- fn screen(&self) -> Rc<dyn platform::Screen> {
+ fn display(&self) -> Rc<dyn PlatformDisplay> {
unsafe {
- Rc::new(Screen {
- native_screen: self.0.as_ref().borrow().native_window.screen(),
- })
+ let screen = self.0.lock().native_window.screen();
+ let device_description: id = msg_send![screen, deviceDescription];
+ let screen_number: id = NSDictionary::valueForKey_(
+ device_description,
+ NSString::alloc(nil).init_str("NSScreenNumber"),
+ );
+
+ let screen_number: u32 = msg_send![screen_number, unsignedIntValue];
+
+ Rc::new(MacDisplay(screen_number))
}
}
- fn mouse_position(&self) -> Vector2F {
+ fn mouse_position(&self) -> Point<Pixels> {
let position = unsafe {
self.0
- .borrow()
+ .lock()
.native_window
.mouseLocationOutsideOfEventStream()
};
- convert_mouse_position(position, self.content_size().y())
+ convert_mouse_position(position, self.content_size().height)
+ }
+
+ fn modifiers(&self) -> Modifiers {
+ unsafe {
+ let modifiers: NSEventModifierFlags = msg_send![class!(NSEvent), modifierFlags];
+
+ let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
+ let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
+ let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
+ let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
+ let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
+
+ Modifiers {
+ control,
+ alt,
+ shift,
+ command,
+ function,
+ }
+ }
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
- fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>) {
- self.0.as_ref().borrow_mut().input_handler = Some(input_handler);
+ fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>) {
+ self.0.as_ref().lock().input_handler = Some(input_handler);
+ }
+
+ fn clear_input_handler(&mut self) {
+ self.0.as_ref().lock().input_handler = None;
}
- fn prompt(
- &self,
- level: platform::PromptLevel,
- msg: &str,
- answers: &[&str],
- ) -> oneshot::Receiver<usize> {
+ fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize> {
// macOs applies overrides to modal window buttons after they are added.
// Two most important for this logic are:
// * Buttons with "Cancel" title will be displayed as the last buttons in the modal
@@ -724,9 +804,9 @@ impl platform::Window for MacWindow {
let alert: id = msg_send![class!(NSAlert), alloc];
let alert: id = msg_send![alert, init];
let alert_style = match level {
- platform::PromptLevel::Info => 1,
- platform::PromptLevel::Warning => 0,
- platform::PromptLevel::Critical => 2,
+ PromptLevel::Info => 1,
+ PromptLevel::Warning => 0,
+ PromptLevel::Critical => 2,
};
let _: () = msg_send![alert, setAlertStyle: alert_style];
let _: () = msg_send![alert, setMessageText: ns_string(msg)];
@@ -747,15 +827,14 @@ impl platform::Window for MacWindow {
let (done_tx, done_rx) = oneshot::channel();
let done_tx = Cell::new(Some(done_tx));
let block = ConcreteBlock::new(move |answer: NSInteger| {
- if let Some(mut done_tx) = done_tx.take() {
- let _ = postage::sink::Sink::try_send(&mut done_tx, answer.try_into().unwrap());
+ if let Some(done_tx) = done_tx.take() {
+ let _ = done_tx.send(answer.try_into().unwrap());
}
});
let block = block.copy();
- let native_window = self.0.borrow().native_window;
- self.0
- .borrow()
- .executor
+ let native_window = self.0.lock().native_window;
+ let executor = self.0.lock().executor.clone();
+ executor
.spawn(async move {
let _: () = msg_send![
alert,
@@ -770,10 +849,9 @@ impl platform::Window for MacWindow {
}
fn activate(&self) {
- let window = self.0.borrow().native_window;
- self.0
- .borrow()
- .executor
+ let window = self.0.lock().native_window;
+ let executor = self.0.lock().executor.clone();
+ executor
.spawn(async move {
unsafe {
let _: () = msg_send![window, makeKeyAndOrderFront: nil];
@@ -785,42 +863,47 @@ impl platform::Window for MacWindow {
fn set_title(&mut self, title: &str) {
unsafe {
let app = NSApplication::sharedApplication(nil);
- let window = self.0.borrow().native_window;
+ let window = self.0.lock().native_window;
let title = ns_string(title);
let _: () = msg_send![app, changeWindowsItem:window title:title filename:false];
let _: () = msg_send![window, setTitle: title];
- self.0.borrow().move_traffic_light();
+ self.0.lock().move_traffic_light();
}
}
fn set_edited(&mut self, edited: bool) {
unsafe {
- let window = self.0.borrow().native_window;
+ let window = self.0.lock().native_window;
msg_send![window, setDocumentEdited: edited as BOOL]
}
// Changing the document edited state resets the traffic light position,
// so we have to move it again.
- self.0.borrow().move_traffic_light();
+ self.0.lock().move_traffic_light();
}
fn show_character_palette(&self) {
- unsafe {
- let app = NSApplication::sharedApplication(nil);
- let window = self.0.borrow().native_window;
- let _: () = msg_send![app, orderFrontCharacterPalette: window];
- }
+ let this = self.0.lock();
+ let window = this.native_window;
+ this.executor
+ .spawn(async move {
+ unsafe {
+ let app = NSApplication::sharedApplication(nil);
+ let _: () = msg_send![app, orderFrontCharacterPalette: window];
+ }
+ })
+ .detach();
}
fn minimize(&self) {
- let window = self.0.borrow().native_window;
+ let window = self.0.lock().native_window;
unsafe {
window.miniaturize_(nil);
}
}
fn zoom(&self) {
- let this = self.0.borrow();
+ let this = self.0.lock();
let window = this.native_window;
this.executor
.spawn(async move {
@@ -831,12 +914,8 @@ impl platform::Window for MacWindow {
.detach();
}
- fn present_scene(&mut self, scene: Scene) {
- self.0.as_ref().borrow_mut().present_scene(scene);
- }
-
fn toggle_full_screen(&self) {
- let this = self.0.borrow();
+ let this = self.0.lock();
let window = this.native_window;
this.executor
.spawn(async move {
@@ -847,50 +926,47 @@ impl platform::Window for MacWindow {
.detach();
}
- fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>) {
- self.0.as_ref().borrow_mut().event_callback = Some(callback);
+ fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>) {
+ self.0.as_ref().lock().event_callback = Some(callback);
}
- fn on_active_status_change(&mut self, callback: Box<dyn FnMut(bool)>) {
- self.0.as_ref().borrow_mut().activate_callback = Some(callback);
+ fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
+ self.0.as_ref().lock().activate_callback = Some(callback);
}
- fn on_resize(&mut self, callback: Box<dyn FnMut()>) {
- self.0.as_ref().borrow_mut().resize_callback = Some(callback);
+ fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
+ self.0.as_ref().lock().resize_callback = Some(callback);
}
- fn on_fullscreen(&mut self, callback: Box<dyn FnMut(bool)>) {
- self.0.as_ref().borrow_mut().fullscreen_callback = Some(callback);
+ fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
+ self.0.as_ref().lock().fullscreen_callback = Some(callback);
}
- fn on_moved(&mut self, callback: Box<dyn FnMut()>) {
- self.0.as_ref().borrow_mut().moved_callback = Some(callback);
+ fn on_moved(&self, callback: Box<dyn FnMut()>) {
+ self.0.as_ref().lock().moved_callback = Some(callback);
}
- fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>) {
- self.0.as_ref().borrow_mut().should_close_callback = Some(callback);
+ fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
+ self.0.as_ref().lock().should_close_callback = Some(callback);
}
- fn on_close(&mut self, callback: Box<dyn FnOnce()>) {
- self.0.as_ref().borrow_mut().close_callback = Some(callback);
+ fn on_close(&self, callback: Box<dyn FnOnce()>) {
+ self.0.as_ref().lock().close_callback = Some(callback);
}
- fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>) {
- self.0.borrow_mut().appearance_changed_callback = Some(callback);
+ fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
+ self.0.lock().appearance_changed_callback = Some(callback);
}
- fn is_topmost_for_position(&self, position: Vector2F) -> bool {
- let self_borrow = self.0.borrow();
+ fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool {
+ let self_borrow = self.0.lock();
let self_handle = self_borrow.handle;
unsafe {
let app = NSApplication::sharedApplication(nil);
// Convert back to screen coordinates
- let screen_point = position.to_screen_ns_point(
- self_borrow.native_window,
- self_borrow.content_size().y() as f64,
- );
+ let screen_point = self_borrow.to_screen_ns_point(position);
let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:screen_point belowWindowWithWindowNumber:0];
let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number];
@@ -898,7 +974,7 @@ impl platform::Window for MacWindow {
let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS];
let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS];
if is_panel == YES || is_window == YES {
- let topmost_window = get_window_state(&*top_most_window).borrow().handle;
+ let topmost_window = get_window_state(&*top_most_window).lock().handle;
topmost_window == self_handle
} else {
// Someone else's window is on top
@@ -906,6 +982,17 @@ impl platform::Window for MacWindow {
}
}
}
+
+ fn invalidate(&self) {
+ let this = self.0.lock();
+ unsafe {
+ let _: () = msg_send![this.native_window.contentView(), setNeedsDisplay: YES];
+ }
+ }
+
+ fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
+ self.0.lock().renderer.sprite_atlas().clone()
+ }
}
fn get_scale_factor(native_window: id) -> f32 {
@@ -915,9 +1002,9 @@ fn get_scale_factor(native_window: id) -> f32 {
}
}
-unsafe fn get_window_state(object: &Object) -> Rc<RefCell<WindowState>> {
+unsafe fn get_window_state(object: &Object) -> Arc<Mutex<MacWindowState>> {
let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
- let rc1 = Rc::from_raw(raw as *mut RefCell<WindowState>);
+ let rc1 = Arc::from_raw(raw as *mut Mutex<MacWindowState>);
let rc2 = rc1.clone();
mem::forget(rc1);
rc2
@@ -925,7 +1012,7 @@ unsafe fn get_window_state(object: &Object) -> Rc<RefCell<WindowState>> {
unsafe fn drop_window_state(object: &Object) {
let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
- Rc::from_raw(raw as *mut RefCell<WindowState>);
+ Rc::from_raw(raw as *mut RefCell<MacWindowState>);
}
extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
@@ -956,35 +1043,35 @@ extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) {
extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL {
let window_state = unsafe { get_window_state(this) };
- let mut window_state_borrow = window_state.as_ref().borrow_mut();
+ let mut lock = window_state.as_ref().lock();
- let window_height = window_state_borrow.content_size().y();
- let event = unsafe { Event::from_native(native_event, Some(window_height)) };
+ let window_height = lock.content_size().height;
+ let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) };
- if let Some(Event::KeyDown(event)) = event {
+ if let Some(InputEvent::KeyDown(event)) = event {
// For certain keystrokes, macOS will first dispatch a "key equivalent" event.
// If that event isn't handled, it will then dispatch a "key down" event. GPUI
// makes no distinction between these two types of events, so we need to ignore
// the "key down" event if we've already just processed its "key equivalent" version.
if key_equivalent {
- window_state_borrow.last_key_equivalent = Some(event.clone());
- } else if window_state_borrow.last_key_equivalent.take().as_ref() == Some(&event) {
+ lock.last_key_equivalent = Some(event.clone());
+ } else if lock.last_key_equivalent.take().as_ref() == Some(&event) {
return NO;
}
let keydown = event.keystroke.clone();
- let fn_modifier = keydown.function;
+ let fn_modifier = keydown.modifiers.function;
// Ignore events from held-down keys after some of the initially-pressed keys
// were released.
if event.is_held {
- if window_state_borrow.last_fresh_keydown.as_ref() != Some(&keydown) {
+ if lock.last_fresh_keydown.as_ref() != Some(&keydown) {
return YES;
}
} else {
- window_state_borrow.last_fresh_keydown = Some(keydown);
+ lock.last_fresh_keydown = Some(keydown);
}
- window_state_borrow.pending_key_down = Some((event, None));
- drop(window_state_borrow);
+ lock.pending_key_down = Some((event, None));
+ drop(lock);
// Send the event to the input context for IME handling, unless the `fn` modifier is
// being pressed.
@@ -996,19 +1083,49 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
}
let mut handled = false;
- let mut window_state_borrow = window_state.borrow_mut();
- let ime_text = window_state_borrow.ime_text.clone();
- if let Some((event, insert_text)) = window_state_borrow.pending_key_down.take() {
+ let mut lock = window_state.lock();
+ let ime_text = lock.ime_text.clone();
+ if let Some((event, insert_text)) = lock.pending_key_down.take() {
let is_held = event.is_held;
- if let Some(mut callback) = window_state_borrow.event_callback.take() {
- drop(window_state_borrow);
+ if let Some(mut callback) = lock.event_callback.take() {
+ drop(lock);
let is_composing =
with_input_handler(this, |input_handler| input_handler.marked_text_range())
.flatten()
.is_some();
if !is_composing {
- handled = callback(Event::KeyDown(event));
+ // if the IME has changed the key, we'll first emit an event with the character
+ // generated by the IME system; then fallback to the keystroke if that is not
+ // handled.
+ // cases that we have working:
+ // - " on a brazillian layout by typing <quote><space>
+ // - ctrl-` on a brazillian layout by typing <ctrl-`>
+ // - $ on a czech QWERTY layout by typing <alt-4>
+ // - 4 on a czech QWERTY layout by typing <shift-4>
+ // - ctrl-4 on a czech QWERTY layout by typing <ctrl-alt-4> (or <ctrl-shift-4>)
+ if ime_text.is_some() && ime_text.as_ref() != Some(&event.keystroke.key) {
+ let event_with_ime_text = KeyDownEvent {
+ is_held: false,
+ keystroke: Keystroke {
+ // we match ctrl because some use-cases need it.
+ // we don't match alt because it's often used to generate the optional character
+ // we don't match shift because we're not here with letters (usually)
+ // we don't match cmd/fn because they don't seem to use IME
+ modifiers: Default::default(),
+ key: ime_text.clone().unwrap(),
+ ime_key: None, // todo!("handle IME key")
+ },
+ };
+ handled = callback(InputEvent::KeyDown(event_with_ime_text));
+ }
+ if !handled {
+ // empty key happens when you type a deadkey in input composition.
+ // (e.g. on a brazillian keyboard typing quote is a deadkey)
+ if !event.keystroke.key.is_empty() {
+ handled = callback(InputEvent::KeyDown(event));
+ }
+ }
}
if !handled {
@@ -1033,7 +1150,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
}
}
- window_state.borrow_mut().event_callback = Some(callback);
+ window_state.lock().event_callback = Some(callback);
}
} else {
handled = true;
@@ -1047,108 +1164,103 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
let window_state = unsafe { get_window_state(this) };
- let weak_window_state = Rc::downgrade(&window_state);
- let mut window_state_borrow = window_state.as_ref().borrow_mut();
- let is_active = unsafe { window_state_borrow.native_window.isKeyWindow() == YES };
+ let weak_window_state = Arc::downgrade(&window_state);
+ let mut lock = window_state.as_ref().lock();
+ let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
- let window_height = window_state_borrow.content_size().y();
- let event = unsafe { Event::from_native(native_event, Some(window_height)) };
+ let window_height = lock.content_size().height;
+ let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) };
if let Some(mut event) = event {
- let synthesized_second_event = match &mut event {
- Event::MouseDown(
- event @ MouseButtonEvent {
+ match &mut event {
+ InputEvent::MouseDown(
+ event @ MouseDownEvent {
button: MouseButton::Left,
- modifiers: Modifiers { ctrl: true, .. },
+ modifiers: Modifiers { control: true, .. },
..
},
) => {
- *event = MouseButtonEvent {
+ // On mac, a ctrl-left click should be handled as a right click.
+ *event = MouseDownEvent {
button: MouseButton::Right,
modifiers: Modifiers {
- ctrl: false,
+ control: false,
..event.modifiers
},
click_count: 1,
..*event
};
-
- Some(Event::MouseUp(MouseButtonEvent {
- button: MouseButton::Right,
- ..*event
- }))
}
// Because we map a ctrl-left_down to a right_down -> right_up let's ignore
// the ctrl-left_up to avoid having a mismatch in button down/up events if the
// user is still holding ctrl when releasing the left mouse button
- Event::MouseUp(MouseButtonEvent {
- button: MouseButton::Left,
- modifiers: Modifiers { ctrl: true, .. },
- ..
- }) => {
- window_state_borrow.synthetic_drag_counter += 1;
- return;
+ InputEvent::MouseUp(
+ event @ MouseUpEvent {
+ button: MouseButton::Left,
+ modifiers: Modifiers { control: true, .. },
+ ..
+ },
+ ) => {
+ *event = MouseUpEvent {
+ button: MouseButton::Right,
+ modifiers: Modifiers {
+ control: false,
+ ..event.modifiers
+ },
+ click_count: 1,
+ ..*event
+ };
}
- _ => None,
+ _ => {}
};
match &event {
- Event::MouseMoved(
- event @ MouseMovedEvent {
+ InputEvent::MouseMove(
+ event @ MouseMoveEvent {
pressed_button: Some(_),
..
},
) => {
- window_state_borrow.synthetic_drag_counter += 1;
- window_state_borrow
- .executor
+ lock.synthetic_drag_counter += 1;
+ let executor = lock.executor.clone();
+ executor
.spawn(synthetic_drag(
weak_window_state,
- window_state_borrow.synthetic_drag_counter,
- *event,
+ lock.synthetic_drag_counter,
+ event.clone(),
))
.detach();
}
- Event::MouseMoved(_)
- if !(is_active || window_state_borrow.kind == WindowKind::PopUp) =>
- {
- return
- }
+ InputEvent::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
- Event::MouseUp(MouseButtonEvent {
- button: MouseButton::Left,
- ..
- }) => {
- window_state_borrow.synthetic_drag_counter += 1;
+ InputEvent::MouseUp(MouseUpEvent { .. }) => {
+ lock.synthetic_drag_counter += 1;
}
- Event::ModifiersChanged(ModifiersChangedEvent { modifiers }) => {
+ InputEvent::ModifiersChanged(ModifiersChangedEvent { modifiers }) => {
// Only raise modifiers changed event when they have actually changed
- if let Some(Event::ModifiersChanged(ModifiersChangedEvent {
+ if let Some(InputEvent::ModifiersChanged(ModifiersChangedEvent {
modifiers: prev_modifiers,
- })) = &window_state_borrow.previous_modifiers_changed_event
+ })) = &lock.previous_modifiers_changed_event
{
if prev_modifiers == modifiers {
return;
}
}
- window_state_borrow.previous_modifiers_changed_event = Some(event.clone());
+ lock.previous_modifiers_changed_event = Some(event.clone());
}
_ => {}
}
- if let Some(mut callback) = window_state_borrow.event_callback.take() {
- drop(window_state_borrow);
+ if let Some(mut callback) = lock.event_callback.take() {
+ drop(lock);
callback(event);
- if let Some(event) = synthesized_second_event {
- callback(event);
- }
- window_state.borrow_mut().event_callback = Some(callback);
+ window_state.lock().event_callback = Some(callback);
}
}
}
@@ -1,434 +1,9 @@
-use super::{AppVersion, CursorStyle, WindowBounds};
-use crate::{
- geometry::{
- rect::RectF,
- vector::{vec2f, Vector2F},
- },
- keymap_matcher::KeymapMatcher,
- Action, AnyWindowHandle, ClipboardItem, Menu,
-};
-use anyhow::{anyhow, Result};
-use collections::VecDeque;
-use parking_lot::Mutex;
-use postage::oneshot;
-use std::{
- any::Any,
- cell::RefCell,
- path::{Path, PathBuf},
- rc::Rc,
- sync::Arc,
-};
-use time::UtcOffset;
-
-struct Dispatcher;
-
-impl super::Dispatcher for Dispatcher {
- fn is_main_thread(&self) -> bool {
- true
- }
-
- fn run_on_main_thread(&self, task: async_task::Runnable) {
- task.run();
- }
-}
-
-pub fn foreground_platform() -> ForegroundPlatform {
- ForegroundPlatform::default()
-}
-
-#[derive(Default)]
-pub struct ForegroundPlatform {
- last_prompt_for_new_path_args: RefCell<Option<(PathBuf, oneshot::Sender<Option<PathBuf>>)>>,
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl ForegroundPlatform {
- pub(crate) fn simulate_new_path_selection(
- &self,
- result: impl FnOnce(PathBuf) -> Option<PathBuf>,
- ) {
- let (dir_path, mut done_tx) = self
- .last_prompt_for_new_path_args
- .take()
- .expect("prompt_for_new_path was not called");
- let _ = postage::sink::Sink::try_send(&mut done_tx, result(dir_path));
- }
-
- pub(crate) fn did_prompt_for_new_path(&self) -> bool {
- self.last_prompt_for_new_path_args.borrow().is_some()
- }
-}
-
-impl super::ForegroundPlatform for ForegroundPlatform {
- fn on_become_active(&self, _: Box<dyn FnMut()>) {}
- fn on_resign_active(&self, _: Box<dyn FnMut()>) {}
- fn on_quit(&self, _: Box<dyn FnMut()>) {}
- fn on_reopen(&self, _: Box<dyn FnMut()>) {}
- fn on_event(&self, _: Box<dyn FnMut(crate::platform::Event) -> bool>) {}
- fn on_open_urls(&self, _: Box<dyn FnMut(Vec<String>)>) {}
-
- fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {
- unimplemented!()
- }
-
- fn on_menu_command(&self, _: Box<dyn FnMut(&dyn Action)>) {}
- fn on_validate_menu_command(&self, _: Box<dyn FnMut(&dyn Action) -> bool>) {}
- fn on_will_open_menu(&self, _: Box<dyn FnMut()>) {}
- fn set_menus(&self, _: Vec<Menu>, _: &KeymapMatcher) {}
-
- fn prompt_for_paths(
- &self,
- _: super::PathPromptOptions,
- ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
- let (_done_tx, done_rx) = oneshot::channel();
- done_rx
- }
-
- fn prompt_for_new_path(&self, path: &Path) -> oneshot::Receiver<Option<PathBuf>> {
- let (done_tx, done_rx) = oneshot::channel();
- *self.last_prompt_for_new_path_args.borrow_mut() = Some((path.to_path_buf(), done_tx));
- done_rx
- }
-
- fn reveal_path(&self, _: &Path) {}
-}
-
-pub fn platform() -> Platform {
- Platform::new()
-}
-
-pub struct Platform {
- dispatcher: Arc<dyn super::Dispatcher>,
- fonts: Arc<dyn super::FontSystem>,
- current_clipboard_item: Mutex<Option<ClipboardItem>>,
- cursor: Mutex<CursorStyle>,
- active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
- active_screen: Screen,
-}
-
-impl Platform {
- fn new() -> Self {
- Self {
- dispatcher: Arc::new(Dispatcher),
- fonts: Arc::new(super::current::FontSystem::new()),
- current_clipboard_item: Default::default(),
- cursor: Mutex::new(CursorStyle::Arrow),
- active_window: Default::default(),
- active_screen: Screen::new(),
- }
- }
-}
-
-impl super::Platform for Platform {
- fn dispatcher(&self) -> Arc<dyn super::Dispatcher> {
- self.dispatcher.clone()
- }
-
- fn fonts(&self) -> std::sync::Arc<dyn super::FontSystem> {
- self.fonts.clone()
- }
-
- fn activate(&self, _ignoring_other_apps: bool) {}
-
- fn hide(&self) {}
-
- fn hide_other_apps(&self) {}
-
- fn unhide_other_apps(&self) {}
-
- fn quit(&self) {}
-
- fn screen_by_id(&self, uuid: uuid::Uuid) -> Option<Rc<dyn crate::platform::Screen>> {
- if self.active_screen.uuid == uuid {
- Some(Rc::new(self.active_screen.clone()))
- } else {
- None
- }
- }
-
- fn screens(&self) -> Vec<Rc<dyn crate::platform::Screen>> {
- vec![Rc::new(self.active_screen.clone())]
- }
-
- fn open_window(
- &self,
- handle: AnyWindowHandle,
- options: super::WindowOptions,
- _executor: Rc<super::executor::Foreground>,
- ) -> Box<dyn super::Window> {
- *self.active_window.lock() = Some(handle);
- Box::new(Window::new(
- handle,
- match options.bounds {
- WindowBounds::Maximized | WindowBounds::Fullscreen => vec2f(1024., 768.),
- WindowBounds::Fixed(rect) => rect.size(),
- },
- self.active_window.clone(),
- Rc::new(self.active_screen.clone()),
- ))
- }
-
- fn main_window(&self) -> Option<AnyWindowHandle> {
- self.active_window.lock().clone()
- }
-
- fn add_status_item(&self, handle: AnyWindowHandle) -> Box<dyn crate::platform::Window> {
- Box::new(Window::new(
- handle,
- vec2f(24., 24.),
- self.active_window.clone(),
- Rc::new(self.active_screen.clone()),
- ))
- }
-
- fn write_to_clipboard(&self, item: ClipboardItem) {
- *self.current_clipboard_item.lock() = Some(item);
- }
-
- fn read_from_clipboard(&self) -> Option<ClipboardItem> {
- self.current_clipboard_item.lock().clone()
- }
-
- fn open_url(&self, _: &str) {}
-
- fn write_credentials(&self, _: &str, _: &str, _: &[u8]) -> Result<()> {
- Ok(())
- }
-
- fn read_credentials(&self, _: &str) -> Result<Option<(String, Vec<u8>)>> {
- Ok(None)
- }
-
- fn delete_credentials(&self, _: &str) -> Result<()> {
- Ok(())
- }
-
- fn set_cursor_style(&self, style: CursorStyle) {
- *self.cursor.lock() = style;
- }
-
- fn should_auto_hide_scrollbars(&self) -> bool {
- false
- }
-
- fn local_timezone(&self) -> UtcOffset {
- UtcOffset::UTC
- }
-
- fn path_for_auxiliary_executable(&self, _name: &str) -> Result<PathBuf> {
- Err(anyhow!("app not running inside a bundle"))
- }
-
- fn app_path(&self) -> Result<PathBuf> {
- Err(anyhow!("app not running inside a bundle"))
- }
-
- fn app_version(&self) -> Result<AppVersion> {
- Ok(AppVersion {
- major: 1,
- minor: 0,
- patch: 0,
- })
- }
-
- fn os_name(&self) -> &'static str {
- "test"
- }
-
- fn os_version(&self) -> Result<AppVersion> {
- Ok(AppVersion {
- major: 1,
- minor: 0,
- patch: 0,
- })
- }
-
- fn restart(&self) {}
-}
-
-#[derive(Debug, Clone)]
-pub struct Screen {
- uuid: uuid::Uuid,
-}
-
-impl Screen {
- fn new() -> Self {
- Self {
- uuid: uuid::Uuid::new_v4(),
- }
- }
-}
-
-impl super::Screen for Screen {
- fn as_any(&self) -> &dyn Any {
- self
- }
-
- fn bounds(&self) -> RectF {
- RectF::new(Vector2F::zero(), Vector2F::new(1920., 1080.))
- }
-
- fn content_bounds(&self) -> RectF {
- RectF::new(Vector2F::zero(), Vector2F::new(1920., 1080.))
- }
-
- fn display_uuid(&self) -> Option<uuid::Uuid> {
- Some(self.uuid)
- }
-}
-
-pub struct Window {
- handle: AnyWindowHandle,
- pub(crate) size: Vector2F,
- scale_factor: f32,
- current_scene: Option<crate::Scene>,
- event_handlers: Vec<Box<dyn FnMut(super::Event) -> bool>>,
- pub(crate) resize_handlers: Vec<Box<dyn FnMut()>>,
- pub(crate) moved_handlers: Vec<Box<dyn FnMut()>>,
- close_handlers: Vec<Box<dyn FnOnce()>>,
- fullscreen_handlers: Vec<Box<dyn FnMut(bool)>>,
- pub(crate) active_status_change_handlers: Vec<Box<dyn FnMut(bool)>>,
- pub(crate) should_close_handler: Option<Box<dyn FnMut() -> bool>>,
- pub(crate) title: Option<String>,
- pub(crate) edited: bool,
- pub(crate) pending_prompts: RefCell<VecDeque<oneshot::Sender<usize>>>,
- active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
- screen: Rc<Screen>,
-}
-
-impl Window {
- pub fn new(
- handle: AnyWindowHandle,
- size: Vector2F,
- active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
- screen: Rc<Screen>,
- ) -> Self {
- Self {
- handle,
- size,
- event_handlers: Default::default(),
- resize_handlers: Default::default(),
- moved_handlers: Default::default(),
- close_handlers: Default::default(),
- should_close_handler: Default::default(),
- active_status_change_handlers: Default::default(),
- fullscreen_handlers: Default::default(),
- scale_factor: 1.0,
- current_scene: None,
- title: None,
- edited: false,
- pending_prompts: Default::default(),
- active_window,
- screen,
- }
- }
-
- pub fn title(&self) -> Option<String> {
- self.title.clone()
- }
-}
-
-impl super::Window for Window {
- fn bounds(&self) -> WindowBounds {
- WindowBounds::Fixed(RectF::new(Vector2F::zero(), self.size))
- }
-
- fn content_size(&self) -> Vector2F {
- self.size
- }
-
- fn scale_factor(&self) -> f32 {
- self.scale_factor
- }
-
- fn titlebar_height(&self) -> f32 {
- 24.
- }
-
- fn appearance(&self) -> crate::platform::Appearance {
- crate::platform::Appearance::Light
- }
-
- fn screen(&self) -> Rc<dyn crate::platform::Screen> {
- self.screen.clone()
- }
-
- fn mouse_position(&self) -> Vector2F {
- Vector2F::zero()
- }
-
- fn as_any_mut(&mut self) -> &mut dyn Any {
- self
- }
-
- fn set_input_handler(&mut self, _: Box<dyn crate::platform::InputHandler>) {}
-
- fn prompt(
- &self,
- _: crate::platform::PromptLevel,
- _: &str,
- _: &[&str],
- ) -> oneshot::Receiver<usize> {
- let (done_tx, done_rx) = oneshot::channel();
- self.pending_prompts.borrow_mut().push_back(done_tx);
- done_rx
- }
-
- fn activate(&self) {
- *self.active_window.lock() = Some(self.handle);
- }
-
- fn set_title(&mut self, title: &str) {
- self.title = Some(title.to_string())
- }
-
- fn set_edited(&mut self, edited: bool) {
- self.edited = edited;
- }
-
- fn show_character_palette(&self) {}
-
- fn minimize(&self) {}
-
- fn zoom(&self) {}
-
- fn present_scene(&mut self, scene: crate::Scene) {
- self.current_scene = Some(scene);
- }
-
- fn toggle_full_screen(&self) {}
-
- fn on_event(&mut self, callback: Box<dyn FnMut(crate::platform::Event) -> bool>) {
- self.event_handlers.push(callback);
- }
-
- fn on_active_status_change(&mut self, callback: Box<dyn FnMut(bool)>) {
- self.active_status_change_handlers.push(callback);
- }
-
- fn on_resize(&mut self, callback: Box<dyn FnMut()>) {
- self.resize_handlers.push(callback);
- }
-
- fn on_fullscreen(&mut self, callback: Box<dyn FnMut(bool)>) {
- self.fullscreen_handlers.push(callback)
- }
-
- fn on_moved(&mut self, callback: Box<dyn FnMut()>) {
- self.moved_handlers.push(callback);
- }
-
- fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>) {
- self.should_close_handler = Some(callback);
- }
-
- fn on_close(&mut self, callback: Box<dyn FnOnce()>) {
- self.close_handlers.push(callback);
- }
-
- fn on_appearance_changed(&mut self, _: Box<dyn FnMut()>) {}
-
- fn is_topmost_for_position(&self, _position: Vector2F) -> bool {
- true
- }
-}
+mod dispatcher;
+mod display;
+mod platform;
+mod window;
+
+pub use dispatcher::*;
+pub use display::*;
+pub use platform::*;
+pub use window::*;
@@ -1,565 +1,778 @@
-mod mouse_event;
-mod mouse_region;
-mod region;
-
-#[cfg(debug_assertions)]
-use collections::HashSet;
-use derive_more::Mul;
-use schemars::JsonSchema;
-use serde::Deserialize;
-use serde_derive::Serialize;
-use std::{
- any::{Any, TypeId},
- borrow::Cow,
- rc::Rc,
- sync::Arc,
-};
-
use crate::{
- color::Color,
- fonts::{FontId, GlyphId},
- geometry::{rect::RectF, vector::Vector2F},
- platform::{current::Surface, CursorStyle},
- ImageData, WindowContext,
+ point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, Hsla, Pixels, Point,
+ ScaledPixels, StackingOrder,
};
-pub use mouse_event::*;
-pub use mouse_region::*;
+use collections::BTreeMap;
+use std::{fmt::Debug, iter::Peekable, mem, slice};
-pub struct SceneBuilder {
- stacking_contexts: Vec<StackingContext>,
- active_stacking_context_stack: Vec<usize>,
- /// Used by the gpui2 crate.
- pub event_handlers: Vec<EventHandler>,
- #[cfg(debug_assertions)]
- mouse_region_ids: HashSet<MouseRegionId>,
-}
+// Exported to metal
+pub(crate) type PointF = Point<f32>;
+#[allow(non_camel_case_types, unused)]
+pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
-pub struct Scene {
- scale_factor: f32,
- stacking_contexts: Vec<StackingContext>,
- event_handlers: Vec<EventHandler>,
-}
+pub type LayerId = u32;
-struct StackingContext {
- layers: Vec<Layer>,
- active_layer_stack: Vec<usize>,
- z_index: usize,
-}
+pub type DrawOrder = u32;
#[derive(Default)]
-pub struct Layer {
- clip_bounds: Option<RectF>,
+pub(crate) struct SceneBuilder {
+ last_order: Option<(StackingOrder, LayerId)>,
+ layers_by_order: BTreeMap<StackingOrder, LayerId>,
+ shadows: Vec<Shadow>,
quads: Vec<Quad>,
+ paths: Vec<Path<ScaledPixels>>,
underlines: Vec<Underline>,
- images: Vec<Image>,
+ monochrome_sprites: Vec<MonochromeSprite>,
+ polychrome_sprites: Vec<PolychromeSprite>,
surfaces: Vec<Surface>,
- shadows: Vec<Shadow>,
- glyphs: Vec<Glyph>,
- image_glyphs: Vec<ImageGlyph>,
- icons: Vec<Icon>,
- paths: Vec<Path>,
- cursor_regions: Vec<CursorRegion>,
- mouse_regions: Vec<MouseRegion>,
-}
-
-#[derive(Copy, Clone)]
-pub struct CursorRegion {
- pub bounds: RectF,
- pub style: CursorStyle,
}
-#[derive(Default, Debug)]
-pub struct Quad {
- pub bounds: RectF,
- pub background: Option<Color>,
- pub border: Border,
- pub corner_radii: CornerRadii,
-}
-
-#[derive(Default, Debug, Mul, Clone, Copy, Serialize, JsonSchema)]
-pub struct CornerRadii {
- pub top_left: f32,
- pub top_right: f32,
- pub bottom_right: f32,
- pub bottom_left: f32,
-}
-
-impl<'de> Deserialize<'de> for CornerRadii {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: serde::Deserializer<'de>,
- {
- #[derive(Deserialize)]
- pub struct CornerRadiiHelper {
- pub top_left: Option<f32>,
- pub top_right: Option<f32>,
- pub bottom_right: Option<f32>,
- pub bottom_left: Option<f32>,
+impl SceneBuilder {
+ pub fn build(&mut self) -> Scene {
+ let mut orders = vec![0; self.layers_by_order.len()];
+ for (ix, layer_id) in self.layers_by_order.values().enumerate() {
+ orders[*layer_id as usize] = ix as u32;
}
+ self.layers_by_order.clear();
+ self.last_order = None;
- #[derive(Deserialize)]
- #[serde(untagged)]
- enum RadiusOrRadii {
- Radius(f32),
- Radii(CornerRadiiHelper),
+ for shadow in &mut self.shadows {
+ shadow.order = orders[shadow.order as usize];
}
+ self.shadows.sort_by_key(|shadow| shadow.order);
- let json = RadiusOrRadii::deserialize(deserializer)?;
-
- let result = match json {
- RadiusOrRadii::Radius(radius) => CornerRadii::from(radius),
- RadiusOrRadii::Radii(CornerRadiiHelper {
- top_left,
- top_right,
- bottom_right,
- bottom_left,
- }) => CornerRadii {
- top_left: top_left.unwrap_or(0.0),
- top_right: top_right.unwrap_or(0.0),
- bottom_right: bottom_right.unwrap_or(0.0),
- bottom_left: bottom_left.unwrap_or(0.0),
- },
- };
+ for quad in &mut self.quads {
+ quad.order = orders[quad.order as usize];
+ }
+ self.quads.sort_by_key(|quad| quad.order);
- Ok(result)
- }
-}
+ for path in &mut self.paths {
+ path.order = orders[path.order as usize];
+ }
+ self.paths.sort_by_key(|path| path.order);
-impl From<f32> for CornerRadii {
- fn from(radius: f32) -> Self {
- Self {
- top_left: radius,
- top_right: radius,
- bottom_right: radius,
- bottom_left: radius,
+ for underline in &mut self.underlines {
+ underline.order = orders[underline.order as usize];
}
- }
-}
+ self.underlines.sort_by_key(|underline| underline.order);
-#[derive(Debug)]
-pub struct Shadow {
- pub bounds: RectF,
- pub corner_radii: CornerRadii,
- pub sigma: f32,
- pub color: Color,
-}
+ for monochrome_sprite in &mut self.monochrome_sprites {
+ monochrome_sprite.order = orders[monochrome_sprite.order as usize];
+ }
+ self.monochrome_sprites.sort_by_key(|sprite| sprite.order);
-#[derive(Debug, Clone, Copy)]
-pub struct Glyph {
- pub font_id: FontId,
- pub font_size: f32,
- pub id: GlyphId,
- pub origin: Vector2F,
- pub color: Color,
-}
+ for polychrome_sprite in &mut self.polychrome_sprites {
+ polychrome_sprite.order = orders[polychrome_sprite.order as usize];
+ }
+ self.polychrome_sprites.sort_by_key(|sprite| sprite.order);
-#[derive(Debug)]
-pub struct ImageGlyph {
- pub font_id: FontId,
- pub font_size: f32,
- pub id: GlyphId,
- pub origin: Vector2F,
-}
+ for surface in &mut self.surfaces {
+ surface.order = orders[surface.order as usize];
+ }
+ self.surfaces.sort_by_key(|surface| surface.order);
-pub struct Icon {
- pub bounds: RectF,
- pub svg: usvg::Tree,
- pub path: Cow<'static, str>,
- pub color: Color,
-}
+ Scene {
+ shadows: mem::take(&mut self.shadows),
+ quads: mem::take(&mut self.quads),
+ paths: mem::take(&mut self.paths),
+ underlines: mem::take(&mut self.underlines),
+ monochrome_sprites: mem::take(&mut self.monochrome_sprites),
+ polychrome_sprites: mem::take(&mut self.polychrome_sprites),
+ surfaces: mem::take(&mut self.surfaces),
+ }
+ }
-#[derive(Clone, Copy, Default, Debug)]
-pub struct Border {
- pub color: Color,
- pub top: f32,
- pub right: f32,
- pub bottom: f32,
- pub left: f32,
-}
+ pub fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
+ let primitive = primitive.into();
+ let clipped_bounds = primitive
+ .bounds()
+ .intersect(&primitive.content_mask().bounds);
+ if clipped_bounds.size.width <= ScaledPixels(0.)
+ || clipped_bounds.size.height <= ScaledPixels(0.)
+ {
+ return;
+ }
-#[derive(Clone, Copy, Default, Debug)]
-pub struct Underline {
- pub origin: Vector2F,
- pub width: f32,
- pub thickness: f32,
- pub color: Color,
- pub squiggly: bool,
-}
+ let layer_id = self.layer_id_for_order(order);
+ match primitive {
+ Primitive::Shadow(mut shadow) => {
+ shadow.order = layer_id;
+ self.shadows.push(shadow);
+ }
+ Primitive::Quad(mut quad) => {
+ quad.order = layer_id;
+ self.quads.push(quad);
+ }
+ Primitive::Path(mut path) => {
+ path.order = layer_id;
+ path.id = PathId(self.paths.len());
+ self.paths.push(path);
+ }
+ Primitive::Underline(mut underline) => {
+ underline.order = layer_id;
+ self.underlines.push(underline);
+ }
+ Primitive::MonochromeSprite(mut sprite) => {
+ sprite.order = layer_id;
+ self.monochrome_sprites.push(sprite);
+ }
+ Primitive::PolychromeSprite(mut sprite) => {
+ sprite.order = layer_id;
+ self.polychrome_sprites.push(sprite);
+ }
+ Primitive::Surface(mut surface) => {
+ surface.order = layer_id;
+ self.surfaces.push(surface);
+ }
+ }
+ }
-#[derive(Debug)]
-pub struct Path {
- pub bounds: RectF,
- pub color: Color,
- pub vertices: Vec<PathVertex>,
-}
+ fn layer_id_for_order(&mut self, order: &StackingOrder) -> u32 {
+ if let Some((last_order, last_layer_id)) = self.last_order.as_ref() {
+ if last_order == order {
+ return *last_layer_id;
+ }
+ };
-#[derive(Debug)]
-pub struct PathVertex {
- pub xy_position: Vector2F,
- pub st_position: Vector2F,
+ let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) {
+ *layer_id
+ } else {
+ let next_id = self.layers_by_order.len() as LayerId;
+ self.layers_by_order.insert(order.clone(), next_id);
+ next_id
+ };
+ self.last_order = Some((order.clone(), layer_id));
+ layer_id
+ }
}
-pub struct Image {
- pub bounds: RectF,
- pub border: Border,
- pub corner_radii: CornerRadii,
- pub grayscale: bool,
- pub data: Arc<ImageData>,
+pub struct Scene {
+ pub shadows: Vec<Shadow>,
+ pub quads: Vec<Quad>,
+ pub paths: Vec<Path<ScaledPixels>>,
+ pub underlines: Vec<Underline>,
+ pub monochrome_sprites: Vec<MonochromeSprite>,
+ pub polychrome_sprites: Vec<PolychromeSprite>,
+ pub surfaces: Vec<Surface>,
}
impl Scene {
- pub fn scale_factor(&self) -> f32 {
- self.scale_factor
- }
-
- pub fn layers(&self) -> impl Iterator<Item = &Layer> {
- self.stacking_contexts.iter().flat_map(|s| &s.layers)
- }
-
- pub fn cursor_regions(&self) -> Vec<CursorRegion> {
- self.layers()
- .flat_map(|layer| &layer.cursor_regions)
- .copied()
- .collect()
- }
-
- pub fn mouse_regions(&self) -> Vec<(MouseRegion, usize)> {
- self.stacking_contexts
- .iter()
- .flat_map(|context| {
- context
- .layers
- .iter()
- .flat_map(|layer| &layer.mouse_regions)
- .map(|region| (region.clone(), context.z_index))
- })
- .collect()
+ pub fn paths(&self) -> &[Path<ScaledPixels>] {
+ &self.paths
+ }
+
+ pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> {
+ BatchIterator {
+ shadows: &self.shadows,
+ shadows_start: 0,
+ shadows_iter: self.shadows.iter().peekable(),
+ quads: &self.quads,
+ quads_start: 0,
+ quads_iter: self.quads.iter().peekable(),
+ paths: &self.paths,
+ paths_start: 0,
+ paths_iter: self.paths.iter().peekable(),
+ underlines: &self.underlines,
+ underlines_start: 0,
+ underlines_iter: self.underlines.iter().peekable(),
+ monochrome_sprites: &self.monochrome_sprites,
+ monochrome_sprites_start: 0,
+ monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
+ polychrome_sprites: &self.polychrome_sprites,
+ polychrome_sprites_start: 0,
+ polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
+ surfaces: &self.surfaces,
+ surfaces_start: 0,
+ surfaces_iter: self.surfaces.iter().peekable(),
+ }
}
+}
- pub fn take_event_handlers(&mut self) -> Vec<EventHandler> {
- self.event_handlers
- .sort_by(|a, b| a.order.cmp(&b.order).reverse());
- std::mem::take(&mut self.event_handlers)
- }
+struct BatchIterator<'a> {
+ shadows: &'a [Shadow],
+ shadows_start: usize,
+ shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
+ quads: &'a [Quad],
+ quads_start: usize,
+ quads_iter: Peekable<slice::Iter<'a, Quad>>,
+ paths: &'a [Path<ScaledPixels>],
+ paths_start: usize,
+ paths_iter: Peekable<slice::Iter<'a, Path<ScaledPixels>>>,
+ underlines: &'a [Underline],
+ underlines_start: usize,
+ underlines_iter: Peekable<slice::Iter<'a, Underline>>,
+ monochrome_sprites: &'a [MonochromeSprite],
+ monochrome_sprites_start: usize,
+ monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
+ polychrome_sprites: &'a [PolychromeSprite],
+ polychrome_sprites_start: usize,
+ polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
+ surfaces: &'a [Surface],
+ surfaces_start: usize,
+ surfaces_iter: Peekable<slice::Iter<'a, Surface>>,
}
-impl SceneBuilder {
- pub fn new() -> Self {
- let mut this = SceneBuilder {
- stacking_contexts: Vec::new(),
- active_stacking_context_stack: Vec::new(),
- #[cfg(debug_assertions)]
- mouse_region_ids: HashSet::default(),
- event_handlers: Vec::new(),
+impl<'a> Iterator for BatchIterator<'a> {
+ type Item = PrimitiveBatch<'a>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let mut orders_and_kinds = [
+ (
+ self.shadows_iter.peek().map(|s| s.order),
+ PrimitiveKind::Shadow,
+ ),
+ (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
+ (self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
+ (
+ self.underlines_iter.peek().map(|u| u.order),
+ PrimitiveKind::Underline,
+ ),
+ (
+ self.monochrome_sprites_iter.peek().map(|s| s.order),
+ PrimitiveKind::MonochromeSprite,
+ ),
+ (
+ self.polychrome_sprites_iter.peek().map(|s| s.order),
+ PrimitiveKind::PolychromeSprite,
+ ),
+ (
+ self.surfaces_iter.peek().map(|s| s.order),
+ PrimitiveKind::Surface,
+ ),
+ ];
+ orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
+
+ let first = orders_and_kinds[0];
+ let second = orders_and_kinds[1];
+ let (batch_kind, max_order) = if first.0.is_some() {
+ (first.1, second.0.unwrap_or(u32::MAX))
+ } else {
+ return None;
};
- this.clear();
- this
- }
-
- pub fn clear(&mut self) {
- self.stacking_contexts.clear();
- self.stacking_contexts.push(StackingContext::new(None, 0));
- self.active_stacking_context_stack.clear();
- self.active_stacking_context_stack.push(0);
- #[cfg(debug_assertions)]
- self.mouse_region_ids.clear();
- }
-
- pub fn build(&mut self, scale_factor: f32) -> Scene {
- let mut stacking_contexts = std::mem::take(&mut self.stacking_contexts);
- stacking_contexts.sort_by_key(|context| context.z_index);
- let event_handlers = std::mem::take(&mut self.event_handlers);
- self.clear();
- Scene {
- scale_factor,
- stacking_contexts,
- event_handlers,
+ match batch_kind {
+ PrimitiveKind::Shadow => {
+ let shadows_start = self.shadows_start;
+ let mut shadows_end = shadows_start + 1;
+ self.shadows_iter.next();
+ while self
+ .shadows_iter
+ .next_if(|shadow| shadow.order < max_order)
+ .is_some()
+ {
+ shadows_end += 1;
+ }
+ self.shadows_start = shadows_end;
+ Some(PrimitiveBatch::Shadows(
+ &self.shadows[shadows_start..shadows_end],
+ ))
+ }
+ PrimitiveKind::Quad => {
+ let quads_start = self.quads_start;
+ let mut quads_end = quads_start + 1;
+ self.quads_iter.next();
+ while self
+ .quads_iter
+ .next_if(|quad| quad.order < max_order)
+ .is_some()
+ {
+ quads_end += 1;
+ }
+ self.quads_start = quads_end;
+ Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
+ }
+ PrimitiveKind::Path => {
+ let paths_start = self.paths_start;
+ let mut paths_end = paths_start + 1;
+ self.paths_iter.next();
+ while self
+ .paths_iter
+ .next_if(|path| path.order < max_order)
+ .is_some()
+ {
+ paths_end += 1;
+ }
+ self.paths_start = paths_end;
+ Some(PrimitiveBatch::Paths(&self.paths[paths_start..paths_end]))
+ }
+ PrimitiveKind::Underline => {
+ let underlines_start = self.underlines_start;
+ let mut underlines_end = underlines_start + 1;
+ self.underlines_iter.next();
+ while self
+ .underlines_iter
+ .next_if(|underline| underline.order < max_order)
+ .is_some()
+ {
+ underlines_end += 1;
+ }
+ self.underlines_start = underlines_end;
+ Some(PrimitiveBatch::Underlines(
+ &self.underlines[underlines_start..underlines_end],
+ ))
+ }
+ PrimitiveKind::MonochromeSprite => {
+ let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
+ let sprites_start = self.monochrome_sprites_start;
+ let mut sprites_end = sprites_start + 1;
+ self.monochrome_sprites_iter.next();
+ while self
+ .monochrome_sprites_iter
+ .next_if(|sprite| {
+ sprite.order < max_order && sprite.tile.texture_id == texture_id
+ })
+ .is_some()
+ {
+ sprites_end += 1;
+ }
+ self.monochrome_sprites_start = sprites_end;
+ Some(PrimitiveBatch::MonochromeSprites {
+ texture_id,
+ sprites: &self.monochrome_sprites[sprites_start..sprites_end],
+ })
+ }
+ PrimitiveKind::PolychromeSprite => {
+ let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
+ let sprites_start = self.polychrome_sprites_start;
+ let mut sprites_end = self.polychrome_sprites_start + 1;
+ self.polychrome_sprites_iter.next();
+ while self
+ .polychrome_sprites_iter
+ .next_if(|sprite| {
+ sprite.order < max_order && sprite.tile.texture_id == texture_id
+ })
+ .is_some()
+ {
+ sprites_end += 1;
+ }
+ self.polychrome_sprites_start = sprites_end;
+ Some(PrimitiveBatch::PolychromeSprites {
+ texture_id,
+ sprites: &self.polychrome_sprites[sprites_start..sprites_end],
+ })
+ }
+ PrimitiveKind::Surface => {
+ let surfaces_start = self.surfaces_start;
+ let mut surfaces_end = surfaces_start + 1;
+ self.surfaces_iter.next();
+ while self
+ .surfaces_iter
+ .next_if(|surface| surface.order < max_order)
+ .is_some()
+ {
+ surfaces_end += 1;
+ }
+ self.surfaces_start = surfaces_end;
+ Some(PrimitiveBatch::Surfaces(
+ &self.surfaces[surfaces_start..surfaces_end],
+ ))
+ }
}
}
+}
- pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>, z_index: Option<usize>) {
- let z_index = z_index.unwrap_or_else(|| self.active_stacking_context().z_index + 1);
- self.active_stacking_context_stack
- .push(self.stacking_contexts.len());
- self.stacking_contexts
- .push(StackingContext::new(clip_bounds, z_index))
- }
-
- pub fn pop_stacking_context(&mut self) {
- self.active_stacking_context_stack.pop();
- assert!(!self.active_stacking_context_stack.is_empty());
- }
-
- pub fn push_layer(&mut self, clip_bounds: Option<RectF>) {
- self.active_stacking_context().push_layer(clip_bounds);
- }
-
- pub fn pop_layer(&mut self) {
- self.active_stacking_context().pop_layer();
- }
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
+pub enum PrimitiveKind {
+ Shadow,
+ #[default]
+ Quad,
+ Path,
+ Underline,
+ MonochromeSprite,
+ PolychromeSprite,
+ Surface,
+}
- pub fn push_quad(&mut self, quad: Quad) {
- self.active_layer().push_quad(quad)
- }
+pub enum Primitive {
+ Shadow(Shadow),
+ Quad(Quad),
+ Path(Path<ScaledPixels>),
+ Underline(Underline),
+ MonochromeSprite(MonochromeSprite),
+ PolychromeSprite(PolychromeSprite),
+ Surface(Surface),
+}
- pub fn push_cursor_region(&mut self, region: CursorRegion) {
- if can_draw(region.bounds) {
- self.active_layer().push_cursor_region(region);
+impl Primitive {
+ pub fn bounds(&self) -> &Bounds<ScaledPixels> {
+ match self {
+ Primitive::Shadow(shadow) => &shadow.bounds,
+ Primitive::Quad(quad) => &quad.bounds,
+ Primitive::Path(path) => &path.bounds,
+ Primitive::Underline(underline) => &underline.bounds,
+ Primitive::MonochromeSprite(sprite) => &sprite.bounds,
+ Primitive::PolychromeSprite(sprite) => &sprite.bounds,
+ Primitive::Surface(surface) => &surface.bounds,
}
}
- pub fn push_mouse_region(&mut self, region: MouseRegion) {
- if can_draw(region.bounds) {
- // Ensure that Regions cannot be added to a scene with the same region id.
- #[cfg(debug_assertions)]
- let region_id;
- #[cfg(debug_assertions)]
- {
- region_id = region.id();
- }
-
- if self.active_layer().push_mouse_region(region) {
- #[cfg(debug_assertions)]
- {
- if !self.mouse_region_ids.insert(region_id) {
- let tag_name = region_id.tag_type_name();
- panic!("Same MouseRegionId: {region_id:?} inserted multiple times to the same scene. \
- Will cause problems! Look for MouseRegion that uses Tag: {tag_name}");
- }
- }
- }
+ pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
+ match self {
+ Primitive::Shadow(shadow) => &shadow.content_mask,
+ Primitive::Quad(quad) => &quad.content_mask,
+ Primitive::Path(path) => &path.content_mask,
+ Primitive::Underline(underline) => &underline.content_mask,
+ Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
+ Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
+ Primitive::Surface(surface) => &surface.content_mask,
}
}
+}
- pub fn push_image(&mut self, image: Image) {
- self.active_layer().push_image(image)
- }
+#[derive(Debug)]
+pub(crate) enum PrimitiveBatch<'a> {
+ Shadows(&'a [Shadow]),
+ Quads(&'a [Quad]),
+ Paths(&'a [Path<ScaledPixels>]),
+ Underlines(&'a [Underline]),
+ MonochromeSprites {
+ texture_id: AtlasTextureId,
+ sprites: &'a [MonochromeSprite],
+ },
+ PolychromeSprites {
+ texture_id: AtlasTextureId,
+ sprites: &'a [PolychromeSprite],
+ },
+ Surfaces(&'a [Surface]),
+}
- pub fn push_surface(&mut self, surface: Surface) {
- self.active_layer().push_surface(surface)
- }
+#[derive(Default, Debug, Clone, Eq, PartialEq)]
+#[repr(C)]
+pub struct Quad {
+ pub order: u32, // Initially a LayerId, then a DrawOrder.
+ pub bounds: Bounds<ScaledPixels>,
+ pub content_mask: ContentMask<ScaledPixels>,
+ pub background: Hsla,
+ pub border_color: Hsla,
+ pub corner_radii: Corners<ScaledPixels>,
+ pub border_widths: Edges<ScaledPixels>,
+}
- pub fn push_underline(&mut self, underline: Underline) {
- self.active_layer().push_underline(underline)
+impl Ord for Quad {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.order.cmp(&other.order)
}
+}
- pub fn push_shadow(&mut self, shadow: Shadow) {
- self.active_layer().push_shadow(shadow)
+impl PartialOrd for Quad {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.cmp(other))
}
+}
- pub fn push_glyph(&mut self, glyph: Glyph) {
- self.active_layer().push_glyph(glyph)
+impl From<Quad> for Primitive {
+ fn from(quad: Quad) -> Self {
+ Primitive::Quad(quad)
}
+}
- pub fn push_image_glyph(&mut self, image_glyph: ImageGlyph) {
- self.active_layer().push_image_glyph(image_glyph)
- }
+#[derive(Debug, Clone, Eq, PartialEq)]
+#[repr(C)]
+pub struct Underline {
+ pub order: u32,
+ pub bounds: Bounds<ScaledPixels>,
+ pub content_mask: ContentMask<ScaledPixels>,
+ pub thickness: ScaledPixels,
+ pub color: Hsla,
+ pub wavy: bool,
+}
- pub fn push_icon(&mut self, icon: Icon) {
- self.active_layer().push_icon(icon)
+impl Ord for Underline {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.order.cmp(&other.order)
}
+}
- pub fn push_path(&mut self, path: Path) {
- self.active_layer().push_path(path);
+impl PartialOrd for Underline {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.cmp(other))
}
+}
- fn active_stacking_context(&mut self) -> &mut StackingContext {
- let ix = *self.active_stacking_context_stack.last().unwrap();
- &mut self.stacking_contexts[ix]
+impl From<Underline> for Primitive {
+ fn from(underline: Underline) -> Self {
+ Primitive::Underline(underline)
}
+}
- fn active_layer(&mut self) -> &mut Layer {
- self.active_stacking_context().active_layer()
- }
+#[derive(Debug, Clone, Eq, PartialEq)]
+#[repr(C)]
+pub struct Shadow {
+ pub order: u32,
+ pub bounds: Bounds<ScaledPixels>,
+ pub corner_radii: Corners<ScaledPixels>,
+ pub content_mask: ContentMask<ScaledPixels>,
+ pub color: Hsla,
+ pub blur_radius: ScaledPixels,
}
-impl StackingContext {
- fn new(clip_bounds: Option<RectF>, z_index: usize) -> Self {
- Self {
- layers: vec![Layer::new(clip_bounds)],
- active_layer_stack: vec![0],
- z_index,
- }
+impl Ord for Shadow {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.order.cmp(&other.order)
}
+}
- fn active_layer(&mut self) -> &mut Layer {
- &mut self.layers[*self.active_layer_stack.last().unwrap()]
+impl PartialOrd for Shadow {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.cmp(other))
}
+}
- fn push_layer(&mut self, clip_bounds: Option<RectF>) {
- let parent_clip_bounds = self.active_layer().clip_bounds();
- let clip_bounds = clip_bounds
- .map(|clip_bounds| {
- clip_bounds
- .intersection(parent_clip_bounds.unwrap_or(clip_bounds))
- .unwrap_or_else(|| {
- if !clip_bounds.is_empty() {
- log::warn!("specified clip bounds are disjoint from parent layer");
- }
- RectF::default()
- })
- })
- .or(parent_clip_bounds);
-
- let ix = self.layers.len();
- self.layers.push(Layer::new(clip_bounds));
- self.active_layer_stack.push(ix);
+impl From<Shadow> for Primitive {
+ fn from(shadow: Shadow) -> Self {
+ Primitive::Shadow(shadow)
}
+}
- fn pop_layer(&mut self) {
- self.active_layer_stack.pop().unwrap();
- assert!(!self.active_layer_stack.is_empty());
- }
+#[derive(Clone, Debug, Eq, PartialEq)]
+#[repr(C)]
+pub struct MonochromeSprite {
+ pub order: u32,
+ pub bounds: Bounds<ScaledPixels>,
+ pub content_mask: ContentMask<ScaledPixels>,
+ pub color: Hsla,
+ pub tile: AtlasTile,
}
-impl Layer {
- pub fn new(clip_bounds: Option<RectF>) -> Self {
- Self {
- clip_bounds,
- quads: Default::default(),
- underlines: Default::default(),
- images: Default::default(),
- surfaces: Default::default(),
- shadows: Default::default(),
- image_glyphs: Default::default(),
- glyphs: Default::default(),
- icons: Default::default(),
- paths: Default::default(),
- cursor_regions: Default::default(),
- mouse_regions: Default::default(),
+impl Ord for MonochromeSprite {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ match self.order.cmp(&other.order) {
+ std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
+ order => order,
}
}
+}
- pub fn clip_bounds(&self) -> Option<RectF> {
- self.clip_bounds
+impl PartialOrd for MonochromeSprite {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.cmp(other))
}
+}
- fn push_quad(&mut self, quad: Quad) {
- if can_draw(quad.bounds) {
- self.quads.push(quad);
- }
+impl From<MonochromeSprite> for Primitive {
+ fn from(sprite: MonochromeSprite) -> Self {
+ Primitive::MonochromeSprite(sprite)
}
+}
- pub fn quads(&self) -> &[Quad] {
- self.quads.as_slice()
- }
+#[derive(Clone, Debug, Eq, PartialEq)]
+#[repr(C)]
+pub struct PolychromeSprite {
+ pub order: u32,
+ pub bounds: Bounds<ScaledPixels>,
+ pub content_mask: ContentMask<ScaledPixels>,
+ pub corner_radii: Corners<ScaledPixels>,
+ pub tile: AtlasTile,
+ pub grayscale: bool,
+}
- fn push_cursor_region(&mut self, region: CursorRegion) {
- if let Some(bounds) = region
- .bounds
- .intersection(self.clip_bounds.unwrap_or(region.bounds))
- {
- if can_draw(bounds) {
- self.cursor_regions.push(region);
- }
+impl Ord for PolychromeSprite {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ match self.order.cmp(&other.order) {
+ std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
+ order => order,
}
}
+}
- fn push_mouse_region(&mut self, region: MouseRegion) -> bool {
- if let Some(bounds) = region
- .bounds
- .intersection(self.clip_bounds.unwrap_or(region.bounds))
- {
- if can_draw(bounds) {
- self.mouse_regions.push(region);
- return true;
- }
- }
- false
+impl PartialOrd for PolychromeSprite {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.cmp(other))
}
+}
- fn push_underline(&mut self, underline: Underline) {
- if underline.width > 0. {
- self.underlines.push(underline);
- }
+impl From<PolychromeSprite> for Primitive {
+ fn from(sprite: PolychromeSprite) -> Self {
+ Primitive::PolychromeSprite(sprite)
}
+}
- pub fn underlines(&self) -> &[Underline] {
- self.underlines.as_slice()
- }
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct Surface {
+ pub order: u32,
+ pub bounds: Bounds<ScaledPixels>,
+ pub content_mask: ContentMask<ScaledPixels>,
+ pub image_buffer: media::core_video::CVImageBuffer,
+}
- fn push_image(&mut self, image: Image) {
- if can_draw(image.bounds) {
- self.images.push(image);
- }
+impl Ord for Surface {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.order.cmp(&other.order)
}
+}
- pub fn images(&self) -> &[Image] {
- self.images.as_slice()
+impl PartialOrd for Surface {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.cmp(other))
}
+}
- fn push_surface(&mut self, surface: Surface) {
- if can_draw(surface.bounds) {
- self.surfaces.push(surface);
- }
+impl From<Surface> for Primitive {
+ fn from(surface: Surface) -> Self {
+ Primitive::Surface(surface)
}
+}
- pub fn surfaces(&self) -> &[Surface] {
- self.surfaces.as_slice()
- }
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub(crate) struct PathId(pub(crate) usize);
+
+#[derive(Debug)]
+pub struct Path<P: Clone + Default + Debug> {
+ pub(crate) id: PathId,
+ order: u32,
+ pub(crate) bounds: Bounds<P>,
+ pub(crate) content_mask: ContentMask<P>,
+ pub(crate) vertices: Vec<PathVertex<P>>,
+ pub(crate) color: Hsla,
+ start: Point<P>,
+ current: Point<P>,
+ contour_count: usize,
+}
- fn push_shadow(&mut self, shadow: Shadow) {
- if can_draw(shadow.bounds) {
- self.shadows.push(shadow);
+impl Path<Pixels> {
+ pub fn new(start: Point<Pixels>) -> Self {
+ Self {
+ id: PathId(0),
+ order: 0,
+ vertices: Vec::new(),
+ start,
+ current: start,
+ bounds: Bounds {
+ origin: start,
+ size: Default::default(),
+ },
+ content_mask: Default::default(),
+ color: Default::default(),
+ contour_count: 0,
}
}
- pub fn shadows(&self) -> &[Shadow] {
- self.shadows.as_slice()
+ pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
+ Path {
+ id: self.id,
+ order: self.order,
+ bounds: self.bounds.scale(factor),
+ content_mask: self.content_mask.scale(factor),
+ vertices: self
+ .vertices
+ .iter()
+ .map(|vertex| vertex.scale(factor))
+ .collect(),
+ start: self.start.map(|start| start.scale(factor)),
+ current: self.current.scale(factor),
+ contour_count: self.contour_count,
+ color: self.color,
+ }
}
- fn push_image_glyph(&mut self, glyph: ImageGlyph) {
- self.image_glyphs.push(glyph);
+ pub fn line_to(&mut self, to: Point<Pixels>) {
+ self.contour_count += 1;
+ if self.contour_count > 1 {
+ self.push_triangle(
+ (self.start, self.current, to),
+ (point(0., 1.), point(0., 1.), point(0., 1.)),
+ );
+ }
+ self.current = to;
}
- pub fn image_glyphs(&self) -> &[ImageGlyph] {
- self.image_glyphs.as_slice()
- }
+ pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
+ self.contour_count += 1;
+ if self.contour_count > 1 {
+ self.push_triangle(
+ (self.start, self.current, to),
+ (point(0., 1.), point(0., 1.), point(0., 1.)),
+ );
+ }
- fn push_glyph(&mut self, glyph: Glyph) {
- self.glyphs.push(glyph);
+ self.push_triangle(
+ (self.current, ctrl, to),
+ (point(0., 0.), point(0.5, 0.), point(1., 1.)),
+ );
+ self.current = to;
}
- pub fn glyphs(&self) -> &[Glyph] {
- self.glyphs.as_slice()
+ fn push_triangle(
+ &mut self,
+ xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
+ st: (Point<f32>, Point<f32>, Point<f32>),
+ ) {
+ self.bounds = self
+ .bounds
+ .union(&Bounds {
+ origin: xy.0,
+ size: Default::default(),
+ })
+ .union(&Bounds {
+ origin: xy.1,
+ size: Default::default(),
+ })
+ .union(&Bounds {
+ origin: xy.2,
+ size: Default::default(),
+ });
+
+ self.vertices.push(PathVertex {
+ xy_position: xy.0,
+ st_position: st.0,
+ content_mask: Default::default(),
+ });
+ self.vertices.push(PathVertex {
+ xy_position: xy.1,
+ st_position: st.1,
+ content_mask: Default::default(),
+ });
+ self.vertices.push(PathVertex {
+ xy_position: xy.2,
+ st_position: st.2,
+ content_mask: Default::default(),
+ });
}
+}
- pub fn push_icon(&mut self, icon: Icon) {
- if can_draw(icon.bounds) {
- self.icons.push(icon);
- }
- }
+impl Eq for Path<ScaledPixels> {}
- pub fn icons(&self) -> &[Icon] {
- self.icons.as_slice()
+impl PartialEq for Path<ScaledPixels> {
+ fn eq(&self, other: &Self) -> bool {
+ self.order == other.order
}
+}
- fn push_path(&mut self, path: Path) {
- if can_draw(path.bounds) {
- self.paths.push(path);
- }
+impl Ord for Path<ScaledPixels> {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.order.cmp(&other.order)
}
+}
- pub fn paths(&self) -> &[Path] {
- self.paths.as_slice()
+impl PartialOrd for Path<ScaledPixels> {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.cmp(other))
}
}
-impl MouseRegion {
- pub fn id(&self) -> MouseRegionId {
- self.id
+impl From<Path<ScaledPixels>> for Primitive {
+ fn from(path: Path<ScaledPixels>) -> Self {
+ Primitive::Path(path)
}
}
-pub struct EventHandler {
- pub order: u32,
- // The &dyn Any parameter below expects an event.
- pub handler: Rc<dyn Fn(&dyn Any, &mut WindowContext) -> bool>,
- pub event_type: TypeId,
+#[derive(Clone, Debug)]
+#[repr(C)]
+pub struct PathVertex<P: Clone + Default + Debug> {
+ pub(crate) xy_position: Point<P>,
+ pub(crate) st_position: Point<f32>,
+ pub(crate) content_mask: ContentMask<P>,
}
-fn can_draw(bounds: RectF) -> bool {
- let size = bounds.size();
- size.x() > 0. && size.y() > 0.
+impl PathVertex<Pixels> {
+ pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
+ PathVertex {
+ xy_position: self.xy_position.scale(factor),
+ st_position: self.st_position,
+ content_mask: self.content_mask.scale(factor),
+ }
+ }
}
+
+#[derive(Copy, Clone, Debug)]
+pub struct AtlasId(pub(crate) usize);
@@ -1,270 +0,0 @@
-use crate::{
- platform::{MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent},
- scene::mouse_region::HandlerKey,
-};
-use pathfinder_geometry::{rect::RectF, vector::Vector2F};
-use std::{
- mem::{discriminant, Discriminant},
- ops::Deref,
-};
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseMove {
- pub region: RectF,
- pub platform_event: MouseMovedEvent,
-}
-
-impl Deref for MouseMove {
- type Target = MouseMovedEvent;
-
- fn deref(&self) -> &Self::Target {
- &self.platform_event
- }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseMoveOut {
- pub region: RectF,
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseDrag {
- pub region: RectF,
- pub prev_mouse_position: Vector2F,
- pub platform_event: MouseMovedEvent,
- pub end: bool,
-}
-
-impl Deref for MouseDrag {
- type Target = MouseMovedEvent;
-
- fn deref(&self) -> &Self::Target {
- &self.platform_event
- }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseHover {
- pub region: RectF,
- pub started: bool,
- pub platform_event: MouseMovedEvent,
-}
-
-impl Deref for MouseHover {
- type Target = MouseMovedEvent;
-
- fn deref(&self) -> &Self::Target {
- &self.platform_event
- }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseDown {
- pub region: RectF,
- pub platform_event: MouseButtonEvent,
-}
-
-impl Deref for MouseDown {
- type Target = MouseButtonEvent;
-
- fn deref(&self) -> &Self::Target {
- &self.platform_event
- }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseUp {
- pub region: RectF,
- pub platform_event: MouseButtonEvent,
-}
-
-impl Deref for MouseUp {
- type Target = MouseButtonEvent;
-
- fn deref(&self) -> &Self::Target {
- &self.platform_event
- }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseClick {
- pub region: RectF,
- pub platform_event: MouseButtonEvent,
-}
-
-impl Deref for MouseClick {
- type Target = MouseButtonEvent;
-
- fn deref(&self) -> &Self::Target {
- &self.platform_event
- }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseClickOut {
- pub region: RectF,
- pub platform_event: MouseButtonEvent,
-}
-
-impl Deref for MouseClickOut {
- type Target = MouseButtonEvent;
-
- fn deref(&self) -> &Self::Target {
- &self.platform_event
- }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseDownOut {
- pub region: RectF,
- pub platform_event: MouseButtonEvent,
-}
-
-impl Deref for MouseDownOut {
- type Target = MouseButtonEvent;
-
- fn deref(&self) -> &Self::Target {
- &self.platform_event
- }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseUpOut {
- pub region: RectF,
- pub platform_event: MouseButtonEvent,
-}
-
-impl Deref for MouseUpOut {
- type Target = MouseButtonEvent;
-
- fn deref(&self) -> &Self::Target {
- &self.platform_event
- }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct MouseScrollWheel {
- pub region: RectF,
- pub platform_event: ScrollWheelEvent,
-}
-
-impl Deref for MouseScrollWheel {
- type Target = ScrollWheelEvent;
-
- fn deref(&self) -> &Self::Target {
- &self.platform_event
- }
-}
-
-#[derive(Debug, Clone)]
-pub enum MouseEvent {
- Move(MouseMove),
- MoveOut(MouseMoveOut),
- Drag(MouseDrag),
- Hover(MouseHover),
- Down(MouseDown),
- Up(MouseUp),
- Click(MouseClick),
- ClickOut(MouseClickOut),
- DownOut(MouseDownOut),
- UpOut(MouseUpOut),
- ScrollWheel(MouseScrollWheel),
-}
-
-impl MouseEvent {
- pub fn set_region(&mut self, region: RectF) {
- match self {
- MouseEvent::Move(r) => r.region = region,
- MouseEvent::MoveOut(r) => r.region = region,
- MouseEvent::Drag(r) => r.region = region,
- MouseEvent::Hover(r) => r.region = region,
- MouseEvent::Down(r) => r.region = region,
- MouseEvent::Up(r) => r.region = region,
- MouseEvent::Click(r) => r.region = region,
- MouseEvent::ClickOut(r) => r.region = region,
- MouseEvent::DownOut(r) => r.region = region,
- MouseEvent::UpOut(r) => r.region = region,
- MouseEvent::ScrollWheel(r) => r.region = region,
- }
- }
-
- /// When true, mouse event handlers must call cx.propagate_event() to bubble
- /// the event to handlers they are painted on top of.
- pub fn is_capturable(&self) -> bool {
- match self {
- MouseEvent::Move(_) => true,
- MouseEvent::MoveOut(_) => false,
- MouseEvent::Drag(_) => true,
- MouseEvent::Hover(_) => false,
- MouseEvent::Down(_) => true,
- MouseEvent::Up(_) => true,
- MouseEvent::Click(_) => true,
- MouseEvent::ClickOut(_) => true,
- MouseEvent::DownOut(_) => false,
- MouseEvent::UpOut(_) => false,
- MouseEvent::ScrollWheel(_) => true,
- }
- }
-}
-
-impl MouseEvent {
- pub fn move_disc() -> Discriminant<MouseEvent> {
- discriminant(&MouseEvent::Move(Default::default()))
- }
-
- pub fn move_out_disc() -> Discriminant<MouseEvent> {
- discriminant(&MouseEvent::MoveOut(Default::default()))
- }
-
- pub fn drag_disc() -> Discriminant<MouseEvent> {
- discriminant(&MouseEvent::Drag(Default::default()))
- }
-
- pub fn hover_disc() -> Discriminant<MouseEvent> {
- discriminant(&MouseEvent::Hover(Default::default()))
- }
-
- pub fn down_disc() -> Discriminant<MouseEvent> {
- discriminant(&MouseEvent::Down(Default::default()))
- }
-
- pub fn up_disc() -> Discriminant<MouseEvent> {
- discriminant(&MouseEvent::Up(Default::default()))
- }
-
- pub fn up_out_disc() -> Discriminant<MouseEvent> {
- discriminant(&MouseEvent::UpOut(Default::default()))
- }
-
- pub fn click_disc() -> Discriminant<MouseEvent> {
- discriminant(&MouseEvent::Click(Default::default()))
- }
-
- pub fn click_out_disc() -> Discriminant<MouseEvent> {
- discriminant(&MouseEvent::ClickOut(Default::default()))
- }
-
- pub fn down_out_disc() -> Discriminant<MouseEvent> {
- discriminant(&MouseEvent::DownOut(Default::default()))
- }
-
- pub fn scroll_wheel_disc() -> Discriminant<MouseEvent> {
- discriminant(&MouseEvent::ScrollWheel(Default::default()))
- }
-
- pub fn handler_key(&self) -> HandlerKey {
- match self {
- MouseEvent::Move(_) => HandlerKey::new(Self::move_disc(), None),
- MouseEvent::MoveOut(_) => HandlerKey::new(Self::move_out_disc(), None),
- MouseEvent::Drag(e) => HandlerKey::new(Self::drag_disc(), e.pressed_button),
- MouseEvent::Hover(_) => HandlerKey::new(Self::hover_disc(), None),
- MouseEvent::Down(e) => HandlerKey::new(Self::down_disc(), Some(e.button)),
- MouseEvent::Up(e) => HandlerKey::new(Self::up_disc(), Some(e.button)),
- MouseEvent::Click(e) => HandlerKey::new(Self::click_disc(), Some(e.button)),
- MouseEvent::ClickOut(e) => HandlerKey::new(Self::click_out_disc(), Some(e.button)),
- MouseEvent::UpOut(e) => HandlerKey::new(Self::up_out_disc(), Some(e.button)),
- MouseEvent::DownOut(e) => HandlerKey::new(Self::down_out_disc(), Some(e.button)),
- MouseEvent::ScrollWheel(_) => HandlerKey::new(Self::scroll_wheel_disc(), None),
- }
- }
-}
@@ -1,555 +0,0 @@
-use crate::{platform::MouseButton, window::WindowContext, EventContext, TypeTag, ViewContext};
-use collections::HashMap;
-use pathfinder_geometry::rect::RectF;
-use smallvec::SmallVec;
-use std::{any::Any, fmt::Debug, mem::Discriminant, rc::Rc};
-
-use super::{
- mouse_event::{
- MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, MouseMove, MouseUp,
- MouseUpOut,
- },
- MouseClickOut, MouseMoveOut, MouseScrollWheel,
-};
-
-#[derive(Clone)]
-pub struct MouseRegion {
- pub id: MouseRegionId,
- pub bounds: RectF,
- pub handlers: HandlerSet,
- pub hoverable: bool,
- pub notify_on_hover: bool,
- pub notify_on_click: bool,
-}
-
-impl MouseRegion {
- /// Region ID is used to track semantically equivalent mouse regions across render passes.
- /// e.g. if you have mouse handlers attached to a list item type, then each item of the list
- /// should pass a different (consistent) region_id. If you have one big region that covers your
- /// whole component, just pass the view_id again.
- pub fn new<Tag: 'static>(view_id: usize, region_id: usize, bounds: RectF) -> Self {
- Self::from_handlers(
- TypeTag::new::<Tag>(),
- view_id,
- region_id,
- bounds,
- Default::default(),
- )
- }
-
- pub fn handle_all<Tag: 'static>(view_id: usize, region_id: usize, bounds: RectF) -> Self {
- Self::from_handlers(
- TypeTag::new::<Tag>(),
- view_id,
- region_id,
- bounds,
- HandlerSet::capture_all(),
- )
- }
-
- pub fn from_handlers(
- tag: TypeTag,
- view_id: usize,
- region_id: usize,
- bounds: RectF,
- handlers: HandlerSet,
- ) -> Self {
- Self {
- id: MouseRegionId {
- view_id,
- tag,
- region_id,
- },
- bounds,
- handlers,
- hoverable: true,
- notify_on_hover: false,
- notify_on_click: false,
- }
- }
-
- pub fn on_down<V, F>(mut self, button: MouseButton, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
- {
- self.handlers = self.handlers.on_down(button, handler);
- self
- }
-
- pub fn on_up<V, F>(mut self, button: MouseButton, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
- {
- self.handlers = self.handlers.on_up(button, handler);
- self
- }
-
- pub fn on_click<V, F>(mut self, button: MouseButton, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
- {
- self.handlers = self.handlers.on_click(button, handler);
- self
- }
-
- pub fn on_click_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseClickOut, &mut V, &mut EventContext<V>) + 'static,
- {
- self.handlers = self.handlers.on_click_out(button, handler);
- self
- }
-
- pub fn on_down_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
- {
- self.handlers = self.handlers.on_down_out(button, handler);
- self
- }
-
- pub fn on_up_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
- {
- self.handlers = self.handlers.on_up_out(button, handler);
- self
- }
-
- pub fn on_drag<V, F>(mut self, button: MouseButton, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
- {
- self.handlers = self.handlers.on_drag(button, handler);
- self
- }
-
- pub fn on_hover<V, F>(mut self, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
- {
- self.handlers = self.handlers.on_hover(handler);
- self
- }
-
- pub fn on_move<V, F>(mut self, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
- {
- self.handlers = self.handlers.on_move(handler);
- self
- }
-
- pub fn on_move_out<V, F>(mut self, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
- {
- self.handlers = self.handlers.on_move_out(handler);
- self
- }
-
- pub fn on_scroll<V, F>(mut self, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
- {
- self.handlers = self.handlers.on_scroll(handler);
- self
- }
-
- pub fn with_hoverable(mut self, is_hoverable: bool) -> Self {
- self.hoverable = is_hoverable;
- self
- }
-
- pub fn with_notify_on_hover(mut self, notify: bool) -> Self {
- self.notify_on_hover = notify;
- self
- }
-
- pub fn with_notify_on_click(mut self, notify: bool) -> Self {
- self.notify_on_click = notify;
- self
- }
-}
-
-#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
-pub struct MouseRegionId {
- view_id: usize,
- tag: TypeTag,
- region_id: usize,
-}
-
-impl MouseRegionId {
- pub(crate) fn new(tag: TypeTag, view_id: usize, region_id: usize) -> Self {
- MouseRegionId {
- view_id,
- region_id,
- tag,
- }
- }
-
- pub fn view_id(&self) -> usize {
- self.view_id
- }
-
- #[cfg(debug_assertions)]
- pub fn tag_type_name(&self) -> &'static str {
- self.tag.type_name()
- }
-}
-
-pub type HandlerCallback = Rc<dyn Fn(MouseEvent, &mut dyn Any, &mut WindowContext, usize) -> bool>;
-
-#[derive(Clone, PartialEq, Eq, Hash)]
-pub struct HandlerKey {
- event_kind: Discriminant<MouseEvent>,
- button: Option<MouseButton>,
-}
-
-impl HandlerKey {
- pub fn new(event_kind: Discriminant<MouseEvent>, button: Option<MouseButton>) -> HandlerKey {
- HandlerKey { event_kind, button }
- }
-}
-
-#[derive(Clone, Default)]
-pub struct HandlerSet {
- set: HashMap<HandlerKey, SmallVec<[HandlerCallback; 1]>>,
-}
-
-impl HandlerSet {
- pub fn capture_all() -> Self {
- let mut set: HashMap<HandlerKey, SmallVec<[HandlerCallback; 1]>> = HashMap::default();
-
- set.insert(
- HandlerKey::new(MouseEvent::move_disc(), None),
- SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
- );
- set.insert(
- HandlerKey::new(MouseEvent::hover_disc(), None),
- SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
- );
- for button in MouseButton::all() {
- set.insert(
- HandlerKey::new(MouseEvent::drag_disc(), Some(button)),
- SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
- );
- set.insert(
- HandlerKey::new(MouseEvent::down_disc(), Some(button)),
- SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
- );
- set.insert(
- HandlerKey::new(MouseEvent::up_disc(), Some(button)),
- SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
- );
- set.insert(
- HandlerKey::new(MouseEvent::click_disc(), Some(button)),
- SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
- );
- set.insert(
- HandlerKey::new(MouseEvent::click_out_disc(), Some(button)),
- SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
- );
- set.insert(
- HandlerKey::new(MouseEvent::down_out_disc(), Some(button)),
- SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
- );
- set.insert(
- HandlerKey::new(MouseEvent::up_out_disc(), Some(button)),
- SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
- );
- }
- set.insert(
- HandlerKey::new(MouseEvent::scroll_wheel_disc(), None),
- SmallVec::from_buf([Rc::new(|_, _, _, _| true)]),
- );
-
- HandlerSet { set }
- }
-
- pub fn get(&self, key: &HandlerKey) -> Option<&[HandlerCallback]> {
- self.set.get(key).map(|vec| vec.as_slice())
- }
-
- pub fn contains(
- &self,
- discriminant: Discriminant<MouseEvent>,
- button: Option<MouseButton>,
- ) -> bool {
- self.set
- .contains_key(&HandlerKey::new(discriminant, button))
- }
-
- fn insert(
- &mut self,
- event_kind: Discriminant<MouseEvent>,
- button: Option<MouseButton>,
- callback: HandlerCallback,
- ) {
- use std::collections::hash_map::Entry;
-
- match self.set.entry(HandlerKey::new(event_kind, button)) {
- Entry::Occupied(mut vec) => {
- vec.get_mut().push(callback);
- }
-
- Entry::Vacant(entry) => {
- entry.insert(SmallVec::from_buf([callback]));
- }
- }
- }
-
- pub fn on_move<V, F>(mut self, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
- {
- self.insert(MouseEvent::move_disc(), None,
- Rc::new(move |region_event, view, cx, view_id| {
- if let MouseEvent::Move(e) = region_event {
- let view = view.downcast_mut().unwrap();
- let mut cx = ViewContext::mutable(cx, view_id);
- let mut cx = EventContext::new(&mut cx);
- handler(e, view, &mut cx);
- cx.handled
- } else {
- panic!(
- "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Move, found {:?}",
- region_event);
- }
- }));
- self
- }
-
- pub fn on_move_out<V, F>(mut self, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
- {
- self.insert(MouseEvent::move_out_disc(), None,
- Rc::new(move |region_event, view, cx, view_id| {
- if let MouseEvent::MoveOut(e) = region_event {
- let view = view.downcast_mut().unwrap();
- let mut cx = ViewContext::<V>::mutable(cx, view_id);
- let mut cx = EventContext::new(&mut cx);
- handler(e, view, &mut cx);
- cx.handled
- } else {
- panic!(
- "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::MoveOut, found {:?}",
- region_event);
- }
- }));
- self
- }
-
- pub fn on_down<V, F>(mut self, button: MouseButton, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
- {
- self.insert(MouseEvent::down_disc(), Some(button),
- Rc::new(move |region_event, view, cx, view_id| {
- if let MouseEvent::Down(e) = region_event {
- let view = view.downcast_mut().unwrap();
- let mut cx = ViewContext::mutable(cx, view_id);
- let mut cx = EventContext::new(&mut cx);
- handler(e, view, &mut cx);
- cx.handled
- } else {
- panic!(
- "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Down, found {:?}",
- region_event);
- }
- }));
- self
- }
-
- pub fn on_up<V, F>(mut self, button: MouseButton, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
- {
- self.insert(MouseEvent::up_disc(), Some(button),
- Rc::new(move |region_event, view, cx, view_id| {
- if let MouseEvent::Up(e) = region_event {
- let view = view.downcast_mut().unwrap();
- let mut cx = ViewContext::mutable(cx, view_id);
- let mut cx = EventContext::new(&mut cx);
- handler(e, view, &mut cx);
- cx.handled
- } else {
- panic!(
- "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Up, found {:?}",
- region_event);
- }
- }));
- self
- }
-
- pub fn on_click<V, F>(mut self, button: MouseButton, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
- {
- self.insert(MouseEvent::click_disc(), Some(button),
- Rc::new(move |region_event, view, cx, view_id| {
- if let MouseEvent::Click(e) = region_event {
- let view = view.downcast_mut().unwrap();
- let mut cx = ViewContext::mutable(cx, view_id);
- let mut cx = EventContext::new(&mut cx);
- handler(e, view, &mut cx);
- cx.handled
- } else {
- panic!(
- "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}",
- region_event);
- }
- }));
- self
- }
-
- pub fn on_click_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseClickOut, &mut V, &mut EventContext<V>) + 'static,
- {
- self.insert(MouseEvent::click_out_disc(), Some(button),
- Rc::new(move |region_event, view, cx, view_id| {
- if let MouseEvent::ClickOut(e) = region_event {
- let view = view.downcast_mut().unwrap();
- let mut cx = ViewContext::mutable(cx, view_id);
- let mut cx = EventContext::new(&mut cx);
- handler(e, view, &mut cx);
- cx.handled
- } else {
- panic!(
- "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::ClickOut, found {:?}",
- region_event);
- }
- }));
- self
- }
-
- pub fn on_down_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
- {
- self.insert(MouseEvent::down_out_disc(), Some(button),
- Rc::new(move |region_event, view, cx, view_id| {
- if let MouseEvent::DownOut(e) = region_event {
- let view = view.downcast_mut().unwrap();
- let mut cx = ViewContext::mutable(cx, view_id);
- let mut cx = EventContext::new(&mut cx);
- handler(e, view, &mut cx);
- cx.handled
- } else {
- panic!(
- "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DownOut, found {:?}",
- region_event);
- }
- }));
- self
- }
-
- pub fn on_up_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
- {
- self.insert(MouseEvent::up_out_disc(), Some(button),
- Rc::new(move |region_event, view, cx, view_id| {
- if let MouseEvent::UpOut(e) = region_event {
- let view = view.downcast_mut().unwrap();
- let mut cx = ViewContext::mutable(cx, view_id);
- let mut cx = EventContext::new(&mut cx);
- handler(e, view, &mut cx);
- cx.handled
- } else {
- panic!(
- "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::UpOut, found {:?}",
- region_event);
- }
- }));
- self
- }
-
- pub fn on_drag<V, F>(mut self, button: MouseButton, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
- {
- self.insert(MouseEvent::drag_disc(), Some(button),
- Rc::new(move |region_event, view, cx, view_id| {
- if let MouseEvent::Drag(e) = region_event {
- let view = view.downcast_mut().unwrap();
- let mut cx = ViewContext::mutable(cx, view_id);
- let mut cx = EventContext::new(&mut cx);
- handler(e, view, &mut cx);
- cx.handled
- } else {
- panic!(
- "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Drag, found {:?}",
- region_event);
- }
- }));
- self
- }
-
- pub fn on_hover<V, F>(mut self, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
- {
- self.insert(MouseEvent::hover_disc(), None,
- Rc::new(move |region_event, view, cx, view_id| {
- if let MouseEvent::Hover(e) = region_event {
- let view = view.downcast_mut().unwrap();
- let mut cx = ViewContext::mutable(cx, view_id);
- let mut cx = EventContext::new(&mut cx);
- handler(e, view, &mut cx);
- cx.handled
- } else {
- panic!(
- "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}",
- region_event);
- }
- }));
- self
- }
-
- pub fn on_scroll<V, F>(mut self, handler: F) -> Self
- where
- V: 'static,
- F: Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
- {
- self.insert(MouseEvent::scroll_wheel_disc(), None,
- Rc::new(move |region_event, view, cx, view_id| {
- if let MouseEvent::ScrollWheel(e) = region_event {
- let view = view.downcast_mut().unwrap();
- let mut cx = ViewContext::mutable(cx, view_id);
- let mut cx = EventContext::new(&mut cx);
- handler(e, view, &mut cx);
- cx.handled
- } else {
- panic!(
- "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::ScrollWheel, found {:?}",
- region_event
- );
- }
- }));
- self
- }
-}
@@ -1,7 +0,0 @@
-// use crate::geometry::rect::RectF;
-// use crate::WindowContext;
-
-// struct Region {
-// pub bounds: RectF,
-// pub click_handler: Option<Rc<dyn Fn(&dyn Any, MouseEvent, &mut WindowContext)>>,
-// }
@@ -1,186 +1,51 @@
+use crate::{Entity, Subscription, TestAppContext, TestDispatcher};
+use futures::StreamExt as _;
+use rand::prelude::*;
+use smol::channel;
use std::{
- fmt::Write,
+ env,
panic::{self, RefUnwindSafe},
- rc::Rc,
- sync::{
- atomic::{AtomicU64, Ordering::SeqCst},
- Arc,
- },
};
-use futures::StreamExt;
-use parking_lot::Mutex;
-use smol::channel;
-
-use crate::{
- app::ref_counts::LeakDetector,
- elements::Empty,
- executor::{self, ExecutorEvent},
- platform,
- platform::Platform,
- util::CwdBacktrace,
- AnyElement, AppContext, Element, Entity, FontCache, Handle, Subscription, TestAppContext, View,
- ViewContext,
-};
-
-#[cfg(test)]
-#[ctor::ctor]
-fn init_logger() {
- if std::env::var("RUST_LOG").is_ok() {
- env_logger::init();
- }
-}
-
-// #[global_allocator]
-// static ALLOC: dhat::Alloc = dhat::Alloc;
-
pub fn run_test(
mut num_iterations: u64,
- mut starting_seed: u64,
max_retries: usize,
- detect_nondeterminism: bool,
- test_fn: &mut (dyn RefUnwindSafe
- + Fn(
- &mut AppContext,
- Rc<platform::test::ForegroundPlatform>,
- Arc<executor::Deterministic>,
- u64,
- )),
+ test_fn: &mut (dyn RefUnwindSafe + Fn(TestDispatcher, u64)),
on_fail_fn: Option<fn()>,
- fn_name: String,
+ _fn_name: String, // todo!("re-enable fn_name")
) {
- // let _profiler = dhat::Profiler::new_heap();
-
+ let starting_seed = env::var("SEED")
+ .map(|seed| seed.parse().expect("invalid SEED variable"))
+ .unwrap_or(0);
let is_randomized = num_iterations > 1;
- if is_randomized {
- if let Ok(value) = std::env::var("SEED") {
- starting_seed = value.parse().expect("invalid SEED variable");
- }
- if let Ok(value) = std::env::var("ITERATIONS") {
- num_iterations = value.parse().expect("invalid ITERATIONS variable");
- }
+ if let Ok(iterations) = env::var("ITERATIONS") {
+ num_iterations = iterations.parse().expect("invalid ITERATIONS variable");
}
- let atomic_seed = AtomicU64::new(starting_seed as u64);
- let mut retries = 0;
-
- loop {
- let result = panic::catch_unwind(|| {
- let foreground_platform = Rc::new(platform::test::foreground_platform());
- let platform = Arc::new(platform::test::platform());
- let font_system = platform.fonts();
- let font_cache = Arc::new(FontCache::new(font_system));
- let mut prev_runnable_history: Option<Vec<ExecutorEvent>> = None;
-
- for _ in 0..num_iterations {
- let seed = atomic_seed.load(SeqCst);
-
- if is_randomized {
- eprintln!("seed = {seed}");
- }
-
- let deterministic = executor::Deterministic::new(seed);
- if detect_nondeterminism {
- deterministic.set_previous_execution_history(prev_runnable_history.clone());
- deterministic.enable_runnable_backtrace();
- }
-
- let leak_detector = Arc::new(Mutex::new(LeakDetector::default()));
- let mut cx = TestAppContext::new(
- foreground_platform.clone(),
- platform.clone(),
- deterministic.build_foreground(usize::MAX),
- deterministic.build_background(),
- font_cache.clone(),
- leak_detector.clone(),
- 0,
- fn_name.clone(),
- );
- cx.update(|cx| {
- test_fn(cx, foreground_platform.clone(), deterministic.clone(), seed);
- });
-
- cx.remove_all_windows();
- deterministic.run_until_parked();
- cx.update(|cx| cx.clear_globals());
-
- leak_detector.lock().detect();
-
- if detect_nondeterminism {
- let curr_runnable_history = deterministic.execution_history();
- if let Some(prev_runnable_history) = prev_runnable_history {
- let mut prev_entries = prev_runnable_history.iter().fuse();
- let mut curr_entries = curr_runnable_history.iter().fuse();
-
- let mut nondeterministic = false;
- let mut common_history_prefix = Vec::new();
- let mut prev_history_suffix = Vec::new();
- let mut curr_history_suffix = Vec::new();
- loop {
- match (prev_entries.next(), curr_entries.next()) {
- (None, None) => break,
- (None, Some(curr_id)) => curr_history_suffix.push(*curr_id),
- (Some(prev_id), None) => prev_history_suffix.push(*prev_id),
- (Some(prev_id), Some(curr_id)) => {
- if nondeterministic {
- prev_history_suffix.push(*prev_id);
- curr_history_suffix.push(*curr_id);
- } else if prev_id == curr_id {
- common_history_prefix.push(*curr_id);
- } else {
- nondeterministic = true;
- prev_history_suffix.push(*prev_id);
- curr_history_suffix.push(*curr_id);
- }
- }
- }
- }
-
- if nondeterministic {
- let mut error = String::new();
- writeln!(&mut error, "Common prefix: {:?}", common_history_prefix)
- .unwrap();
- writeln!(&mut error, "Previous suffix: {:?}", prev_history_suffix)
- .unwrap();
- writeln!(&mut error, "Current suffix: {:?}", curr_history_suffix)
- .unwrap();
-
- let last_common_backtrace = common_history_prefix
- .last()
- .map(|event| deterministic.runnable_backtrace(event.id()));
-
- writeln!(
- &mut error,
- "Last future that ran on both executions: {:?}",
- last_common_backtrace.as_ref().map(CwdBacktrace)
- )
- .unwrap();
- panic!("Detected non-determinism.\n{}", error);
- }
- }
- prev_runnable_history = Some(curr_runnable_history);
- }
-
- if !detect_nondeterminism {
- atomic_seed.fetch_add(1, SeqCst);
- }
+ for seed in starting_seed..starting_seed + num_iterations {
+ let mut retry = 0;
+ loop {
+ if is_randomized {
+ eprintln!("seed = {seed}");
}
- });
-
- match result {
- Ok(_) => {
- break;
- }
- Err(error) => {
- if retries < max_retries {
- retries += 1;
- println!("retrying: attempt {}", retries);
- } else {
- if is_randomized {
- eprintln!("failing seed: {}", atomic_seed.load(SeqCst));
+ let result = panic::catch_unwind(|| {
+ let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(seed));
+ test_fn(dispatcher, seed);
+ });
+
+ match result {
+ Ok(_) => break,
+ Err(error) => {
+ if retry < max_retries {
+ println!("retrying: attempt {}", retry);
+ retry += 1;
+ } else {
+ if is_randomized {
+ eprintln!("failing seed: {}", seed);
+ }
+ on_fail_fn.map(|f| f());
+ panic::resume_unwind(error);
}
- on_fail_fn.map(|f| f());
- panic::resume_unwind(error);
}
}
}
@@ -192,7 +57,7 @@ pub struct Observation<T> {
_subscription: Subscription,
}
-impl<T> futures::Stream for Observation<T> {
+impl<T: 'static> futures::Stream for Observation<T> {
type Item = T;
fn poll_next(
@@ -203,7 +68,7 @@ impl<T> futures::Stream for Observation<T> {
}
}
-pub fn observe<T: Entity>(entity: &impl Handle<T>, cx: &mut TestAppContext) -> Observation<()> {
+pub fn observe<T: 'static>(entity: &impl Entity<T>, cx: &mut TestAppContext) -> Observation<()> {
let (tx, rx) = smol::channel::unbounded();
let _subscription = cx.update(|cx| {
cx.observe(entity, move |_, _| {
@@ -213,36 +78,3 @@ pub fn observe<T: Entity>(entity: &impl Handle<T>, cx: &mut TestAppContext) -> O
Observation { rx, _subscription }
}
-
-pub fn subscribe<T: Entity>(
- entity: &impl Handle<T>,
- cx: &mut TestAppContext,
-) -> Observation<T::Event>
-where
- T::Event: Clone,
-{
- let (tx, rx) = smol::channel::unbounded();
- let _subscription = cx.update(|cx| {
- cx.subscribe(entity, move |_, event, _| {
- let _ = smol::block_on(tx.send(event.clone()));
- })
- });
-
- Observation { rx, _subscription }
-}
-
-pub struct EmptyView;
-
-impl Entity for EmptyView {
- type Event = ();
-}
-
-impl View for EmptyView {
- fn ui_name() -> &'static str {
- "empty view"
- }
-
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- Empty::new().into_any()
- }
-}
@@ -1,876 +0,0 @@
-use crate::{
- color::Color,
- fonts::{FontId, GlyphId, Underline},
- geometry::{
- rect::RectF,
- vector::{vec2f, Vector2F},
- },
- platform,
- platform::FontSystem,
- scene,
- window::WindowContext,
-};
-use ordered_float::OrderedFloat;
-use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
-use smallvec::SmallVec;
-use std::{
- borrow::Borrow,
- collections::HashMap,
- hash::{Hash, Hasher},
- iter,
- sync::Arc,
-};
-
-pub struct TextLayoutCache {
- prev_frame: Mutex<HashMap<OwnedCacheKey, Arc<LineLayout>>>,
- curr_frame: RwLock<HashMap<OwnedCacheKey, Arc<LineLayout>>>,
- fonts: Arc<dyn platform::FontSystem>,
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub struct RunStyle {
- pub color: Color,
- pub font_id: FontId,
- pub underline: Underline,
-}
-
-impl TextLayoutCache {
- pub fn new(fonts: Arc<dyn platform::FontSystem>) -> Self {
- Self {
- prev_frame: Mutex::new(HashMap::new()),
- curr_frame: RwLock::new(HashMap::new()),
- fonts,
- }
- }
-
- pub fn finish_frame(&self) {
- let mut prev_frame = self.prev_frame.lock();
- let mut curr_frame = self.curr_frame.write();
- std::mem::swap(&mut *prev_frame, &mut *curr_frame);
- curr_frame.clear();
- }
-
- pub fn layout_str<'a>(
- &'a self,
- text: &'a str,
- font_size: f32,
- runs: &'a [(usize, RunStyle)],
- ) -> Line {
- let key = &BorrowedCacheKey {
- text,
- font_size: OrderedFloat(font_size),
- runs,
- } as &dyn CacheKey;
- let curr_frame = self.curr_frame.upgradable_read();
- if let Some(layout) = curr_frame.get(key) {
- return Line::new(layout.clone(), runs);
- }
-
- let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
- if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
- curr_frame.insert(key, layout.clone());
- Line::new(layout, runs)
- } else {
- let layout = Arc::new(self.fonts.layout_line(text, font_size, runs));
- let key = OwnedCacheKey {
- text: text.into(),
- font_size: OrderedFloat(font_size),
- runs: SmallVec::from(runs),
- };
- curr_frame.insert(key, layout.clone());
- Line::new(layout, runs)
- }
- }
-}
-
-trait CacheKey {
- fn key(&self) -> BorrowedCacheKey;
-}
-
-impl<'a> PartialEq for (dyn CacheKey + 'a) {
- fn eq(&self, other: &dyn CacheKey) -> bool {
- self.key() == other.key()
- }
-}
-
-impl<'a> Eq for (dyn CacheKey + 'a) {}
-
-impl<'a> Hash for (dyn CacheKey + 'a) {
- fn hash<H: Hasher>(&self, state: &mut H) {
- self.key().hash(state)
- }
-}
-
-#[derive(Eq)]
-struct OwnedCacheKey {
- text: String,
- font_size: OrderedFloat<f32>,
- runs: SmallVec<[(usize, RunStyle); 1]>,
-}
-
-impl CacheKey for OwnedCacheKey {
- fn key(&self) -> BorrowedCacheKey {
- BorrowedCacheKey {
- text: self.text.as_str(),
- font_size: self.font_size,
- runs: self.runs.as_slice(),
- }
- }
-}
-
-impl PartialEq for OwnedCacheKey {
- fn eq(&self, other: &Self) -> bool {
- self.key().eq(&other.key())
- }
-}
-
-impl Hash for OwnedCacheKey {
- fn hash<H: Hasher>(&self, state: &mut H) {
- self.key().hash(state);
- }
-}
-
-impl<'a> Borrow<dyn CacheKey + 'a> for OwnedCacheKey {
- fn borrow(&self) -> &(dyn CacheKey + 'a) {
- self as &dyn CacheKey
- }
-}
-
-#[derive(Copy, Clone)]
-struct BorrowedCacheKey<'a> {
- text: &'a str,
- font_size: OrderedFloat<f32>,
- runs: &'a [(usize, RunStyle)],
-}
-
-impl<'a> CacheKey for BorrowedCacheKey<'a> {
- fn key(&self) -> BorrowedCacheKey {
- *self
- }
-}
-
-impl<'a> PartialEq for BorrowedCacheKey<'a> {
- fn eq(&self, other: &Self) -> bool {
- self.text == other.text
- && self.font_size == other.font_size
- && self.runs.len() == other.runs.len()
- && self.runs.iter().zip(other.runs.iter()).all(
- |((len_a, style_a), (len_b, style_b))| {
- len_a == len_b && style_a.font_id == style_b.font_id
- },
- )
- }
-}
-
-impl<'a> Hash for BorrowedCacheKey<'a> {
- fn hash<H: Hasher>(&self, state: &mut H) {
- self.text.hash(state);
- self.font_size.hash(state);
- for (len, style_id) in self.runs {
- len.hash(state);
- style_id.font_id.hash(state);
- }
- }
-}
-
-#[derive(Default, Debug, Clone)]
-pub struct Line {
- layout: Arc<LineLayout>,
- style_runs: SmallVec<[StyleRun; 32]>,
-}
-
-#[derive(Debug, Clone, Copy)]
-struct StyleRun {
- len: u32,
- color: Color,
- underline: Underline,
-}
-
-#[derive(Default, Debug)]
-pub struct LineLayout {
- pub width: f32,
- pub ascent: f32,
- pub descent: f32,
- pub runs: Vec<Run>,
- pub len: usize,
- pub font_size: f32,
-}
-
-#[derive(Debug)]
-pub struct Run {
- pub font_id: FontId,
- pub glyphs: Vec<Glyph>,
-}
-
-#[derive(Clone, Debug)]
-pub struct Glyph {
- pub id: GlyphId,
- pub position: Vector2F,
- pub index: usize,
- pub is_emoji: bool,
-}
-
-impl Line {
- pub fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
- let mut style_runs = SmallVec::new();
- for (len, style) in runs {
- style_runs.push(StyleRun {
- len: *len as u32,
- color: style.color,
- underline: style.underline,
- });
- }
- Self { layout, style_runs }
- }
-
- pub fn runs(&self) -> &[Run] {
- &self.layout.runs
- }
-
- pub fn width(&self) -> f32 {
- self.layout.width
- }
-
- pub fn font_size(&self) -> f32 {
- self.layout.font_size
- }
-
- pub fn x_for_index(&self, index: usize) -> f32 {
- for run in &self.layout.runs {
- for glyph in &run.glyphs {
- if glyph.index >= index {
- return glyph.position.x();
- }
- }
- }
- self.layout.width
- }
-
- pub fn font_for_index(&self, index: usize) -> Option<FontId> {
- for run in &self.layout.runs {
- for glyph in &run.glyphs {
- if glyph.index >= index {
- return Some(run.font_id);
- }
- }
- }
-
- None
- }
-
- pub fn len(&self) -> usize {
- self.layout.len
- }
-
- pub fn is_empty(&self) -> bool {
- self.layout.len == 0
- }
-
- /// index_for_x returns the character containing the given x coordinate.
- /// (e.g. to handle a mouse-click)
- pub fn index_for_x(&self, x: f32) -> Option<usize> {
- if x >= self.layout.width {
- None
- } else {
- for run in self.layout.runs.iter().rev() {
- for glyph in run.glyphs.iter().rev() {
- if glyph.position.x() <= x {
- return Some(glyph.index);
- }
- }
- }
- Some(0)
- }
- }
-
- /// closest_index_for_x returns the character boundary closest to the given x coordinate
- /// (e.g. to handle aligning up/down arrow keys)
- pub fn closest_index_for_x(&self, x: f32) -> usize {
- let mut prev_index = 0;
- let mut prev_x = 0.0;
-
- for run in self.layout.runs.iter() {
- for glyph in run.glyphs.iter() {
- if glyph.position.x() >= x {
- if glyph.position.x() - x < x - prev_x {
- return glyph.index;
- } else {
- return prev_index;
- }
- }
- prev_index = glyph.index;
- prev_x = glyph.position.x();
- }
- }
- prev_index
- }
-
- pub fn paint(
- &self,
- origin: Vector2F,
- visible_bounds: RectF,
- line_height: f32,
- cx: &mut WindowContext,
- ) {
- let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
- let baseline_offset = vec2f(0., padding_top + self.layout.ascent);
-
- let mut style_runs = self.style_runs.iter();
- let mut run_end = 0;
- let mut color = Color::black();
- let mut underline = None;
-
- for run in &self.layout.runs {
- let max_glyph_width = cx
- .font_cache
- .bounding_box(run.font_id, self.layout.font_size)
- .x();
-
- for glyph in &run.glyphs {
- let glyph_origin = origin + baseline_offset + glyph.position;
- if glyph_origin.x() > visible_bounds.upper_right().x() {
- break;
- }
-
- let mut finished_underline = None;
- if glyph.index >= run_end {
- if let Some(style_run) = style_runs.next() {
- if let Some((_, underline_style)) = underline {
- if style_run.underline != underline_style {
- finished_underline = underline.take();
- }
- }
- if style_run.underline.thickness.into_inner() > 0. {
- underline.get_or_insert((
- vec2f(
- glyph_origin.x(),
- origin.y() + baseline_offset.y() + 0.618 * self.layout.descent,
- ),
- Underline {
- color: Some(
- style_run.underline.color.unwrap_or(style_run.color),
- ),
- thickness: style_run.underline.thickness,
- squiggly: style_run.underline.squiggly,
- },
- ));
- }
-
- run_end += style_run.len as usize;
- color = style_run.color;
- } else {
- run_end = self.layout.len;
- finished_underline = underline.take();
- }
- }
-
- if glyph_origin.x() + max_glyph_width < visible_bounds.origin().x() {
- continue;
- }
-
- if let Some((underline_origin, underline_style)) = finished_underline {
- cx.scene().push_underline(scene::Underline {
- origin: underline_origin,
- width: glyph_origin.x() - underline_origin.x(),
- thickness: underline_style.thickness.into(),
- color: underline_style.color.unwrap(),
- squiggly: underline_style.squiggly,
- });
- }
-
- if glyph.is_emoji {
- cx.scene().push_image_glyph(scene::ImageGlyph {
- font_id: run.font_id,
- font_size: self.layout.font_size,
- id: glyph.id,
- origin: glyph_origin,
- });
- } else {
- cx.scene().push_glyph(scene::Glyph {
- font_id: run.font_id,
- font_size: self.layout.font_size,
- id: glyph.id,
- origin: glyph_origin,
- color,
- });
- }
- }
- }
-
- if let Some((underline_start, underline_style)) = underline.take() {
- let line_end_x = origin.x() + self.layout.width;
- cx.scene().push_underline(scene::Underline {
- origin: underline_start,
- width: line_end_x - underline_start.x(),
- color: underline_style.color.unwrap(),
- thickness: underline_style.thickness.into(),
- squiggly: underline_style.squiggly,
- });
- }
- }
-
- pub fn paint_wrapped(
- &self,
- origin: Vector2F,
- visible_bounds: RectF,
- line_height: f32,
- boundaries: &[ShapedBoundary],
- cx: &mut WindowContext,
- ) {
- let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
- let baseline_offset = vec2f(0., padding_top + self.layout.ascent);
-
- let mut boundaries = boundaries.into_iter().peekable();
- let mut color_runs = self.style_runs.iter();
- let mut style_run_end = 0;
- let mut color = Color::black();
- let mut underline: Option<(Vector2F, Underline)> = None;
-
- let mut glyph_origin = origin;
- let mut prev_position = 0.;
- for (run_ix, run) in self.layout.runs.iter().enumerate() {
- for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
- glyph_origin.set_x(glyph_origin.x() + glyph.position.x() - prev_position);
-
- if boundaries
- .peek()
- .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
- {
- boundaries.next();
- if let Some((underline_origin, underline_style)) = underline {
- cx.scene().push_underline(scene::Underline {
- origin: underline_origin,
- width: glyph_origin.x() - underline_origin.x(),
- thickness: underline_style.thickness.into(),
- color: underline_style.color.unwrap(),
- squiggly: underline_style.squiggly,
- });
- }
-
- glyph_origin = vec2f(origin.x(), glyph_origin.y() + line_height);
- }
- prev_position = glyph.position.x();
-
- let mut finished_underline = None;
- if glyph.index >= style_run_end {
- if let Some(style_run) = color_runs.next() {
- style_run_end += style_run.len as usize;
- color = style_run.color;
- if let Some((_, underline_style)) = underline {
- if style_run.underline != underline_style {
- finished_underline = underline.take();
- }
- }
- if style_run.underline.thickness.into_inner() > 0. {
- underline.get_or_insert((
- glyph_origin
- + vec2f(0., baseline_offset.y() + 0.618 * self.layout.descent),
- Underline {
- color: Some(
- style_run.underline.color.unwrap_or(style_run.color),
- ),
- thickness: style_run.underline.thickness,
- squiggly: style_run.underline.squiggly,
- },
- ));
- }
- } else {
- style_run_end = self.layout.len;
- color = Color::black();
- finished_underline = underline.take();
- }
- }
-
- if let Some((underline_origin, underline_style)) = finished_underline {
- cx.scene().push_underline(scene::Underline {
- origin: underline_origin,
- width: glyph_origin.x() - underline_origin.x(),
- thickness: underline_style.thickness.into(),
- color: underline_style.color.unwrap(),
- squiggly: underline_style.squiggly,
- });
- }
-
- let glyph_bounds = RectF::new(
- glyph_origin,
- cx.font_cache
- .bounding_box(run.font_id, self.layout.font_size),
- );
- if glyph_bounds.intersects(visible_bounds) {
- if glyph.is_emoji {
- cx.scene().push_image_glyph(scene::ImageGlyph {
- font_id: run.font_id,
- font_size: self.layout.font_size,
- id: glyph.id,
- origin: glyph_bounds.origin() + baseline_offset,
- });
- } else {
- cx.scene().push_glyph(scene::Glyph {
- font_id: run.font_id,
- font_size: self.layout.font_size,
- id: glyph.id,
- origin: glyph_bounds.origin() + baseline_offset,
- color,
- });
- }
- }
- }
- }
-
- if let Some((underline_origin, underline_style)) = underline.take() {
- let line_end_x = glyph_origin.x() + self.layout.width - prev_position;
- cx.scene().push_underline(scene::Underline {
- origin: underline_origin,
- width: line_end_x - underline_origin.x(),
- thickness: underline_style.thickness.into(),
- color: underline_style.color.unwrap(),
- squiggly: underline_style.squiggly,
- });
- }
- }
-}
-
-impl Run {
- pub fn glyphs(&self) -> &[Glyph] {
- &self.glyphs
- }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub struct Boundary {
- pub ix: usize,
- pub next_indent: u32,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub struct ShapedBoundary {
- pub run_ix: usize,
- pub glyph_ix: usize,
-}
-
-impl Boundary {
- fn new(ix: usize, next_indent: u32) -> Self {
- Self { ix, next_indent }
- }
-}
-
-pub struct LineWrapper {
- font_system: Arc<dyn FontSystem>,
- pub(crate) font_id: FontId,
- pub(crate) font_size: f32,
- cached_ascii_char_widths: [f32; 128],
- cached_other_char_widths: HashMap<char, f32>,
-}
-
-impl LineWrapper {
- pub const MAX_INDENT: u32 = 256;
-
- pub fn new(font_id: FontId, font_size: f32, font_system: Arc<dyn FontSystem>) -> Self {
- Self {
- font_system,
- font_id,
- font_size,
- cached_ascii_char_widths: [f32::NAN; 128],
- cached_other_char_widths: HashMap::new(),
- }
- }
-
- pub fn wrap_line<'a>(
- &'a mut self,
- line: &'a str,
- wrap_width: f32,
- ) -> impl Iterator<Item = Boundary> + 'a {
- let mut width = 0.0;
- let mut first_non_whitespace_ix = None;
- let mut indent = None;
- let mut last_candidate_ix = 0;
- let mut last_candidate_width = 0.0;
- let mut last_wrap_ix = 0;
- let mut prev_c = '\0';
- let mut char_indices = line.char_indices();
- iter::from_fn(move || {
- for (ix, c) in char_indices.by_ref() {
- if c == '\n' {
- continue;
- }
-
- if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
- last_candidate_ix = ix;
- last_candidate_width = width;
- }
-
- if c != ' ' && first_non_whitespace_ix.is_none() {
- first_non_whitespace_ix = Some(ix);
- }
-
- let char_width = self.width_for_char(c);
- width += char_width;
- if width > wrap_width && ix > last_wrap_ix {
- if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
- {
- indent = Some(
- Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
- );
- }
-
- if last_candidate_ix > 0 {
- last_wrap_ix = last_candidate_ix;
- width -= last_candidate_width;
- last_candidate_ix = 0;
- } else {
- last_wrap_ix = ix;
- width = char_width;
- }
-
- let indent_width =
- indent.map(|indent| indent as f32 * self.width_for_char(' '));
- width += indent_width.unwrap_or(0.);
-
- return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
- }
- prev_c = c;
- }
-
- None
- })
- }
-
- pub fn wrap_shaped_line<'a>(
- &'a mut self,
- str: &'a str,
- line: &'a Line,
- wrap_width: f32,
- ) -> impl Iterator<Item = ShapedBoundary> + 'a {
- let mut first_non_whitespace_ix = None;
- let mut last_candidate_ix = None;
- let mut last_candidate_x = 0.0;
- let mut last_wrap_ix = ShapedBoundary {
- run_ix: 0,
- glyph_ix: 0,
- };
- let mut last_wrap_x = 0.;
- let mut prev_c = '\0';
- let mut glyphs = line
- .runs()
- .iter()
- .enumerate()
- .flat_map(move |(run_ix, run)| {
- run.glyphs()
- .iter()
- .enumerate()
- .map(move |(glyph_ix, glyph)| {
- let character = str[glyph.index..].chars().next().unwrap();
- (
- ShapedBoundary { run_ix, glyph_ix },
- character,
- glyph.position.x(),
- )
- })
- })
- .peekable();
-
- iter::from_fn(move || {
- while let Some((ix, c, x)) = glyphs.next() {
- if c == '\n' {
- continue;
- }
-
- if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
- last_candidate_ix = Some(ix);
- last_candidate_x = x;
- }
-
- if c != ' ' && first_non_whitespace_ix.is_none() {
- first_non_whitespace_ix = Some(ix);
- }
-
- let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
- let width = next_x - last_wrap_x;
- if width > wrap_width && ix > last_wrap_ix {
- if let Some(last_candidate_ix) = last_candidate_ix.take() {
- last_wrap_ix = last_candidate_ix;
- last_wrap_x = last_candidate_x;
- } else {
- last_wrap_ix = ix;
- last_wrap_x = x;
- }
-
- return Some(last_wrap_ix);
- }
- prev_c = c;
- }
-
- None
- })
- }
-
- fn is_boundary(&self, prev: char, next: char) -> bool {
- (prev == ' ') && (next != ' ')
- }
-
- #[inline(always)]
- fn width_for_char(&mut self, c: char) -> f32 {
- if (c as u32) < 128 {
- let mut width = self.cached_ascii_char_widths[c as usize];
- if width.is_nan() {
- width = self.compute_width_for_char(c);
- self.cached_ascii_char_widths[c as usize] = width;
- }
- width
- } else {
- let mut width = self
- .cached_other_char_widths
- .get(&c)
- .copied()
- .unwrap_or(f32::NAN);
- if width.is_nan() {
- width = self.compute_width_for_char(c);
- self.cached_other_char_widths.insert(c, width);
- }
- width
- }
- }
-
- fn compute_width_for_char(&self, c: char) -> f32 {
- self.font_system
- .layout_line(
- &c.to_string(),
- self.font_size,
- &[(
- 1,
- RunStyle {
- font_id: self.font_id,
- color: Default::default(),
- underline: Default::default(),
- },
- )],
- )
- .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
- }
- ],
- );
- }
-}
@@ -1,12 +1,15 @@
+#[cfg(any(test, feature = "test-support"))]
+use std::time::Duration;
+
+#[cfg(any(test, feature = "test-support"))]
+use futures::Future;
+
+#[cfg(any(test, feature = "test-support"))]
use smol::future::FutureExt;
-use std::{future::Future, time::Duration};
-pub fn post_inc(value: &mut usize) -> usize {
- let prev = *value;
- *value += 1;
- prev
-}
+pub use util::*;
+#[cfg(any(test, feature = "test-support"))]
pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
where
F: Future<Output = T>,
@@ -1,5 +0,0 @@
-mod select;
-
-pub use select::{ItemType, Select, SelectStyle};
-
-pub fn init(_: &mut super::AppContext) {}
@@ -1,156 +0,0 @@
-use crate::{
- elements::*,
- platform::{CursorStyle, MouseButton},
- AppContext, Entity, View, ViewContext, WeakViewHandle,
-};
-
-pub struct Select {
- handle: WeakViewHandle<Self>,
- render_item: Box<dyn Fn(usize, ItemType, bool, &mut ViewContext<Select>) -> AnyElement<Self>>,
- selected_item_ix: usize,
- item_count: usize,
- is_open: bool,
- list_state: UniformListState,
- build_style: Option<Box<dyn FnMut(&mut AppContext) -> SelectStyle>>,
-}
-
-#[derive(Clone, Default)]
-pub struct SelectStyle {
- pub header: ContainerStyle,
- pub menu: ContainerStyle,
-}
-
-pub enum ItemType {
- Header,
- Selected,
- Unselected,
-}
-
-pub enum Event {}
-
-impl Select {
- pub fn new<
- F: 'static + Fn(usize, ItemType, bool, &mut ViewContext<Self>) -> AnyElement<Self>,
- >(
- item_count: usize,
- cx: &mut ViewContext<Self>,
- render_item: F,
- ) -> Self {
- Self {
- handle: cx.weak_handle(),
- render_item: Box::new(render_item),
- selected_item_ix: 0,
- item_count,
- is_open: false,
- list_state: UniformListState::default(),
- build_style: Default::default(),
- }
- }
-
- pub fn with_style(mut self, f: impl 'static + FnMut(&mut AppContext) -> SelectStyle) -> Self {
- self.build_style = Some(Box::new(f));
- self
- }
-
- pub fn set_item_count(&mut self, count: usize, cx: &mut ViewContext<Self>) {
- if count != self.item_count {
- self.item_count = count;
- cx.notify();
- }
- }
-
- fn toggle(&mut self, cx: &mut ViewContext<Self>) {
- self.is_open = !self.is_open;
- cx.notify();
- }
-
- pub fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
- if ix != self.selected_item_ix || self.is_open {
- self.selected_item_ix = ix;
- self.is_open = false;
- cx.notify();
- }
- }
-
- pub fn selected_index(&self) -> usize {
- self.selected_item_ix
- }
-}
-
-impl Entity for Select {
- type Event = Event;
-}
-
-impl View for Select {
- fn ui_name() -> &'static str {
- "Select"
- }
-
- fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- if self.item_count == 0 {
- return Empty::new().into_any();
- }
-
- enum Header {}
- enum Item {}
-
- let style = if let Some(build_style) = self.build_style.as_mut() {
- (build_style)(cx)
- } else {
- Default::default()
- };
- let mut result = Flex::column().with_child(
- MouseEventHandler::new::<Header, _>(self.handle.id(), cx, |mouse_state, cx| {
- (self.render_item)(
- self.selected_item_ix,
- ItemType::Header,
- mouse_state.hovered(),
- cx,
- )
- .contained()
- .with_style(style.header)
- })
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, this, cx| {
- this.toggle(cx);
- }),
- );
- if self.is_open {
- result.add_child(Overlay::new(
- UniformList::new(
- self.list_state.clone(),
- self.item_count,
- cx,
- move |this, mut range, items, cx| {
- let selected_item_ix = this.selected_item_ix;
- range.end = range.end.min(this.item_count);
- items.extend(range.map(|ix| {
- MouseEventHandler::new::<Item, _>(ix, cx, |mouse_state, cx| {
- (this.render_item)(
- ix,
- if ix == selected_item_ix {
- ItemType::Selected
- } else {
- ItemType::Unselected
- },
- mouse_state.hovered(),
- cx,
- )
- })
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, this, cx| {
- this.set_selected_index(ix, cx);
- })
- .into_any()
- }))
- },
- )
- .constrained()
- .with_max_height(200.)
- .contained()
- .with_style(style.menu),
- ));
- }
- result.into_any()
- }
-}
@@ -1,14 +0,0 @@
-use gpui::{elements::Empty, Element, ViewContext};
-// use gpui_macros::Element;
-
-#[test]
-fn test_derive_render_element() {
- #[derive(Element)]
- struct TestElement {}
-
- impl TestElement {
- fn render<V: 'static>(&mut self, _: &mut V, _: &mut ViewContext<V>) -> impl Element<V> {
- Empty::new()
- }
- }
-}
@@ -1,92 +0,0 @@
-[package]
-name = "gpui2"
-version = "0.1.0"
-edition = "2021"
-authors = ["Nathan Sobo <nathan@zed.dev>"]
-description = "The next version of Zed's GPU-accelerated UI framework"
-publish = false
-
-[features]
-test-support = ["backtrace", "dhat", "env_logger", "collections/test-support", "util/test-support"]
-
-# Suppress a panic when both GPUI1 and GPUI2 are loaded.
-#
-# This is used in the `theme_importer` where we need to depend on both
-# GPUI1 and GPUI2 in order to convert Zed1 themes to Zed2 themes.
-allow-multiple-gpui-versions = ["util/allow-multiple-gpui-versions"]
-
-[lib]
-path = "src/gpui2.rs"
-doctest = false
-
-[dependencies]
-collections = { path = "../collections" }
-gpui2_macros = { path = "../gpui2_macros" }
-util = { path = "../util" }
-sum_tree = { path = "../sum_tree" }
-sqlez = { path = "../sqlez" }
-async-task = "4.0.3"
-backtrace = { version = "0.3", optional = true }
-ctor.workspace = true
-linkme = "0.3"
-derive_more.workspace = true
-dhat = { version = "0.3", optional = true }
-env_logger = { version = "0.9", optional = true }
-etagere = "0.2"
-futures.workspace = true
-image = "0.23"
-itertools = "0.10"
-lazy_static.workspace = true
-log.workspace = true
-num_cpus = "1.13"
-ordered-float.workspace = true
-parking = "2.0.0"
-parking_lot.workspace = true
-pathfinder_geometry = "0.5"
-postage.workspace = true
-rand.workspace = true
-refineable.workspace = true
-resvg = "0.14"
-seahash = "4.1"
-serde.workspace = true
-serde_derive.workspace = true
-serde_json.workspace = true
-smallvec.workspace = true
-smol.workspace = true
-taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "1876f72bee5e376023eaa518aa7b8a34c769bd1b" }
-thiserror.workspace = true
-time.workspace = true
-tiny-skia = "0.5"
-usvg = { version = "0.14", features = [] }
-uuid = { version = "1.1.2", features = ["v4"] }
-waker-fn = "1.1.0"
-slotmap = "1.0.6"
-schemars.workspace = true
-bitflags = "2.4.0"
-
-[dev-dependencies]
-backtrace = "0.3"
-collections = { path = "../collections", features = ["test-support"] }
-dhat = "0.3"
-env_logger.workspace = true
-png = "0.16"
-simplelog = "0.9"
-util = { path = "../util", features = ["test-support"] }
-
-[build-dependencies]
-bindgen = "0.65.1"
-cbindgen = "0.26.0"
-
-[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"
@@ -1,137 +0,0 @@
-use std::{
- env,
- path::{Path, PathBuf},
- process::{self, Command},
-};
-
-use cbindgen::Config;
-
-fn main() {
- generate_dispatch_bindings();
- let header_path = generate_shader_bindings();
- compile_metal_shaders(&header_path);
-}
-
-fn generate_dispatch_bindings() {
- println!("cargo:rustc-link-lib=framework=System");
- println!("cargo:rerun-if-changed=src/platform/mac/dispatch.h");
-
- let bindings = bindgen::Builder::default()
- .header("src/platform/mac/dispatch.h")
- .allowlist_var("_dispatch_main_q")
- .allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
- .allowlist_var("DISPATCH_TIME_NOW")
- .allowlist_function("dispatch_get_global_queue")
- .allowlist_function("dispatch_async_f")
- .allowlist_function("dispatch_after_f")
- .allowlist_function("dispatch_time")
- .parse_callbacks(Box::new(bindgen::CargoCallbacks))
- .layout_tests(false)
- .generate()
- .expect("unable to generate bindings");
-
- let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
- bindings
- .write_to_file(out_path.join("dispatch_sys.rs"))
- .expect("couldn't write dispatch bindings");
-}
-
-fn generate_shader_bindings() -> PathBuf {
- let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
- let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
- let mut config = Config::default();
- config.include_guard = Some("SCENE_H".into());
- config.language = cbindgen::Language::C;
- config.export.include.extend([
- "Bounds".into(),
- "Corners".into(),
- "Edges".into(),
- "Size".into(),
- "Pixels".into(),
- "PointF".into(),
- "Hsla".into(),
- "ContentMask".into(),
- "Uniforms".into(),
- "AtlasTile".into(),
- "PathRasterizationInputIndex".into(),
- "PathVertex_ScaledPixels".into(),
- "ShadowInputIndex".into(),
- "Shadow".into(),
- "QuadInputIndex".into(),
- "Underline".into(),
- "UnderlineInputIndex".into(),
- "Quad".into(),
- "SpriteInputIndex".into(),
- "MonochromeSprite".into(),
- "PolychromeSprite".into(),
- "PathSprite".into(),
- "SurfaceInputIndex".into(),
- "SurfaceBounds".into(),
- ]);
- config.no_includes = true;
- config.enumeration.prefix_with_name = true;
- cbindgen::Builder::new()
- .with_src(crate_dir.join("src/scene.rs"))
- .with_src(crate_dir.join("src/geometry.rs"))
- .with_src(crate_dir.join("src/color.rs"))
- .with_src(crate_dir.join("src/window.rs"))
- .with_src(crate_dir.join("src/platform.rs"))
- .with_src(crate_dir.join("src/platform/mac/metal_renderer.rs"))
- .with_config(config)
- .generate()
- .expect("Unable to generate bindings")
- .write_to_file(&output_path);
-
- output_path
-}
-
-fn compile_metal_shaders(header_path: &Path) {
- let shader_path = "./src/platform/mac/shaders.metal";
- 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");
-
- println!("cargo:rerun-if-changed={}", header_path.display());
- println!("cargo:rerun-if-changed={}", shader_path);
-
- let output = Command::new("xcrun")
- .args([
- "-sdk",
- "macosx",
- "metal",
- "-gline-tables-only",
- "-mmacosx-version-min=10.15.7",
- "-MO",
- "-c",
- shader_path,
- "-include",
- &header_path.to_str().unwrap(),
- "-o",
- ])
- .arg(&air_output_path)
- .output()
- .unwrap();
-
- if !output.status.success() {
- eprintln!(
- "metal shader compilation failed:\n{}",
- String::from_utf8_lossy(&output.stderr)
- );
- process::exit(1);
- }
-
- let output = Command::new("xcrun")
- .args(["-sdk", "macosx", "metallib"])
- .arg(air_output_path)
- .arg("-o")
- .arg(metallib_output_path)
- .output()
- .unwrap();
-
- if !output.status.success() {
- eprintln!(
- "metallib compilation failed:\n{}",
- String::from_utf8_lossy(&output.stderr)
- );
- process::exit(1);
- }
-}
@@ -1,1264 +0,0 @@
-mod async_context;
-mod entity_map;
-mod model_context;
-#[cfg(any(test, feature = "test-support"))]
-mod test_context;
-
-pub use async_context::*;
-use derive_more::{Deref, DerefMut};
-pub use entity_map::*;
-pub use model_context::*;
-use refineable::Refineable;
-use smol::future::FutureExt;
-#[cfg(any(test, feature = "test-support"))]
-pub use test_context::*;
-use time::UtcOffset;
-
-use crate::{
- current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
- AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
- DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, KeyBinding, Keymap,
- Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render,
- SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement,
- TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId,
-};
-use anyhow::{anyhow, Result};
-use collections::{FxHashMap, FxHashSet, VecDeque};
-use futures::{channel::oneshot, future::LocalBoxFuture, Future};
-use parking_lot::Mutex;
-use slotmap::SlotMap;
-use std::{
- any::{type_name, TypeId},
- cell::{Ref, RefCell, RefMut},
- marker::PhantomData,
- mem,
- ops::{Deref, DerefMut},
- path::{Path, PathBuf},
- rc::{Rc, Weak},
- sync::{atomic::Ordering::SeqCst, Arc},
- time::Duration,
-};
-use util::{
- http::{self, HttpClient},
- ResultExt,
-};
-
-/// Temporary(?) wrapper around RefCell<AppContext> to help us debug any double borrows.
-/// Strongly consider removing after stabilization.
-pub struct AppCell {
- app: RefCell<AppContext>,
-}
-
-impl AppCell {
- #[track_caller]
- pub fn borrow(&self) -> AppRef {
- if option_env!("TRACK_THREAD_BORROWS").is_some() {
- let thread_id = std::thread::current().id();
- eprintln!("borrowed {thread_id:?}");
- }
- AppRef(self.app.borrow())
- }
-
- #[track_caller]
- pub fn borrow_mut(&self) -> AppRefMut {
- if option_env!("TRACK_THREAD_BORROWS").is_some() {
- let thread_id = std::thread::current().id();
- eprintln!("borrowed {thread_id:?}");
- }
- AppRefMut(self.app.borrow_mut())
- }
-}
-
-#[derive(Deref, DerefMut)]
-pub struct AppRef<'a>(Ref<'a, AppContext>);
-
-impl<'a> Drop for AppRef<'a> {
- fn drop(&mut self) {
- if option_env!("TRACK_THREAD_BORROWS").is_some() {
- let thread_id = std::thread::current().id();
- eprintln!("dropped borrow from {thread_id:?}");
- }
- }
-}
-
-#[derive(Deref, DerefMut)]
-pub struct AppRefMut<'a>(RefMut<'a, AppContext>);
-
-impl<'a> Drop for AppRefMut<'a> {
- fn drop(&mut self) {
- if option_env!("TRACK_THREAD_BORROWS").is_some() {
- let thread_id = std::thread::current().id();
- eprintln!("dropped {thread_id:?}");
- }
- }
-}
-
-pub struct App(Rc<AppCell>);
-
-/// Represents an application before it is fully launched. Once your app is
-/// configured, you'll start the app with `App::run`.
-impl App {
- /// Builds an app with the given asset source.
- pub fn production(asset_source: Arc<dyn AssetSource>) -> Self {
- Self(AppContext::new(
- current_platform(),
- asset_source,
- http::client(),
- ))
- }
-
- /// Start the application. The provided callback will be called once the
- /// app is fully launched.
- pub fn run<F>(self, on_finish_launching: F)
- where
- F: 'static + FnOnce(&mut AppContext),
- {
- let this = self.0.clone();
- let platform = self.0.borrow().platform.clone();
- platform.run(Box::new(move || {
- let cx = &mut *this.borrow_mut();
- on_finish_launching(cx);
- }));
- }
-
- /// Register a handler to be invoked when the platform instructs the application
- /// to open one or more URLs.
- pub fn on_open_urls<F>(&self, mut callback: F) -> &Self
- where
- F: 'static + FnMut(Vec<String>, &mut AppContext),
- {
- let this = Rc::downgrade(&self.0);
- self.0.borrow().platform.on_open_urls(Box::new(move |urls| {
- if let Some(app) = this.upgrade() {
- callback(urls, &mut app.borrow_mut());
- }
- }));
- self
- }
-
- pub fn on_reopen<F>(&self, mut callback: F) -> &Self
- where
- F: 'static + FnMut(&mut AppContext),
- {
- let this = Rc::downgrade(&self.0);
- self.0.borrow_mut().platform.on_reopen(Box::new(move || {
- if let Some(app) = this.upgrade() {
- callback(&mut app.borrow_mut());
- }
- }));
- self
- }
-
- pub fn metadata(&self) -> AppMetadata {
- self.0.borrow().app_metadata.clone()
- }
-
- pub fn background_executor(&self) -> BackgroundExecutor {
- self.0.borrow().background_executor.clone()
- }
-
- pub fn foreground_executor(&self) -> ForegroundExecutor {
- self.0.borrow().foreground_executor.clone()
- }
-
- pub fn text_system(&self) -> Arc<TextSystem> {
- self.0.borrow().text_system.clone()
- }
-}
-
-pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
-type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
-type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
-type KeystrokeObserver = Box<dyn FnMut(&KeystrokeEvent, &mut WindowContext) + 'static>;
-type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, ()> + 'static>;
-type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
-type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
-
-// struct FrameConsumer {
-// next_frame_callbacks: Vec<FrameCallback>,
-// task: Task<()>,
-// display_linker
-// }
-
-pub struct AppContext {
- pub(crate) this: Weak<AppCell>,
- pub(crate) platform: Rc<dyn Platform>,
- app_metadata: AppMetadata,
- text_system: Arc<TextSystem>,
- flushing_effects: bool,
- pending_updates: usize,
- pub(crate) actions: Rc<ActionRegistry>,
- pub(crate) active_drag: Option<AnyDrag>,
- pub(crate) active_tooltip: Option<AnyTooltip>,
- pub(crate) next_frame_callbacks: FxHashMap<DisplayId, Vec<FrameCallback>>,
- pub(crate) frame_consumers: FxHashMap<DisplayId, Task<()>>,
- pub(crate) background_executor: BackgroundExecutor,
- pub(crate) foreground_executor: ForegroundExecutor,
- pub(crate) svg_renderer: SvgRenderer,
- asset_source: Arc<dyn AssetSource>,
- pub(crate) image_cache: ImageCache,
- pub(crate) text_style_stack: Vec<TextStyleRefinement>,
- pub(crate) globals_by_type: FxHashMap<TypeId, Box<dyn Any>>,
- pub(crate) entities: EntityMap,
- pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>,
- pub(crate) windows: SlotMap<WindowId, Option<Window>>,
- pub(crate) keymap: Arc<Mutex<Keymap>>,
- pub(crate) global_action_listeners:
- FxHashMap<TypeId, Vec<Rc<dyn Fn(&dyn Any, DispatchPhase, &mut Self)>>>,
- pending_effects: VecDeque<Effect>,
- pub(crate) pending_notifications: FxHashSet<EntityId>,
- pub(crate) pending_global_notifications: FxHashSet<TypeId>,
- pub(crate) observers: SubscriberSet<EntityId, Handler>,
- // TypeId is the type of the event that the listener callback expects
- pub(crate) event_listeners: SubscriberSet<EntityId, (TypeId, Listener)>,
- pub(crate) keystroke_observers: SubscriberSet<(), KeystrokeObserver>,
- pub(crate) release_listeners: SubscriberSet<EntityId, ReleaseListener>,
- pub(crate) global_observers: SubscriberSet<TypeId, Handler>,
- pub(crate) quit_observers: SubscriberSet<(), QuitHandler>,
- pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
- pub(crate) propagate_event: bool,
-}
-
-impl AppContext {
- pub(crate) fn new(
- platform: Rc<dyn Platform>,
- asset_source: Arc<dyn AssetSource>,
- http_client: Arc<dyn HttpClient>,
- ) -> Rc<AppCell> {
- let executor = platform.background_executor();
- let foreground_executor = platform.foreground_executor();
- assert!(
- executor.is_main_thread(),
- "must construct App on main thread"
- );
-
- let text_system = Arc::new(TextSystem::new(platform.text_system()));
- let entities = EntityMap::new();
-
- let app_metadata = AppMetadata {
- os_name: platform.os_name(),
- os_version: platform.os_version().ok(),
- app_version: platform.app_version().ok(),
- };
-
- let app = Rc::new_cyclic(|this| AppCell {
- app: RefCell::new(AppContext {
- this: this.clone(),
- platform: platform.clone(),
- app_metadata,
- text_system,
- actions: Rc::new(ActionRegistry::default()),
- flushing_effects: false,
- pending_updates: 0,
- active_drag: None,
- active_tooltip: None,
- next_frame_callbacks: FxHashMap::default(),
- frame_consumers: FxHashMap::default(),
- background_executor: executor,
- foreground_executor,
- svg_renderer: SvgRenderer::new(asset_source.clone()),
- asset_source,
- image_cache: ImageCache::new(http_client),
- text_style_stack: Vec::new(),
- globals_by_type: FxHashMap::default(),
- entities,
- new_view_observers: SubscriberSet::new(),
- windows: SlotMap::with_key(),
- keymap: Arc::new(Mutex::new(Keymap::default())),
- global_action_listeners: FxHashMap::default(),
- pending_effects: VecDeque::new(),
- pending_notifications: FxHashSet::default(),
- pending_global_notifications: FxHashSet::default(),
- observers: SubscriberSet::new(),
- event_listeners: SubscriberSet::new(),
- release_listeners: SubscriberSet::new(),
- keystroke_observers: SubscriberSet::new(),
- global_observers: SubscriberSet::new(),
- quit_observers: SubscriberSet::new(),
- layout_id_buffer: Default::default(),
- propagate_event: true,
- }),
- });
-
- init_app_menus(platform.as_ref(), &mut app.borrow_mut());
-
- platform.on_quit(Box::new({
- let cx = app.clone();
- move || {
- cx.borrow_mut().shutdown();
- }
- }));
-
- app
- }
-
- /// Quit the application gracefully. Handlers registered with `ModelContext::on_app_quit`
- /// will be given 100ms to complete before exiting.
- pub fn shutdown(&mut self) {
- let mut futures = Vec::new();
-
- for observer in self.quit_observers.remove(&()) {
- futures.push(observer(self));
- }
-
- self.windows.clear();
- self.flush_effects();
-
- let futures = futures::future::join_all(futures);
- if self
- .background_executor
- .block_with_timeout(Duration::from_millis(100), futures)
- .is_err()
- {
- log::error!("timed out waiting on app_will_quit");
- }
- }
-
- pub fn quit(&mut self) {
- self.platform.quit();
- }
-
- pub fn app_metadata(&self) -> AppMetadata {
- self.app_metadata.clone()
- }
-
- /// Schedules all windows in the application to be redrawn. This can be called
- /// multiple times in an update cycle and still result in a single redraw.
- pub fn refresh(&mut self) {
- self.pending_effects.push_back(Effect::Refresh);
- }
- pub(crate) fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
- self.pending_updates += 1;
- let result = update(self);
- if !self.flushing_effects && self.pending_updates == 1 {
- self.flushing_effects = true;
- self.flush_effects();
- self.flushing_effects = false;
- }
- self.pending_updates -= 1;
- result
- }
-
- pub fn observe<W, E>(
- &mut self,
- entity: &E,
- mut on_notify: impl FnMut(E, &mut AppContext) + 'static,
- ) -> Subscription
- where
- W: 'static,
- E: Entity<W>,
- {
- self.observe_internal(entity, move |e, cx| {
- on_notify(e, cx);
- true
- })
- }
-
- pub fn observe_internal<W, E>(
- &mut self,
- entity: &E,
- mut on_notify: impl FnMut(E, &mut AppContext) -> bool + 'static,
- ) -> Subscription
- where
- W: 'static,
- E: Entity<W>,
- {
- let entity_id = entity.entity_id();
- let handle = entity.downgrade();
- let (subscription, activate) = self.observers.insert(
- entity_id,
- Box::new(move |cx| {
- if let Some(handle) = E::upgrade_from(&handle) {
- on_notify(handle, cx)
- } else {
- false
- }
- }),
- );
- self.defer(move |_| activate());
- subscription
- }
-
- pub fn subscribe<T, E, Evt>(
- &mut self,
- entity: &E,
- mut on_event: impl FnMut(E, &Evt, &mut AppContext) + 'static,
- ) -> Subscription
- where
- T: 'static + EventEmitter<Evt>,
- E: Entity<T>,
- Evt: 'static,
- {
- self.subscribe_internal(entity, move |entity, event, cx| {
- on_event(entity, event, cx);
- true
- })
- }
-
- pub(crate) fn subscribe_internal<T, E, Evt>(
- &mut self,
- entity: &E,
- mut on_event: impl FnMut(E, &Evt, &mut AppContext) -> bool + 'static,
- ) -> Subscription
- where
- T: 'static + EventEmitter<Evt>,
- E: Entity<T>,
- Evt: 'static,
- {
- let entity_id = entity.entity_id();
- let entity = entity.downgrade();
- let (subscription, activate) = self.event_listeners.insert(
- entity_id,
- (
- TypeId::of::<Evt>(),
- Box::new(move |event, cx| {
- let event: &Evt = event.downcast_ref().expect("invalid event type");
- if let Some(handle) = E::upgrade_from(&entity) {
- on_event(handle, event, cx)
- } else {
- false
- }
- }),
- ),
- );
- self.defer(move |_| activate());
- subscription
- }
-
- pub fn windows(&self) -> Vec<AnyWindowHandle> {
- self.windows
- .values()
- .filter_map(|window| Some(window.as_ref()?.handle))
- .collect()
- }
-
- pub fn active_window(&self) -> Option<AnyWindowHandle> {
- self.platform.active_window()
- }
-
- /// Opens a new window with the given option and the root view returned by the given function.
- /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
- /// functionality.
- pub fn open_window<V: 'static + Render>(
- &mut self,
- options: crate::WindowOptions,
- build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
- ) -> WindowHandle<V> {
- self.update(|cx| {
- let id = cx.windows.insert(None);
- let handle = WindowHandle::new(id);
- let mut window = Window::new(handle.into(), options, cx);
- let root_view = build_root_view(&mut WindowContext::new(cx, &mut window));
- window.root_view.replace(root_view.into());
- cx.windows.get_mut(id).unwrap().replace(window);
- handle
- })
- }
-
- /// Instructs the platform to activate the application by bringing it to the foreground.
- pub fn activate(&self, ignoring_other_apps: bool) {
- self.platform.activate(ignoring_other_apps);
- }
-
- pub fn hide(&self) {
- self.platform.hide();
- }
-
- pub fn hide_other_apps(&self) {
- self.platform.hide_other_apps();
- }
-
- pub fn unhide_other_apps(&self) {
- self.platform.unhide_other_apps();
- }
-
- /// Returns the list of currently active displays.
- pub fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
- self.platform.displays()
- }
-
- /// Writes data to the platform clipboard.
- pub fn write_to_clipboard(&self, item: ClipboardItem) {
- self.platform.write_to_clipboard(item)
- }
-
- /// Reads data from the platform clipboard.
- pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
- self.platform.read_from_clipboard()
- }
-
- /// Writes credentials to the platform keychain.
- pub fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
- self.platform.write_credentials(url, username, password)
- }
-
- /// Reads credentials from the platform keychain.
- pub fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
- self.platform.read_credentials(url)
- }
-
- /// Deletes credentials from the platform keychain.
- pub fn delete_credentials(&self, url: &str) -> Result<()> {
- self.platform.delete_credentials(url)
- }
-
- /// Directs the platform's default browser to open the given URL.
- pub fn open_url(&self, url: &str) {
- self.platform.open_url(url);
- }
-
- pub fn app_path(&self) -> Result<PathBuf> {
- self.platform.app_path()
- }
-
- pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
- self.platform.path_for_auxiliary_executable(name)
- }
-
- pub fn double_click_interval(&self) -> Duration {
- self.platform.double_click_interval()
- }
-
- pub fn prompt_for_paths(
- &self,
- options: PathPromptOptions,
- ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
- self.platform.prompt_for_paths(options)
- }
-
- pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
- self.platform.prompt_for_new_path(directory)
- }
-
- pub fn reveal_path(&self, path: &Path) {
- self.platform.reveal_path(path)
- }
-
- pub fn should_auto_hide_scrollbars(&self) -> bool {
- self.platform.should_auto_hide_scrollbars()
- }
-
- pub fn restart(&self) {
- self.platform.restart()
- }
-
- pub fn local_timezone(&self) -> UtcOffset {
- self.platform.local_timezone()
- }
-
- pub(crate) fn push_effect(&mut self, effect: Effect) {
- match &effect {
- Effect::Notify { emitter } => {
- if !self.pending_notifications.insert(*emitter) {
- return;
- }
- }
- Effect::NotifyGlobalObservers { global_type } => {
- if !self.pending_global_notifications.insert(*global_type) {
- return;
- }
- }
- _ => {}
- };
-
- self.pending_effects.push_back(effect);
- }
-
- /// Called at the end of AppContext::update to complete any side effects
- /// such as notifying observers, emitting events, etc. Effects can themselves
- /// cause effects, so we continue looping until all effects are processed.
- fn flush_effects(&mut self) {
- loop {
- self.release_dropped_entities();
- self.release_dropped_focus_handles();
-
- if let Some(effect) = self.pending_effects.pop_front() {
- match effect {
- Effect::Notify { emitter } => {
- self.apply_notify_effect(emitter);
- }
-
- Effect::Emit {
- emitter,
- event_type,
- event,
- } => self.apply_emit_effect(emitter, event_type, event),
-
- Effect::Refresh => {
- self.apply_refresh_effect();
- }
-
- Effect::NotifyGlobalObservers { global_type } => {
- self.apply_notify_global_observers_effect(global_type);
- }
-
- Effect::Defer { callback } => {
- self.apply_defer_effect(callback);
- }
- }
- } else {
- for window in self.windows.values() {
- if let Some(window) = window.as_ref() {
- if window.dirty {
- window.platform_window.invalidate();
- }
- }
- }
-
- #[cfg(any(test, feature = "test-support"))]
- for window in self
- .windows
- .values()
- .filter_map(|window| {
- let window = window.as_ref()?;
- (window.dirty || window.focus_invalidated).then_some(window.handle)
- })
- .collect::<Vec<_>>()
- {
- self.update_window(window, |_, cx| cx.draw()).unwrap();
- }
-
- if self.pending_effects.is_empty() {
- break;
- }
- }
- }
- }
-
- /// Repeatedly called during `flush_effects` to release any entities whose
- /// reference count has become zero. We invoke any release observers before dropping
- /// each entity.
- fn release_dropped_entities(&mut self) {
- loop {
- let dropped = self.entities.take_dropped();
- if dropped.is_empty() {
- break;
- }
-
- for (entity_id, mut entity) in dropped {
- self.observers.remove(&entity_id);
- self.event_listeners.remove(&entity_id);
- for release_callback in self.release_listeners.remove(&entity_id) {
- release_callback(entity.as_mut(), self);
- }
- }
- }
- }
-
- /// Repeatedly called during `flush_effects` to handle a focused handle being dropped.
- fn release_dropped_focus_handles(&mut self) {
- for window_handle in self.windows() {
- window_handle
- .update(self, |_, cx| {
- let mut blur_window = false;
- let focus = cx.window.focus;
- cx.window.focus_handles.write().retain(|handle_id, count| {
- if count.load(SeqCst) == 0 {
- if focus == Some(handle_id) {
- blur_window = true;
- }
- false
- } else {
- true
- }
- });
-
- if blur_window {
- cx.blur();
- }
- })
- .unwrap();
- }
- }
-
- fn apply_notify_effect(&mut self, emitter: EntityId) {
- self.pending_notifications.remove(&emitter);
-
- self.observers
- .clone()
- .retain(&emitter, |handler| handler(self));
- }
-
- fn apply_emit_effect(&mut self, emitter: EntityId, event_type: TypeId, event: Box<dyn Any>) {
- self.event_listeners
- .clone()
- .retain(&emitter, |(stored_type, handler)| {
- if *stored_type == event_type {
- handler(event.as_ref(), self)
- } else {
- true
- }
- });
- }
-
- fn apply_refresh_effect(&mut self) {
- for window in self.windows.values_mut() {
- if let Some(window) = window.as_mut() {
- window.dirty = true;
- }
- }
- }
-
- fn apply_notify_global_observers_effect(&mut self, type_id: TypeId) {
- self.pending_global_notifications.remove(&type_id);
- self.global_observers
- .clone()
- .retain(&type_id, |observer| observer(self));
- }
-
- fn apply_defer_effect(&mut self, callback: Box<dyn FnOnce(&mut Self) + 'static>) {
- callback(self);
- }
-
- /// Creates an `AsyncAppContext`, which can be cloned and has a static lifetime
- /// so it can be held across `await` points.
- pub fn to_async(&self) -> AsyncAppContext {
- AsyncAppContext {
- app: unsafe { mem::transmute(self.this.clone()) },
- background_executor: self.background_executor.clone(),
- foreground_executor: self.foreground_executor.clone(),
- }
- }
-
- /// Obtains a reference to the executor, which can be used to spawn futures.
- pub fn background_executor(&self) -> &BackgroundExecutor {
- &self.background_executor
- }
-
- /// Obtains a reference to the executor, which can be used to spawn futures.
- pub fn foreground_executor(&self) -> &ForegroundExecutor {
- &self.foreground_executor
- }
-
- /// Spawns the future returned by the given function on the thread pool. The closure will be invoked
- /// with AsyncAppContext, which allows the application state to be accessed across await points.
- pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
- where
- Fut: Future<Output = R> + 'static,
- R: 'static,
- {
- self.foreground_executor.spawn(f(self.to_async()))
- }
-
- /// Schedules the given function to be run at the end of the current effect cycle, allowing entities
- /// that are currently on the stack to be returned to the app.
- pub fn defer(&mut self, f: impl FnOnce(&mut AppContext) + 'static) {
- self.push_effect(Effect::Defer {
- callback: Box::new(f),
- });
- }
-
- /// Accessor for the application's asset source, which is provided when constructing the `App`.
- pub fn asset_source(&self) -> &Arc<dyn AssetSource> {
- &self.asset_source
- }
-
- /// Accessor for the text system.
- pub fn text_system(&self) -> &Arc<TextSystem> {
- &self.text_system
- }
-
- /// The current text style. Which is composed of all the style refinements provided to `with_text_style`.
- pub fn text_style(&self) -> TextStyle {
- let mut style = TextStyle::default();
- for refinement in &self.text_style_stack {
- style.refine(refinement);
- }
- style
- }
-
- /// Check whether a global of the given type has been assigned.
- pub fn has_global<G: 'static>(&self) -> bool {
- self.globals_by_type.contains_key(&TypeId::of::<G>())
- }
-
- /// Access the global of the given type. Panics if a global for that type has not been assigned.
- #[track_caller]
- pub fn global<G: 'static>(&self) -> &G {
- self.globals_by_type
- .get(&TypeId::of::<G>())
- .map(|any_state| any_state.downcast_ref::<G>().unwrap())
- .ok_or_else(|| anyhow!("no state of type {} exists", type_name::<G>()))
- .unwrap()
- }
-
- /// Access the global of the given type if a value has been assigned.
- pub fn try_global<G: 'static>(&self) -> Option<&G> {
- self.globals_by_type
- .get(&TypeId::of::<G>())
- .map(|any_state| any_state.downcast_ref::<G>().unwrap())
- }
-
- /// Access the global of the given type mutably. Panics if a global for that type has not been assigned.
- #[track_caller]
- pub fn global_mut<G: 'static>(&mut self) -> &mut G {
- let global_type = TypeId::of::<G>();
- self.push_effect(Effect::NotifyGlobalObservers { global_type });
- self.globals_by_type
- .get_mut(&global_type)
- .and_then(|any_state| any_state.downcast_mut::<G>())
- .ok_or_else(|| anyhow!("no state of type {} exists", type_name::<G>()))
- .unwrap()
- }
-
- /// Access the global of the given type mutably. A default value is assigned if a global of this type has not
- /// yet been assigned.
- pub fn default_global<G: 'static + Default>(&mut self) -> &mut G {
- let global_type = TypeId::of::<G>();
- self.push_effect(Effect::NotifyGlobalObservers { global_type });
- self.globals_by_type
- .entry(global_type)
- .or_insert_with(|| Box::<G>::default())
- .downcast_mut::<G>()
- .unwrap()
- }
-
- /// Set the value of the global of the given type.
- pub fn set_global<G: Any>(&mut self, global: G) {
- let global_type = TypeId::of::<G>();
- self.push_effect(Effect::NotifyGlobalObservers { global_type });
- self.globals_by_type.insert(global_type, Box::new(global));
- }
-
- /// Clear all stored globals. Does not notify global observers.
- #[cfg(any(test, feature = "test-support"))]
- pub fn clear_globals(&mut self) {
- self.globals_by_type.drain();
- }
-
- /// Remove the global of the given type from the app context. Does not notify global observers.
- pub fn remove_global<G: Any>(&mut self) -> G {
- let global_type = TypeId::of::<G>();
- *self
- .globals_by_type
- .remove(&global_type)
- .unwrap_or_else(|| panic!("no global added for {}", std::any::type_name::<G>()))
- .downcast()
- .unwrap()
- }
-
- /// Update the global of the given type with a closure. Unlike `global_mut`, this method provides
- /// your closure with mutable access to the `AppContext` and the global simultaneously.
- pub fn update_global<G: 'static, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R {
- let mut global = self.lease_global::<G>();
- let result = f(&mut global, self);
- self.end_global_lease(global);
- result
- }
-
- /// Register a callback to be invoked when a global of the given type is updated.
- pub fn observe_global<G: 'static>(
- &mut self,
- mut f: impl FnMut(&mut Self) + 'static,
- ) -> Subscription {
- let (subscription, activate) = self.global_observers.insert(
- TypeId::of::<G>(),
- Box::new(move |cx| {
- f(cx);
- true
- }),
- );
- self.defer(move |_| activate());
- subscription
- }
-
- /// Move the global of the given type to the stack.
- pub(crate) fn lease_global<G: 'static>(&mut self) -> GlobalLease<G> {
- GlobalLease::new(
- self.globals_by_type
- .remove(&TypeId::of::<G>())
- .ok_or_else(|| anyhow!("no global registered of type {}", type_name::<G>()))
- .unwrap(),
- )
- }
-
- /// Restore the global of the given type after it is moved to the stack.
- pub(crate) fn end_global_lease<G: 'static>(&mut self, lease: GlobalLease<G>) {
- let global_type = TypeId::of::<G>();
- self.push_effect(Effect::NotifyGlobalObservers { global_type });
- self.globals_by_type.insert(global_type, lease.global);
- }
-
- pub fn observe_new_views<V: 'static>(
- &mut self,
- on_new: impl 'static + Fn(&mut V, &mut ViewContext<V>),
- ) -> Subscription {
- let (subscription, activate) = self.new_view_observers.insert(
- TypeId::of::<V>(),
- Box::new(move |any_view: AnyView, cx: &mut WindowContext| {
- any_view
- .downcast::<V>()
- .unwrap()
- .update(cx, |view_state, cx| {
- on_new(view_state, cx);
- })
- }),
- );
- activate();
- subscription
- }
-
- pub fn observe_release<E, T>(
- &mut self,
- handle: &E,
- on_release: impl FnOnce(&mut T, &mut AppContext) + 'static,
- ) -> Subscription
- where
- E: Entity<T>,
- T: 'static,
- {
- let (subscription, activate) = self.release_listeners.insert(
- handle.entity_id(),
- Box::new(move |entity, cx| {
- let entity = entity.downcast_mut().expect("invalid entity type");
- on_release(entity, cx)
- }),
- );
- activate();
- subscription
- }
-
- pub fn observe_keystrokes(
- &mut self,
- f: impl FnMut(&KeystrokeEvent, &mut WindowContext) + 'static,
- ) -> Subscription {
- let (subscription, activate) = self.keystroke_observers.insert((), Box::new(f));
- activate();
- subscription
- }
-
- pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) {
- self.text_style_stack.push(text_style);
- }
-
- pub(crate) fn pop_text_style(&mut self) {
- self.text_style_stack.pop();
- }
-
- /// Register key bindings.
- pub fn bind_keys(&mut self, bindings: impl IntoIterator<Item = KeyBinding>) {
- self.keymap.lock().add_bindings(bindings);
- self.pending_effects.push_back(Effect::Refresh);
- }
-
- /// Register a global listener for actions invoked via the keyboard.
- pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Self) + 'static) {
- self.global_action_listeners
- .entry(TypeId::of::<A>())
- .or_default()
- .push(Rc::new(move |action, phase, cx| {
- if phase == DispatchPhase::Bubble {
- let action = action.downcast_ref().unwrap();
- listener(action, cx)
- }
- }));
- }
-
- /// Event handlers propagate events by default. Call this method to stop dispatching to
- /// event handlers with a lower z-index (mouse) or higher in the tree (keyboard). This is
- /// the opposite of [propagate]. It's also possible to cancel a call to [propagate] by
- /// calling this method before effects are flushed.
- pub fn stop_propagation(&mut self) {
- self.propagate_event = false;
- }
-
- /// Action handlers stop propagation by default during the bubble phase of action dispatch
- /// dispatching to action handlers higher in the element tree. This is the opposite of
- /// [stop_propagation]. It's also possible to cancel a call to [stop_propagate] by calling
- /// this method before effects are flushed.
- pub fn propagate(&mut self) {
- self.propagate_event = true;
- }
-
- pub fn build_action(
- &self,
- name: &str,
- data: Option<serde_json::Value>,
- ) -> Result<Box<dyn Action>> {
- self.actions.build_action(name, data)
- }
-
- pub fn all_action_names(&self) -> &[SharedString] {
- self.actions.all_action_names()
- }
-
- pub fn on_app_quit<Fut>(
- &mut self,
- mut on_quit: impl FnMut(&mut AppContext) -> Fut + 'static,
- ) -> Subscription
- where
- Fut: 'static + Future<Output = ()>,
- {
- let (subscription, activate) = self.quit_observers.insert(
- (),
- Box::new(move |cx| {
- let future = on_quit(cx);
- future.boxed_local()
- }),
- );
- activate();
- subscription
- }
-
- pub(crate) fn clear_pending_keystrokes(&mut self) {
- for window in self.windows() {
- window
- .update(self, |_, cx| {
- cx.window
- .rendered_frame
- .dispatch_tree
- .clear_pending_keystrokes();
- cx.window
- .next_frame
- .dispatch_tree
- .clear_pending_keystrokes();
- })
- .ok();
- }
- }
-
- pub fn is_action_available(&mut self, action: &dyn Action) -> bool {
- if let Some(window) = self.active_window() {
- if let Ok(window_action_available) =
- window.update(self, |_, cx| cx.is_action_available(action))
- {
- return window_action_available;
- }
- }
-
- self.global_action_listeners
- .contains_key(&action.as_any().type_id())
- }
-
- pub fn set_menus(&mut self, menus: Vec<Menu>) {
- self.platform.set_menus(menus, &self.keymap.lock());
- }
-
- pub fn dispatch_action(&mut self, action: &dyn Action) {
- if let Some(active_window) = self.active_window() {
- active_window
- .update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
- .log_err();
- } else {
- self.propagate_event = true;
-
- if let Some(mut global_listeners) = self
- .global_action_listeners
- .remove(&action.as_any().type_id())
- {
- for listener in &global_listeners {
- listener(action.as_any(), DispatchPhase::Capture, self);
- if !self.propagate_event {
- break;
- }
- }
-
- global_listeners.extend(
- self.global_action_listeners
- .remove(&action.as_any().type_id())
- .unwrap_or_default(),
- );
-
- self.global_action_listeners
- .insert(action.as_any().type_id(), global_listeners);
- }
-
- if self.propagate_event {
- if let Some(mut global_listeners) = self
- .global_action_listeners
- .remove(&action.as_any().type_id())
- {
- for listener in global_listeners.iter().rev() {
- listener(action.as_any(), DispatchPhase::Bubble, self);
- if !self.propagate_event {
- break;
- }
- }
-
- global_listeners.extend(
- self.global_action_listeners
- .remove(&action.as_any().type_id())
- .unwrap_or_default(),
- );
-
- self.global_action_listeners
- .insert(action.as_any().type_id(), global_listeners);
- }
- }
- }
- }
-
- pub fn has_active_drag(&self) -> bool {
- self.active_drag.is_some()
- }
-
- pub fn active_drag<T: 'static>(&self) -> Option<&T> {
- self.active_drag
- .as_ref()
- .and_then(|drag| drag.value.downcast_ref())
- }
-}
-
-impl Context for AppContext {
- type Result<T> = T;
-
- /// Build an entity that is owned by the application. The given function will be invoked with
- /// a `ModelContext` and must return an object representing the entity. A `Model` will be returned
- /// which can be used to access the entity in a context.
- fn new_model<T: 'static>(
- &mut self,
- build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
- ) -> Model<T> {
- self.update(|cx| {
- let slot = cx.entities.reserve();
- let entity = build_model(&mut ModelContext::new(cx, slot.downgrade()));
- cx.entities.insert(slot, entity)
- })
- }
-
- /// Update the entity referenced by the given model. The function is passed a mutable reference to the
- /// entity along with a `ModelContext` for the entity.
- fn update_model<T: 'static, R>(
- &mut self,
- model: &Model<T>,
- update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
- ) -> R {
- self.update(|cx| {
- let mut entity = cx.entities.lease(model);
- let result = update(&mut entity, &mut ModelContext::new(cx, model.downgrade()));
- cx.entities.end_lease(entity);
- result
- })
- }
-
- fn update_window<T, F>(&mut self, handle: AnyWindowHandle, update: F) -> Result<T>
- where
- F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
- {
- self.update(|cx| {
- let mut window = cx
- .windows
- .get_mut(handle.id)
- .ok_or_else(|| anyhow!("window not found"))?
- .take()
- .unwrap();
-
- let root_view = window.root_view.clone().unwrap();
- let result = update(root_view, &mut WindowContext::new(cx, &mut window));
-
- if window.removed {
- cx.windows.remove(handle.id);
- } else {
- cx.windows
- .get_mut(handle.id)
- .ok_or_else(|| anyhow!("window not found"))?
- .replace(window);
- }
-
- Ok(result)
- })
- }
-
- fn read_model<T, R>(
- &self,
- handle: &Model<T>,
- read: impl FnOnce(&T, &AppContext) -> R,
- ) -> Self::Result<R>
- where
- T: 'static,
- {
- let entity = self.entities.read(handle);
- read(entity, self)
- }
-
- fn read_window<T, R>(
- &self,
- window: &WindowHandle<T>,
- read: impl FnOnce(View<T>, &AppContext) -> R,
- ) -> Result<R>
- where
- T: 'static,
- {
- let window = self
- .windows
- .get(window.id)
- .ok_or_else(|| anyhow!("window not found"))?
- .as_ref()
- .unwrap();
-
- let root_view = window.root_view.clone().unwrap();
- let view = root_view
- .downcast::<T>()
- .map_err(|_| anyhow!("root view's type has changed"))?;
-
- Ok(read(view, self))
- }
-}
-
-/// These effects are processed at the end of each application update cycle.
-pub(crate) enum Effect {
- Notify {
- emitter: EntityId,
- },
- Emit {
- emitter: EntityId,
- event_type: TypeId,
- event: Box<dyn Any>,
- },
- Refresh,
- NotifyGlobalObservers {
- global_type: TypeId,
- },
- Defer {
- callback: Box<dyn FnOnce(&mut AppContext) + 'static>,
- },
-}
-
-/// Wraps a global variable value during `update_global` while the value has been moved to the stack.
-pub(crate) struct GlobalLease<G: 'static> {
- global: Box<dyn Any>,
- global_type: PhantomData<G>,
-}
-
-impl<G: 'static> GlobalLease<G> {
- fn new(global: Box<dyn Any>) -> Self {
- GlobalLease {
- global,
- global_type: PhantomData,
- }
- }
-}
-
-impl<G: 'static> Deref for GlobalLease<G> {
- type Target = G;
-
- fn deref(&self) -> &Self::Target {
- self.global.downcast_ref().unwrap()
- }
-}
-
-impl<G: 'static> DerefMut for GlobalLease<G> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- self.global.downcast_mut().unwrap()
- }
-}
-
-/// Contains state associated with an active drag operation, started by dragging an element
-/// within the window or by dragging into the app from the underlying platform.
-pub struct AnyDrag {
- pub view: AnyView,
- pub value: Box<dyn Any>,
- pub cursor_offset: Point<Pixels>,
-}
-
-#[derive(Clone)]
-pub(crate) struct AnyTooltip {
- pub view: AnyView,
- pub cursor_offset: Point<Pixels>,
-}
-
-#[derive(Debug)]
-pub struct KeystrokeEvent {
- pub keystroke: Keystroke,
- pub action: Option<Box<dyn Action>>,
-}
@@ -1,64 +0,0 @@
-use crate::{size, DevicePixels, Result, SharedString, Size};
-use anyhow::anyhow;
-use image::{Bgra, ImageBuffer};
-use std::{
- borrow::Cow,
- fmt,
- hash::Hash,
- sync::atomic::{AtomicUsize, Ordering::SeqCst},
-};
-
-pub trait AssetSource: 'static + Send + Sync {
- fn load(&self, path: &str) -> Result<Cow<[u8]>>;
- fn list(&self, path: &str) -> Result<Vec<SharedString>>;
-}
-
-impl AssetSource for () {
- fn load(&self, path: &str) -> Result<Cow<[u8]>> {
- Err(anyhow!(
- "get called on empty asset provider with \"{}\"",
- path
- ))
- }
-
- fn list(&self, _path: &str) -> Result<Vec<SharedString>> {
- Ok(vec![])
- }
-}
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
-pub struct ImageId(usize);
-
-pub struct ImageData {
- pub id: ImageId,
- data: ImageBuffer<Bgra<u8>, Vec<u8>>,
-}
-
-impl ImageData {
- pub fn new(data: ImageBuffer<Bgra<u8>, Vec<u8>>) -> Self {
- static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
-
- Self {
- id: ImageId(NEXT_ID.fetch_add(1, SeqCst)),
- data,
- }
- }
-
- pub fn as_bytes(&self) -> &[u8] {
- &self.data
- }
-
- pub fn size(&self) -> Size<DevicePixels> {
- let (width, height) = self.data.dimensions();
- size(width.into(), height.into())
- }
-}
-
-impl fmt::Debug for ImageData {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_struct("ImageData")
- .field("id", &self.id)
- .field("size", &self.data.dimensions())
- .finish()
- }
-}
@@ -1,457 +0,0 @@
-use anyhow::bail;
-use serde::de::{self, Deserialize, Deserializer, Visitor};
-use std::fmt;
-
-pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
- let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
- let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
- let b = (hex & 0xFF) as f32 / 255.0;
- Rgba { r, g, b, a: 1.0 }.into()
-}
-
-pub fn rgba(hex: u32) -> Rgba {
- let r = ((hex >> 24) & 0xFF) as f32 / 255.0;
- let g = ((hex >> 16) & 0xFF) as f32 / 255.0;
- let b = ((hex >> 8) & 0xFF) as f32 / 255.0;
- let a = (hex & 0xFF) as f32 / 255.0;
- Rgba { r, g, b, a }
-}
-
-#[derive(PartialEq, Clone, Copy, Default)]
-pub struct Rgba {
- pub r: f32,
- pub g: f32,
- pub b: f32,
- pub a: f32,
-}
-
-impl fmt::Debug for Rgba {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "rgba({:#010x})", u32::from(*self))
- }
-}
-
-impl Rgba {
- pub fn blend(&self, other: Rgba) -> Self {
- if other.a >= 1.0 {
- other
- } else if other.a <= 0.0 {
- return *self;
- } else {
- return Rgba {
- r: (self.r * (1.0 - other.a)) + (other.r * other.a),
- g: (self.g * (1.0 - other.a)) + (other.g * other.a),
- b: (self.b * (1.0 - other.a)) + (other.b * other.a),
- a: self.a,
- };
- }
- }
-}
-
-impl From<Rgba> for u32 {
- fn from(rgba: Rgba) -> Self {
- let r = (rgba.r * 255.0) as u32;
- let g = (rgba.g * 255.0) as u32;
- let b = (rgba.b * 255.0) as u32;
- let a = (rgba.a * 255.0) as u32;
- (r << 24) | (g << 16) | (b << 8) | a
- }
-}
-
-struct RgbaVisitor;
-
-impl<'de> Visitor<'de> for RgbaVisitor {
- type Value = Rgba;
-
- fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
- formatter.write_str("a string in the format #rrggbb or #rrggbbaa")
- }
-
- fn visit_str<E: de::Error>(self, value: &str) -> Result<Rgba, E> {
- Rgba::try_from(value).map_err(E::custom)
- }
-}
-
-impl<'de> Deserialize<'de> for Rgba {
- fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
- deserializer.deserialize_str(RgbaVisitor)
- }
-}
-
-impl From<Hsla> for Rgba {
- fn from(color: Hsla) -> Self {
- let h = color.h;
- let s = color.s;
- let l = color.l;
-
- let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
- let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
- let m = l - c / 2.0;
- let cm = c + m;
- let xm = x + m;
-
- let (r, g, b) = match (h * 6.0).floor() as i32 {
- 0 | 6 => (cm, xm, m),
- 1 => (xm, cm, m),
- 2 => (m, cm, xm),
- 3 => (m, xm, cm),
- 4 => (xm, m, cm),
- _ => (cm, m, xm),
- };
-
- Rgba {
- r,
- g,
- b,
- a: color.a,
- }
- }
-}
-
-impl TryFrom<&'_ str> for Rgba {
- type Error = anyhow::Error;
-
- fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
- const RGB: usize = "rgb".len();
- const RGBA: usize = "rgba".len();
- const RRGGBB: usize = "rrggbb".len();
- const RRGGBBAA: usize = "rrggbbaa".len();
-
- const EXPECTED_FORMATS: &str = "Expected #rgb, #rgba, #rrggbb, or #rrggbbaa";
-
- let Some(("", hex)) = value.trim().split_once('#') else {
- bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}");
- };
-
- let (r, g, b, a) = match hex.len() {
- RGB | RGBA => {
- let r = u8::from_str_radix(&hex[0..1], 16)?;
- let g = u8::from_str_radix(&hex[1..2], 16)?;
- let b = u8::from_str_radix(&hex[2..3], 16)?;
- let a = if hex.len() == RGBA {
- u8::from_str_radix(&hex[3..4], 16)?
- } else {
- 0xf
- };
-
- /// Duplicates a given hex digit.
- /// E.g., `0xf` -> `0xff`.
- const fn duplicate(value: u8) -> u8 {
- value << 4 | value
- }
-
- (duplicate(r), duplicate(g), duplicate(b), duplicate(a))
- }
- RRGGBB | RRGGBBAA => {
- let r = u8::from_str_radix(&hex[0..2], 16)?;
- let g = u8::from_str_radix(&hex[2..4], 16)?;
- let b = u8::from_str_radix(&hex[4..6], 16)?;
- let a = if hex.len() == RRGGBBAA {
- u8::from_str_radix(&hex[6..8], 16)?
- } else {
- 0xff
- };
- (r, g, b, a)
- }
- _ => bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}"),
- };
-
- Ok(Rgba {
- r: r as f32 / 255.,
- g: g as f32 / 255.,
- b: b as f32 / 255.,
- a: a as f32 / 255.,
- })
- }
-}
-
-#[derive(Default, Copy, Clone, Debug)]
-#[repr(C)]
-pub struct Hsla {
- pub h: f32,
- pub s: f32,
- pub l: f32,
- pub a: f32,
-}
-
-impl PartialEq for Hsla {
- fn eq(&self, other: &Self) -> bool {
- self.h
- .total_cmp(&other.h)
- .then(self.s.total_cmp(&other.s))
- .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a)))
- .is_eq()
- }
-}
-
-impl PartialOrd for Hsla {
- fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
- // SAFETY: The total ordering relies on this always being Some()
- Some(
- self.h
- .total_cmp(&other.h)
- .then(self.s.total_cmp(&other.s))
- .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a))),
- )
- }
-}
-
-impl Ord for Hsla {
- fn cmp(&self, other: &Self) -> std::cmp::Ordering {
- // SAFETY: The partial comparison is a total comparison
- unsafe { self.partial_cmp(other).unwrap_unchecked() }
- }
-}
-
-impl Hsla {
- pub fn to_rgb(self) -> Rgba {
- self.into()
- }
-
- pub fn red() -> Self {
- red()
- }
-
- pub fn green() -> Self {
- green()
- }
-
- pub fn blue() -> Self {
- blue()
- }
-
- pub fn black() -> Self {
- black()
- }
-
- pub fn white() -> Self {
- white()
- }
-
- pub fn transparent_black() -> Self {
- transparent_black()
- }
-}
-
-impl Eq for Hsla {}
-
-pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
- Hsla {
- h: h.clamp(0., 1.),
- s: s.clamp(0., 1.),
- l: l.clamp(0., 1.),
- a: a.clamp(0., 1.),
- }
-}
-
-pub fn black() -> Hsla {
- Hsla {
- h: 0.,
- s: 0.,
- l: 0.,
- a: 1.,
- }
-}
-
-pub fn transparent_black() -> Hsla {
- Hsla {
- h: 0.,
- s: 0.,
- l: 0.,
- a: 0.,
- }
-}
-
-pub fn white() -> Hsla {
- Hsla {
- h: 0.,
- s: 0.,
- l: 1.,
- a: 1.,
- }
-}
-
-pub fn red() -> Hsla {
- Hsla {
- h: 0.,
- s: 1.,
- l: 0.5,
- a: 1.,
- }
-}
-
-pub fn blue() -> Hsla {
- Hsla {
- h: 0.6,
- s: 1.,
- l: 0.5,
- a: 1.,
- }
-}
-
-pub fn green() -> Hsla {
- Hsla {
- h: 0.33,
- s: 1.,
- l: 0.5,
- a: 1.,
- }
-}
-
-pub fn yellow() -> Hsla {
- Hsla {
- h: 0.16,
- s: 1.,
- l: 0.5,
- a: 1.,
- }
-}
-
-impl Hsla {
- /// Returns true if the HSLA color is fully transparent, false otherwise.
- pub fn is_transparent(&self) -> bool {
- self.a == 0.0
- }
-
- /// Blends `other` on top of `self` based on `other`'s alpha value. The resulting color is a combination of `self`'s and `other`'s colors.
- ///
- /// If `other`'s alpha value is 1.0 or greater, `other` color is fully opaque, thus `other` is returned as the output color.
- /// If `other`'s alpha value is 0.0 or less, `other` color is fully transparent, thus `self` is returned as the output color.
- /// Else, the output color is calculated as a blend of `self` and `other` based on their weighted alpha values.
- ///
- /// Assumptions:
- /// - Alpha values are contained in the range [0, 1], with 1 as fully opaque and 0 as fully transparent.
- /// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing it's own alpha value.
- /// - RGB color components are contained in the range [0, 1].
- /// - If `self` and `other` colors are out of the valid range, the blend operation's output and behavior is undefined.
- pub fn blend(self, other: Hsla) -> Hsla {
- let alpha = other.a;
-
- if alpha >= 1.0 {
- other
- } else if alpha <= 0.0 {
- return self;
- } else {
- let converted_self = Rgba::from(self);
- let converted_other = Rgba::from(other);
- let blended_rgb = converted_self.blend(converted_other);
- return Hsla::from(blended_rgb);
- }
- }
-
- /// Fade out the color by a given factor. This factor should be between 0.0 and 1.0.
- /// Where 0.0 will leave the color unchanged, and 1.0 will completely fade out the color.
- pub fn fade_out(&mut self, factor: f32) {
- self.a *= 1.0 - factor.clamp(0., 1.);
- }
-}
-
-// impl From<Hsla> for Rgba {
-// fn from(value: Hsla) -> Self {
-// let h = value.h;
-// let s = value.s;
-// let l = value.l;
-
-// let c = (1 - |2L - 1|) X s
-// }
-// }
-
-impl From<Rgba> for Hsla {
- fn from(color: Rgba) -> Self {
- let r = color.r;
- let g = color.g;
- let b = color.b;
-
- let max = r.max(g.max(b));
- let min = r.min(g.min(b));
- let delta = max - min;
-
- let l = (max + min) / 2.0;
- let s = if l == 0.0 || l == 1.0 {
- 0.0
- } else if l < 0.5 {
- delta / (2.0 * l)
- } else {
- delta / (2.0 - 2.0 * l)
- };
-
- let h = if delta == 0.0 {
- 0.0
- } else if max == r {
- ((g - b) / delta).rem_euclid(6.0) / 6.0
- } else if max == g {
- ((b - r) / delta + 2.0) / 6.0
- } else {
- ((r - g) / delta + 4.0) / 6.0
- };
-
- Hsla {
- h,
- s,
- l,
- a: color.a,
- }
- }
-}
-
-impl<'de> Deserialize<'de> for Hsla {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- // First, deserialize it into Rgba
- let rgba = Rgba::deserialize(deserializer)?;
-
- // Then, use the From<Rgba> for Hsla implementation to convert it
- Ok(Hsla::from(rgba))
- }
-}
-
-#[cfg(test)]
-mod tests {
- use serde_json::json;
-
- use super::*;
-
- #[test]
- fn test_deserialize_three_value_hex_to_rgba() {
- let actual: Rgba = serde_json::from_value(json!("#f09")).unwrap();
-
- assert_eq!(actual, rgba(0xff0099ff))
- }
-
- #[test]
- fn test_deserialize_four_value_hex_to_rgba() {
- let actual: Rgba = serde_json::from_value(json!("#f09f")).unwrap();
-
- assert_eq!(actual, rgba(0xff0099ff))
- }
-
- #[test]
- fn test_deserialize_six_value_hex_to_rgba() {
- let actual: Rgba = serde_json::from_value(json!("#ff0099")).unwrap();
-
- assert_eq!(actual, rgba(0xff0099ff))
- }
-
- #[test]
- fn test_deserialize_eight_value_hex_to_rgba() {
- let actual: Rgba = serde_json::from_value(json!("#ff0099ff")).unwrap();
-
- assert_eq!(actual, rgba(0xff0099ff))
- }
-
- #[test]
- fn test_deserialize_eight_value_hex_with_padding_to_rgba() {
- let actual: Rgba = serde_json::from_value(json!(" #f5f5f5ff ")).unwrap();
-
- assert_eq!(actual, rgba(0xf5f5f5ff))
- }
-
- #[test]
- fn test_deserialize_eight_value_hex_with_mixed_case_to_rgba() {
- let actual: Rgba = serde_json::from_value(json!("#DeAdbEeF")).unwrap();
-
- assert_eq!(actual, rgba(0xdeadbeef))
- }
-}
@@ -1,54 +0,0 @@
-use refineable::Refineable as _;
-
-use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled, WindowContext};
-
-pub fn canvas(callback: impl 'static + FnOnce(&Bounds<Pixels>, &mut WindowContext)) -> Canvas {
- Canvas {
- paint_callback: Some(Box::new(callback)),
- style: StyleRefinement::default(),
- }
-}
-
-pub struct Canvas {
- paint_callback: Option<Box<dyn FnOnce(&Bounds<Pixels>, &mut WindowContext)>>,
- style: StyleRefinement,
-}
-
-impl IntoElement for Canvas {
- type Element = Self;
-
- fn element_id(&self) -> Option<crate::ElementId> {
- None
- }
-
- fn into_element(self) -> Self::Element {
- self
- }
-}
-
-impl Element for Canvas {
- type State = Style;
-
- fn request_layout(
- &mut self,
- _: Option<Self::State>,
- cx: &mut WindowContext,
- ) -> (crate::LayoutId, Self::State) {
- let mut style = Style::default();
- style.refine(&self.style);
- let layout_id = cx.request_layout(&style, []);
- (layout_id, style)
- }
-
- fn paint(&mut self, bounds: Bounds<Pixels>, style: &mut Style, cx: &mut WindowContext) {
- style.paint(bounds, cx, |cx| {
- (self.paint_callback.take().unwrap())(&bounds, cx)
- });
- }
-}
-
-impl Styled for Canvas {
- fn style(&mut self) -> &mut crate::StyleRefinement {
- &mut self.style
- }
-}
@@ -1,560 +0,0 @@
-use crate::{
- point, px, AnyElement, AvailableSpace, BorrowAppContext, Bounds, DispatchPhase, Element,
- IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled,
- WindowContext,
-};
-use collections::VecDeque;
-use refineable::Refineable as _;
-use std::{cell::RefCell, ops::Range, rc::Rc};
-use sum_tree::{Bias, SumTree};
-
-pub fn list(state: ListState) -> List {
- List {
- state,
- style: StyleRefinement::default(),
- }
-}
-
-pub struct List {
- state: ListState,
- style: StyleRefinement,
-}
-
-#[derive(Clone)]
-pub struct ListState(Rc<RefCell<StateInner>>);
-
-struct StateInner {
- last_layout_bounds: Option<Bounds<Pixels>>,
- render_item: Box<dyn FnMut(usize, &mut WindowContext) -> AnyElement>,
- items: SumTree<ListItem>,
- logical_scroll_top: Option<ListOffset>,
- alignment: ListAlignment,
- overdraw: Pixels,
- #[allow(clippy::type_complexity)]
- scroll_handler: Option<Box<dyn FnMut(&ListScrollEvent, &mut WindowContext)>>,
-}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum ListAlignment {
- Top,
- Bottom,
-}
-
-pub struct ListScrollEvent {
- pub visible_range: Range<usize>,
- pub count: usize,
-}
-
-#[derive(Clone)]
-enum ListItem {
- Unrendered,
- Rendered { height: Pixels },
-}
-
-#[derive(Clone, Debug, Default, PartialEq)]
-struct ListItemSummary {
- count: usize,
- rendered_count: usize,
- unrendered_count: usize,
- height: Pixels,
-}
-
-#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
-struct Count(usize);
-
-#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
-struct RenderedCount(usize);
-
-#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
-struct UnrenderedCount(usize);
-
-#[derive(Clone, Debug, Default)]
-struct Height(Pixels);
-
-impl ListState {
- pub fn new<F>(
- element_count: usize,
- orientation: ListAlignment,
- overdraw: Pixels,
- render_item: F,
- ) -> Self
- where
- F: 'static + FnMut(usize, &mut WindowContext) -> AnyElement,
- {
- let mut items = SumTree::new();
- items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
- Self(Rc::new(RefCell::new(StateInner {
- last_layout_bounds: None,
- render_item: Box::new(render_item),
- items,
- logical_scroll_top: None,
- alignment: orientation,
- overdraw,
- scroll_handler: None,
- })))
- }
-
- pub fn reset(&self, element_count: usize) {
- let state = &mut *self.0.borrow_mut();
- state.logical_scroll_top = None;
- state.items = SumTree::new();
- state
- .items
- .extend((0..element_count).map(|_| ListItem::Unrendered), &());
- }
-
- pub fn item_count(&self) -> usize {
- self.0.borrow().items.summary().count
- }
-
- pub fn splice(&self, old_range: Range<usize>, count: usize) {
- let state = &mut *self.0.borrow_mut();
-
- if let Some(ListOffset {
- item_ix,
- offset_in_item,
- }) = state.logical_scroll_top.as_mut()
- {
- if old_range.contains(item_ix) {
- *item_ix = old_range.start;
- *offset_in_item = px(0.);
- } else if old_range.end <= *item_ix {
- *item_ix = *item_ix - (old_range.end - old_range.start) + count;
- }
- }
-
- let mut old_heights = state.items.cursor::<Count>();
- let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &());
- old_heights.seek_forward(&Count(old_range.end), Bias::Right, &());
-
- new_heights.extend((0..count).map(|_| ListItem::Unrendered), &());
- new_heights.append(old_heights.suffix(&()), &());
- drop(old_heights);
- state.items = new_heights;
- }
-
- pub fn set_scroll_handler(
- &self,
- handler: impl FnMut(&ListScrollEvent, &mut WindowContext) + 'static,
- ) {
- self.0.borrow_mut().scroll_handler = Some(Box::new(handler))
- }
-
- pub fn logical_scroll_top(&self) -> ListOffset {
- self.0.borrow().logical_scroll_top()
- }
-
- pub fn scroll_to(&self, mut scroll_top: ListOffset) {
- let state = &mut *self.0.borrow_mut();
- let item_count = state.items.summary().count;
- if scroll_top.item_ix >= item_count {
- scroll_top.item_ix = item_count;
- scroll_top.offset_in_item = px(0.);
- }
- state.logical_scroll_top = Some(scroll_top);
- }
-
- pub fn scroll_to_reveal_item(&self, ix: usize) {
- let state = &mut *self.0.borrow_mut();
- let mut scroll_top = state.logical_scroll_top();
- let height = state
- .last_layout_bounds
- .map_or(px(0.), |bounds| bounds.size.height);
-
- if ix <= scroll_top.item_ix {
- scroll_top.item_ix = ix;
- scroll_top.offset_in_item = px(0.);
- } else {
- let mut cursor = state.items.cursor::<ListItemSummary>();
- cursor.seek(&Count(ix + 1), Bias::Right, &());
- let bottom = cursor.start().height;
- let goal_top = px(0.).max(bottom - height);
-
- cursor.seek(&Height(goal_top), Bias::Left, &());
- let start_ix = cursor.start().count;
- let start_item_top = cursor.start().height;
-
- if start_ix >= scroll_top.item_ix {
- scroll_top.item_ix = start_ix;
- scroll_top.offset_in_item = goal_top - start_item_top;
- }
- }
-
- state.logical_scroll_top = Some(scroll_top);
- }
-
- /// Get the bounds for the given item in window coordinates.
- pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
- let state = &*self.0.borrow();
- let bounds = state.last_layout_bounds.unwrap_or_default();
- let scroll_top = state.logical_scroll_top();
-
- if ix < scroll_top.item_ix {
- return None;
- }
-
- let mut cursor = state.items.cursor::<(Count, Height)>();
- cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
-
- let scroll_top = cursor.start().1 .0 + scroll_top.offset_in_item;
-
- cursor.seek_forward(&Count(ix), Bias::Right, &());
- if let Some(&ListItem::Rendered { height }) = cursor.item() {
- let &(Count(count), Height(top)) = cursor.start();
- if count == ix {
- let top = bounds.top() + top - scroll_top;
- return Some(Bounds::from_corners(
- point(bounds.left(), top),
- point(bounds.right(), top + height),
- ));
- }
- }
- None
- }
-}
-
-impl StateInner {
- fn visible_range(&self, height: Pixels, scroll_top: &ListOffset) -> Range<usize> {
- let mut cursor = self.items.cursor::<ListItemSummary>();
- cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
- let start_y = cursor.start().height + scroll_top.offset_in_item;
- cursor.seek_forward(&Height(start_y + height), Bias::Left, &());
- scroll_top.item_ix..cursor.start().count + 1
- }
-
- fn scroll(
- &mut self,
- scroll_top: &ListOffset,
- height: Pixels,
- delta: Point<Pixels>,
- cx: &mut WindowContext,
- ) {
- let scroll_max = (self.items.summary().height - height).max(px(0.));
- let new_scroll_top = (self.scroll_top(scroll_top) - delta.y)
- .max(px(0.))
- .min(scroll_max);
-
- if self.alignment == ListAlignment::Bottom && new_scroll_top == scroll_max {
- self.logical_scroll_top = None;
- } else {
- let mut cursor = self.items.cursor::<ListItemSummary>();
- cursor.seek(&Height(new_scroll_top), Bias::Right, &());
- let item_ix = cursor.start().count;
- let offset_in_item = new_scroll_top - cursor.start().height;
- self.logical_scroll_top = Some(ListOffset {
- item_ix,
- offset_in_item,
- });
- }
-
- if self.scroll_handler.is_some() {
- let visible_range = self.visible_range(height, scroll_top);
- self.scroll_handler.as_mut().unwrap()(
- &ListScrollEvent {
- visible_range,
- count: self.items.summary().count,
- },
- cx,
- );
- }
-
- cx.notify();
- }
-
- fn logical_scroll_top(&self) -> ListOffset {
- self.logical_scroll_top
- .unwrap_or_else(|| match self.alignment {
- ListAlignment::Top => ListOffset {
- item_ix: 0,
- offset_in_item: px(0.),
- },
- ListAlignment::Bottom => ListOffset {
- item_ix: self.items.summary().count,
- offset_in_item: px(0.),
- },
- })
- }
-
- fn scroll_top(&self, logical_scroll_top: &ListOffset) -> Pixels {
- let mut cursor = self.items.cursor::<ListItemSummary>();
- cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right, &());
- cursor.start().height + logical_scroll_top.offset_in_item
- }
-}
-
-impl std::fmt::Debug for ListItem {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Self::Unrendered => write!(f, "Unrendered"),
- Self::Rendered { height, .. } => {
- f.debug_struct("Rendered").field("height", height).finish()
- }
- }
- }
-}
-
-#[derive(Debug, Clone, Copy, Default)]
-pub struct ListOffset {
- pub item_ix: usize,
- pub offset_in_item: Pixels,
-}
-
-impl Element for List {
- type State = ();
-
- fn request_layout(
- &mut self,
- _state: Option<Self::State>,
- cx: &mut crate::WindowContext,
- ) -> (crate::LayoutId, Self::State) {
- let mut style = Style::default();
- style.refine(&self.style);
- let layout_id = cx.with_text_style(style.text_style().cloned(), |cx| {
- cx.request_layout(&style, None)
- });
- (layout_id, ())
- }
-
- fn paint(
- &mut self,
- bounds: crate::Bounds<crate::Pixels>,
- _state: &mut Self::State,
- cx: &mut crate::WindowContext,
- ) {
- let state = &mut *self.state.0.borrow_mut();
-
- // If the width of the list has changed, invalidate all cached item heights
- if state.last_layout_bounds.map_or(true, |last_bounds| {
- last_bounds.size.width != bounds.size.width
- }) {
- state.items = SumTree::from_iter(
- (0..state.items.summary().count).map(|_| ListItem::Unrendered),
- &(),
- )
- }
-
- let old_items = state.items.clone();
- let mut measured_items = VecDeque::new();
- let mut item_elements = VecDeque::new();
- let mut rendered_height = px(0.);
- let mut scroll_top = state.logical_scroll_top();
-
- let available_item_space = Size {
- width: AvailableSpace::Definite(bounds.size.width),
- height: AvailableSpace::MinContent,
- };
-
- // Render items after the scroll top, including those in the trailing overdraw
- let mut cursor = old_items.cursor::<Count>();
- cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
- for (ix, item) in cursor.by_ref().enumerate() {
- let visible_height = rendered_height - scroll_top.offset_in_item;
- if visible_height >= bounds.size.height + state.overdraw {
- break;
- }
-
- // Use the previously cached height if available
- let mut height = if let ListItem::Rendered { height } = item {
- Some(*height)
- } else {
- None
- };
-
- // If we're within the visible area or the height wasn't cached, render and measure the item's element
- if visible_height < bounds.size.height || height.is_none() {
- let mut element = (state.render_item)(scroll_top.item_ix + ix, cx);
- let element_size = element.measure(available_item_space, cx);
- height = Some(element_size.height);
- if visible_height < bounds.size.height {
- item_elements.push_back(element);
- }
- }
-
- let height = height.unwrap();
- rendered_height += height;
- measured_items.push_back(ListItem::Rendered { height });
- }
-
- // Prepare to start walking upward from the item at the scroll top.
- cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
-
- // If the rendered items do not fill the visible region, then adjust
- // the scroll top upward.
- if rendered_height - scroll_top.offset_in_item < bounds.size.height {
- while rendered_height < bounds.size.height {
- cursor.prev(&());
- if cursor.item().is_some() {
- let mut element = (state.render_item)(cursor.start().0, cx);
- let element_size = element.measure(available_item_space, cx);
-
- rendered_height += element_size.height;
- measured_items.push_front(ListItem::Rendered {
- height: element_size.height,
- });
- item_elements.push_front(element)
- } else {
- break;
- }
- }
-
- scroll_top = ListOffset {
- item_ix: cursor.start().0,
- offset_in_item: rendered_height - bounds.size.height,
- };
-
- match state.alignment {
- ListAlignment::Top => {
- scroll_top.offset_in_item = scroll_top.offset_in_item.max(px(0.));
- state.logical_scroll_top = Some(scroll_top);
- }
- ListAlignment::Bottom => {
- scroll_top = ListOffset {
- item_ix: cursor.start().0,
- offset_in_item: rendered_height - bounds.size.height,
- };
- state.logical_scroll_top = None;
- }
- };
- }
-
- // Measure items in the leading overdraw
- let mut leading_overdraw = scroll_top.offset_in_item;
- while leading_overdraw < state.overdraw {
- cursor.prev(&());
- if let Some(item) = cursor.item() {
- let height = if let ListItem::Rendered { height } = item {
- *height
- } else {
- let mut element = (state.render_item)(cursor.start().0, cx);
- element.measure(available_item_space, cx).height
- };
-
- leading_overdraw += height;
- measured_items.push_front(ListItem::Rendered { height });
- } else {
- break;
- }
- }
-
- let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len());
- let mut cursor = old_items.cursor::<Count>();
- let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &());
- new_items.extend(measured_items, &());
- cursor.seek(&Count(measured_range.end), Bias::Right, &());
- new_items.append(cursor.suffix(&()), &());
-
- // Paint the visible items
- let mut item_origin = bounds.origin;
- item_origin.y -= scroll_top.offset_in_item;
- for item_element in &mut item_elements {
- let item_height = item_element.measure(available_item_space, cx).height;
- item_element.draw(item_origin, available_item_space, cx);
- item_origin.y += item_height;
- }
-
- state.items = new_items;
- state.last_layout_bounds = Some(bounds);
-
- let list_state = self.state.clone();
- let height = bounds.size.height;
- cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
- if phase == DispatchPhase::Bubble
- && bounds.contains(&event.position)
- && cx.was_top_layer(&event.position, cx.stacking_order())
- {
- list_state.0.borrow_mut().scroll(
- &scroll_top,
- height,
- event.delta.pixel_delta(px(20.)),
- cx,
- )
- }
- });
- }
-}
-
-impl IntoElement for List {
- type Element = Self;
-
- fn element_id(&self) -> Option<crate::ElementId> {
- None
- }
-
- fn into_element(self) -> Self::Element {
- self
- }
-}
-
-impl Styled for List {
- fn style(&mut self) -> &mut StyleRefinement {
- &mut self.style
- }
-}
-
-impl sum_tree::Item for ListItem {
- type Summary = ListItemSummary;
-
- fn summary(&self) -> Self::Summary {
- match self {
- ListItem::Unrendered => ListItemSummary {
- count: 1,
- rendered_count: 0,
- unrendered_count: 1,
- height: px(0.),
- },
- ListItem::Rendered { height } => ListItemSummary {
- count: 1,
- rendered_count: 1,
- unrendered_count: 0,
- height: *height,
- },
- }
- }
-}
-
-impl sum_tree::Summary for ListItemSummary {
- type Context = ();
-
- fn add_summary(&mut self, summary: &Self, _: &()) {
- self.count += summary.count;
- self.rendered_count += summary.rendered_count;
- self.unrendered_count += summary.unrendered_count;
- self.height += summary.height;
- }
-}
-
-impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Count {
- fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
- self.0 += summary.count;
- }
-}
-
-impl<'a> sum_tree::Dimension<'a, ListItemSummary> for RenderedCount {
- fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
- self.0 += summary.rendered_count;
- }
-}
-
-impl<'a> sum_tree::Dimension<'a, ListItemSummary> for UnrenderedCount {
- fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
- self.0 += summary.unrendered_count;
- }
-}
-
-impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Height {
- fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
- self.0 += summary.height;
- }
-}
-
-impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Count {
- fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering {
- self.0.partial_cmp(&other.count).unwrap()
- }
-}
-
-impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
- fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering {
- self.0.partial_cmp(&other.height).unwrap()
- }
-}
@@ -1,241 +0,0 @@
-use smallvec::SmallVec;
-use taffy::style::{Display, Position};
-
-use crate::{
- point, AnyElement, BorrowWindow, Bounds, Element, IntoElement, LayoutId, ParentElement, Pixels,
- Point, Size, Style, WindowContext,
-};
-
-pub struct OverlayState {
- child_layout_ids: SmallVec<[LayoutId; 4]>,
-}
-
-pub struct Overlay {
- children: SmallVec<[AnyElement; 2]>,
- anchor_corner: AnchorCorner,
- fit_mode: OverlayFitMode,
- // todo!();
- anchor_position: Option<Point<Pixels>>,
- // position_mode: OverlayPositionMode,
-}
-
-/// overlay gives you a floating element that will avoid overflowing the window bounds.
-/// Its children should have no margin to avoid measurement issues.
-pub fn overlay() -> Overlay {
- Overlay {
- children: SmallVec::new(),
- anchor_corner: AnchorCorner::TopLeft,
- fit_mode: OverlayFitMode::SwitchAnchor,
- anchor_position: None,
- }
-}
-
-impl Overlay {
- /// Sets which corner of the overlay should be anchored to the current position.
- pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
- self.anchor_corner = anchor;
- self
- }
-
- /// Sets the position in window co-ordinates
- /// (otherwise the location the overlay is rendered is used)
- pub fn position(mut self, anchor: Point<Pixels>) -> Self {
- self.anchor_position = Some(anchor);
- self
- }
-
- /// Snap to window edge instead of switching anchor corner when an overflow would occur.
- pub fn snap_to_window(mut self) -> Self {
- self.fit_mode = OverlayFitMode::SnapToWindow;
- self
- }
-}
-
-impl ParentElement for Overlay {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.children
- }
-}
-
-impl Element for Overlay {
- type State = OverlayState;
-
- fn request_layout(
- &mut self,
- _: Option<Self::State>,
- cx: &mut WindowContext,
- ) -> (crate::LayoutId, Self::State) {
- let child_layout_ids = self
- .children
- .iter_mut()
- .map(|child| child.request_layout(cx))
- .collect::<SmallVec<_>>();
-
- let overlay_style = Style {
- position: Position::Absolute,
- display: Display::Flex,
- ..Style::default()
- };
-
- let layout_id = cx.request_layout(&overlay_style, child_layout_ids.iter().copied());
-
- (layout_id, OverlayState { child_layout_ids })
- }
-
- fn paint(
- &mut self,
- bounds: crate::Bounds<crate::Pixels>,
- element_state: &mut Self::State,
- cx: &mut WindowContext,
- ) {
- if element_state.child_layout_ids.is_empty() {
- return;
- }
-
- let mut child_min = point(Pixels::MAX, Pixels::MAX);
- let mut child_max = Point::default();
- for child_layout_id in &element_state.child_layout_ids {
- let child_bounds = cx.layout_bounds(*child_layout_id);
- child_min = child_min.min(&child_bounds.origin);
- child_max = child_max.max(&child_bounds.lower_right());
- }
- let size: Size<Pixels> = (child_max - child_min).into();
- let origin = self.anchor_position.unwrap_or(bounds.origin);
-
- let mut desired = self.anchor_corner.get_bounds(origin, size);
- let limits = Bounds {
- origin: Point::default(),
- size: cx.viewport_size(),
- };
-
- if self.fit_mode == OverlayFitMode::SwitchAnchor {
- let mut anchor_corner = self.anchor_corner;
-
- if desired.left() < limits.left() || desired.right() > limits.right() {
- let switched = anchor_corner
- .switch_axis(Axis::Horizontal)
- .get_bounds(origin, size);
- if !(switched.left() < limits.left() || switched.right() > limits.right()) {
- anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
- desired = switched
- }
- }
-
- if desired.top() < limits.top() || desired.bottom() > limits.bottom() {
- let switched = anchor_corner
- .switch_axis(Axis::Vertical)
- .get_bounds(origin, size);
- if !(switched.top() < limits.top() || switched.bottom() > limits.bottom()) {
- desired = switched;
- }
- }
- }
-
- // Snap the horizontal edges of the overlay to the horizontal edges of the window if
- // its horizontal bounds overflow, aligning to the left if it is wider than the limits.
- if desired.right() > limits.right() {
- desired.origin.x -= desired.right() - limits.right();
- }
- if desired.left() < limits.left() {
- desired.origin.x = limits.origin.x;
- }
-
- // Snap the vertical edges of the overlay to the vertical edges of the window if
- // its vertical bounds overflow, aligning to the top if it is taller than the limits.
- if desired.bottom() > limits.bottom() {
- desired.origin.y -= desired.bottom() - limits.bottom();
- }
- if desired.top() < limits.top() {
- desired.origin.y = limits.origin.y;
- }
-
- let mut offset = cx.element_offset() + desired.origin - bounds.origin;
- offset = point(offset.x.round(), offset.y.round());
- cx.with_absolute_element_offset(offset, |cx| {
- cx.break_content_mask(|cx| {
- for child in &mut self.children {
- child.paint(cx);
- }
- })
- })
- }
-}
-
-impl IntoElement for Overlay {
- type Element = Self;
-
- fn element_id(&self) -> Option<crate::ElementId> {
- None
- }
-
- fn into_element(self) -> Self::Element {
- self
- }
-}
-
-enum Axis {
- Horizontal,
- Vertical,
-}
-
-#[derive(Copy, Clone, PartialEq)]
-pub enum OverlayFitMode {
- SnapToWindow,
- SwitchAnchor,
-}
-
-#[derive(Clone, Copy, PartialEq, Eq)]
-pub enum AnchorCorner {
- TopLeft,
- TopRight,
- BottomLeft,
- BottomRight,
-}
-
-impl AnchorCorner {
- fn get_bounds(&self, origin: Point<Pixels>, size: Size<Pixels>) -> Bounds<Pixels> {
- let origin = match self {
- Self::TopLeft => origin,
- Self::TopRight => Point {
- x: origin.x - size.width,
- y: origin.y,
- },
- Self::BottomLeft => Point {
- x: origin.x,
- y: origin.y - size.height,
- },
- Self::BottomRight => Point {
- x: origin.x - size.width,
- y: origin.y - size.height,
- },
- };
-
- Bounds { origin, size }
- }
-
- pub fn corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> {
- match self {
- Self::TopLeft => bounds.origin,
- Self::TopRight => bounds.upper_right(),
- Self::BottomLeft => bounds.lower_left(),
- Self::BottomRight => bounds.lower_right(),
- }
- }
-
- fn switch_axis(self, axis: Axis) -> Self {
- match axis {
- Axis::Vertical => match self {
- AnchorCorner::TopLeft => AnchorCorner::BottomLeft,
- AnchorCorner::TopRight => AnchorCorner::BottomRight,
- AnchorCorner::BottomLeft => AnchorCorner::TopLeft,
- AnchorCorner::BottomRight => AnchorCorner::TopRight,
- },
- Axis::Horizontal => match self {
- AnchorCorner::TopLeft => AnchorCorner::TopRight,
- AnchorCorner::TopRight => AnchorCorner::TopLeft,
- AnchorCorner::BottomLeft => AnchorCorner::BottomRight,
- AnchorCorner::BottomRight => AnchorCorner::BottomLeft,
- },
- }
- }
-}
@@ -1,78 +0,0 @@
-use crate::{
- Bounds, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity,
- IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext,
-};
-use util::ResultExt;
-
-pub struct Svg {
- interactivity: Interactivity,
- path: Option<SharedString>,
-}
-
-pub fn svg() -> Svg {
- Svg {
- interactivity: Interactivity::default(),
- path: None,
- }
-}
-
-impl Svg {
- pub fn path(mut self, path: impl Into<SharedString>) -> Self {
- self.path = Some(path.into());
- self
- }
-}
-
-impl Element for Svg {
- type State = InteractiveElementState;
-
- fn request_layout(
- &mut self,
- element_state: Option<Self::State>,
- cx: &mut WindowContext,
- ) -> (LayoutId, Self::State) {
- self.interactivity.layout(element_state, cx, |style, cx| {
- cx.request_layout(&style, None)
- })
- }
-
- fn paint(
- &mut self,
- bounds: Bounds<Pixels>,
- element_state: &mut Self::State,
- cx: &mut WindowContext,
- ) where
- Self: Sized,
- {
- self.interactivity
- .paint(bounds, bounds.size, element_state, cx, |style, _, cx| {
- if let Some((path, color)) = self.path.as_ref().zip(style.text.color) {
- cx.paint_svg(bounds, path.clone(), color).log_err();
- }
- })
- }
-}
-
-impl IntoElement for Svg {
- type Element = Self;
-
- fn element_id(&self) -> Option<ElementId> {
- self.interactivity.element_id.clone()
- }
-
- fn into_element(self) -> Self::Element {
- self
- }
-}
-
-impl Styled for Svg {
- fn style(&mut self) -> &mut StyleRefinement {
- &mut self.interactivity.base_style
- }
-}
-
-impl InteractiveElement for Svg {
- fn interactivity(&mut self) -> &mut Interactivity {
- &mut self.interactivity
- }
-}
@@ -1,423 +0,0 @@
-use crate::{
- Bounds, DispatchPhase, Element, ElementId, HighlightStyle, IntoElement, LayoutId,
- MouseDownEvent, MouseUpEvent, Pixels, Point, SharedString, Size, TextRun, TextStyle,
- WhiteSpace, WindowContext, WrappedLine,
-};
-use anyhow::anyhow;
-use parking_lot::{Mutex, MutexGuard};
-use smallvec::SmallVec;
-use std::{cell::Cell, mem, ops::Range, rc::Rc, sync::Arc};
-use util::ResultExt;
-
-impl Element for &'static str {
- type State = TextState;
-
- fn request_layout(
- &mut self,
- _: Option<Self::State>,
- cx: &mut WindowContext,
- ) -> (LayoutId, Self::State) {
- let mut state = TextState::default();
- let layout_id = state.layout(SharedString::from(*self), None, cx);
- (layout_id, state)
- }
-
- fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
- state.paint(bounds, self, cx)
- }
-}
-
-impl IntoElement for &'static str {
- type Element = Self;
-
- fn element_id(&self) -> Option<ElementId> {
- None
- }
-
- fn into_element(self) -> Self::Element {
- self
- }
-}
-
-impl Element for SharedString {
- type State = TextState;
-
- fn request_layout(
- &mut self,
- _: Option<Self::State>,
- cx: &mut WindowContext,
- ) -> (LayoutId, Self::State) {
- let mut state = TextState::default();
- let layout_id = state.layout(self.clone(), None, cx);
- (layout_id, state)
- }
-
- fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
- let text_str: &str = self.as_ref();
- state.paint(bounds, text_str, cx)
- }
-}
-
-impl IntoElement for SharedString {
- type Element = Self;
-
- fn element_id(&self) -> Option<ElementId> {
- None
- }
-
- fn into_element(self) -> Self::Element {
- self
- }
-}
-
-/// Renders text with runs of different styles.
-///
-/// Callers are responsible for setting the correct style for each run.
-/// For text with a uniform style, you can usually avoid calling this constructor
-/// and just pass text directly.
-pub struct StyledText {
- text: SharedString,
- runs: Option<Vec<TextRun>>,
-}
-
-impl StyledText {
- pub fn new(text: impl Into<SharedString>) -> Self {
- StyledText {
- text: text.into(),
- runs: None,
- }
- }
-
- pub fn with_highlights(
- mut self,
- default_style: &TextStyle,
- highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
- ) -> Self {
- let mut runs = Vec::new();
- let mut ix = 0;
- for (range, highlight) in highlights {
- if ix < range.start {
- runs.push(default_style.clone().to_run(range.start - ix));
- }
- runs.push(
- default_style
- .clone()
- .highlight(highlight)
- .to_run(range.len()),
- );
- ix = range.end;
- }
- if ix < self.text.len() {
- runs.push(default_style.to_run(self.text.len() - ix));
- }
- self.runs = Some(runs);
- self
- }
-}
-
-impl Element for StyledText {
- type State = TextState;
-
- fn request_layout(
- &mut self,
- _: Option<Self::State>,
- cx: &mut WindowContext,
- ) -> (LayoutId, Self::State) {
- let mut state = TextState::default();
- let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
- (layout_id, state)
- }
-
- fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
- state.paint(bounds, &self.text, cx)
- }
-}
-
-impl IntoElement for StyledText {
- type Element = Self;
-
- fn element_id(&self) -> Option<crate::ElementId> {
- None
- }
-
- fn into_element(self) -> Self::Element {
- self
- }
-}
-
-#[derive(Default, Clone)]
-pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
-
-struct TextStateInner {
- lines: SmallVec<[WrappedLine; 1]>,
- line_height: Pixels,
- wrap_width: Option<Pixels>,
- size: Option<Size<Pixels>>,
-}
-
-impl TextState {
- fn lock(&self) -> MutexGuard<Option<TextStateInner>> {
- self.0.lock()
- }
-
- fn layout(
- &mut self,
- text: SharedString,
- runs: Option<Vec<TextRun>>,
- cx: &mut WindowContext,
- ) -> LayoutId {
- let text_style = cx.text_style();
- let font_size = text_style.font_size.to_pixels(cx.rem_size());
- let line_height = text_style
- .line_height
- .to_pixels(font_size.into(), cx.rem_size());
-
- let runs = if let Some(runs) = runs {
- runs
- } else {
- vec![text_style.to_run(text.len())]
- };
-
- let layout_id = cx.request_measured_layout(Default::default(), {
- let element_state = self.clone();
-
- move |known_dimensions, available_space, cx| {
- let wrap_width = if text_style.white_space == WhiteSpace::Normal {
- known_dimensions.width.or(match available_space.width {
- crate::AvailableSpace::Definite(x) => Some(x),
- _ => None,
- })
- } else {
- None
- };
-
- if let Some(text_state) = element_state.0.lock().as_ref() {
- if text_state.size.is_some()
- && (wrap_width.is_none() || wrap_width == text_state.wrap_width)
- {
- return text_state.size.unwrap();
- }
- }
-
- let Some(lines) = cx
- .text_system()
- .shape_text(
- &text, font_size, &runs, wrap_width, // Wrap if we know the width.
- )
- .log_err()
- else {
- element_state.lock().replace(TextStateInner {
- lines: Default::default(),
- line_height,
- wrap_width,
- size: Some(Size::default()),
- });
- return Size::default();
- };
-
- let mut size: Size<Pixels> = Size::default();
- for line in &lines {
- let line_size = line.size(line_height);
- size.height += line_size.height;
- size.width = size.width.max(line_size.width).ceil();
- }
-
- element_state.lock().replace(TextStateInner {
- lines,
- line_height,
- wrap_width,
- size: Some(size),
- });
-
- size
- }
- });
-
- layout_id
- }
-
- fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut WindowContext) {
- let element_state = self.lock();
- let element_state = element_state
- .as_ref()
- .ok_or_else(|| anyhow!("measurement has not been performed on {}", text))
- .unwrap();
-
- let line_height = element_state.line_height;
- let mut line_origin = bounds.origin;
- for line in &element_state.lines {
- line.paint(line_origin, line_height, cx).log_err();
- line_origin.y += line.size(line_height).height;
- }
- }
-
- fn index_for_position(&self, bounds: Bounds<Pixels>, position: Point<Pixels>) -> Option<usize> {
- if !bounds.contains(&position) {
- return None;
- }
-
- let element_state = self.lock();
- let element_state = element_state
- .as_ref()
- .expect("measurement has not been performed");
-
- let line_height = element_state.line_height;
- let mut line_origin = bounds.origin;
- let mut line_start_ix = 0;
- for line in &element_state.lines {
- let line_bottom = line_origin.y + line.size(line_height).height;
- if position.y > line_bottom {
- line_origin.y = line_bottom;
- line_start_ix += line.len() + 1;
- } else {
- let position_within_line = position - line_origin;
- let index_within_line =
- line.index_for_position(position_within_line, line_height)?;
- return Some(line_start_ix + index_within_line);
- }
- }
-
- None
- }
-}
-
-pub struct InteractiveText {
- element_id: ElementId,
- text: StyledText,
- click_listener:
- Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut WindowContext<'_>)>>,
- clickable_ranges: Vec<Range<usize>>,
-}
-
-struct InteractiveTextClickEvent {
- mouse_down_index: usize,
- mouse_up_index: usize,
-}
-
-pub struct InteractiveTextState {
- text_state: TextState,
- mouse_down_index: Rc<Cell<Option<usize>>>,
-}
-
-impl InteractiveText {
- pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self {
- Self {
- element_id: id.into(),
- text,
- click_listener: None,
- clickable_ranges: Vec::new(),
- }
- }
-
- pub fn on_click(
- mut self,
- ranges: Vec<Range<usize>>,
- listener: impl Fn(usize, &mut WindowContext<'_>) + 'static,
- ) -> Self {
- self.click_listener = Some(Box::new(move |ranges, event, cx| {
- for (range_ix, range) in ranges.iter().enumerate() {
- if range.contains(&event.mouse_down_index) && range.contains(&event.mouse_up_index)
- {
- listener(range_ix, cx);
- }
- }
- }));
- self.clickable_ranges = ranges;
- self
- }
-}
-
-impl Element for InteractiveText {
- type State = InteractiveTextState;
-
- fn request_layout(
- &mut self,
- state: Option<Self::State>,
- cx: &mut WindowContext,
- ) -> (LayoutId, Self::State) {
- if let Some(InteractiveTextState {
- mouse_down_index, ..
- }) = state
- {
- let (layout_id, text_state) = self.text.request_layout(None, cx);
- let element_state = InteractiveTextState {
- text_state,
- mouse_down_index,
- };
- (layout_id, element_state)
- } else {
- let (layout_id, text_state) = self.text.request_layout(None, cx);
- let element_state = InteractiveTextState {
- text_state,
- mouse_down_index: Rc::default(),
- };
- (layout_id, element_state)
- }
- }
-
- fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
- if let Some(click_listener) = self.click_listener.take() {
- let mouse_position = cx.mouse_position();
- if let Some(ix) = state.text_state.index_for_position(bounds, mouse_position) {
- if self
- .clickable_ranges
- .iter()
- .any(|range| range.contains(&ix))
- && cx.was_top_layer(&mouse_position, cx.stacking_order())
- {
- cx.set_cursor_style(crate::CursorStyle::PointingHand)
- }
- }
-
- let text_state = state.text_state.clone();
- let mouse_down = state.mouse_down_index.clone();
- if let Some(mouse_down_index) = mouse_down.get() {
- let clickable_ranges = mem::take(&mut self.clickable_ranges);
- cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
- if phase == DispatchPhase::Bubble {
- if let Some(mouse_up_index) =
- text_state.index_for_position(bounds, event.position)
- {
- click_listener(
- &clickable_ranges,
- InteractiveTextClickEvent {
- mouse_down_index,
- mouse_up_index,
- },
- cx,
- )
- }
-
- mouse_down.take();
- cx.notify();
- }
- });
- } else {
- cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
- if phase == DispatchPhase::Bubble {
- if let Some(mouse_down_index) =
- text_state.index_for_position(bounds, event.position)
- {
- mouse_down.set(Some(mouse_down_index));
- cx.notify();
- }
- }
- });
- }
- }
-
- self.text.paint(bounds, &mut state.text_state, cx)
- }
-}
-
-impl IntoElement for InteractiveText {
- type Element = Self;
-
- fn element_id(&self) -> Option<ElementId> {
- Some(self.element_id.clone())
- }
-
- fn into_element(self) -> Self::Element {
- self
- }
-}
@@ -1,316 +0,0 @@
-use crate::{
- point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element,
- ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId,
- Pixels, Point, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
-};
-use smallvec::SmallVec;
-use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
-use taffy::style::Overflow;
-
-/// uniform_list provides lazy rendering for a set of items that are of uniform height.
-/// When rendered into a container with overflow-y: hidden and a fixed (or max) height,
-/// uniform_list will only render the visible subset of items.
-#[track_caller]
-pub fn uniform_list<I, R, V>(
- view: View<V>,
- id: I,
- item_count: usize,
- f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> Vec<R>,
-) -> UniformList
-where
- I: Into<ElementId>,
- R: IntoElement,
- V: Render,
-{
- let id = id.into();
- let mut base_style = StyleRefinement::default();
- base_style.overflow.y = Some(Overflow::Scroll);
-
- let render_range = move |range, cx: &mut WindowContext| {
- view.update(cx, |this, cx| {
- f(this, range, cx)
- .into_iter()
- .map(|component| component.into_any_element())
- .collect()
- })
- };
-
- UniformList {
- id: id.clone(),
- item_count,
- item_to_measure_index: 0,
- render_items: Box::new(render_range),
- interactivity: Interactivity {
- element_id: Some(id),
- base_style: Box::new(base_style),
-
- #[cfg(debug_assertions)]
- location: Some(*core::panic::Location::caller()),
-
- ..Default::default()
- },
- scroll_handle: None,
- }
-}
-
-pub struct UniformList {
- id: ElementId,
- item_count: usize,
- item_to_measure_index: usize,
- render_items:
- Box<dyn for<'a> Fn(Range<usize>, &'a mut WindowContext) -> SmallVec<[AnyElement; 64]>>,
- interactivity: Interactivity,
- scroll_handle: Option<UniformListScrollHandle>,
-}
-
-#[derive(Clone, Default)]
-pub struct UniformListScrollHandle(Rc<RefCell<Option<ScrollHandleState>>>);
-
-#[derive(Clone, Debug)]
-struct ScrollHandleState {
- item_height: Pixels,
- list_height: Pixels,
- scroll_offset: Rc<RefCell<Point<Pixels>>>,
-}
-
-impl UniformListScrollHandle {
- pub fn new() -> Self {
- Self(Rc::new(RefCell::new(None)))
- }
-
- pub fn scroll_to_item(&self, ix: usize) {
- if let Some(state) = &*self.0.borrow() {
- let mut scroll_offset = state.scroll_offset.borrow_mut();
- let item_top = state.item_height * ix;
- let item_bottom = item_top + state.item_height;
- let scroll_top = -scroll_offset.y;
- if item_top < scroll_top {
- scroll_offset.y = -item_top;
- } else if item_bottom > scroll_top + state.list_height {
- scroll_offset.y = -(item_bottom - state.list_height);
- }
- }
- }
-
- pub fn scroll_top(&self) -> Pixels {
- if let Some(state) = &*self.0.borrow() {
- -state.scroll_offset.borrow().y
- } else {
- Pixels::ZERO
- }
- }
-}
-
-impl Styled for UniformList {
- fn style(&mut self) -> &mut StyleRefinement {
- &mut self.interactivity.base_style
- }
-}
-
-#[derive(Default)]
-pub struct UniformListState {
- interactive: InteractiveElementState,
- item_size: Size<Pixels>,
-}
-
-impl Element for UniformList {
- type State = UniformListState;
-
- fn request_layout(
- &mut self,
- state: Option<Self::State>,
- cx: &mut WindowContext,
- ) -> (LayoutId, Self::State) {
- let max_items = self.item_count;
- let item_size = state
- .as_ref()
- .map(|s| s.item_size)
- .unwrap_or_else(|| self.measure_item(None, cx));
-
- let (layout_id, interactive) =
- self.interactivity
- .layout(state.map(|s| s.interactive), cx, |style, cx| {
- cx.request_measured_layout(
- style,
- move |known_dimensions, available_space, _cx| {
- let desired_height = item_size.height * max_items;
- let width =
- known_dimensions
- .width
- .unwrap_or(match available_space.width {
- AvailableSpace::Definite(x) => x,
- AvailableSpace::MinContent | AvailableSpace::MaxContent => {
- item_size.width
- }
- });
-
- let height = match available_space.height {
- AvailableSpace::Definite(height) => desired_height.min(height),
- AvailableSpace::MinContent | AvailableSpace::MaxContent => {
- desired_height
- }
- };
- size(width, height)
- },
- )
- });
-
- let element_state = UniformListState {
- interactive,
- item_size,
- };
-
- (layout_id, element_state)
- }
-
- fn paint(
- &mut self,
- bounds: Bounds<crate::Pixels>,
- element_state: &mut Self::State,
- cx: &mut WindowContext,
- ) {
- let style =
- self.interactivity
- .compute_style(Some(bounds), &mut element_state.interactive, cx);
- let border = style.border_widths.to_pixels(cx.rem_size());
- let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
-
- let padded_bounds = Bounds::from_corners(
- bounds.origin + point(border.left + padding.left, border.top + padding.top),
- bounds.lower_right()
- - point(border.right + padding.right, border.bottom + padding.bottom),
- );
-
- let item_size = element_state.item_size;
- let content_size = Size {
- width: padded_bounds.size.width,
- height: item_size.height * self.item_count + padding.top + padding.bottom,
- };
-
- let shared_scroll_offset = element_state
- .interactive
- .scroll_offset
- .get_or_insert_with(|| {
- if let Some(scroll_handle) = self.scroll_handle.as_ref() {
- if let Some(scroll_handle) = scroll_handle.0.borrow().as_ref() {
- return scroll_handle.scroll_offset.clone();
- }
- }
-
- Rc::default()
- })
- .clone();
-
- let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height;
-
- self.interactivity.paint(
- bounds,
- content_size,
- &mut element_state.interactive,
- cx,
- |style, mut scroll_offset, cx| {
- let border = style.border_widths.to_pixels(cx.rem_size());
- let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
-
- let padded_bounds = Bounds::from_corners(
- bounds.origin + point(border.left + padding.left, border.top),
- bounds.lower_right() - point(border.right + padding.right, border.bottom),
- );
-
- if self.item_count > 0 {
- let content_height =
- item_height * self.item_count + padding.top + padding.bottom;
- let min_scroll_offset = padded_bounds.size.height - content_height;
- let is_scrolled = scroll_offset.y != px(0.);
-
- if is_scrolled && scroll_offset.y < min_scroll_offset {
- shared_scroll_offset.borrow_mut().y = min_scroll_offset;
- scroll_offset.y = min_scroll_offset;
- }
-
- if let Some(scroll_handle) = self.scroll_handle.clone() {
- scroll_handle.0.borrow_mut().replace(ScrollHandleState {
- item_height,
- list_height: padded_bounds.size.height,
- scroll_offset: shared_scroll_offset,
- });
- }
-
- let first_visible_element_ix =
- (-(scroll_offset.y + padding.top) / item_height).floor() as usize;
- let last_visible_element_ix = ((-scroll_offset.y + padded_bounds.size.height)
- / item_height)
- .ceil() as usize;
- let visible_range = first_visible_element_ix
- ..cmp::min(last_visible_element_ix, self.item_count);
-
- let mut items = (self.render_items)(visible_range.clone(), cx);
- cx.with_z_index(1, |cx| {
- let content_mask = ContentMask { bounds };
- cx.with_content_mask(Some(content_mask), |cx| {
- for (item, ix) in items.iter_mut().zip(visible_range) {
- let item_origin = padded_bounds.origin
- + point(
- px(0.),
- item_height * ix + scroll_offset.y + padding.top,
- );
- let available_space = size(
- AvailableSpace::Definite(padded_bounds.size.width),
- AvailableSpace::Definite(item_height),
- );
- item.draw(item_origin, available_space, cx);
- }
- });
- });
- }
- },
- )
- }
-}
-
-impl IntoElement for UniformList {
- type Element = Self;
-
- fn element_id(&self) -> Option<crate::ElementId> {
- Some(self.id.clone())
- }
-
- fn into_element(self) -> Self::Element {
- self
- }
-}
-
-impl UniformList {
- pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
- self.item_to_measure_index = item_index.unwrap_or(0);
- self
- }
-
- fn measure_item(&self, list_width: Option<Pixels>, cx: &mut WindowContext) -> Size<Pixels> {
- if self.item_count == 0 {
- return Size::default();
- }
-
- let item_ix = cmp::min(self.item_to_measure_index, self.item_count - 1);
- let mut items = (self.render_items)(item_ix..item_ix + 1, cx);
- let mut item_to_measure = items.pop().unwrap();
- let available_space = size(
- list_width.map_or(AvailableSpace::MinContent, |width| {
- AvailableSpace::Definite(width)
- }),
- AvailableSpace::MinContent,
- );
- item_to_measure.measure(available_space, cx)
- }
-
- pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
- self.scroll_handle = Some(handle);
- self
- }
-}
-
-impl InteractiveElement for UniformList {
- fn interactivity(&mut self) -> &mut crate::Interactivity {
- &mut self.interactivity
- }
-}
@@ -1,402 +0,0 @@
-use crate::{AppContext, PlatformDispatcher};
-use futures::{channel::mpsc, pin_mut, FutureExt};
-use smol::prelude::*;
-use std::{
- fmt::Debug,
- marker::PhantomData,
- mem,
- num::NonZeroUsize,
- pin::Pin,
- rc::Rc,
- sync::{
- atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
- Arc,
- },
- task::{Context, Poll},
- time::Duration,
-};
-use util::TryFutureExt;
-use waker_fn::waker_fn;
-
-#[cfg(any(test, feature = "test-support"))]
-use rand::rngs::StdRng;
-
-#[derive(Clone)]
-pub struct BackgroundExecutor {
- dispatcher: Arc<dyn PlatformDispatcher>,
-}
-
-#[derive(Clone)]
-pub struct ForegroundExecutor {
- dispatcher: Arc<dyn PlatformDispatcher>,
- not_send: PhantomData<Rc<()>>,
-}
-
-#[must_use]
-#[derive(Debug)]
-pub enum Task<T> {
- Ready(Option<T>),
- Spawned(async_task::Task<T>),
-}
-
-impl<T> Task<T> {
- pub fn ready(val: T) -> Self {
- Task::Ready(Some(val))
- }
-
- pub fn detach(self) {
- match self {
- Task::Ready(_) => {}
- Task::Spawned(task) => task.detach(),
- }
- }
-}
-
-impl<E, T> Task<Result<T, E>>
-where
- T: 'static,
- E: 'static + Debug,
-{
- #[track_caller]
- pub fn detach_and_log_err(self, cx: &mut AppContext) {
- let location = core::panic::Location::caller();
- cx.foreground_executor()
- .spawn(self.log_tracked_err(*location))
- .detach();
- }
-}
-
-impl<T> Future for Task<T> {
- type Output = T;
-
- fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
- match unsafe { self.get_unchecked_mut() } {
- Task::Ready(val) => Poll::Ready(val.take().unwrap()),
- Task::Spawned(task) => task.poll(cx),
- }
- }
-}
-
-#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
-pub struct TaskLabel(NonZeroUsize);
-
-impl Default for TaskLabel {
- fn default() -> Self {
- Self::new()
- }
-}
-
-impl TaskLabel {
- pub fn new() -> Self {
- static NEXT_TASK_LABEL: AtomicUsize = AtomicUsize::new(1);
- Self(NEXT_TASK_LABEL.fetch_add(1, SeqCst).try_into().unwrap())
- }
-}
-
-type AnyLocalFuture<R> = Pin<Box<dyn 'static + Future<Output = R>>>;
-
-type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
-
-impl BackgroundExecutor {
- pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
- Self { dispatcher }
- }
-
- /// Enqueues the given future to be run to completion on a background thread.
- pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
- where
- R: Send + 'static,
- {
- self.spawn_internal::<R>(Box::pin(future), None)
- }
-
- /// Enqueues the given future to be run to completion on a background thread.
- /// The given label can be used to control the priority of the task in tests.
- pub fn spawn_labeled<R>(
- &self,
- label: TaskLabel,
- future: impl Future<Output = R> + Send + 'static,
- ) -> Task<R>
- where
- R: Send + 'static,
- {
- self.spawn_internal::<R>(Box::pin(future), Some(label))
- }
-
- fn spawn_internal<R: Send + 'static>(
- &self,
- future: AnyFuture<R>,
- label: Option<TaskLabel>,
- ) -> Task<R> {
- let dispatcher = self.dispatcher.clone();
- let (runnable, task) =
- async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable, label));
- runnable.schedule();
- Task::Spawned(task)
- }
-
- #[cfg(any(test, feature = "test-support"))]
- #[track_caller]
- pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
- if let Ok(value) = self.block_internal(false, future, usize::MAX) {
- value
- } else {
- unreachable!()
- }
- }
-
- pub fn block<R>(&self, future: impl Future<Output = R>) -> R {
- if let Ok(value) = self.block_internal(true, future, usize::MAX) {
- value
- } else {
- unreachable!()
- }
- }
-
- #[track_caller]
- pub(crate) fn block_internal<R>(
- &self,
- background_only: bool,
- future: impl Future<Output = R>,
- mut max_ticks: usize,
- ) -> Result<R, ()> {
- pin_mut!(future);
- let unparker = self.dispatcher.unparker();
- let awoken = Arc::new(AtomicBool::new(false));
-
- let waker = waker_fn({
- let awoken = awoken.clone();
- move || {
- awoken.store(true, SeqCst);
- unparker.unpark();
- }
- });
- let mut cx = std::task::Context::from_waker(&waker);
-
- loop {
- match future.as_mut().poll(&mut cx) {
- Poll::Ready(result) => return Ok(result),
- Poll::Pending => {
- if max_ticks == 0 {
- return Err(());
- }
- max_ticks -= 1;
-
- if !self.dispatcher.tick(background_only) {
- if awoken.swap(false, SeqCst) {
- continue;
- }
-
- #[cfg(any(test, feature = "test-support"))]
- if let Some(test) = self.dispatcher.as_test() {
- if !test.parking_allowed() {
- let mut backtrace_message = String::new();
- if let Some(backtrace) = test.waiting_backtrace() {
- backtrace_message =
- format!("\nbacktrace of waiting future:\n{:?}", backtrace);
- }
- panic!("parked with nothing left to run\n{:?}", backtrace_message)
- }
- }
-
- self.dispatcher.park();
- }
- }
- }
- }
- }
-
- pub fn block_with_timeout<R>(
- &self,
- duration: Duration,
- future: impl Future<Output = R>,
- ) -> Result<R, impl Future<Output = R>> {
- let mut future = Box::pin(future.fuse());
- if duration.is_zero() {
- return Err(future);
- }
-
- #[cfg(any(test, feature = "test-support"))]
- let max_ticks = self
- .dispatcher
- .as_test()
- .map_or(usize::MAX, |dispatcher| dispatcher.gen_block_on_ticks());
- #[cfg(not(any(test, feature = "test-support")))]
- let max_ticks = usize::MAX;
-
- let mut timer = self.timer(duration).fuse();
-
- let timeout = async {
- futures::select_biased! {
- value = future => Ok(value),
- _ = timer => Err(()),
- }
- };
- match self.block_internal(true, timeout, max_ticks) {
- Ok(Ok(value)) => Ok(value),
- _ => Err(future),
- }
- }
-
- pub async fn scoped<'scope, F>(&self, scheduler: F)
- where
- F: FnOnce(&mut Scope<'scope>),
- {
- let mut scope = Scope::new(self.clone());
- (scheduler)(&mut scope);
- let spawned = mem::take(&mut scope.futures)
- .into_iter()
- .map(|f| self.spawn(f))
- .collect::<Vec<_>>();
- for task in spawned {
- task.await;
- }
- }
-
- pub fn timer(&self, duration: Duration) -> Task<()> {
- let (runnable, task) = async_task::spawn(async move {}, {
- let dispatcher = self.dispatcher.clone();
- move |runnable| dispatcher.dispatch_after(duration, runnable)
- });
- runnable.schedule();
- Task::Spawned(task)
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn start_waiting(&self) {
- self.dispatcher.as_test().unwrap().start_waiting();
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn finish_waiting(&self) {
- self.dispatcher.as_test().unwrap().finish_waiting();
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn simulate_random_delay(&self) -> impl Future<Output = ()> {
- self.dispatcher.as_test().unwrap().simulate_random_delay()
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn deprioritize(&self, task_label: TaskLabel) {
- self.dispatcher.as_test().unwrap().deprioritize(task_label)
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn advance_clock(&self, duration: Duration) {
- self.dispatcher.as_test().unwrap().advance_clock(duration)
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn tick(&self) -> bool {
- self.dispatcher.as_test().unwrap().tick(false)
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn run_until_parked(&self) {
- self.dispatcher.as_test().unwrap().run_until_parked()
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn allow_parking(&self) {
- self.dispatcher.as_test().unwrap().allow_parking();
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn rng(&self) -> StdRng {
- self.dispatcher.as_test().unwrap().rng()
- }
-
- pub fn num_cpus(&self) -> usize {
- num_cpus::get()
- }
-
- pub fn is_main_thread(&self) -> bool {
- self.dispatcher.is_main_thread()
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
- self.dispatcher.as_test().unwrap().set_block_on_ticks(range);
- }
-}
-
-impl ForegroundExecutor {
- pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
- Self {
- dispatcher,
- not_send: PhantomData,
- }
- }
-
- /// Enqueues the given closure to be run on any thread. The closure returns
- /// a future which will be run to completion on any available thread.
- pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
- where
- R: 'static,
- {
- let dispatcher = self.dispatcher.clone();
- fn inner<R: 'static>(
- dispatcher: Arc<dyn PlatformDispatcher>,
- future: AnyLocalFuture<R>,
- ) -> Task<R> {
- let (runnable, task) = async_task::spawn_local(future, move |runnable| {
- dispatcher.dispatch_on_main_thread(runnable)
- });
- runnable.schedule();
- Task::Spawned(task)
- }
- inner::<R>(dispatcher, Box::pin(future))
- }
-}
-
-pub struct Scope<'a> {
- executor: BackgroundExecutor,
- futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
- tx: Option<mpsc::Sender<()>>,
- rx: mpsc::Receiver<()>,
- lifetime: PhantomData<&'a ()>,
-}
-
-impl<'a> Scope<'a> {
- fn new(executor: BackgroundExecutor) -> Self {
- let (tx, rx) = mpsc::channel(1);
- Self {
- executor,
- tx: Some(tx),
- rx,
- futures: Default::default(),
- lifetime: PhantomData,
- }
- }
-
- pub fn spawn<F>(&mut self, f: F)
- where
- F: Future<Output = ()> + Send + 'a,
- {
- let tx = self.tx.clone().unwrap();
-
- // Safety: The 'a lifetime is guaranteed to outlive any of these futures because
- // dropping this `Scope` blocks until all of the futures have resolved.
- let f = unsafe {
- mem::transmute::<
- Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
- Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
- >(Box::pin(async move {
- f.await;
- drop(tx);
- }))
- };
- self.futures.push(f);
- }
-}
-
-impl<'a> Drop for Scope<'a> {
- fn drop(&mut self) {
- self.tx.take().unwrap();
-
- // Wait until the channel is closed, which means that all of the spawned
- // futures have resolved.
- self.executor.block(self.rx.next());
- }
-}
@@ -1,2796 +0,0 @@
-use core::fmt::Debug;
-use derive_more::{Add, AddAssign, Div, DivAssign, Mul, Neg, Sub, SubAssign};
-use refineable::Refineable;
-use serde_derive::{Deserialize, Serialize};
-use std::{
- cmp::{self, PartialOrd},
- fmt,
- ops::{Add, Div, Mul, MulAssign, Sub},
-};
-
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-pub enum Axis {
- Vertical,
- Horizontal,
-}
-
-impl Axis {
- pub fn invert(&self) -> Self {
- match self {
- Axis::Vertical => Axis::Horizontal,
- Axis::Horizontal => Axis::Vertical,
- }
- }
-}
-
-pub trait Along {
- type Unit;
-
- fn along(&self, axis: Axis) -> Self::Unit;
-
- fn apply_along(&self, axis: Axis, f: impl FnOnce(Self::Unit) -> Self::Unit) -> Self;
-}
-
-impl sqlez::bindable::StaticColumnCount for Axis {}
-impl sqlez::bindable::Bind for Axis {
- fn bind(
- &self,
- statement: &sqlez::statement::Statement,
- start_index: i32,
- ) -> anyhow::Result<i32> {
- match self {
- Axis::Horizontal => "Horizontal",
- Axis::Vertical => "Vertical",
- }
- .bind(statement, start_index)
- }
-}
-
-impl sqlez::bindable::Column for Axis {
- fn column(
- statement: &mut sqlez::statement::Statement,
- start_index: i32,
- ) -> anyhow::Result<(Self, i32)> {
- String::column(statement, start_index).and_then(|(axis_text, next_index)| {
- Ok((
- match axis_text.as_str() {
- "Horizontal" => Axis::Horizontal,
- "Vertical" => Axis::Vertical,
- _ => anyhow::bail!("Stored serialized item kind is incorrect"),
- },
- next_index,
- ))
- })
- }
-}
-
-/// Describes a location in a 2D cartesian coordinate space.
-///
-/// It holds two public fields, `x` and `y`, which represent the coordinates in the space.
-/// The type `T` for the coordinates can be any type that implements `Default`, `Clone`, and `Debug`.
-///
-/// # Examples
-///
-/// ```
-/// # use zed::Point;
-/// let point = Point { x: 10, y: 20 };
-/// println!("{:?}", point); // Outputs: Point { x: 10, y: 20 }
-/// ```
-#[derive(Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash)]
-#[refineable(Debug)]
-#[repr(C)]
-pub struct Point<T: Default + Clone + Debug> {
- pub x: T,
- pub y: T,
-}
-
-/// Constructs a new `Point<T>` with the given x and y coordinates.
-///
-/// # Arguments
-///
-/// * `x` - The x coordinate of the point.
-/// * `y` - The y coordinate of the point.
-///
-/// # Returns
-///
-/// Returns a `Point<T>` with the specified coordinates.
-///
-/// # Examples
-///
-/// ```
-/// # use zed::Point;
-/// let p = point(10, 20);
-/// assert_eq!(p.x, 10);
-/// assert_eq!(p.y, 20);
-/// ```
-pub fn point<T: Clone + Debug + Default>(x: T, y: T) -> Point<T> {
- Point { x, y }
-}
-
-impl<T: Clone + Debug + Default> Point<T> {
- /// Creates a new `Point` with the specified `x` and `y` coordinates.
- ///
- /// # Arguments
- ///
- /// * `x` - The horizontal coordinate of the point.
- /// * `y` - The vertical coordinate of the point.
- ///
- /// # Examples
- ///
- /// ```
- /// let p = Point::new(10, 20);
- /// assert_eq!(p.x, 10);
- /// assert_eq!(p.y, 20);
- /// ```
- pub const fn new(x: T, y: T) -> Self {
- Self { x, y }
- }
-
- /// Transforms the point to a `Point<U>` by applying the given function to both coordinates.
- ///
- /// This method allows for converting a `Point<T>` to a `Point<U>` by specifying a closure
- /// that defines how to convert between the two types. The closure is applied to both the `x`
- /// and `y` coordinates, resulting in a new point of the desired type.
- ///
- /// # Arguments
- ///
- /// * `f` - A closure that takes a value of type `T` and returns a value of type `U`.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::Point;
- /// let p = Point { x: 3, y: 4 };
- /// let p_float = p.map(|coord| coord as f32);
- /// assert_eq!(p_float, Point { x: 3.0, y: 4.0 });
- /// ```
- pub fn map<U: Clone + Default + Debug>(&self, f: impl Fn(T) -> U) -> Point<U> {
- Point {
- x: f(self.x.clone()),
- y: f(self.y.clone()),
- }
- }
-}
-
-impl<T: Clone + Debug + Default> Along for Point<T> {
- type Unit = T;
-
- fn along(&self, axis: Axis) -> T {
- match axis {
- Axis::Horizontal => self.x.clone(),
- Axis::Vertical => self.y.clone(),
- }
- }
-
- fn apply_along(&self, axis: Axis, f: impl FnOnce(T) -> T) -> Point<T> {
- match axis {
- Axis::Horizontal => Point {
- x: f(self.x.clone()),
- y: self.y.clone(),
- },
- Axis::Vertical => Point {
- x: self.x.clone(),
- y: f(self.y.clone()),
- },
- }
- }
-}
-
-impl Point<Pixels> {
- /// Scales the point by a given factor, which is typically derived from the resolution
- /// of a target display to ensure proper sizing of UI elements.
- ///
- /// # Arguments
- ///
- /// * `factor` - The scaling factor to apply to both the x and y coordinates.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Point, Pixels, ScaledPixels};
- /// let p = Point { x: Pixels(10.0), y: Pixels(20.0) };
- /// let scaled_p = p.scale(1.5);
- /// assert_eq!(scaled_p, Point { x: ScaledPixels(15.0), y: ScaledPixels(30.0) });
- /// ```
- pub fn scale(&self, factor: f32) -> Point<ScaledPixels> {
- Point {
- x: self.x.scale(factor),
- y: self.y.scale(factor),
- }
- }
-
- /// Calculates the Euclidean distance from the origin (0, 0) to this point.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::Point;
- /// # use zed::Pixels;
- /// let p = Point { x: Pixels(3.0), y: Pixels(4.0) };
- /// assert_eq!(p.magnitude(), 5.0);
- /// ```
- pub fn magnitude(&self) -> f64 {
- ((self.x.0.powi(2) + self.y.0.powi(2)) as f64).sqrt()
- }
-}
-
-impl<T, Rhs> Mul<Rhs> for Point<T>
-where
- T: Mul<Rhs, Output = T> + Clone + Default + Debug,
- Rhs: Clone + Debug,
-{
- type Output = Point<T>;
-
- fn mul(self, rhs: Rhs) -> Self::Output {
- Point {
- x: self.x * rhs.clone(),
- y: self.y * rhs,
- }
- }
-}
-
-impl<T, S> MulAssign<S> for Point<T>
-where
- T: Clone + Mul<S, Output = T> + Default + Debug,
- S: Clone,
-{
- fn mul_assign(&mut self, rhs: S) {
- self.x = self.x.clone() * rhs.clone();
- self.y = self.y.clone() * rhs;
- }
-}
-
-impl<T, S> Div<S> for Point<T>
-where
- T: Div<S, Output = T> + Clone + Default + Debug,
- S: Clone,
-{
- type Output = Self;
-
- fn div(self, rhs: S) -> Self::Output {
- Self {
- x: self.x / rhs.clone(),
- y: self.y / rhs,
- }
- }
-}
-
-impl<T> Point<T>
-where
- T: PartialOrd + Clone + Default + Debug,
-{
- /// Returns a new point with the maximum values of each dimension from `self` and `other`.
- ///
- /// # Arguments
- ///
- /// * `other` - A reference to another `Point` to compare with `self`.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::Point;
- /// let p1 = Point { x: 3, y: 7 };
- /// let p2 = Point { x: 5, y: 2 };
- /// let max_point = p1.max(&p2);
- /// assert_eq!(max_point, Point { x: 5, y: 7 });
- /// ```
- pub fn max(&self, other: &Self) -> Self {
- Point {
- x: if self.x > other.x {
- self.x.clone()
- } else {
- other.x.clone()
- },
- y: if self.y > other.y {
- self.y.clone()
- } else {
- other.y.clone()
- },
- }
- }
-
- /// Returns a new point with the minimum values of each dimension from `self` and `other`.
- ///
- /// # Arguments
- ///
- /// * `other` - A reference to another `Point` to compare with `self`.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::Point;
- /// let p1 = Point { x: 3, y: 7 };
- /// let p2 = Point { x: 5, y: 2 };
- /// let min_point = p1.min(&p2);
- /// assert_eq!(min_point, Point { x: 3, y: 2 });
- /// ```
- pub fn min(&self, other: &Self) -> Self {
- Point {
- x: if self.x <= other.x {
- self.x.clone()
- } else {
- other.x.clone()
- },
- y: if self.y <= other.y {
- self.y.clone()
- } else {
- other.y.clone()
- },
- }
- }
-
- /// Clamps the point to a specified range.
- ///
- /// Given a minimum point and a maximum point, this method constrains the current point
- /// such that its coordinates do not exceed the range defined by the minimum and maximum points.
- /// If the current point's coordinates are less than the minimum, they are set to the minimum.
- /// If they are greater than the maximum, they are set to the maximum.
- ///
- /// # Arguments
- ///
- /// * `min` - A reference to a `Point` representing the minimum allowable coordinates.
- /// * `max` - A reference to a `Point` representing the maximum allowable coordinates.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::Point;
- /// let p = Point { x: 10, y: 20 };
- /// let min = Point { x: 0, y: 5 };
- /// let max = Point { x: 15, y: 25 };
- /// let clamped_p = p.clamp(&min, &max);
- /// assert_eq!(clamped_p, Point { x: 10, y: 20 });
- ///
- /// let p_out_of_bounds = Point { x: -5, y: 30 };
- /// let clamped_p_out_of_bounds = p_out_of_bounds.clamp(&min, &max);
- /// assert_eq!(clamped_p_out_of_bounds, Point { x: 0, y: 25 });
- /// ```
- pub fn clamp(&self, min: &Self, max: &Self) -> Self {
- self.max(min).min(max)
- }
-}
-
-impl<T: Clone + Default + Debug> Clone for Point<T> {
- fn clone(&self) -> Self {
- Self {
- x: self.x.clone(),
- y: self.y.clone(),
- }
- }
-}
-
-/// A structure representing a two-dimensional size with width and height in a given unit.
-///
-/// This struct is generic over the type `T`, which can be any type that implements `Clone`, `Default`, and `Debug`.
-/// It is commonly used to specify dimensions for elements in a UI, such as a window or element.
-#[derive(Refineable, Default, Clone, Copy, PartialEq, Div, Hash, Serialize, Deserialize)]
-#[refineable(Debug)]
-#[repr(C)]
-pub struct Size<T: Clone + Default + Debug> {
- pub width: T,
- pub height: T,
-}
-
-/// Constructs a new `Size<T>` with the provided width and height.
-///
-/// # Arguments
-///
-/// * `width` - The width component of the `Size`.
-/// * `height` - The height component of the `Size`.
-///
-/// # Examples
-///
-/// ```
-/// # use zed::Size;
-/// let my_size = size(10, 20);
-/// assert_eq!(my_size.width, 10);
-/// assert_eq!(my_size.height, 20);
-/// ```
-pub fn size<T>(width: T, height: T) -> Size<T>
-where
- T: Clone + Default + Debug,
-{
- Size { width, height }
-}
-
-impl<T> Size<T>
-where
- T: Clone + Default + Debug,
-{
- /// Applies a function to the width and height of the size, producing a new `Size<U>`.
- ///
- /// This method allows for converting a `Size<T>` to a `Size<U>` by specifying a closure
- /// that defines how to convert between the two types. The closure is applied to both the `width`
- /// and `height`, resulting in a new size of the desired type.
- ///
- /// # Arguments
- ///
- /// * `f` - A closure that takes a value of type `T` and returns a value of type `U`.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::Size;
- /// let my_size = Size { width: 10, height: 20 };
- /// let my_new_size = my_size.map(|dimension| dimension as f32 * 1.5);
- /// assert_eq!(my_new_size, Size { width: 15.0, height: 30.0 });
- /// ```
- pub fn map<U>(&self, f: impl Fn(T) -> U) -> Size<U>
- where
- U: Clone + Default + Debug,
- {
- Size {
- width: f(self.width.clone()),
- height: f(self.height.clone()),
- }
- }
-}
-
-impl Size<Pixels> {
- /// Scales the size by a given factor.
- ///
- /// This method multiplies both the width and height by the provided scaling factor,
- /// resulting in a new `Size<ScaledPixels>` that is proportionally larger or smaller
- /// depending on the factor.
- ///
- /// # Arguments
- ///
- /// * `factor` - The scaling factor to apply to the width and height.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Size, Pixels, ScaledPixels};
- /// let size = Size { width: Pixels(100.0), height: Pixels(50.0) };
- /// let scaled_size = size.scale(2.0);
- /// assert_eq!(scaled_size, Size { width: ScaledPixels(200.0), height: ScaledPixels(100.0) });
- /// ```
- pub fn scale(&self, factor: f32) -> Size<ScaledPixels> {
- Size {
- width: self.width.scale(factor),
- height: self.height.scale(factor),
- }
- }
-}
-
-impl<T> Along for Size<T>
-where
- T: Clone + Default + Debug,
-{
- type Unit = T;
-
- fn along(&self, axis: Axis) -> T {
- match axis {
- Axis::Horizontal => self.width.clone(),
- Axis::Vertical => self.height.clone(),
- }
- }
-
- /// Returns the value of this size along the given axis.
- fn apply_along(&self, axis: Axis, f: impl FnOnce(T) -> T) -> Self {
- match axis {
- Axis::Horizontal => Size {
- width: f(self.width.clone()),
- height: self.height.clone(),
- },
- Axis::Vertical => Size {
- width: self.width.clone(),
- height: f(self.height.clone()),
- },
- }
- }
-}
-
-impl<T> Size<T>
-where
- T: PartialOrd + Clone + Default + Debug,
-{
- /// Returns a new `Size` with the maximum width and height from `self` and `other`.
- ///
- /// # Arguments
- ///
- /// * `other` - A reference to another `Size` to compare with `self`.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::Size;
- /// let size1 = Size { width: 30, height: 40 };
- /// let size2 = Size { width: 50, height: 20 };
- /// let max_size = size1.max(&size2);
- /// assert_eq!(max_size, Size { width: 50, height: 40 });
- /// ```
- pub fn max(&self, other: &Self) -> Self {
- Size {
- width: if self.width >= other.width {
- self.width.clone()
- } else {
- other.width.clone()
- },
- height: if self.height >= other.height {
- self.height.clone()
- } else {
- other.height.clone()
- },
- }
- }
-}
-
-impl<T> Sub for Size<T>
-where
- T: Sub<Output = T> + Clone + Default + Debug,
-{
- type Output = Size<T>;
-
- fn sub(self, rhs: Self) -> Self::Output {
- Size {
- width: self.width - rhs.width,
- height: self.height - rhs.height,
- }
- }
-}
-
-impl<T, Rhs> Mul<Rhs> for Size<T>
-where
- T: Mul<Rhs, Output = Rhs> + Clone + Default + Debug,
- Rhs: Clone + Default + Debug,
-{
- type Output = Size<Rhs>;
-
- fn mul(self, rhs: Rhs) -> Self::Output {
- Size {
- width: self.width * rhs.clone(),
- height: self.height * rhs,
- }
- }
-}
-
-impl<T, S> MulAssign<S> for Size<T>
-where
- T: Mul<S, Output = T> + Clone + Default + Debug,
- S: Clone,
-{
- fn mul_assign(&mut self, rhs: S) {
- self.width = self.width.clone() * rhs.clone();
- self.height = self.height.clone() * rhs;
- }
-}
-
-impl<T> Eq for Size<T> where T: Eq + Default + Debug + Clone {}
-
-impl<T> Debug for Size<T>
-where
- T: Clone + Default + Debug,
-{
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "Size {{ {:?} × {:?} }}", self.width, self.height)
- }
-}
-
-impl<T: Clone + Default + Debug> From<Point<T>> for Size<T> {
- fn from(point: Point<T>) -> Self {
- Self {
- width: point.x,
- height: point.y,
- }
- }
-}
-
-impl From<Size<Pixels>> for Size<GlobalPixels> {
- fn from(size: Size<Pixels>) -> Self {
- Size {
- width: GlobalPixels(size.width.0),
- height: GlobalPixels(size.height.0),
- }
- }
-}
-
-impl From<Size<Pixels>> for Size<DefiniteLength> {
- fn from(size: Size<Pixels>) -> Self {
- Size {
- width: size.width.into(),
- height: size.height.into(),
- }
- }
-}
-
-impl From<Size<Pixels>> for Size<AbsoluteLength> {
- fn from(size: Size<Pixels>) -> Self {
- Size {
- width: size.width.into(),
- height: size.height.into(),
- }
- }
-}
-
-impl Size<Length> {
- /// Returns a `Size` with both width and height set to fill the available space.
- ///
- /// This function creates a `Size` instance where both the width and height are set to `Length::Definite(DefiniteLength::Fraction(1.0))`,
- /// which represents 100% of the available space in both dimensions.
- ///
- /// # Returns
- ///
- /// A `Size<Length>` that will fill the available space when used in a layout.
- pub fn full() -> Self {
- Self {
- width: relative(1.).into(),
- height: relative(1.).into(),
- }
- }
-}
-
-impl Size<Length> {
- /// Returns a `Size` with both width and height set to `auto`, which allows the layout engine to determine the size.
- ///
- /// This function creates a `Size` instance where both the width and height are set to `Length::Auto`,
- /// indicating that their size should be computed based on the layout context, such as the content size or
- /// available space.
- ///
- /// # Returns
- ///
- /// A `Size<Length>` with width and height set to `Length::Auto`.
- pub fn auto() -> Self {
- Self {
- width: Length::Auto,
- height: Length::Auto,
- }
- }
-}
-
-/// Represents a rectangular area in a 2D space with an origin point and a size.
-///
-/// The `Bounds` struct is generic over a type `T` which represents the type of the coordinate system.
-/// The origin is represented as a `Point<T>` which defines the upper-left corner of the rectangle,
-/// and the size is represented as a `Size<T>` which defines the width and height of the rectangle.
-///
-/// # Examples
-///
-/// ```
-/// # use zed::{Bounds, Point, Size};
-/// let origin = Point { x: 0, y: 0 };
-/// let size = Size { width: 10, height: 20 };
-/// let bounds = Bounds::new(origin, size);
-///
-/// assert_eq!(bounds.origin, origin);
-/// assert_eq!(bounds.size, size);
-/// ```
-#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
-#[refineable(Debug)]
-#[repr(C)]
-pub struct Bounds<T: Clone + Default + Debug> {
- pub origin: Point<T>,
- pub size: Size<T>,
-}
-
-impl<T> Bounds<T>
-where
- T: Clone + Debug + Sub<Output = T> + Default,
-{
- /// Constructs a `Bounds` from two corner points: the upper-left and lower-right corners.
- ///
- /// This function calculates the origin and size of the `Bounds` based on the provided corner points.
- /// The origin is set to the upper-left corner, and the size is determined by the difference between
- /// the x and y coordinates of the lower-right and upper-left points.
- ///
- /// # Arguments
- ///
- /// * `upper_left` - A `Point<T>` representing the upper-left corner of the rectangle.
- /// * `lower_right` - A `Point<T>` representing the lower-right corner of the rectangle.
- ///
- /// # Returns
- ///
- /// Returns a `Bounds<T>` that encompasses the area defined by the two corner points.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Bounds, Point};
- /// let upper_left = Point { x: 0, y: 0 };
- /// let lower_right = Point { x: 10, y: 10 };
- /// let bounds = Bounds::from_corners(upper_left, lower_right);
- ///
- /// assert_eq!(bounds.origin, upper_left);
- /// assert_eq!(bounds.size.width, 10);
- /// assert_eq!(bounds.size.height, 10);
- /// ```
- pub fn from_corners(upper_left: Point<T>, lower_right: Point<T>) -> Self {
- let origin = Point {
- x: upper_left.x.clone(),
- y: upper_left.y.clone(),
- };
- let size = Size {
- width: lower_right.x - upper_left.x,
- height: lower_right.y - upper_left.y,
- };
- Bounds { origin, size }
- }
-
- /// Creates a new `Bounds` with the specified origin and size.
- ///
- /// # Arguments
- ///
- /// * `origin` - A `Point<T>` representing the origin of the bounds.
- /// * `size` - A `Size<T>` representing the size of the bounds.
- ///
- /// # Returns
- ///
- /// Returns a `Bounds<T>` that has the given origin and size.
- pub fn new(origin: Point<T>, size: Size<T>) -> Self {
- Bounds { origin, size }
- }
-}
-
-impl<T> Bounds<T>
-where
- T: Clone + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T> + Default + Half,
-{
- /// Checks if this `Bounds` intersects with another `Bounds`.
- ///
- /// Two `Bounds` instances intersect if they overlap in the 2D space they occupy.
- /// This method checks if there is any overlapping area between the two bounds.
- ///
- /// # Arguments
- ///
- /// * `other` - A reference to another `Bounds` to check for intersection with.
- ///
- /// # Returns
- ///
- /// Returns `true` if there is any intersection between the two bounds, `false` otherwise.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Bounds, Point, Size};
- /// let bounds1 = Bounds {
- /// origin: Point { x: 0, y: 0 },
- /// size: Size { width: 10, height: 10 },
- /// };
- /// let bounds2 = Bounds {
- /// origin: Point { x: 5, y: 5 },
- /// size: Size { width: 10, height: 10 },
- /// };
- /// let bounds3 = Bounds {
- /// origin: Point { x: 20, y: 20 },
- /// size: Size { width: 10, height: 10 },
- /// };
- ///
- /// assert_eq!(bounds1.intersects(&bounds2), true); // Overlapping bounds
- /// assert_eq!(bounds1.intersects(&bounds3), false); // Non-overlapping bounds
- /// ```
- pub fn intersects(&self, other: &Bounds<T>) -> bool {
- let my_lower_right = self.lower_right();
- let their_lower_right = other.lower_right();
-
- self.origin.x < their_lower_right.x
- && my_lower_right.x > other.origin.x
- && self.origin.y < their_lower_right.y
- && my_lower_right.y > other.origin.y
- }
-
- /// Dilates the bounds by a specified amount in all directions.
- ///
- /// This method expands the bounds by the given `amount`, increasing the size
- /// and adjusting the origin so that the bounds grow outwards equally in all directions.
- /// The resulting bounds will have its width and height increased by twice the `amount`
- /// (since it grows in both directions), and the origin will be moved by `-amount`
- /// in both the x and y directions.
- ///
- /// # Arguments
- ///
- /// * `amount` - The amount by which to dilate the bounds.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Bounds, Point, Size};
- /// let mut bounds = Bounds {
- /// origin: Point { x: 10, y: 10 },
- /// size: Size { width: 10, height: 10 },
- /// };
- /// bounds.dilate(5);
- /// assert_eq!(bounds, Bounds {
- /// origin: Point { x: 5, y: 5 },
- /// size: Size { width: 20, height: 20 },
- /// });
- /// ```
- pub fn dilate(&mut self, amount: T) {
- self.origin.x = self.origin.x.clone() - amount.clone();
- self.origin.y = self.origin.y.clone() - amount.clone();
- let double_amount = amount.clone() + amount;
- self.size.width = self.size.width.clone() + double_amount.clone();
- self.size.height = self.size.height.clone() + double_amount;
- }
-
- /// Returns the center point of the bounds.
- ///
- /// Calculates the center by taking the origin's x and y coordinates and adding half the width and height
- /// of the bounds, respectively. The center is represented as a `Point<T>` where `T` is the type of the
- /// coordinate system.
- ///
- /// # Returns
- ///
- /// A `Point<T>` representing the center of the bounds.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Bounds, Point, Size};
- /// let bounds = Bounds {
- /// origin: Point { x: 0, y: 0 },
- /// size: Size { width: 10, height: 20 },
- /// };
- /// let center = bounds.center();
- /// assert_eq!(center, Point { x: 5, y: 10 });
- /// ```
- pub fn center(&self) -> Point<T> {
- Point {
- x: self.origin.x.clone() + self.size.width.clone().half(),
- y: self.origin.y.clone() + self.size.height.clone().half(),
- }
- }
-}
-
-impl<T: Clone + Default + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
- /// Calculates the intersection of two `Bounds` objects.
- ///
- /// This method computes the overlapping region of two `Bounds`. If the bounds do not intersect,
- /// the resulting `Bounds` will have a size with width and height of zero.
- ///
- /// # Arguments
- ///
- /// * `other` - A reference to another `Bounds` to intersect with.
- ///
- /// # Returns
- ///
- /// Returns a `Bounds` representing the intersection area. If there is no intersection,
- /// the returned `Bounds` will have a size with width and height of zero.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Bounds, Point, Size};
- /// let bounds1 = Bounds {
- /// origin: Point { x: 0, y: 0 },
- /// size: Size { width: 10, height: 10 },
- /// };
- /// let bounds2 = Bounds {
- /// origin: Point { x: 5, y: 5 },
- /// size: Size { width: 10, height: 10 },
- /// };
- /// let intersection = bounds1.intersect(&bounds2);
- ///
- /// assert_eq!(intersection, Bounds {
- /// origin: Point { x: 5, y: 5 },
- /// size: Size { width: 5, height: 5 },
- /// });
- /// ```
- pub fn intersect(&self, other: &Self) -> Self {
- let upper_left = self.origin.max(&other.origin);
- let lower_right = self.lower_right().min(&other.lower_right());
- Self::from_corners(upper_left, lower_right)
- }
-
- /// Computes the union of two `Bounds`.
- ///
- /// This method calculates the smallest `Bounds` that contains both the current `Bounds` and the `other` `Bounds`.
- /// The resulting `Bounds` will have an origin that is the minimum of the origins of the two `Bounds`,
- /// and a size that encompasses the furthest extents of both `Bounds`.
- ///
- /// # Arguments
- ///
- /// * `other` - A reference to another `Bounds` to create a union with.
- ///
- /// # Returns
- ///
- /// Returns a `Bounds` representing the union of the two `Bounds`.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Bounds, Point, Size};
- /// let bounds1 = Bounds {
- /// origin: Point { x: 0, y: 0 },
- /// size: Size { width: 10, height: 10 },
- /// };
- /// let bounds2 = Bounds {
- /// origin: Point { x: 5, y: 5 },
- /// size: Size { width: 15, height: 15 },
- /// };
- /// let union_bounds = bounds1.union(&bounds2);
- ///
- /// assert_eq!(union_bounds, Bounds {
- /// origin: Point { x: 0, y: 0 },
- /// size: Size { width: 20, height: 20 },
- /// });
- /// ```
- pub fn union(&self, other: &Self) -> Self {
- let top_left = self.origin.min(&other.origin);
- let bottom_right = self.lower_right().max(&other.lower_right());
- Bounds::from_corners(top_left, bottom_right)
- }
-}
-
-impl<T, Rhs> Mul<Rhs> for Bounds<T>
-where
- T: Mul<Rhs, Output = Rhs> + Clone + Default + Debug,
- Point<T>: Mul<Rhs, Output = Point<Rhs>>,
- Rhs: Clone + Default + Debug,
-{
- type Output = Bounds<Rhs>;
-
- fn mul(self, rhs: Rhs) -> Self::Output {
- Bounds {
- origin: self.origin * rhs.clone(),
- size: self.size * rhs,
- }
- }
-}
-
-impl<T, S> MulAssign<S> for Bounds<T>
-where
- T: Mul<S, Output = T> + Clone + Default + Debug,
- S: Clone,
-{
- fn mul_assign(&mut self, rhs: S) {
- self.origin *= rhs.clone();
- self.size *= rhs;
- }
-}
-
-impl<T, S> Div<S> for Bounds<T>
-where
- Size<T>: Div<S, Output = Size<T>>,
- T: Div<S, Output = T> + Default + Clone + Debug,
- S: Clone,
-{
- type Output = Self;
-
- fn div(self, rhs: S) -> Self {
- Self {
- origin: self.origin / rhs.clone(),
- size: self.size / rhs,
- }
- }
-}
-
-impl<T> Bounds<T>
-where
- T: Add<T, Output = T> + Clone + Default + Debug,
-{
- /// Returns the top edge of the bounds.
- ///
- /// # Returns
- ///
- /// A value of type `T` representing the y-coordinate of the top edge of the bounds.
- pub fn top(&self) -> T {
- self.origin.y.clone()
- }
-
- /// Returns the bottom edge of the bounds.
- ///
- /// # Returns
- ///
- /// A value of type `T` representing the y-coordinate of the bottom edge of the bounds.
- pub fn bottom(&self) -> T {
- self.origin.y.clone() + self.size.height.clone()
- }
-
- /// Returns the left edge of the bounds.
- ///
- /// # Returns
- ///
- /// A value of type `T` representing the x-coordinate of the left edge of the bounds.
- pub fn left(&self) -> T {
- self.origin.x.clone()
- }
-
- /// Returns the right edge of the bounds.
- ///
- /// # Returns
- ///
- /// A value of type `T` representing the x-coordinate of the right edge of the bounds.
- pub fn right(&self) -> T {
- self.origin.x.clone() + self.size.width.clone()
- }
-
- /// Returns the upper-right corner point of the bounds.
- ///
- /// # Returns
- ///
- /// A `Point<T>` representing the upper-right corner of the bounds.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Bounds, Point, Size};
- /// let bounds = Bounds {
- /// origin: Point { x: 0, y: 0 },
- /// size: Size { width: 10, height: 20 },
- /// };
- /// let upper_right = bounds.upper_right();
- /// assert_eq!(upper_right, Point { x: 10, y: 0 });
- /// ```
- pub fn upper_right(&self) -> Point<T> {
- Point {
- x: self.origin.x.clone() + self.size.width.clone(),
- y: self.origin.y.clone(),
- }
- }
-
- /// Returns the lower-right corner point of the bounds.
- ///
- /// # Returns
- ///
- /// A `Point<T>` representing the lower-right corner of the bounds.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Bounds, Point, Size};
- /// let bounds = Bounds {
- /// origin: Point { x: 0, y: 0 },
- /// size: Size { width: 10, height: 20 },
- /// };
- /// let lower_right = bounds.lower_right();
- /// assert_eq!(lower_right, Point { x: 10, y: 20 });
- /// ```
- pub fn lower_right(&self) -> Point<T> {
- Point {
- x: self.origin.x.clone() + self.size.width.clone(),
- y: self.origin.y.clone() + self.size.height.clone(),
- }
- }
-
- /// Returns the lower-left corner point of the bounds.
- ///
- /// # Returns
- ///
- /// A `Point<T>` representing the lower-left corner of the bounds.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Bounds, Point, Size};
- /// let bounds = Bounds {
- /// origin: Point { x: 0, y: 0 },
- /// size: Size { width: 10, height: 20 },
- /// };
- /// let lower_left = bounds.lower_left();
- /// assert_eq!(lower_left, Point { x: 0, y: 20 });
- /// ```
- pub fn lower_left(&self) -> Point<T> {
- Point {
- x: self.origin.x.clone(),
- y: self.origin.y.clone() + self.size.height.clone(),
- }
- }
-}
-
-impl<T> Bounds<T>
-where
- T: Add<T, Output = T> + PartialOrd + Clone + Default + Debug,
-{
- /// Checks if the given point is within the bounds.
- ///
- /// This method determines whether a point lies inside the rectangle defined by the bounds,
- /// including the edges. The point is considered inside if its x-coordinate is greater than
- /// or equal to the left edge and less than or equal to the right edge, and its y-coordinate
- /// is greater than or equal to the top edge and less than or equal to the bottom edge of the bounds.
- ///
- /// # Arguments
- ///
- /// * `point` - A reference to a `Point<T>` that represents the point to check.
- ///
- /// # Returns
- ///
- /// Returns `true` if the point is within the bounds, `false` otherwise.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Point, Bounds};
- /// let bounds = Bounds {
- /// origin: Point { x: 0, y: 0 },
- /// size: Size { width: 10, height: 10 },
- /// };
- /// let inside_point = Point { x: 5, y: 5 };
- /// let outside_point = Point { x: 15, y: 15 };
- ///
- /// assert!(bounds.contains_point(&inside_point));
- /// assert!(!bounds.contains_point(&outside_point));
- /// ```
- pub fn contains(&self, point: &Point<T>) -> bool {
- point.x >= self.origin.x
- && point.x <= self.origin.x.clone() + self.size.width.clone()
- && point.y >= self.origin.y
- && point.y <= self.origin.y.clone() + self.size.height.clone()
- }
-
- /// Applies a function to the origin and size of the bounds, producing a new `Bounds<U>`.
- ///
- /// This method allows for converting a `Bounds<T>` to a `Bounds<U>` by specifying a closure
- /// that defines how to convert between the two types. The closure is applied to the `origin` and
- /// `size` fields, resulting in new bounds of the desired type.
- ///
- /// # Arguments
- ///
- /// * `f` - A closure that takes a value of type `T` and returns a value of type `U`.
- ///
- /// # Returns
- ///
- /// Returns a new `Bounds<U>` with the origin and size mapped by the provided function.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Bounds, Point, Size};
- /// let bounds = Bounds {
- /// origin: Point { x: 10.0, y: 10.0 },
- /// size: Size { width: 10.0, height: 20.0 },
- /// };
- /// let new_bounds = bounds.map(|value| value as f64 * 1.5);
- ///
- /// assert_eq!(new_bounds, Bounds {
- /// origin: Point { x: 15.0, y: 15.0 },
- /// size: Size { width: 15.0, height: 30.0 },
- /// });
- pub fn map<U>(&self, f: impl Fn(T) -> U) -> Bounds<U>
- where
- U: Clone + Default + Debug,
- {
- Bounds {
- origin: self.origin.map(&f),
- size: self.size.map(f),
- }
- }
-}
-
-impl Bounds<Pixels> {
- /// Scales the bounds by a given factor, typically used to adjust for display scaling.
- ///
- /// This method multiplies the origin and size of the bounds by the provided scaling factor,
- /// resulting in a new `Bounds<ScaledPixels>` that is proportionally larger or smaller
- /// depending on the scaling factor. This can be used to ensure that the bounds are properly
- /// scaled for different display densities.
- ///
- /// # Arguments
- ///
- /// * `factor` - The scaling factor to apply to the origin and size, typically the display's scaling factor.
- ///
- /// # Returns
- ///
- /// Returns a new `Bounds<ScaledPixels>` that represents the scaled bounds.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Bounds, Point, Size, Pixels};
- /// let bounds = Bounds {
- /// origin: Point { x: Pixels(10.0), y: Pixels(20.0) },
- /// size: Size { width: Pixels(30.0), height: Pixels(40.0) },
- /// };
- /// let display_scale_factor = 2.0;
- /// let scaled_bounds = bounds.scale(display_scale_factor);
- /// assert_eq!(scaled_bounds, Bounds {
- /// origin: Point { x: ScaledPixels(20.0), y: ScaledPixels(40.0) },
- /// size: Size { width: ScaledPixels(60.0), height: ScaledPixels(80.0) },
- /// });
- /// ```
- pub fn scale(&self, factor: f32) -> Bounds<ScaledPixels> {
- Bounds {
- origin: self.origin.scale(factor),
- size: self.size.scale(factor),
- }
- }
-}
-
-impl<T: Clone + Debug + Copy + Default> Copy for Bounds<T> {}
-
-/// Represents the edges of a box in a 2D space, such as padding or margin.
-///
-/// Each field represents the size of the edge on one side of the box: `top`, `right`, `bottom`, and `left`.
-///
-/// # Examples
-///
-/// ```
-/// # use zed::Edges;
-/// let edges = Edges {
-/// top: 10.0,
-/// right: 20.0,
-/// bottom: 30.0,
-/// left: 40.0,
-/// };
-///
-/// assert_eq!(edges.top, 10.0);
-/// assert_eq!(edges.right, 20.0);
-/// assert_eq!(edges.bottom, 30.0);
-/// assert_eq!(edges.left, 40.0);
-/// ```
-#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
-#[refineable(Debug)]
-#[repr(C)]
-pub struct Edges<T: Clone + Default + Debug> {
- pub top: T,
- pub right: T,
- pub bottom: T,
- pub left: T,
-}
-
-impl<T> Mul for Edges<T>
-where
- T: Mul<Output = T> + Clone + Default + Debug,
-{
- type Output = Self;
-
- fn mul(self, rhs: Self) -> Self::Output {
- Self {
- top: self.top.clone() * rhs.top,
- right: self.right.clone() * rhs.right,
- bottom: self.bottom.clone() * rhs.bottom,
- left: self.left.clone() * rhs.left,
- }
- }
-}
-
-impl<T, S> MulAssign<S> for Edges<T>
-where
- T: Mul<S, Output = T> + Clone + Default + Debug,
- S: Clone,
-{
- fn mul_assign(&mut self, rhs: S) {
- self.top = self.top.clone() * rhs.clone();
- self.right = self.right.clone() * rhs.clone();
- self.bottom = self.bottom.clone() * rhs.clone();
- self.left = self.left.clone() * rhs;
- }
-}
-
-impl<T: Clone + Default + Debug + Copy> Copy for Edges<T> {}
-
-impl<T: Clone + Default + Debug> Edges<T> {
- /// Constructs `Edges` where all sides are set to the same specified value.
- ///
- /// This function creates an `Edges` instance with the `top`, `right`, `bottom`, and `left` fields all initialized
- /// to the same value provided as an argument. This is useful when you want to have uniform edges around a box,
- /// such as padding or margin with the same size on all sides.
- ///
- /// # Arguments
- ///
- /// * `value` - The value to set for all four sides of the edges.
- ///
- /// # Returns
- ///
- /// An `Edges` instance with all sides set to the given value.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::Edges;
- /// let uniform_edges = Edges::all(10.0);
- /// assert_eq!(uniform_edges.top, 10.0);
- /// assert_eq!(uniform_edges.right, 10.0);
- /// assert_eq!(uniform_edges.bottom, 10.0);
- /// assert_eq!(uniform_edges.left, 10.0);
- /// ```
- pub fn all(value: T) -> Self {
- Self {
- top: value.clone(),
- right: value.clone(),
- bottom: value.clone(),
- left: value,
- }
- }
-
- /// Applies a function to each field of the `Edges`, producing a new `Edges<U>`.
- ///
- /// This method allows for converting an `Edges<T>` to an `Edges<U>` by specifying a closure
- /// that defines how to convert between the two types. The closure is applied to each field
- /// (`top`, `right`, `bottom`, `left`), resulting in new edges of the desired type.
- ///
- /// # Arguments
- ///
- /// * `f` - A closure that takes a reference to a value of type `T` and returns a value of type `U`.
- ///
- /// # Returns
- ///
- /// Returns a new `Edges<U>` with each field mapped by the provided function.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::Edges;
- /// let edges = Edges { top: 10, right: 20, bottom: 30, left: 40 };
- /// let edges_float = edges.map(|&value| value as f32 * 1.1);
- /// assert_eq!(edges_float, Edges { top: 11.0, right: 22.0, bottom: 33.0, left: 44.0 });
- /// ```
- pub fn map<U>(&self, f: impl Fn(&T) -> U) -> Edges<U>
- where
- U: Clone + Default + Debug,
- {
- Edges {
- top: f(&self.top),
- right: f(&self.right),
- bottom: f(&self.bottom),
- left: f(&self.left),
- }
- }
-
- /// Checks if any of the edges satisfy a given predicate.
- ///
- /// This method applies a predicate function to each field of the `Edges` and returns `true` if any field satisfies the predicate.
- ///
- /// # Arguments
- ///
- /// * `predicate` - A closure that takes a reference to a value of type `T` and returns a `bool`.
- ///
- /// # Returns
- ///
- /// Returns `true` if the predicate returns `true` for any of the edge values, `false` otherwise.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::Edges;
- /// let edges = Edges {
- /// top: 10,
- /// right: 0,
- /// bottom: 5,
- /// left: 0,
- /// };
- ///
- /// assert!(edges.any(|value| *value == 0));
- /// assert!(edges.any(|value| *value > 0));
- /// assert!(!edges.any(|value| *value > 10));
- /// ```
- pub fn any<F: Fn(&T) -> bool>(&self, predicate: F) -> bool {
- predicate(&self.top)
- || predicate(&self.right)
- || predicate(&self.bottom)
- || predicate(&self.left)
- }
-}
-
-impl Edges<Length> {
- /// Sets the edges of the `Edges` struct to `auto`, which is a special value that allows the layout engine to automatically determine the size of the edges.
- ///
- /// This is typically used in layout contexts where the exact size of the edges is not important, or when the size should be calculated based on the content or container.
- ///
- /// # Returns
- ///
- /// Returns an `Edges<Length>` with all edges set to `Length::Auto`.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::Edges;
- /// let auto_edges = Edges::auto();
- /// assert_eq!(auto_edges.top, Length::Auto);
- /// assert_eq!(auto_edges.right, Length::Auto);
- /// assert_eq!(auto_edges.bottom, Length::Auto);
- /// assert_eq!(auto_edges.left, Length::Auto);
- /// ```
- pub fn auto() -> Self {
- Self {
- top: Length::Auto,
- right: Length::Auto,
- bottom: Length::Auto,
- left: Length::Auto,
- }
- }
-
- /// Sets the edges of the `Edges` struct to zero, which means no size or thickness.
- ///
- /// This is typically used when you want to specify that a box (like a padding or margin area)
- /// should have no edges, effectively making it non-existent or invisible in layout calculations.
- ///
- /// # Returns
- ///
- /// Returns an `Edges<Length>` with all edges set to zero length.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::Edges;
- /// let no_edges = Edges::zero();
- /// assert_eq!(no_edges.top, Length::Definite(DefiniteLength::from(Pixels(0.))));
- /// assert_eq!(no_edges.right, Length::Definite(DefiniteLength::from(Pixels(0.))));
- /// assert_eq!(no_edges.bottom, Length::Definite(DefiniteLength::from(Pixels(0.))));
- /// assert_eq!(no_edges.left, Length::Definite(DefiniteLength::from(Pixels(0.))));
- /// ```
- pub fn zero() -> Self {
- Self {
- top: px(0.).into(),
- right: px(0.).into(),
- bottom: px(0.).into(),
- left: px(0.).into(),
- }
- }
-}
-
-impl Edges<DefiniteLength> {
- /// Sets the edges of the `Edges` struct to zero, which means no size or thickness.
- ///
- /// This is typically used when you want to specify that a box (like a padding or margin area)
- /// should have no edges, effectively making it non-existent or invisible in layout calculations.
- ///
- /// # Returns
- ///
- /// Returns an `Edges<DefiniteLength>` with all edges set to zero length.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::Edges;
- /// let no_edges = Edges::zero();
- /// assert_eq!(no_edges.top, DefiniteLength::from(zed::px(0.)));
- /// assert_eq!(no_edges.right, DefiniteLength::from(zed::px(0.)));
- /// assert_eq!(no_edges.bottom, DefiniteLength::from(zed::px(0.)));
- /// assert_eq!(no_edges.left, DefiniteLength::from(zed::px(0.)));
- /// ```
- pub fn zero() -> Self {
- Self {
- top: px(0.).into(),
- right: px(0.).into(),
- bottom: px(0.).into(),
- left: px(0.).into(),
- }
- }
-
- /// Converts the `DefiniteLength` to `Pixels` based on the parent size and the REM size.
- ///
- /// This method allows for a `DefiniteLength` value to be converted into pixels, taking into account
- /// the size of the parent element (for percentage-based lengths) and the size of a rem unit (for rem-based lengths).
- ///
- /// # Arguments
- ///
- /// * `parent_size` - `Size<AbsoluteLength>` representing the size of the parent element.
- /// * `rem_size` - `Pixels` representing the size of one REM unit.
- ///
- /// # Returns
- ///
- /// Returns an `Edges<Pixels>` representing the edges with lengths converted to pixels.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Edges, DefiniteLength, px, AbsoluteLength, Size};
- /// let edges = Edges {
- /// top: DefiniteLength::Absolute(AbsoluteLength::Pixels(px(10.0))),
- /// right: DefiniteLength::Fraction(0.5),
- /// bottom: DefiniteLength::Absolute(AbsoluteLength::Rems(rems(2.0))),
- /// left: DefiniteLength::Fraction(0.25),
- /// };
- /// let parent_size = Size {
- /// width: AbsoluteLength::Pixels(px(200.0)),
- /// height: AbsoluteLength::Pixels(px(100.0)),
- /// };
- /// let rem_size = px(16.0);
- /// let edges_in_pixels = edges.to_pixels(parent_size, rem_size);
- ///
- /// assert_eq!(edges_in_pixels.top, px(10.0)); // Absolute length in pixels
- /// assert_eq!(edges_in_pixels.right, px(100.0)); // 50% of parent width
- /// assert_eq!(edges_in_pixels.bottom, px(32.0)); // 2 rems
- /// assert_eq!(edges_in_pixels.left, px(50.0)); // 25% of parent width
- /// ```
- pub fn to_pixels(&self, parent_size: Size<AbsoluteLength>, rem_size: Pixels) -> Edges<Pixels> {
- Edges {
- top: self.top.to_pixels(parent_size.height, rem_size),
- right: self.right.to_pixels(parent_size.width, rem_size),
- bottom: self.bottom.to_pixels(parent_size.height, rem_size),
- left: self.left.to_pixels(parent_size.width, rem_size),
- }
- }
-}
-
-impl Edges<AbsoluteLength> {
- /// Sets the edges of the `Edges` struct to zero, which means no size or thickness.
- ///
- /// This is typically used when you want to specify that a box (like a padding or margin area)
- /// should have no edges, effectively making it non-existent or invisible in layout calculations.
- ///
- /// # Returns
- ///
- /// Returns an `Edges<AbsoluteLength>` with all edges set to zero length.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::Edges;
- /// let no_edges = Edges::zero();
- /// assert_eq!(no_edges.top, AbsoluteLength::Pixels(Pixels(0.0)));
- /// assert_eq!(no_edges.right, AbsoluteLength::Pixels(Pixels(0.0)));
- /// assert_eq!(no_edges.bottom, AbsoluteLength::Pixels(Pixels(0.0)));
- /// assert_eq!(no_edges.left, AbsoluteLength::Pixels(Pixels(0.0)));
- /// ```
- pub fn zero() -> Self {
- Self {
- top: px(0.).into(),
- right: px(0.).into(),
- bottom: px(0.).into(),
- left: px(0.).into(),
- }
- }
-
- /// Converts the `AbsoluteLength` to `Pixels` based on the `rem_size`.
- ///
- /// If the `AbsoluteLength` is already in pixels, it simply returns the corresponding `Pixels` value.
- /// If the `AbsoluteLength` is in rems, it multiplies the number of rems by the `rem_size` to convert it to pixels.
- ///
- /// # Arguments
- ///
- /// * `rem_size` - The size of one rem unit in pixels.
- ///
- /// # Returns
- ///
- /// Returns an `Edges<Pixels>` representing the edges with lengths converted to pixels.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Edges, AbsoluteLength, Pixels, px};
- /// let edges = Edges {
- /// top: AbsoluteLength::Pixels(px(10.0)),
- /// right: AbsoluteLength::Rems(rems(1.0)),
- /// bottom: AbsoluteLength::Pixels(px(20.0)),
- /// left: AbsoluteLength::Rems(rems(2.0)),
- /// };
- /// let rem_size = px(16.0);
- /// let edges_in_pixels = edges.to_pixels(rem_size);
- ///
- /// assert_eq!(edges_in_pixels.top, px(10.0)); // Already in pixels
- /// assert_eq!(edges_in_pixels.right, px(16.0)); // 1 rem converted to pixels
- /// assert_eq!(edges_in_pixels.bottom, px(20.0)); // Already in pixels
- /// assert_eq!(edges_in_pixels.left, px(32.0)); // 2 rems converted to pixels
- /// ```
- pub fn to_pixels(&self, rem_size: Pixels) -> Edges<Pixels> {
- Edges {
- top: self.top.to_pixels(rem_size),
- right: self.right.to_pixels(rem_size),
- bottom: self.bottom.to_pixels(rem_size),
- left: self.left.to_pixels(rem_size),
- }
- }
-}
-
-impl Edges<Pixels> {
- /// Scales the `Edges<Pixels>` by a given factor, returning `Edges<ScaledPixels>`.
- ///
- /// This method is typically used for adjusting the edge sizes for different display densities or scaling factors.
- ///
- /// # Arguments
- ///
- /// * `factor` - The scaling factor to apply to each edge.
- ///
- /// # Returns
- ///
- /// Returns a new `Edges<ScaledPixels>` where each edge is the result of scaling the original edge by the given factor.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Edges, Pixels};
- /// let edges = Edges {
- /// top: Pixels(10.0),
- /// right: Pixels(20.0),
- /// bottom: Pixels(30.0),
- /// left: Pixels(40.0),
- /// };
- /// let scaled_edges = edges.scale(2.0);
- /// assert_eq!(scaled_edges.top, ScaledPixels(20.0));
- /// assert_eq!(scaled_edges.right, ScaledPixels(40.0));
- /// assert_eq!(scaled_edges.bottom, ScaledPixels(60.0));
- /// assert_eq!(scaled_edges.left, ScaledPixels(80.0));
- /// ```
- pub fn scale(&self, factor: f32) -> Edges<ScaledPixels> {
- Edges {
- top: self.top.scale(factor),
- right: self.right.scale(factor),
- bottom: self.bottom.scale(factor),
- left: self.left.scale(factor),
- }
- }
-
- /// Returns the maximum value of any edge.
- ///
- /// # Returns
- ///
- /// The maximum `Pixels` value among all four edges.
- pub fn max(&self) -> Pixels {
- self.top.max(self.right).max(self.bottom).max(self.left)
- }
-}
-
-impl From<f32> for Edges<Pixels> {
- fn from(val: f32) -> Self {
- Edges {
- top: val.into(),
- right: val.into(),
- bottom: val.into(),
- left: val.into(),
- }
- }
-}
-
-/// Represents the corners of a box in a 2D space, such as border radius.
-///
-/// Each field represents the size of the corner on one side of the box: `top_left`, `top_right`, `bottom_right`, and `bottom_left`.
-/// ```
-#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
-#[refineable(Debug)]
-#[repr(C)]
-pub struct Corners<T: Clone + Default + Debug> {
- pub top_left: T,
- pub top_right: T,
- pub bottom_right: T,
- pub bottom_left: T,
-}
-
-impl<T> Corners<T>
-where
- T: Clone + Default + Debug,
-{
- /// Constructs `Corners` where all sides are set to the same specified value.
- ///
- /// This function creates a `Corners` instance with the `top_left`, `top_right`, `bottom_right`, and `bottom_left` fields all initialized
- /// to the same value provided as an argument. This is useful when you want to have uniform corners around a box,
- /// such as a uniform border radius on a rectangle.
- ///
- /// # Arguments
- ///
- /// * `value` - The value to set for all four corners.
- ///
- /// # Returns
- ///
- /// An `Corners` instance with all corners set to the given value.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::Corners;
- /// let uniform_corners = Corners::all(5.0);
- /// assert_eq!(uniform_corners.top_left, 5.0);
- /// assert_eq!(uniform_corners.top_right, 5.0);
- /// assert_eq!(uniform_corners.bottom_right, 5.0);
- /// assert_eq!(uniform_corners.bottom_left, 5.0);
- /// ```
- pub fn all(value: T) -> Self {
- Self {
- top_left: value.clone(),
- top_right: value.clone(),
- bottom_right: value.clone(),
- bottom_left: value,
- }
- }
-}
-
-impl Corners<AbsoluteLength> {
- /// Converts the `AbsoluteLength` to `Pixels` based on the provided size and rem size, ensuring the resulting
- /// `Pixels` do not exceed half of the maximum of the provided size's width and height.
- ///
- /// This method is particularly useful when dealing with corner radii, where the radius in pixels should not
- /// exceed half the size of the box it applies to, to avoid the corners overlapping.
- ///
- /// # Arguments
- ///
- /// * `size` - The `Size<Pixels>` against which the maximum allowable radius is determined.
- /// * `rem_size` - The size of one REM unit in pixels, used for conversion if the `AbsoluteLength` is in REMs.
- ///
- /// # Returns
- ///
- /// Returns a `Corners<Pixels>` instance with each corner's length converted to pixels and clamped to the
- /// maximum allowable radius based on the provided size.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Corners, AbsoluteLength, Pixels, Size};
- /// let corners = Corners {
- /// top_left: AbsoluteLength::Pixels(Pixels(15.0)),
- /// top_right: AbsoluteLength::Rems(Rems(1.0)),
- /// bottom_right: AbsoluteLength::Pixels(Pixels(20.0)),
- /// bottom_left: AbsoluteLength::Rems(Rems(2.0)),
- /// };
- /// let size = Size { width: Pixels(100.0), height: Pixels(50.0) };
- /// let rem_size = Pixels(16.0);
- /// let corners_in_pixels = corners.to_pixels(size, rem_size);
- ///
- /// // The resulting corners should not exceed half the size of the smallest dimension (50.0 / 2.0 = 25.0).
- /// assert_eq!(corners_in_pixels.top_left, Pixels(15.0));
- /// assert_eq!(corners_in_pixels.top_right, Pixels(16.0)); // 1 rem converted to pixels
- /// assert_eq!(corners_in_pixels.bottom_right, Pixels(20.0).min(Pixels(25.0))); // Clamped to 25.0
- /// assert_eq!(corners_in_pixels.bottom_left, Pixels(32.0).min(Pixels(25.0))); // 2 rems converted to pixels and clamped
- /// ```
- pub fn to_pixels(&self, size: Size<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
- let max = size.width.max(size.height) / 2.;
- Corners {
- top_left: self.top_left.to_pixels(rem_size).min(max),
- top_right: self.top_right.to_pixels(rem_size).min(max),
- bottom_right: self.bottom_right.to_pixels(rem_size).min(max),
- bottom_left: self.bottom_left.to_pixels(rem_size).min(max),
- }
- }
-}
-
-impl Corners<Pixels> {
- /// Scales the `Corners<Pixels>` by a given factor, returning `Corners<ScaledPixels>`.
- ///
- /// This method is typically used for adjusting the corner sizes for different display densities or scaling factors.
- ///
- /// # Arguments
- ///
- /// * `factor` - The scaling factor to apply to each corner.
- ///
- /// # Returns
- ///
- /// Returns a new `Corners<ScaledPixels>` where each corner is the result of scaling the original corner by the given factor.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Corners, Pixels};
- /// let corners = Corners {
- /// top_left: Pixels(10.0),
- /// top_right: Pixels(20.0),
- /// bottom_right: Pixels(30.0),
- /// bottom_left: Pixels(40.0),
- /// };
- /// let scaled_corners = corners.scale(2.0);
- /// assert_eq!(scaled_corners.top_left, ScaledPixels(20.0));
- /// assert_eq!(scaled_corners.top_right, ScaledPixels(40.0));
- /// assert_eq!(scaled_corners.bottom_right, ScaledPixels(60.0));
- /// assert_eq!(scaled_corners.bottom_left, ScaledPixels(80.0));
- /// ```
- pub fn scale(&self, factor: f32) -> Corners<ScaledPixels> {
- Corners {
- top_left: self.top_left.scale(factor),
- top_right: self.top_right.scale(factor),
- bottom_right: self.bottom_right.scale(factor),
- bottom_left: self.bottom_left.scale(factor),
- }
- }
-
- /// Returns the maximum value of any corner.
- ///
- /// # Returns
- ///
- /// The maximum `Pixels` value among all four corners.
- pub fn max(&self) -> Pixels {
- self.top_left
- .max(self.top_right)
- .max(self.bottom_right)
- .max(self.bottom_left)
- }
-}
-
-impl<T: Clone + Default + Debug> Corners<T> {
- /// Applies a function to each field of the `Corners`, producing a new `Corners<U>`.
- ///
- /// This method allows for converting a `Corners<T>` to a `Corners<U>` by specifying a closure
- /// that defines how to convert between the two types. The closure is applied to each field
- /// (`top_left`, `top_right`, `bottom_right`, `bottom_left`), resulting in new corners of the desired type.
- ///
- /// # Arguments
- ///
- /// * `f` - A closure that takes a reference to a value of type `T` and returns a value of type `U`.
- ///
- /// # Returns
- ///
- /// Returns a new `Corners<U>` with each field mapped by the provided function.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{Corners, Pixels};
- /// let corners = Corners {
- /// top_left: Pixels(10.0),
- /// top_right: Pixels(20.0),
- /// bottom_right: Pixels(30.0),
- /// bottom_left: Pixels(40.0),
- /// };
- /// let corners_in_rems = corners.map(|&px| Rems(px.0 / 16.0));
- /// assert_eq!(corners_in_rems, Corners {
- /// top_left: Rems(0.625),
- /// top_right: Rems(1.25),
- /// bottom_right: Rems(1.875),
- /// bottom_left: Rems(2.5),
- /// });
- /// ```
- pub fn map<U>(&self, f: impl Fn(&T) -> U) -> Corners<U>
- where
- U: Clone + Default + Debug,
- {
- Corners {
- top_left: f(&self.top_left),
- top_right: f(&self.top_right),
- bottom_right: f(&self.bottom_right),
- bottom_left: f(&self.bottom_left),
- }
- }
-}
-
-impl<T> Mul for Corners<T>
-where
- T: Mul<Output = T> + Clone + Default + Debug,
-{
- type Output = Self;
-
- fn mul(self, rhs: Self) -> Self::Output {
- Self {
- top_left: self.top_left.clone() * rhs.top_left,
- top_right: self.top_right.clone() * rhs.top_right,
- bottom_right: self.bottom_right.clone() * rhs.bottom_right,
- bottom_left: self.bottom_left.clone() * rhs.bottom_left,
- }
- }
-}
-
-impl<T, S> MulAssign<S> for Corners<T>
-where
- T: Mul<S, Output = T> + Clone + Default + Debug,
- S: Clone,
-{
- fn mul_assign(&mut self, rhs: S) {
- self.top_left = self.top_left.clone() * rhs.clone();
- self.top_right = self.top_right.clone() * rhs.clone();
- self.bottom_right = self.bottom_right.clone() * rhs.clone();
- self.bottom_left = self.bottom_left.clone() * rhs;
- }
-}
-
-impl<T> Copy for Corners<T> where T: Copy + Clone + Default + Debug {}
-
-impl From<f32> for Corners<Pixels> {
- fn from(val: f32) -> Self {
- Corners {
- top_left: val.into(),
- top_right: val.into(),
- bottom_right: val.into(),
- bottom_left: val.into(),
- }
- }
-}
-
-impl From<Pixels> for Corners<Pixels> {
- fn from(val: Pixels) -> Self {
- Corners {
- top_left: val,
- top_right: val,
- bottom_right: val,
- bottom_left: val,
- }
- }
-}
-
-/// Represents a length in pixels, the base unit of measurement in the UI framework.
-///
-/// `Pixels` is a value type that represents an absolute length in pixels, which is used
-/// for specifying sizes, positions, and distances in the UI. It is the fundamental unit
-/// of measurement for all visual elements and layout calculations.
-///
-/// The inner value is an `f32`, allowing for sub-pixel precision which can be useful for
-/// anti-aliasing and animations. However, when applied to actual pixel grids, the value
-/// is typically rounded to the nearest integer.
-///
-/// # Examples
-///
-/// ```
-/// use zed::Pixels;
-///
-/// // Define a length of 10 pixels
-/// let length = Pixels(10.0);
-///
-/// // Define a length and scale it by a factor of 2
-/// let scaled_length = length.scale(2.0);
-/// assert_eq!(scaled_length, Pixels(20.0));
-/// ```
-#[derive(
- Clone,
- Copy,
- Default,
- Add,
- AddAssign,
- Sub,
- SubAssign,
- Neg,
- Div,
- DivAssign,
- PartialEq,
- Serialize,
- Deserialize,
-)]
-#[repr(transparent)]
-pub struct Pixels(pub f32);
-
-impl std::ops::Div for Pixels {
- type Output = f32;
-
- fn div(self, rhs: Self) -> Self::Output {
- self.0 / rhs.0
- }
-}
-
-impl std::ops::DivAssign for Pixels {
- fn div_assign(&mut self, rhs: Self) {
- *self = Self(self.0 / rhs.0);
- }
-}
-
-impl std::ops::RemAssign for Pixels {
- fn rem_assign(&mut self, rhs: Self) {
- self.0 %= rhs.0;
- }
-}
-
-impl std::ops::Rem for Pixels {
- type Output = Self;
-
- fn rem(self, rhs: Self) -> Self {
- Self(self.0 % rhs.0)
- }
-}
-
-impl Mul<f32> for Pixels {
- type Output = Pixels;
-
- fn mul(self, other: f32) -> Pixels {
- Pixels(self.0 * other)
- }
-}
-
-impl Mul<usize> for Pixels {
- type Output = Pixels;
-
- fn mul(self, other: usize) -> Pixels {
- Pixels(self.0 * other as f32)
- }
-}
-
-impl Mul<Pixels> for f32 {
- type Output = Pixels;
-
- fn mul(self, rhs: Pixels) -> Self::Output {
- Pixels(self * rhs.0)
- }
-}
-
-impl MulAssign<f32> for Pixels {
- fn mul_assign(&mut self, other: f32) {
- self.0 *= other;
- }
-}
-
-impl Pixels {
- /// Represents zero pixels.
- pub const ZERO: Pixels = Pixels(0.0);
- /// The maximum value that can be represented by `Pixels`.
- pub const MAX: Pixels = Pixels(f32::MAX);
-
- /// Floors the `Pixels` value to the nearest whole number.
- ///
- /// # Returns
- ///
- /// Returns a new `Pixels` instance with the floored value.
- pub fn floor(&self) -> Self {
- Self(self.0.floor())
- }
-
- /// Rounds the `Pixels` value to the nearest whole number.
- ///
- /// # Returns
- ///
- /// Returns a new `Pixels` instance with the rounded value.
- pub fn round(&self) -> Self {
- Self(self.0.round())
- }
-
- /// Returns the ceiling of the `Pixels` value to the nearest whole number.
- ///
- /// # Returns
- ///
- /// Returns a new `Pixels` instance with the ceiling value.
- pub fn ceil(&self) -> Self {
- Self(self.0.ceil())
- }
-
- /// Scales the `Pixels` value by a given factor, producing `ScaledPixels`.
- ///
- /// This method is used when adjusting pixel values for display scaling factors,
- /// such as high DPI (dots per inch) or Retina displays, where the pixel density is higher and
- /// thus requires scaling to maintain visual consistency and readability.
- ///
- /// The resulting `ScaledPixels` represent the scaled value which can be used for rendering
- /// calculations where display scaling is considered.
- pub fn scale(&self, factor: f32) -> ScaledPixels {
- ScaledPixels(self.0 * factor)
- }
-
- /// Raises the `Pixels` value to a given power.
- ///
- /// # Arguments
- ///
- /// * `exponent` - The exponent to raise the `Pixels` value by.
- ///
- /// # Returns
- ///
- /// Returns a new `Pixels` instance with the value raised to the given exponent.
- pub fn pow(&self, exponent: f32) -> Self {
- Self(self.0.powf(exponent))
- }
-
- /// Returns the absolute value of the `Pixels`.
- ///
- /// # Returns
- ///
- /// A new `Pixels` instance with the absolute value of the original `Pixels`.
- pub fn abs(&self) -> Self {
- Self(self.0.abs())
- }
-}
-
-impl Mul<Pixels> for Pixels {
- type Output = Pixels;
-
- fn mul(self, rhs: Pixels) -> Self::Output {
- Pixels(self.0 * rhs.0)
- }
-}
-
-impl Eq for Pixels {}
-
-impl PartialOrd for Pixels {
- fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
- self.0.partial_cmp(&other.0)
- }
-}
-
-impl Ord for Pixels {
- fn cmp(&self, other: &Self) -> cmp::Ordering {
- self.partial_cmp(other).unwrap()
- }
-}
-
-impl std::hash::Hash for Pixels {
- fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
- self.0.to_bits().hash(state);
- }
-}
-
-impl From<f64> for Pixels {
- fn from(pixels: f64) -> Self {
- Pixels(pixels as f32)
- }
-}
-
-impl From<f32> for Pixels {
- fn from(pixels: f32) -> Self {
- Pixels(pixels)
- }
-}
-
-impl Debug for Pixels {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{} px", self.0)
- }
-}
-
-impl From<Pixels> for f32 {
- fn from(pixels: Pixels) -> Self {
- pixels.0
- }
-}
-
-impl From<&Pixels> for f32 {
- fn from(pixels: &Pixels) -> Self {
- pixels.0
- }
-}
-
-impl From<Pixels> for f64 {
- fn from(pixels: Pixels) -> Self {
- pixels.0 as f64
- }
-}
-
-impl From<Pixels> for u32 {
- fn from(pixels: Pixels) -> Self {
- pixels.0 as u32
- }
-}
-
-impl From<u32> for Pixels {
- fn from(pixels: u32) -> Self {
- Pixels(pixels as f32)
- }
-}
-
-impl From<Pixels> for usize {
- fn from(pixels: Pixels) -> Self {
- pixels.0 as usize
- }
-}
-
-impl From<usize> for Pixels {
- fn from(pixels: usize) -> Self {
- Pixels(pixels as f32)
- }
-}
-
-/// Represents physical pixels on the display.
-///
-/// `DevicePixels` is a unit of measurement that refers to the actual pixels on a device's screen.
-/// This type is used when precise pixel manipulation is required, such as rendering graphics or
-/// interfacing with hardware that operates on the pixel level. Unlike logical pixels that may be
-/// affected by the device's scale factor, `DevicePixels` always correspond to real pixels on the
-/// display.
-#[derive(
- Add, AddAssign, Clone, Copy, Default, Div, Eq, Hash, Ord, PartialEq, PartialOrd, Sub, SubAssign,
-)]
-#[repr(transparent)]
-pub struct DevicePixels(pub(crate) i32);
-
-impl DevicePixels {
- /// Converts the `DevicePixels` value to the number of bytes needed to represent it in memory.
- ///
- /// This function is useful when working with graphical data that needs to be stored in a buffer,
- /// such as images or framebuffers, where each pixel may be represented by a specific number of bytes.
- ///
- /// # Arguments
- ///
- /// * `bytes_per_pixel` - The number of bytes used to represent a single pixel.
- ///
- /// # Returns
- ///
- /// The number of bytes required to represent the `DevicePixels` value in memory.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::DevicePixels;
- /// let pixels = DevicePixels(10); // 10 device pixels
- /// let bytes_per_pixel = 4; // Assume each pixel is represented by 4 bytes (e.g., RGBA)
- /// let total_bytes = pixels.to_bytes(bytes_per_pixel);
- /// assert_eq!(total_bytes, 40); // 10 pixels * 4 bytes/pixel = 40 bytes
- /// ```
- pub fn to_bytes(&self, bytes_per_pixel: u8) -> u32 {
- self.0 as u32 * bytes_per_pixel as u32
- }
-}
-
-impl fmt::Debug for DevicePixels {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{} px (device)", self.0)
- }
-}
-
-impl From<DevicePixels> for i32 {
- fn from(device_pixels: DevicePixels) -> Self {
- device_pixels.0
- }
-}
-
-impl From<i32> for DevicePixels {
- fn from(device_pixels: i32) -> Self {
- DevicePixels(device_pixels)
- }
-}
-
-impl From<u32> for DevicePixels {
- fn from(device_pixels: u32) -> Self {
- DevicePixels(device_pixels as i32)
- }
-}
-
-impl From<DevicePixels> for u32 {
- fn from(device_pixels: DevicePixels) -> Self {
- device_pixels.0 as u32
- }
-}
-
-impl From<DevicePixels> for u64 {
- fn from(device_pixels: DevicePixels) -> Self {
- device_pixels.0 as u64
- }
-}
-
-impl From<u64> for DevicePixels {
- fn from(device_pixels: u64) -> Self {
- DevicePixels(device_pixels as i32)
- }
-}
-
-impl From<DevicePixels> for usize {
- fn from(device_pixels: DevicePixels) -> Self {
- device_pixels.0 as usize
- }
-}
-
-impl From<usize> for DevicePixels {
- fn from(device_pixels: usize) -> Self {
- DevicePixels(device_pixels as i32)
- }
-}
-
-/// Represents scaled pixels that take into account the device's scale factor.
-///
-/// `ScaledPixels` are used to ensure that UI elements appear at the correct size on devices
-/// with different pixel densities. When a device has a higher scale factor (such as Retina displays),
-/// a single logical pixel may correspond to multiple physical pixels. By using `ScaledPixels`,
-/// dimensions and positions can be specified in a way that scales appropriately across different
-/// display resolutions.
-#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
-#[repr(transparent)]
-pub struct ScaledPixels(pub(crate) f32);
-
-impl ScaledPixels {
- /// Floors the `ScaledPixels` value to the nearest whole number.
- ///
- /// # Returns
- ///
- /// Returns a new `ScaledPixels` instance with the floored value.
- pub fn floor(&self) -> Self {
- Self(self.0.floor())
- }
-
- /// Rounds the `ScaledPixels` value to the nearest whole number.
- ///
- /// # Returns
- ///
- /// Returns a new `ScaledPixels` instance with the rounded value.
- pub fn ceil(&self) -> Self {
- Self(self.0.ceil())
- }
-}
-
-impl Eq for ScaledPixels {}
-
-impl Debug for ScaledPixels {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{} px (scaled)", self.0)
- }
-}
-
-impl From<ScaledPixels> for DevicePixels {
- fn from(scaled: ScaledPixels) -> Self {
- DevicePixels(scaled.0.ceil() as i32)
- }
-}
-
-impl From<DevicePixels> for ScaledPixels {
- fn from(device: DevicePixels) -> Self {
- ScaledPixels(device.0 as f32)
- }
-}
-
-impl From<ScaledPixels> for f64 {
- fn from(scaled_pixels: ScaledPixels) -> Self {
- scaled_pixels.0 as f64
- }
-}
-
-/// Represents pixels in a global coordinate space, which can span across multiple displays.
-///
-/// `GlobalPixels` is used when dealing with a coordinate system that is not limited to a single
-/// display's boundaries. This type is particularly useful in multi-monitor setups where
-/// positioning and measurements need to be consistent and relative to a "global" origin point
-/// rather than being relative to any individual display.
-#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
-#[repr(transparent)]
-pub struct GlobalPixels(pub(crate) f32);
-
-impl Debug for GlobalPixels {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{} px (global coordinate space)", self.0)
- }
-}
-
-impl From<GlobalPixels> for f64 {
- fn from(global_pixels: GlobalPixels) -> Self {
- global_pixels.0 as f64
- }
-}
-
-impl From<f64> for GlobalPixels {
- fn from(global_pixels: f64) -> Self {
- GlobalPixels(global_pixels as f32)
- }
-}
-
-impl sqlez::bindable::StaticColumnCount for GlobalPixels {}
-
-impl sqlez::bindable::Bind for GlobalPixels {
- fn bind(
- &self,
- statement: &sqlez::statement::Statement,
- start_index: i32,
- ) -> anyhow::Result<i32> {
- self.0.bind(statement, start_index)
- }
-}
-
-/// Represents a length in rems, a unit based on the font-size of the window, which can be assigned with [WindowContext::set_rem_size].
-///
-/// Rems are used for defining lengths that are scalable and consistent across different UI elements.
-/// The value of `1rem` is typically equal to the font-size of the root element (often the `<html>` element in browsers),
-/// making it a flexible unit that adapts to the user's text size preferences. In this framework, `rems` serve a similar
-/// purpose, allowing for scalable and accessible design that can adjust to different display settings or user preferences.
-///
-/// For example, if the root element's font-size is `16px`, then `1rem` equals `16px`. A length of `2rems` would then be `32px`.
-#[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg)]
-pub struct Rems(pub f32);
-
-impl Mul<Pixels> for Rems {
- type Output = Pixels;
-
- fn mul(self, other: Pixels) -> Pixels {
- Pixels(self.0 * other.0)
- }
-}
-
-impl Debug for Rems {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{} rem", self.0)
- }
-}
-
-/// Represents an absolute length in pixels or rems.
-///
-/// `AbsoluteLength` can be either a fixed number of pixels, which is an absolute measurement not
-/// affected by the current font size, or a number of rems, which is relative to the font size of
-/// the root element. It is used for specifying dimensions that are either independent of or
-/// related to the typographic scale.
-#[derive(Clone, Copy, Debug, Neg)]
-pub enum AbsoluteLength {
- /// A length in pixels.
- Pixels(Pixels),
- /// A length in rems.
- Rems(Rems),
-}
-
-impl AbsoluteLength {
- /// Checks if the absolute length is zero.
- pub fn is_zero(&self) -> bool {
- match self {
- AbsoluteLength::Pixels(px) => px.0 == 0.0,
- AbsoluteLength::Rems(rems) => rems.0 == 0.0,
- }
- }
-}
-
-impl From<Pixels> for AbsoluteLength {
- fn from(pixels: Pixels) -> Self {
- AbsoluteLength::Pixels(pixels)
- }
-}
-
-impl From<Rems> for AbsoluteLength {
- fn from(rems: Rems) -> Self {
- AbsoluteLength::Rems(rems)
- }
-}
-
-impl AbsoluteLength {
- /// Converts an `AbsoluteLength` to `Pixels` based on a given `rem_size`.
- ///
- /// # Arguments
- ///
- /// * `rem_size` - The size of one rem in pixels.
- ///
- /// # Returns
- ///
- /// Returns the `AbsoluteLength` as `Pixels`.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{AbsoluteLength, Pixels};
- /// let length_in_pixels = AbsoluteLength::Pixels(Pixels(42.0));
- /// let length_in_rems = AbsoluteLength::Rems(Rems(2.0));
- /// let rem_size = Pixels(16.0);
- ///
- /// assert_eq!(length_in_pixels.to_pixels(rem_size), Pixels(42.0));
- /// assert_eq!(length_in_rems.to_pixels(rem_size), Pixels(32.0));
- /// ```
- pub fn to_pixels(&self, rem_size: Pixels) -> Pixels {
- match self {
- AbsoluteLength::Pixels(pixels) => *pixels,
- AbsoluteLength::Rems(rems) => *rems * rem_size,
- }
- }
-}
-
-impl Default for AbsoluteLength {
- fn default() -> Self {
- px(0.).into()
- }
-}
-
-/// A non-auto length that can be defined in pixels, rems, or percent of parent.
-///
-/// This enum represents lengths that have a specific value, as opposed to lengths that are automatically
-/// determined by the context. It includes absolute lengths in pixels or rems, and relative lengths as a
-/// fraction of the parent's size.
-#[derive(Clone, Copy, Neg)]
-pub enum DefiniteLength {
- /// An absolute length specified in pixels or rems.
- Absolute(AbsoluteLength),
- /// A relative length specified as a fraction of the parent's size, between 0 and 1.
- Fraction(f32),
-}
-
-impl DefiniteLength {
- /// Converts the `DefiniteLength` to `Pixels` based on a given `base_size` and `rem_size`.
- ///
- /// If the `DefiniteLength` is an absolute length, it will be directly converted to `Pixels`.
- /// If it is a fraction, the fraction will be multiplied by the `base_size` to get the length in pixels.
- ///
- /// # Arguments
- ///
- /// * `base_size` - The base size in `AbsoluteLength` to which the fraction will be applied.
- /// * `rem_size` - The size of one rem in pixels, used to convert rems to pixels.
- ///
- /// # Returns
- ///
- /// Returns the `DefiniteLength` as `Pixels`.
- ///
- /// # Examples
- ///
- /// ```
- /// # use zed::{DefiniteLength, AbsoluteLength, Pixels, px, rems};
- /// let length_in_pixels = DefiniteLength::Absolute(AbsoluteLength::Pixels(px(42.0)));
- /// let length_in_rems = DefiniteLength::Absolute(AbsoluteLength::Rems(rems(2.0)));
- /// let length_as_fraction = DefiniteLength::Fraction(0.5);
- /// let base_size = AbsoluteLength::Pixels(px(100.0));
- /// let rem_size = px(16.0);
- ///
- /// assert_eq!(length_in_pixels.to_pixels(base_size, rem_size), Pixels(42.0));
- /// assert_eq!(length_in_rems.to_pixels(base_size, rem_size), Pixels(32.0));
- /// assert_eq!(length_as_fraction.to_pixels(base_size, rem_size), Pixels(50.0));
- /// ```
- pub fn to_pixels(&self, base_size: AbsoluteLength, rem_size: Pixels) -> Pixels {
- match self {
- DefiniteLength::Absolute(size) => size.to_pixels(rem_size),
- DefiniteLength::Fraction(fraction) => match base_size {
- AbsoluteLength::Pixels(px) => px * *fraction,
- AbsoluteLength::Rems(rems) => rems * rem_size * *fraction,
- },
- }
- }
-}
-
-impl Debug for DefiniteLength {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- DefiniteLength::Absolute(length) => Debug::fmt(length, f),
- DefiniteLength::Fraction(fract) => write!(f, "{}%", (fract * 100.0) as i32),
- }
- }
-}
-
-impl From<Pixels> for DefiniteLength {
- fn from(pixels: Pixels) -> Self {
- Self::Absolute(pixels.into())
- }
-}
-
-impl From<Rems> for DefiniteLength {
- fn from(rems: Rems) -> Self {
- Self::Absolute(rems.into())
- }
-}
-
-impl From<AbsoluteLength> for DefiniteLength {
- fn from(length: AbsoluteLength) -> Self {
- Self::Absolute(length)
- }
-}
-
-impl Default for DefiniteLength {
- fn default() -> Self {
- Self::Absolute(AbsoluteLength::default())
- }
-}
-
-/// A length that can be defined in pixels, rems, percent of parent, or auto.
-#[derive(Clone, Copy)]
-pub enum Length {
- /// A definite length specified either in pixels, rems, or as a fraction of the parent's size.
- Definite(DefiniteLength),
- /// An automatic length that is determined by the context in which it is used.
- Auto,
-}
-
-impl Debug for Length {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Length::Definite(definite_length) => write!(f, "{:?}", definite_length),
- Length::Auto => write!(f, "auto"),
- }
- }
-}
-
-/// Constructs a `DefiniteLength` representing a relative fraction of a parent size.
-///
-/// This function creates a `DefiniteLength` that is a specified fraction of a parent's dimension.
-/// The fraction should be a floating-point number between 0.0 and 1.0, where 1.0 represents 100% of the parent's size.
-///
-/// # Arguments
-///
-/// * `fraction` - The fraction of the parent's size, between 0.0 and 1.0.
-///
-/// # Returns
-///
-/// A `DefiniteLength` representing the relative length as a fraction of the parent's size.
-pub fn relative(fraction: f32) -> DefiniteLength {
- DefiniteLength::Fraction(fraction)
-}
-
-/// Returns the Golden Ratio, i.e. `~(1.0 + sqrt(5.0)) / 2.0`.
-pub fn phi() -> DefiniteLength {
- relative(1.618_034)
-}
-
-/// Constructs a `Rems` value representing a length in rems.
-///
-/// # Arguments
-///
-/// * `rems` - The number of rems for the length.
-///
-/// # Returns
-///
-/// A `Rems` representing the specified number of rems.
-pub fn rems(rems: f32) -> Rems {
- Rems(rems)
-}
-
-/// Constructs a `Pixels` value representing a length in pixels.
-///
-/// # Arguments
-///
-/// * `pixels` - The number of pixels for the length.
-///
-/// # Returns
-///
-/// A `Pixels` representing the specified number of pixels.
-pub const fn px(pixels: f32) -> Pixels {
- Pixels(pixels)
-}
-
-/// Returns a `Length` representing an automatic length.
-///
-/// The `auto` length is often used in layout calculations where the length should be determined
-/// by the layout context itself rather than being explicitly set. This is commonly used in CSS
-/// for properties like `width`, `height`, `margin`, `padding`, etc., where `auto` can be used
-/// to instruct the layout engine to calculate the size based on other factors like the size of the
-/// container or the intrinsic size of the content.
-///
-/// # Returns
-///
-/// A `Length` variant set to `Auto`.
-pub fn auto() -> Length {
- Length::Auto
-}
-
-impl From<Pixels> for Length {
- fn from(pixels: Pixels) -> Self {
- Self::Definite(pixels.into())
- }
-}
-
-impl From<Rems> for Length {
- fn from(rems: Rems) -> Self {
- Self::Definite(rems.into())
- }
-}
-
-impl From<DefiniteLength> for Length {
- fn from(length: DefiniteLength) -> Self {
- Self::Definite(length)
- }
-}
-
-impl From<AbsoluteLength> for Length {
- fn from(length: AbsoluteLength) -> Self {
- Self::Definite(length.into())
- }
-}
-
-impl Default for Length {
- fn default() -> Self {
- Self::Definite(DefiniteLength::default())
- }
-}
-
-impl From<()> for Length {
- fn from(_: ()) -> Self {
- Self::Definite(DefiniteLength::default())
- }
-}
-
-/// Provides a trait for types that can calculate half of their value.
-///
-/// The `Half` trait is used for types that can be evenly divided, returning a new instance of the same type
-/// representing half of the original value. This is commonly used for types that represent measurements or sizes,
-/// such as lengths or pixels, where halving is a frequent operation during layout calculations or animations.
-pub trait Half {
- /// Returns half of the current value.
- ///
- /// # Returns
- ///
- /// A new instance of the implementing type, representing half of the original value.
- fn half(&self) -> Self;
-}
-
-impl Half for f32 {
- fn half(&self) -> Self {
- self / 2.
- }
-}
-
-impl Half for DevicePixels {
- fn half(&self) -> Self {
- Self(self.0 / 2)
- }
-}
-
-impl Half for ScaledPixels {
- fn half(&self) -> Self {
- Self(self.0 / 2.)
- }
-}
-
-impl Half for Pixels {
- fn half(&self) -> Self {
- Self(self.0 / 2.)
- }
-}
-
-impl Half for Rems {
- fn half(&self) -> Self {
- Self(self.0 / 2.)
- }
-}
-
-impl Half for GlobalPixels {
- fn half(&self) -> Self {
- Self(self.0 / 2.)
- }
-}
-
-/// A trait for checking if a value is zero.
-///
-/// This trait provides a method to determine if a value is considered to be zero.
-/// It is implemented for various numeric and length-related types where the concept
-/// of zero is applicable. This can be useful for comparisons, optimizations, or
-/// determining if an operation has a neutral effect.
-pub trait IsZero {
- /// Determines if the value is zero.
- ///
- /// # Returns
- ///
- /// Returns `true` if the value is zero, `false` otherwise.
- fn is_zero(&self) -> bool;
-}
-
-impl IsZero for DevicePixels {
- fn is_zero(&self) -> bool {
- self.0 == 0
- }
-}
-
-impl IsZero for ScaledPixels {
- fn is_zero(&self) -> bool {
- self.0 == 0.
- }
-}
-
-impl IsZero for Pixels {
- fn is_zero(&self) -> bool {
- self.0 == 0.
- }
-}
-
-impl IsZero for Rems {
- fn is_zero(&self) -> bool {
- self.0 == 0.
- }
-}
-
-impl IsZero for AbsoluteLength {
- fn is_zero(&self) -> bool {
- match self {
- AbsoluteLength::Pixels(pixels) => pixels.is_zero(),
- AbsoluteLength::Rems(rems) => rems.is_zero(),
- }
- }
-}
-
-impl IsZero for DefiniteLength {
- fn is_zero(&self) -> bool {
- match self {
- DefiniteLength::Absolute(length) => length.is_zero(),
- DefiniteLength::Fraction(fraction) => *fraction == 0.,
- }
- }
-}
-
-impl IsZero for Length {
- fn is_zero(&self) -> bool {
- match self {
- Length::Definite(length) => length.is_zero(),
- Length::Auto => false,
- }
- }
-}
-
-impl<T: IsZero + Debug + Clone + Default> IsZero for Point<T> {
- fn is_zero(&self) -> bool {
- self.x.is_zero() && self.y.is_zero()
- }
-}
-
-impl<T> IsZero for Size<T>
-where
- T: IsZero + Default + Debug + Clone,
-{
- fn is_zero(&self) -> bool {
- self.width.is_zero() || self.height.is_zero()
- }
-}
-
-impl<T: IsZero + Debug + Clone + Default> IsZero for Bounds<T> {
- fn is_zero(&self) -> bool {
- self.size.is_zero()
- }
-}
-
-impl<T> IsZero for Corners<T>
-where
- T: IsZero + Clone + Default + Debug,
-{
- fn is_zero(&self) -> bool {
- self.top_left.is_zero()
- && self.top_right.is_zero()
- && self.bottom_right.is_zero()
- && self.bottom_left.is_zero()
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_bounds_intersects() {
- let bounds1 = Bounds {
- origin: Point { x: 0.0, y: 0.0 },
- size: Size {
- width: 5.0,
- height: 5.0,
- },
- };
- let bounds2 = Bounds {
- origin: Point { x: 4.0, y: 4.0 },
- size: Size {
- width: 5.0,
- height: 5.0,
- },
- };
- let bounds3 = Bounds {
- origin: Point { x: 10.0, y: 10.0 },
- size: Size {
- width: 5.0,
- height: 5.0,
- },
- };
-
- // Test Case 1: Intersecting bounds
- assert_eq!(bounds1.intersects(&bounds2), true);
-
- // Test Case 2: Non-Intersecting bounds
- assert_eq!(bounds1.intersects(&bounds3), false);
-
- // Test Case 3: Bounds intersecting with themselves
- assert_eq!(bounds1.intersects(&bounds1), true);
- }
-}
@@ -1,215 +0,0 @@
-#[macro_use]
-mod action;
-mod app;
-
-mod arena;
-mod assets;
-mod color;
-mod element;
-mod elements;
-mod executor;
-mod geometry;
-mod image_cache;
-mod input;
-mod interactive;
-mod key_dispatch;
-mod keymap;
-mod platform;
-pub mod prelude;
-mod scene;
-mod shared_string;
-mod style;
-mod styled;
-mod subscription;
-mod svg_renderer;
-mod taffy;
-#[cfg(any(test, feature = "test-support"))]
-pub mod test;
-mod text_system;
-mod util;
-mod view;
-mod window;
-
-mod private {
- /// A mechanism for restricting implementations of a trait to only those in GPUI.
- /// See: https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/
- pub trait Sealed {}
-}
-
-pub use action::*;
-pub use anyhow::Result;
-pub use app::*;
-pub(crate) use arena::*;
-pub use assets::*;
-pub use color::*;
-pub use ctor::ctor;
-pub use element::*;
-pub use elements::*;
-pub use executor::*;
-pub use geometry::*;
-pub use gpui2_macros::*;
-pub use image_cache::*;
-pub use input::*;
-pub use interactive::*;
-pub use key_dispatch::*;
-pub use keymap::*;
-pub use linkme;
-pub use platform::*;
-use private::Sealed;
-pub use refineable::*;
-pub use scene::*;
-pub use serde;
-pub use serde_derive;
-pub use serde_json;
-pub use shared_string::*;
-pub use smallvec;
-pub use smol::Timer;
-pub use style::*;
-pub use styled::*;
-pub use subscription::*;
-pub use svg_renderer::*;
-pub use taffy::{AvailableSpace, LayoutId};
-#[cfg(any(test, feature = "test-support"))]
-pub use test::*;
-pub use text_system::*;
-pub use util::arc_cow::ArcCow;
-pub use view::*;
-pub use window::*;
-
-use std::{
- any::{Any, TypeId},
- borrow::BorrowMut,
-};
-use taffy::TaffyLayoutEngine;
-
-pub trait Context {
- type Result<T>;
-
- fn new_model<T: 'static>(
- &mut self,
- build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
- ) -> Self::Result<Model<T>>;
-
- fn update_model<T, R>(
- &mut self,
- handle: &Model<T>,
- update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
- ) -> Self::Result<R>
- where
- T: 'static;
-
- fn read_model<T, R>(
- &self,
- handle: &Model<T>,
- read: impl FnOnce(&T, &AppContext) -> R,
- ) -> Self::Result<R>
- where
- T: 'static;
-
- fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
- where
- F: FnOnce(AnyView, &mut WindowContext<'_>) -> T;
-
- fn read_window<T, R>(
- &self,
- window: &WindowHandle<T>,
- read: impl FnOnce(View<T>, &AppContext) -> R,
- ) -> Result<R>
- where
- T: 'static;
-}
-
-pub trait VisualContext: Context {
- fn new_view<V>(
- &mut self,
- build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
- ) -> Self::Result<View<V>>
- where
- V: 'static + Render;
-
- fn update_view<V: 'static, R>(
- &mut self,
- view: &View<V>,
- update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
- ) -> Self::Result<R>;
-
- fn replace_root_view<V>(
- &mut self,
- build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
- ) -> Self::Result<View<V>>
- where
- V: 'static + Render;
-
- fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
- where
- V: FocusableView;
-
- fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
- where
- V: ManagedView;
-}
-
-pub trait Entity<T>: Sealed {
- type Weak: 'static;
-
- fn entity_id(&self) -> EntityId;
- fn downgrade(&self) -> Self::Weak;
- fn upgrade_from(weak: &Self::Weak) -> Option<Self>
- where
- Self: Sized;
-}
-
-pub trait EventEmitter<E: Any>: 'static {}
-
-pub enum GlobalKey {
- Numeric(usize),
- View(EntityId),
- Type(TypeId),
-}
-
-pub trait BorrowAppContext {
- fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
- where
- F: FnOnce(&mut Self) -> R;
-
- fn set_global<T: 'static>(&mut self, global: T);
-}
-
-impl<C> BorrowAppContext for C
-where
- C: BorrowMut<AppContext>,
-{
- fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
- where
- F: FnOnce(&mut Self) -> R,
- {
- if let Some(style) = style {
- self.borrow_mut().push_text_style(style);
- let result = f(self);
- self.borrow_mut().pop_text_style();
- result
- } else {
- f(self)
- }
- }
-
- fn set_global<G: 'static>(&mut self, global: G) {
- self.borrow_mut().set_global(global)
- }
-}
-
-pub trait Flatten<T> {
- fn flatten(self) -> Result<T>;
-}
-
-impl<T> Flatten<T> for Result<Result<T>> {
- fn flatten(self) -> Result<T> {
- self?
- }
-}
-
-impl<T> Flatten<T> for Result<T> {
- fn flatten(self) -> Result<T> {
- self
- }
-}
@@ -1,107 +0,0 @@
-use crate::{ImageData, ImageId, SharedString};
-use collections::HashMap;
-use futures::{
- future::{BoxFuture, Shared},
- AsyncReadExt, FutureExt, TryFutureExt,
-};
-use image::ImageError;
-use parking_lot::Mutex;
-use std::sync::Arc;
-use thiserror::Error;
-use util::http::{self, HttpClient};
-
-#[derive(PartialEq, Eq, Hash, Clone)]
-pub struct RenderImageParams {
- pub(crate) image_id: ImageId,
-}
-
-#[derive(Debug, Error, Clone)]
-pub enum Error {
- #[error("http error: {0}")]
- Client(#[from] http::Error),
- #[error("IO error: {0}")]
- Io(Arc<std::io::Error>),
- #[error("unexpected http status: {status}, body: {body}")]
- BadStatus {
- status: http::StatusCode,
- body: String,
- },
- #[error("image error: {0}")]
- Image(Arc<ImageError>),
-}
-
-impl From<std::io::Error> for Error {
- fn from(error: std::io::Error) -> Self {
- Error::Io(Arc::new(error))
- }
-}
-
-impl From<ImageError> for Error {
- fn from(error: ImageError) -> Self {
- Error::Image(Arc::new(error))
- }
-}
-
-pub struct ImageCache {
- client: Arc<dyn HttpClient>,
- images: Arc<Mutex<HashMap<SharedString, FetchImageFuture>>>,
-}
-
-type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;
-
-impl ImageCache {
- pub fn new(client: Arc<dyn HttpClient>) -> Self {
- ImageCache {
- client,
- images: Default::default(),
- }
- }
-
- pub fn get(
- &self,
- uri: impl Into<SharedString>,
- ) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
- let uri = uri.into();
- let mut images = self.images.lock();
-
- match images.get(&uri) {
- Some(future) => future.clone(),
- None => {
- let client = self.client.clone();
- let future = {
- let uri = uri.clone();
- async move {
- let mut response = client.get(uri.as_ref(), ().into(), true).await?;
- let mut body = Vec::new();
- response.body_mut().read_to_end(&mut body).await?;
-
- if !response.status().is_success() {
- return Err(Error::BadStatus {
- status: response.status(),
- body: String::from_utf8_lossy(&body).into_owned(),
- });
- }
-
- let format = image::guess_format(&body)?;
- let image =
- image::load_from_memory_with_format(&body, format)?.into_bgra8();
- Ok(Arc::new(ImageData::new(image)))
- }
- }
- .map_err({
- let uri = uri.clone();
-
- move |error| {
- log::log!(log::Level::Error, "{:?} {:?}", &uri, &error);
- error
- }
- })
- .boxed()
- .shared();
-
- images.insert(uri, future.clone());
- future
- }
- }
- }
-}
@@ -1,592 +0,0 @@
-mod app_menu;
-mod keystroke;
-#[cfg(target_os = "macos")]
-mod mac;
-#[cfg(any(test, feature = "test-support"))]
-mod test;
-
-use crate::{
- point, size, Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId,
- FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, Keymap,
- LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
- Scene, SharedString, Size, TaskLabel,
-};
-use anyhow::{anyhow, bail};
-use async_task::Runnable;
-use futures::channel::oneshot;
-use parking::Unparker;
-use seahash::SeaHasher;
-use serde::{Deserialize, Serialize};
-use sqlez::bindable::{Bind, Column, StaticColumnCount};
-use sqlez::statement::Statement;
-use std::borrow::Cow;
-use std::hash::{Hash, Hasher};
-use std::time::Duration;
-use std::{
- any::Any,
- fmt::{self, Debug, Display},
- ops::Range,
- path::{Path, PathBuf},
- rc::Rc,
- str::FromStr,
- sync::Arc,
-};
-use uuid::Uuid;
-
-pub use app_menu::*;
-pub use keystroke::*;
-#[cfg(target_os = "macos")]
-pub use mac::*;
-#[cfg(any(test, feature = "test-support"))]
-pub use test::*;
-pub use time::UtcOffset;
-
-#[cfg(target_os = "macos")]
-pub(crate) fn current_platform() -> Rc<dyn Platform> {
- Rc::new(MacPlatform::new())
-}
-
-pub type DrawWindow = Box<dyn FnMut() -> Result<Scene>>;
-
-pub(crate) trait Platform: 'static {
- fn background_executor(&self) -> BackgroundExecutor;
- fn foreground_executor(&self) -> ForegroundExecutor;
- fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
-
- fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
- fn quit(&self);
- fn restart(&self);
- fn activate(&self, ignoring_other_apps: bool);
- fn hide(&self);
- fn hide_other_apps(&self);
- fn unhide_other_apps(&self);
-
- fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
- fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
- fn active_window(&self) -> Option<AnyWindowHandle>;
- fn open_window(
- &self,
- handle: AnyWindowHandle,
- options: WindowOptions,
- draw: DrawWindow,
- ) -> Box<dyn PlatformWindow>;
-
- fn set_display_link_output_callback(
- &self,
- display_id: DisplayId,
- callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
- );
- fn start_display_link(&self, display_id: DisplayId);
- fn stop_display_link(&self, display_id: DisplayId);
- // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn PlatformWindow>;
-
- fn open_url(&self, url: &str);
- fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
- fn prompt_for_paths(
- &self,
- options: PathPromptOptions,
- ) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
- fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
- fn reveal_path(&self, path: &Path);
-
- fn on_become_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_reopen(&self, callback: Box<dyn FnMut()>);
- fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
-
- fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
- fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
- fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
- fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
-
- fn os_name(&self) -> &'static str;
- fn os_version(&self) -> Result<SemanticVersion>;
- fn app_version(&self) -> Result<SemanticVersion>;
- fn app_path(&self) -> Result<PathBuf>;
- fn local_timezone(&self) -> UtcOffset;
- fn double_click_interval(&self) -> Duration;
- fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
-
- fn set_cursor_style(&self, style: CursorStyle);
- fn should_auto_hide_scrollbars(&self) -> bool;
-
- fn write_to_clipboard(&self, item: ClipboardItem);
- fn read_from_clipboard(&self) -> Option<ClipboardItem>;
-
- fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
- fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
- fn delete_credentials(&self, url: &str) -> Result<()>;
-}
-
-pub trait PlatformDisplay: Send + Sync + Debug {
- fn id(&self) -> DisplayId;
- /// Returns a stable identifier for this display that can be persisted and used
- /// across system restarts.
- fn uuid(&self) -> Result<Uuid>;
- fn as_any(&self) -> &dyn Any;
- fn bounds(&self) -> Bounds<GlobalPixels>;
-}
-
-#[derive(PartialEq, Eq, Hash, Copy, Clone)]
-pub struct DisplayId(pub(crate) u32);
-
-impl Debug for DisplayId {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "DisplayId({})", self.0)
- }
-}
-
-unsafe impl Send for DisplayId {}
-
-pub trait PlatformWindow {
- fn bounds(&self) -> WindowBounds;
- fn content_size(&self) -> Size<Pixels>;
- fn scale_factor(&self) -> f32;
- fn titlebar_height(&self) -> Pixels;
- fn appearance(&self) -> WindowAppearance;
- fn display(&self) -> Rc<dyn PlatformDisplay>;
- fn mouse_position(&self) -> Point<Pixels>;
- fn modifiers(&self) -> Modifiers;
- fn as_any_mut(&mut self) -> &mut dyn Any;
- fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
- fn clear_input_handler(&mut self);
- fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
- fn activate(&self);
- fn set_title(&mut self, title: &str);
- fn set_edited(&mut self, edited: bool);
- fn show_character_palette(&self);
- fn minimize(&self);
- fn zoom(&self);
- fn toggle_full_screen(&self);
- fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
- fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
- fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
- fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);
- fn on_moved(&self, callback: Box<dyn FnMut()>);
- fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
- fn on_close(&self, callback: Box<dyn FnOnce()>);
- fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
- fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
- fn invalidate(&self);
-
- fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
-
- #[cfg(any(test, feature = "test-support"))]
- fn as_test(&mut self) -> Option<&mut TestWindow> {
- None
- }
-}
-
-pub trait PlatformDispatcher: Send + Sync {
- fn is_main_thread(&self) -> bool;
- fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
- fn dispatch_on_main_thread(&self, runnable: Runnable);
- fn dispatch_after(&self, duration: Duration, runnable: Runnable);
- fn tick(&self, background_only: bool) -> bool;
- fn park(&self);
- fn unparker(&self) -> Unparker;
-
- #[cfg(any(test, feature = "test-support"))]
- fn as_test(&self) -> Option<&TestDispatcher> {
- None
- }
-}
-
-pub trait PlatformTextSystem: Send + Sync {
- fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
- fn all_font_families(&self) -> Vec<String>;
- fn font_id(&self, descriptor: &Font) -> Result<FontId>;
- fn font_metrics(&self, font_id: FontId) -> FontMetrics;
- fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
- fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
- fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
- fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
- fn rasterize_glyph(
- &self,
- params: &RenderGlyphParams,
- raster_bounds: Bounds<DevicePixels>,
- ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
- fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
- fn wrap_line(
- &self,
- text: &str,
- font_id: FontId,
- font_size: Pixels,
- width: Pixels,
- ) -> Vec<usize>;
-}
-
-#[derive(Clone, Debug)]
-pub struct AppMetadata {
- pub os_name: &'static str,
- pub os_version: Option<SemanticVersion>,
- pub app_version: Option<SemanticVersion>,
-}
-
-#[derive(PartialEq, Eq, Hash, Clone)]
-pub enum AtlasKey {
- Glyph(RenderGlyphParams),
- Svg(RenderSvgParams),
- Image(RenderImageParams),
-}
-
-impl AtlasKey {
- pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
- match self {
- AtlasKey::Glyph(params) => {
- if params.is_emoji {
- AtlasTextureKind::Polychrome
- } else {
- AtlasTextureKind::Monochrome
- }
- }
- AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
- AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
- }
- }
-}
-
-impl From<RenderGlyphParams> for AtlasKey {
- fn from(params: RenderGlyphParams) -> Self {
- Self::Glyph(params)
- }
-}
-
-impl From<RenderSvgParams> for AtlasKey {
- fn from(params: RenderSvgParams) -> Self {
- Self::Svg(params)
- }
-}
-
-impl From<RenderImageParams> for AtlasKey {
- fn from(params: RenderImageParams) -> Self {
- Self::Image(params)
- }
-}
-
-pub trait PlatformAtlas: Send + Sync {
- fn get_or_insert_with<'a>(
- &self,
- key: &AtlasKey,
- build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
- ) -> Result<AtlasTile>;
-
- fn clear(&self);
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-#[repr(C)]
-pub struct AtlasTile {
- pub(crate) texture_id: AtlasTextureId,
- pub(crate) tile_id: TileId,
- pub(crate) bounds: Bounds<DevicePixels>,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-#[repr(C)]
-pub(crate) struct AtlasTextureId {
- // We use u32 instead of usize for Metal Shader Language compatibility
- pub(crate) index: u32,
- pub(crate) kind: AtlasTextureKind,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-#[repr(C)]
-pub(crate) enum AtlasTextureKind {
- Monochrome = 0,
- Polychrome = 1,
- Path = 2,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
-#[repr(C)]
-pub(crate) struct TileId(pub(crate) u32);
-
-impl From<etagere::AllocId> for TileId {
- fn from(id: etagere::AllocId) -> Self {
- Self(id.serialize())
- }
-}
-
-impl From<TileId> for etagere::AllocId {
- fn from(id: TileId) -> Self {
- Self::deserialize(id.0)
- }
-}
-
-pub trait PlatformInputHandler: 'static {
- fn selected_text_range(&mut self) -> Option<Range<usize>>;
- fn marked_text_range(&mut self) -> Option<Range<usize>>;
- fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String>;
- fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
- fn replace_and_mark_text_in_range(
- &mut self,
- range_utf16: Option<Range<usize>>,
- new_text: &str,
- new_selected_range: Option<Range<usize>>,
- );
- fn unmark_text(&mut self);
- fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
-}
-
-#[derive(Debug)]
-pub struct WindowOptions {
- pub bounds: WindowBounds,
- pub titlebar: Option<TitlebarOptions>,
- pub center: bool,
- pub focus: bool,
- pub show: bool,
- pub kind: WindowKind,
- pub is_movable: bool,
- pub display_id: Option<DisplayId>,
-}
-
-impl Default for WindowOptions {
- fn default() -> Self {
- Self {
- bounds: WindowBounds::default(),
- titlebar: Some(TitlebarOptions {
- title: Default::default(),
- appears_transparent: Default::default(),
- traffic_light_position: Default::default(),
- }),
- center: false,
- focus: true,
- show: true,
- kind: WindowKind::Normal,
- is_movable: true,
- display_id: None,
- }
- }
-}
-
-#[derive(Debug, Default)]
-pub struct TitlebarOptions {
- pub title: Option<SharedString>,
- pub appears_transparent: bool,
- pub traffic_light_position: Option<Point<Pixels>>,
-}
-
-#[derive(Copy, Clone, Debug)]
-pub enum Appearance {
- Light,
- VibrantLight,
- Dark,
- VibrantDark,
-}
-
-impl Default for Appearance {
- fn default() -> Self {
- Self::Light
- }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub enum WindowKind {
- Normal,
- PopUp,
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Default)]
-pub enum WindowBounds {
- Fullscreen,
- #[default]
- Maximized,
- Fixed(Bounds<GlobalPixels>),
-}
-
-impl StaticColumnCount for WindowBounds {
- fn column_count() -> usize {
- 5
- }
-}
-
-impl Bind for WindowBounds {
- fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
- let (region, next_index) = match self {
- WindowBounds::Fullscreen => {
- let next_index = statement.bind(&"Fullscreen", start_index)?;
- (None, next_index)
- }
- WindowBounds::Maximized => {
- let next_index = statement.bind(&"Maximized", start_index)?;
- (None, next_index)
- }
- WindowBounds::Fixed(region) => {
- let next_index = statement.bind(&"Fixed", start_index)?;
- (Some(*region), next_index)
- }
- };
-
- statement.bind(
- ®ion.map(|region| {
- (
- region.origin.x,
- region.origin.y,
- region.size.width,
- region.size.height,
- )
- }),
- next_index,
- )
- }
-}
-
-impl Column for WindowBounds {
- fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
- let (window_state, next_index) = String::column(statement, start_index)?;
- let bounds = match window_state.as_str() {
- "Fullscreen" => WindowBounds::Fullscreen,
- "Maximized" => WindowBounds::Maximized,
- "Fixed" => {
- let ((x, y, width, height), _) = Column::column(statement, next_index)?;
- let x: f64 = x;
- let y: f64 = y;
- let width: f64 = width;
- let height: f64 = height;
- WindowBounds::Fixed(Bounds {
- origin: point(x.into(), y.into()),
- size: size(width.into(), height.into()),
- })
- }
- _ => bail!("Window State did not have a valid string"),
- };
-
- Ok((bounds, next_index + 4))
- }
-}
-
-#[derive(Copy, Clone, Debug)]
-pub enum WindowAppearance {
- Light,
- VibrantLight,
- Dark,
- VibrantDark,
-}
-
-impl Default for WindowAppearance {
- fn default() -> Self {
- Self::Light
- }
-}
-
-#[derive(Copy, Clone, Debug)]
-pub struct PathPromptOptions {
- pub files: bool,
- pub directories: bool,
- pub multiple: bool,
-}
-
-#[derive(Copy, Clone, Debug)]
-pub enum PromptLevel {
- Info,
- Warning,
- Critical,
-}
-
-/// The style of the cursor (pointer)
-#[derive(Copy, Clone, Debug)]
-pub enum CursorStyle {
- Arrow,
- IBeam,
- Crosshair,
- ClosedHand,
- OpenHand,
- PointingHand,
- ResizeLeft,
- ResizeRight,
- ResizeLeftRight,
- ResizeUp,
- ResizeDown,
- ResizeUpDown,
- DisappearingItem,
- IBeamCursorForVerticalLayout,
- OperationNotAllowed,
- DragLink,
- DragCopy,
- ContextualMenu,
-}
-
-impl Default for CursorStyle {
- fn default() -> Self {
- Self::Arrow
- }
-}
-
-#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
-pub struct SemanticVersion {
- major: usize,
- minor: usize,
- patch: usize,
-}
-
-impl FromStr for SemanticVersion {
- type Err = anyhow::Error;
-
- fn from_str(s: &str) -> Result<Self> {
- let mut components = s.trim().split('.');
- let major = components
- .next()
- .ok_or_else(|| anyhow!("missing major version number"))?
- .parse()?;
- let minor = components
- .next()
- .ok_or_else(|| anyhow!("missing minor version number"))?
- .parse()?;
- let patch = components
- .next()
- .ok_or_else(|| anyhow!("missing patch version number"))?
- .parse()?;
- Ok(Self {
- major,
- minor,
- patch,
- })
- }
-}
-
-impl Display for SemanticVersion {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
- }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct ClipboardItem {
- pub(crate) text: String,
- pub(crate) metadata: Option<String>,
-}
-
-impl ClipboardItem {
- pub fn new(text: String) -> Self {
- Self {
- text,
- metadata: None,
- }
- }
-
- pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
- self.metadata = Some(serde_json::to_string(&metadata).unwrap());
- self
- }
-
- pub fn text(&self) -> &String {
- &self.text
- }
-
- pub fn metadata<T>(&self) -> Option<T>
- where
- T: for<'a> Deserialize<'a>,
- {
- self.metadata
- .as_ref()
- .and_then(|m| serde_json::from_str(m).ok())
- }
-
- pub(crate) fn text_hash(text: &str) -> u64 {
- let mut hasher = SeaHasher::new();
- text.hash(&mut hasher);
- hasher.finish()
- }
-}
@@ -1,139 +0,0 @@
-//! Macos screen have a y axis that goings up from the bottom of the screen and
-//! an origin at the bottom left of the main display.
-mod dispatcher;
-mod display;
-mod display_linker;
-mod events;
-mod metal_atlas;
-mod metal_renderer;
-mod open_type;
-mod platform;
-mod text_system;
-mod window;
-mod window_appearence;
-
-use crate::{px, size, GlobalPixels, Pixels, Size};
-use cocoa::{
- base::{id, nil},
- foundation::{NSAutoreleasePool, NSNotFound, NSRect, NSSize, NSString, NSUInteger},
-};
-use metal_renderer::*;
-use objc::runtime::{BOOL, NO, YES};
-use std::ops::Range;
-
-pub use dispatcher::*;
-pub use display::*;
-pub use display_linker::*;
-pub use metal_atlas::*;
-pub use platform::*;
-pub use text_system::*;
-pub use window::*;
-
-trait BoolExt {
- fn to_objc(self) -> BOOL;
-}
-
-impl BoolExt for bool {
- fn to_objc(self) -> BOOL {
- if self {
- YES
- } else {
- NO
- }
- }
-}
-
-#[repr(C)]
-#[derive(Copy, Clone, Debug)]
-struct NSRange {
- pub location: NSUInteger,
- pub length: NSUInteger,
-}
-
-impl NSRange {
- fn invalid() -> Self {
- Self {
- location: NSNotFound as NSUInteger,
- length: 0,
- }
- }
-
- fn is_valid(&self) -> bool {
- self.location != NSNotFound as NSUInteger
- }
-
- fn to_range(self) -> Option<Range<usize>> {
- if self.is_valid() {
- let start = self.location as usize;
- let end = start + self.length as usize;
- Some(start..end)
- } else {
- None
- }
- }
-}
-
-impl From<Range<usize>> for NSRange {
- fn from(range: Range<usize>) -> Self {
- NSRange {
- location: range.start as NSUInteger,
- length: range.len() as NSUInteger,
- }
- }
-}
-
-unsafe impl objc::Encode for NSRange {
- fn encode() -> objc::Encoding {
- let encoding = format!(
- "{{NSRange={}{}}}",
- NSUInteger::encode().as_str(),
- NSUInteger::encode().as_str()
- );
- unsafe { objc::Encoding::from_str(&encoding) }
- }
-}
-
-unsafe fn ns_string(string: &str) -> id {
- NSString::alloc(nil).init_str(string).autorelease()
-}
-
-impl From<NSSize> for Size<Pixels> {
- fn from(value: NSSize) -> Self {
- Size {
- width: px(value.width as f32),
- height: px(value.height as f32),
- }
- }
-}
-
-pub trait NSRectExt {
- fn size(&self) -> Size<Pixels>;
- fn intersects(&self, other: Self) -> bool;
-}
-
-impl From<NSRect> for Size<Pixels> {
- fn from(rect: NSRect) -> Self {
- let NSSize { width, height } = rect.size;
- size(width.into(), height.into())
- }
-}
-
-impl From<NSRect> for Size<GlobalPixels> {
- fn from(rect: NSRect) -> Self {
- let NSSize { width, height } = rect.size;
- size(width.into(), height.into())
- }
-}
-
-// impl NSRectExt for NSRect {
-// fn intersects(&self, other: Self) -> bool {
-// self.size.width > 0.
-// && self.size.height > 0.
-// && other.size.width > 0.
-// && other.size.height > 0.
-// && self.origin.x <= other.origin.x + other.size.width
-// && self.origin.x + self.size.width >= other.origin.x
-// && self.origin.y <= other.origin.y + other.size.height
-// && self.origin.y + self.size.height >= other.origin.y
-// }
-// }
@@ -1 +0,0 @@
-#include <dispatch/dispatch.h>
@@ -1,96 +0,0 @@
-#![allow(non_upper_case_globals)]
-#![allow(non_camel_case_types)]
-#![allow(non_snake_case)]
-
-use crate::{PlatformDispatcher, TaskLabel};
-use async_task::Runnable;
-use objc::{
- class, msg_send,
- runtime::{BOOL, YES},
- sel, sel_impl,
-};
-use parking::{Parker, Unparker};
-use parking_lot::Mutex;
-use std::{ffi::c_void, sync::Arc, time::Duration};
-
-include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
-
-pub fn dispatch_get_main_queue() -> dispatch_queue_t {
- unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
-}
-
-pub struct MacDispatcher {
- parker: Arc<Mutex<Parker>>,
-}
-
-impl Default for MacDispatcher {
- fn default() -> Self {
- Self::new()
- }
-}
-
-impl MacDispatcher {
- pub fn new() -> Self {
- MacDispatcher {
- parker: Arc::new(Mutex::new(Parker::new())),
- }
- }
-}
-
-impl PlatformDispatcher for MacDispatcher {
- fn is_main_thread(&self) -> bool {
- let is_main_thread: BOOL = unsafe { msg_send![class!(NSThread), isMainThread] };
- is_main_thread == YES
- }
-
- fn dispatch(&self, runnable: Runnable, _: Option<TaskLabel>) {
- unsafe {
- dispatch_async_f(
- dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0),
- runnable.into_raw() as *mut c_void,
- Some(trampoline),
- );
- }
- }
-
- fn dispatch_on_main_thread(&self, runnable: Runnable) {
- unsafe {
- dispatch_async_f(
- dispatch_get_main_queue(),
- runnable.into_raw() as *mut c_void,
- Some(trampoline),
- );
- }
- }
-
- fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
- unsafe {
- let queue =
- dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0);
- let when = dispatch_time(DISPATCH_TIME_NOW as u64, duration.as_nanos() as i64);
- dispatch_after_f(
- when,
- queue,
- runnable.into_raw() as *mut c_void,
- Some(trampoline),
- );
- }
- }
-
- fn tick(&self, _background_only: bool) -> bool {
- false
- }
-
- fn park(&self) {
- self.parker.lock().park()
- }
-
- fn unparker(&self) -> Unparker {
- self.parker.lock().unparker()
- }
-}
-
-extern "C" fn trampoline(runnable: *mut c_void) {
- let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
- task.run();
-}
@@ -1,1194 +0,0 @@
-use super::{events::key_to_native, BoolExt};
-use crate::{
- Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
- ForegroundExecutor, InputEvent, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker,
- MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay,
- PlatformTextSystem, PlatformWindow, Result, Scene, SemanticVersion, VideoTimestamp,
- WindowOptions,
-};
-use anyhow::anyhow;
-use block::ConcreteBlock;
-use cocoa::{
- appkit::{
- NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
- NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard,
- NSPasteboardTypeString, NSSavePanel, NSWindow,
- },
- base::{id, nil, selector, BOOL, YES},
- foundation::{
- NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSString,
- NSUInteger, NSURL,
- },
-};
-use core_foundation::{
- base::{CFType, CFTypeRef, OSStatus, TCFType as _},
- boolean::CFBoolean,
- data::CFData,
- dictionary::{CFDictionary, CFDictionaryRef, CFMutableDictionary},
- string::{CFString, CFStringRef},
-};
-use ctor::ctor;
-use futures::channel::oneshot;
-use objc::{
- class,
- declare::ClassDecl,
- msg_send,
- runtime::{Class, Object, Sel},
- sel, sel_impl,
-};
-use parking_lot::Mutex;
-use ptr::null_mut;
-use std::{
- cell::Cell,
- convert::TryInto,
- ffi::{c_void, CStr, OsStr},
- os::{raw::c_char, unix::ffi::OsStrExt},
- path::{Path, PathBuf},
- process::Command,
- ptr,
- rc::Rc,
- slice, str,
- sync::Arc,
- time::Duration,
-};
-use time::UtcOffset;
-
-#[allow(non_upper_case_globals)]
-const NSUTF8StringEncoding: NSUInteger = 4;
-
-#[allow(non_upper_case_globals)]
-pub const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
-
-const MAC_PLATFORM_IVAR: &str = "platform";
-static mut APP_CLASS: *const Class = ptr::null();
-static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
-
-#[ctor]
-unsafe fn build_classes() {
- APP_CLASS = {
- let mut decl = ClassDecl::new("GPUI2Application", class!(NSApplication)).unwrap();
- decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
- decl.add_method(
- sel!(sendEvent:),
- send_event as extern "C" fn(&mut Object, Sel, id),
- );
- decl.register()
- };
-
- APP_DELEGATE_CLASS = {
- let mut decl = ClassDecl::new("GPUI2ApplicationDelegate", class!(NSResponder)).unwrap();
- decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
- decl.add_method(
- sel!(applicationDidFinishLaunching:),
- did_finish_launching as extern "C" fn(&mut Object, Sel, id),
- );
- decl.add_method(
- sel!(applicationShouldHandleReopen:hasVisibleWindows:),
- should_handle_reopen as extern "C" fn(&mut Object, Sel, id, bool),
- );
- decl.add_method(
- sel!(applicationDidBecomeActive:),
- did_become_active as extern "C" fn(&mut Object, Sel, id),
- );
- decl.add_method(
- sel!(applicationDidResignActive:),
- did_resign_active as extern "C" fn(&mut Object, Sel, id),
- );
- decl.add_method(
- sel!(applicationWillTerminate:),
- will_terminate as extern "C" fn(&mut Object, Sel, id),
- );
- decl.add_method(
- sel!(handleGPUIMenuItem:),
- handle_menu_item as extern "C" fn(&mut Object, Sel, id),
- );
- // Add menu item handlers so that OS save panels have the correct key commands
- decl.add_method(
- sel!(cut:),
- handle_menu_item as extern "C" fn(&mut Object, Sel, id),
- );
- decl.add_method(
- sel!(copy:),
- handle_menu_item as extern "C" fn(&mut Object, Sel, id),
- );
- decl.add_method(
- sel!(paste:),
- handle_menu_item as extern "C" fn(&mut Object, Sel, id),
- );
- decl.add_method(
- sel!(selectAll:),
- handle_menu_item as extern "C" fn(&mut Object, Sel, id),
- );
- decl.add_method(
- sel!(undo:),
- handle_menu_item as extern "C" fn(&mut Object, Sel, id),
- );
- decl.add_method(
- sel!(redo:),
- handle_menu_item as extern "C" fn(&mut Object, Sel, id),
- );
- decl.add_method(
- sel!(validateMenuItem:),
- validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool,
- );
- decl.add_method(
- sel!(menuWillOpen:),
- menu_will_open as extern "C" fn(&mut Object, Sel, id),
- );
- decl.add_method(
- sel!(application:openURLs:),
- open_urls as extern "C" fn(&mut Object, Sel, id, id),
- );
- decl.register()
- }
-}
-
-pub struct MacPlatform(Mutex<MacPlatformState>);
-
-pub struct MacPlatformState {
- background_executor: BackgroundExecutor,
- foreground_executor: ForegroundExecutor,
- text_system: Arc<MacTextSystem>,
- display_linker: MacDisplayLinker,
- pasteboard: id,
- text_hash_pasteboard_type: id,
- metadata_pasteboard_type: id,
- become_active: Option<Box<dyn FnMut()>>,
- resign_active: Option<Box<dyn FnMut()>>,
- reopen: Option<Box<dyn FnMut()>>,
- quit: Option<Box<dyn FnMut()>>,
- event: Option<Box<dyn FnMut(InputEvent) -> bool>>,
- menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
- validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
- will_open_menu: Option<Box<dyn FnMut()>>,
- menu_actions: Vec<Box<dyn Action>>,
- open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
- finish_launching: Option<Box<dyn FnOnce()>>,
-}
-
-impl Default for MacPlatform {
- fn default() -> Self {
- Self::new()
- }
-}
-
-impl MacPlatform {
- pub fn new() -> Self {
- let dispatcher = Arc::new(MacDispatcher::new());
- Self(Mutex::new(MacPlatformState {
- background_executor: BackgroundExecutor::new(dispatcher.clone()),
- foreground_executor: ForegroundExecutor::new(dispatcher),
- text_system: Arc::new(MacTextSystem::new()),
- display_linker: MacDisplayLinker::new(),
- pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
- text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
- metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
- become_active: None,
- resign_active: None,
- reopen: None,
- quit: None,
- event: None,
- menu_command: None,
- validate_menu_command: None,
- will_open_menu: None,
- menu_actions: Default::default(),
- open_urls: None,
- finish_launching: None,
- }))
- }
-
- unsafe fn read_from_pasteboard(&self, pasteboard: *mut Object, kind: id) -> Option<&[u8]> {
- let data = pasteboard.dataForType(kind);
- if data == nil {
- None
- } else {
- Some(slice::from_raw_parts(
- data.bytes() as *mut u8,
- data.length() as usize,
- ))
- }
- }
-
- unsafe fn create_menu_bar(
- &self,
- menus: Vec<Menu>,
- delegate: id,
- actions: &mut Vec<Box<dyn Action>>,
- keymap: &Keymap,
- ) -> id {
- let application_menu = NSMenu::new(nil).autorelease();
- application_menu.setDelegate_(delegate);
-
- for menu_config in menus {
- let menu = NSMenu::new(nil).autorelease();
- menu.setTitle_(ns_string(menu_config.name));
- menu.setDelegate_(delegate);
-
- for item_config in menu_config.items {
- menu.addItem_(Self::create_menu_item(
- item_config,
- delegate,
- actions,
- keymap,
- ));
- }
-
- let menu_item = NSMenuItem::new(nil).autorelease();
- menu_item.setSubmenu_(menu);
- application_menu.addItem_(menu_item);
-
- if menu_config.name == "Window" {
- let app: id = msg_send![APP_CLASS, sharedApplication];
- app.setWindowsMenu_(menu);
- }
- }
-
- application_menu
- }
-
- unsafe fn create_menu_item(
- item: MenuItem,
- delegate: id,
- actions: &mut Vec<Box<dyn Action>>,
- keymap: &Keymap,
- ) -> id {
- match item {
- MenuItem::Separator => NSMenuItem::separatorItem(nil),
- MenuItem::Action {
- name,
- action,
- os_action,
- } => {
- let keystrokes = keymap
- .bindings_for_action(action.type_id())
- .find(|binding| binding.action().partial_eq(action.as_ref()))
- .map(|binding| binding.keystrokes());
-
- let selector = match os_action {
- Some(crate::OsAction::Cut) => selector("cut:"),
- Some(crate::OsAction::Copy) => selector("copy:"),
- Some(crate::OsAction::Paste) => selector("paste:"),
- Some(crate::OsAction::SelectAll) => selector("selectAll:"),
- Some(crate::OsAction::Undo) => selector("undo:"),
- Some(crate::OsAction::Redo) => selector("redo:"),
- None => selector("handleGPUIMenuItem:"),
- };
-
- let item;
- if let Some(keystrokes) = keystrokes {
- if keystrokes.len() == 1 {
- let keystroke = &keystrokes[0];
- let mut mask = NSEventModifierFlags::empty();
- for (modifier, flag) in &[
- (
- keystroke.modifiers.command,
- NSEventModifierFlags::NSCommandKeyMask,
- ),
- (
- keystroke.modifiers.control,
- NSEventModifierFlags::NSControlKeyMask,
- ),
- (
- keystroke.modifiers.alt,
- NSEventModifierFlags::NSAlternateKeyMask,
- ),
- (
- keystroke.modifiers.shift,
- NSEventModifierFlags::NSShiftKeyMask,
- ),
- ] {
- if *modifier {
- mask |= *flag;
- }
- }
-
- item = NSMenuItem::alloc(nil)
- .initWithTitle_action_keyEquivalent_(
- ns_string(name),
- selector,
- ns_string(key_to_native(&keystroke.key).as_ref()),
- )
- .autorelease();
- item.setKeyEquivalentModifierMask_(mask);
- }
- // For multi-keystroke bindings, render the keystroke as part of the title.
- else {
- use std::fmt::Write;
-
- let mut name = format!("{name} [");
- for (i, keystroke) in keystrokes.iter().enumerate() {
- if i > 0 {
- name.push(' ');
- }
- write!(&mut name, "{}", keystroke).unwrap();
- }
- name.push(']');
-
- item = NSMenuItem::alloc(nil)
- .initWithTitle_action_keyEquivalent_(
- ns_string(&name),
- selector,
- ns_string(""),
- )
- .autorelease();
- }
- } else {
- item = NSMenuItem::alloc(nil)
- .initWithTitle_action_keyEquivalent_(
- ns_string(name),
- selector,
- ns_string(""),
- )
- .autorelease();
- }
-
- let tag = actions.len() as NSInteger;
- let _: () = msg_send![item, setTag: tag];
- actions.push(action);
- item
- }
- MenuItem::Submenu(Menu { name, items }) => {
- let item = NSMenuItem::new(nil).autorelease();
- let submenu = NSMenu::new(nil).autorelease();
- submenu.setDelegate_(delegate);
- for item in items {
- submenu.addItem_(Self::create_menu_item(item, delegate, actions, keymap));
- }
- item.setSubmenu_(submenu);
- item.setTitle_(ns_string(name));
- item
- }
- }
- }
-}
-
-impl Platform for MacPlatform {
- fn background_executor(&self) -> BackgroundExecutor {
- self.0.lock().background_executor.clone()
- }
-
- fn foreground_executor(&self) -> crate::ForegroundExecutor {
- self.0.lock().foreground_executor.clone()
- }
-
- fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
- self.0.lock().text_system.clone()
- }
-
- fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
- self.0.lock().finish_launching = Some(on_finish_launching);
-
- unsafe {
- let app: id = msg_send![APP_CLASS, sharedApplication];
- let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
- app.setDelegate_(app_delegate);
-
- let self_ptr = self as *const Self as *const c_void;
- (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
- (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
-
- let pool = NSAutoreleasePool::new(nil);
- app.run();
- pool.drain();
-
- (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
- (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
- }
- }
-
- fn quit(&self) {
- // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks
- // synchronously before this method terminates. If we call `Platform::quit` while holding a
- // borrow of the app state (which most of the time we will do), we will end up
- // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve
- // this, we make quitting the application asynchronous so that we aren't holding borrows to
- // the app state on the stack when we actually terminate the app.
-
- use super::dispatcher::{dispatch_async_f, dispatch_get_main_queue};
-
- unsafe {
- dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));
- }
-
- unsafe extern "C" fn quit(_: *mut c_void) {
- let app = NSApplication::sharedApplication(nil);
- let _: () = msg_send![app, terminate: nil];
- }
- }
-
- fn restart(&self) {
- use std::os::unix::process::CommandExt as _;
-
- let app_pid = std::process::id().to_string();
- let app_path = self
- .app_path()
- .ok()
- // When the app is not bundled, `app_path` returns the
- // directory containing the executable. Disregard this
- // and get the path to the executable itself.
- .and_then(|path| (path.extension()?.to_str()? == "app").then_some(path))
- .unwrap_or_else(|| std::env::current_exe().unwrap());
-
- // Wait until this process has exited and then re-open this path.
- let script = r#"
- while kill -0 $0 2> /dev/null; do
- sleep 0.1
- done
- open "$1"
- "#;
-
- let restart_process = Command::new("/bin/bash")
- .arg("-c")
- .arg(script)
- .arg(app_pid)
- .arg(app_path)
- .process_group(0)
- .spawn();
-
- match restart_process {
- Ok(_) => self.quit(),
- Err(e) => log::error!("failed to spawn restart script: {:?}", e),
- }
- }
-
- fn activate(&self, ignoring_other_apps: bool) {
- unsafe {
- let app = NSApplication::sharedApplication(nil);
- app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
- }
- }
-
- fn hide(&self) {
- unsafe {
- let app = NSApplication::sharedApplication(nil);
- let _: () = msg_send![app, hide: nil];
- }
- }
-
- fn hide_other_apps(&self) {
- unsafe {
- let app = NSApplication::sharedApplication(nil);
- let _: () = msg_send![app, hideOtherApplications: nil];
- }
- }
-
- fn unhide_other_apps(&self) {
- unsafe {
- let app = NSApplication::sharedApplication(nil);
- let _: () = msg_send![app, unhideAllApplications: nil];
- }
- }
-
- // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
- // Box::new(StatusItem::add(self.fonts()))
- // }
-
- fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
- MacDisplay::all()
- .map(|screen| Rc::new(screen) as Rc<_>)
- .collect()
- }
-
- fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
- MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
- }
-
- fn active_window(&self) -> Option<AnyWindowHandle> {
- MacWindow::active_window()
- }
-
- fn open_window(
- &self,
- handle: AnyWindowHandle,
- options: WindowOptions,
- draw: Box<dyn FnMut() -> Result<Scene>>,
- ) -> Box<dyn PlatformWindow> {
- Box::new(MacWindow::open(
- handle,
- options,
- draw,
- self.foreground_executor(),
- ))
- }
-
- fn set_display_link_output_callback(
- &self,
- display_id: DisplayId,
- callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
- ) {
- self.0
- .lock()
- .display_linker
- .set_output_callback(display_id, callback);
- }
-
- fn start_display_link(&self, display_id: DisplayId) {
- self.0.lock().display_linker.start(display_id);
- }
-
- fn stop_display_link(&self, display_id: DisplayId) {
- self.0.lock().display_linker.stop(display_id);
- }
-
- fn open_url(&self, url: &str) {
- unsafe {
- let url = NSURL::alloc(nil)
- .initWithString_(ns_string(url))
- .autorelease();
- let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
- msg_send![workspace, openURL: url]
- }
- }
-
- fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
- self.0.lock().open_urls = Some(callback);
- }
-
- fn prompt_for_paths(
- &self,
- options: PathPromptOptions,
- ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
- unsafe {
- let panel = NSOpenPanel::openPanel(nil);
- panel.setCanChooseDirectories_(options.directories.to_objc());
- panel.setCanChooseFiles_(options.files.to_objc());
- panel.setAllowsMultipleSelection_(options.multiple.to_objc());
- panel.setResolvesAliases_(false.to_objc());
- let (done_tx, done_rx) = oneshot::channel();
- let done_tx = Cell::new(Some(done_tx));
- let block = ConcreteBlock::new(move |response: NSModalResponse| {
- let result = if response == NSModalResponse::NSModalResponseOk {
- let mut result = Vec::new();
- let urls = panel.URLs();
- for i in 0..urls.count() {
- let url = urls.objectAtIndex(i);
- if url.isFileURL() == YES {
- if let Ok(path) = ns_url_to_path(url) {
- result.push(path)
- }
- }
- }
- Some(result)
- } else {
- None
- };
-
- if let Some(done_tx) = done_tx.take() {
- let _ = done_tx.send(result);
- }
- });
- let block = block.copy();
- let _: () = msg_send![panel, beginWithCompletionHandler: block];
- done_rx
- }
- }
-
- fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
- unsafe {
- let panel = NSSavePanel::savePanel(nil);
- let path = ns_string(directory.to_string_lossy().as_ref());
- let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc());
- panel.setDirectoryURL(url);
-
- let (done_tx, done_rx) = oneshot::channel();
- let done_tx = Cell::new(Some(done_tx));
- let block = ConcreteBlock::new(move |response: NSModalResponse| {
- let mut result = None;
- if response == NSModalResponse::NSModalResponseOk {
- let url = panel.URL();
- if url.isFileURL() == YES {
- result = ns_url_to_path(panel.URL()).ok()
- }
- }
-
- if let Some(done_tx) = done_tx.take() {
- let _ = done_tx.send(result);
- }
- });
- let block = block.copy();
- let _: () = msg_send![panel, beginWithCompletionHandler: block];
- done_rx
- }
- }
-
- fn reveal_path(&self, path: &Path) {
- unsafe {
- let path = path.to_path_buf();
- self.0
- .lock()
- .background_executor
- .spawn(async move {
- let full_path = ns_string(path.to_str().unwrap_or(""));
- let root_full_path = ns_string("");
- let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
- let _: BOOL = msg_send![
- workspace,
- selectFile: full_path
- inFileViewerRootedAtPath: root_full_path
- ];
- })
- .detach();
- }
- }
-
- fn on_become_active(&self, callback: Box<dyn FnMut()>) {
- self.0.lock().become_active = Some(callback);
- }
-
- fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
- self.0.lock().resign_active = Some(callback);
- }
-
- fn on_quit(&self, callback: Box<dyn FnMut()>) {
- self.0.lock().quit = Some(callback);
- }
-
- fn on_reopen(&self, callback: Box<dyn FnMut()>) {
- self.0.lock().reopen = Some(callback);
- }
-
- fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>) {
- self.0.lock().event = Some(callback);
- }
-
- fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
- self.0.lock().menu_command = Some(callback);
- }
-
- fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
- self.0.lock().will_open_menu = Some(callback);
- }
-
- fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
- self.0.lock().validate_menu_command = Some(callback);
- }
-
- fn os_name(&self) -> &'static str {
- "macOS"
- }
-
- fn double_click_interval(&self) -> Duration {
- unsafe {
- let double_click_interval: f64 = msg_send![class!(NSEvent), doubleClickInterval];
- Duration::from_secs_f64(double_click_interval)
- }
- }
-
- fn os_version(&self) -> Result<SemanticVersion> {
- unsafe {
- let process_info = NSProcessInfo::processInfo(nil);
- let version = process_info.operatingSystemVersion();
- Ok(SemanticVersion {
- major: version.majorVersion as usize,
- minor: version.minorVersion as usize,
- patch: version.patchVersion as usize,
- })
- }
- }
-
- fn app_version(&self) -> Result<SemanticVersion> {
- unsafe {
- let bundle: id = NSBundle::mainBundle();
- if bundle.is_null() {
- Err(anyhow!("app is not running inside a bundle"))
- } else {
- let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")];
- let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
- let bytes = version.UTF8String() as *const u8;
- let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
- version.parse()
- }
- }
- }
-
- fn app_path(&self) -> Result<PathBuf> {
- unsafe {
- let bundle: id = NSBundle::mainBundle();
- if bundle.is_null() {
- Err(anyhow!("app is not running inside a bundle"))
- } else {
- Ok(path_from_objc(msg_send![bundle, bundlePath]))
- }
- }
- }
-
- fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {
- unsafe {
- let app: id = msg_send![APP_CLASS, sharedApplication];
- let mut state = self.0.lock();
- let actions = &mut state.menu_actions;
- app.setMainMenu_(self.create_menu_bar(menus, app.delegate(), actions, keymap));
- }
- }
-
- fn local_timezone(&self) -> UtcOffset {
- unsafe {
- let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
- let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT];
- UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap()
- }
- }
-
- fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
- unsafe {
- let bundle: id = NSBundle::mainBundle();
- if bundle.is_null() {
- Err(anyhow!("app is not running inside a bundle"))
- } else {
- let name = ns_string(name);
- let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];
- if url.is_null() {
- Err(anyhow!("resource not found"))
- } else {
- ns_url_to_path(url)
- }
- }
- }
- }
-
- /// Match cursor style to one of the styles available
- /// in macOS's [NSCursor](https://developer.apple.com/documentation/appkit/nscursor).
- fn set_cursor_style(&self, style: CursorStyle) {
- unsafe {
- let new_cursor: id = match style {
- CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
- CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
- CursorStyle::Crosshair => msg_send![class!(NSCursor), crosshairCursor],
- CursorStyle::ClosedHand => msg_send![class!(NSCursor), closedHandCursor],
- CursorStyle::OpenHand => msg_send![class!(NSCursor), openHandCursor],
- CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
- CursorStyle::ResizeLeft => msg_send![class!(NSCursor), resizeLeftCursor],
- CursorStyle::ResizeRight => msg_send![class!(NSCursor), resizeRightCursor],
- CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor],
- CursorStyle::ResizeUp => msg_send![class!(NSCursor), resizeUpCursor],
- CursorStyle::ResizeDown => msg_send![class!(NSCursor), resizeDownCursor],
- CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
- CursorStyle::DisappearingItem => {
- msg_send![class!(NSCursor), disappearingItemCursor]
- }
- CursorStyle::IBeamCursorForVerticalLayout => {
- msg_send![class!(NSCursor), IBeamCursorForVerticalLayout]
- }
- CursorStyle::OperationNotAllowed => {
- msg_send![class!(NSCursor), operationNotAllowedCursor]
- }
- CursorStyle::DragLink => msg_send![class!(NSCursor), dragLinkCursor],
- CursorStyle::DragCopy => msg_send![class!(NSCursor), dragCopyCursor],
- CursorStyle::ContextualMenu => msg_send![class!(NSCursor), contextualMenuCursor],
- };
-
- let old_cursor: id = msg_send![class!(NSCursor), currentCursor];
- if new_cursor != old_cursor {
- let _: () = msg_send![new_cursor, set];
- }
- }
- }
-
- fn should_auto_hide_scrollbars(&self) -> bool {
- #[allow(non_upper_case_globals)]
- const NSScrollerStyleOverlay: NSInteger = 1;
-
- unsafe {
- let style: NSInteger = msg_send![class!(NSScroller), preferredScrollerStyle];
- style == NSScrollerStyleOverlay
- }
- }
-
- fn write_to_clipboard(&self, item: ClipboardItem) {
- let state = self.0.lock();
- unsafe {
- state.pasteboard.clearContents();
-
- let text_bytes = NSData::dataWithBytes_length_(
- nil,
- item.text.as_ptr() as *const c_void,
- item.text.len() as u64,
- );
- state
- .pasteboard
- .setData_forType(text_bytes, NSPasteboardTypeString);
-
- if let Some(metadata) = item.metadata.as_ref() {
- let hash_bytes = ClipboardItem::text_hash(&item.text).to_be_bytes();
- let hash_bytes = NSData::dataWithBytes_length_(
- nil,
- hash_bytes.as_ptr() as *const c_void,
- hash_bytes.len() as u64,
- );
- state
- .pasteboard
- .setData_forType(hash_bytes, state.text_hash_pasteboard_type);
-
- let metadata_bytes = NSData::dataWithBytes_length_(
- nil,
- metadata.as_ptr() as *const c_void,
- metadata.len() as u64,
- );
- state
- .pasteboard
- .setData_forType(metadata_bytes, state.metadata_pasteboard_type);
- }
- }
- }
-
- fn read_from_clipboard(&self) -> Option<ClipboardItem> {
- let state = self.0.lock();
- unsafe {
- if let Some(text_bytes) =
- self.read_from_pasteboard(state.pasteboard, NSPasteboardTypeString)
- {
- let text = String::from_utf8_lossy(text_bytes).to_string();
- let hash_bytes = self
- .read_from_pasteboard(state.pasteboard, state.text_hash_pasteboard_type)
- .and_then(|bytes| bytes.try_into().ok())
- .map(u64::from_be_bytes);
- let metadata_bytes = self
- .read_from_pasteboard(state.pasteboard, state.metadata_pasteboard_type)
- .and_then(|bytes| String::from_utf8(bytes.to_vec()).ok());
-
- if let Some((hash, metadata)) = hash_bytes.zip(metadata_bytes) {
- if hash == ClipboardItem::text_hash(&text) {
- Some(ClipboardItem {
- text,
- metadata: Some(metadata),
- })
- } else {
- Some(ClipboardItem {
- text,
- metadata: None,
- })
- }
- } else {
- Some(ClipboardItem {
- text,
- metadata: None,
- })
- }
- } else {
- None
- }
- }
- }
-
- fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
- let url = CFString::from(url);
- let username = CFString::from(username);
- let password = CFData::from_buffer(password);
-
- unsafe {
- use security::*;
-
- // First, check if there are already credentials for the given server. If so, then
- // update the username and password.
- let mut verb = "updating";
- let mut query_attrs = CFMutableDictionary::with_capacity(2);
- query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
- query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
-
- let mut attrs = CFMutableDictionary::with_capacity(4);
- attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
- attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
- attrs.set(kSecAttrAccount as *const _, username.as_CFTypeRef());
- attrs.set(kSecValueData as *const _, password.as_CFTypeRef());
-
- let mut status = SecItemUpdate(
- query_attrs.as_concrete_TypeRef(),
- attrs.as_concrete_TypeRef(),
- );
-
- // If there were no existing credentials for the given server, then create them.
- if status == errSecItemNotFound {
- verb = "creating";
- status = SecItemAdd(attrs.as_concrete_TypeRef(), ptr::null_mut());
- }
-
- if status != errSecSuccess {
- return Err(anyhow!("{} password failed: {}", verb, status));
- }
- }
- Ok(())
- }
-
- fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
- let url = CFString::from(url);
- let cf_true = CFBoolean::true_value().as_CFTypeRef();
-
- unsafe {
- use security::*;
-
- // Find any credentials for the given server URL.
- let mut attrs = CFMutableDictionary::with_capacity(5);
- attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
- attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
- attrs.set(kSecReturnAttributes as *const _, cf_true);
- attrs.set(kSecReturnData as *const _, cf_true);
-
- let mut result = CFTypeRef::from(ptr::null());
- let status = SecItemCopyMatching(attrs.as_concrete_TypeRef(), &mut result);
- match status {
- security::errSecSuccess => {}
- security::errSecItemNotFound | security::errSecUserCanceled => return Ok(None),
- _ => return Err(anyhow!("reading password failed: {}", status)),
- }
-
- let result = CFType::wrap_under_create_rule(result)
- .downcast::<CFDictionary>()
- .ok_or_else(|| anyhow!("keychain item was not a dictionary"))?;
- let username = result
- .find(kSecAttrAccount as *const _)
- .ok_or_else(|| anyhow!("account was missing from keychain item"))?;
- let username = CFType::wrap_under_get_rule(*username)
- .downcast::<CFString>()
- .ok_or_else(|| anyhow!("account was not a string"))?;
- let password = result
- .find(kSecValueData as *const _)
- .ok_or_else(|| anyhow!("password was missing from keychain item"))?;
- let password = CFType::wrap_under_get_rule(*password)
- .downcast::<CFData>()
- .ok_or_else(|| anyhow!("password was not a string"))?;
-
- Ok(Some((username.to_string(), password.bytes().to_vec())))
- }
- }
-
- fn delete_credentials(&self, url: &str) -> Result<()> {
- let url = CFString::from(url);
-
- unsafe {
- use security::*;
-
- let mut query_attrs = CFMutableDictionary::with_capacity(2);
- query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
- query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
-
- let status = SecItemDelete(query_attrs.as_concrete_TypeRef());
-
- if status != errSecSuccess {
- return Err(anyhow!("delete password failed: {}", status));
- }
- }
- Ok(())
- }
-}
-
-unsafe fn path_from_objc(path: id) -> PathBuf {
- let len = msg_send![path, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
- let bytes = path.UTF8String() as *const u8;
- let path = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
- PathBuf::from(path)
-}
-
-unsafe fn get_mac_platform(object: &mut Object) -> &MacPlatform {
- let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
- assert!(!platform_ptr.is_null());
- &*(platform_ptr as *const MacPlatform)
-}
-
-extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
- unsafe {
- if let Some(event) = InputEvent::from_native(native_event, None) {
- let platform = get_mac_platform(this);
- if let Some(callback) = platform.0.lock().event.as_mut() {
- if !callback(event) {
- return;
- }
- }
- }
- msg_send![super(this, class!(NSApplication)), sendEvent: native_event]
- }
-}
-
-extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
- unsafe {
- let app: id = msg_send![APP_CLASS, sharedApplication];
- app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
-
- let platform = get_mac_platform(this);
- let callback = platform.0.lock().finish_launching.take();
- if let Some(callback) = callback {
- callback();
- }
- }
-}
-
-extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) {
- if !has_open_windows {
- let platform = unsafe { get_mac_platform(this) };
- if let Some(callback) = platform.0.lock().reopen.as_mut() {
- callback();
- }
- }
-}
-
-extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
- let platform = unsafe { get_mac_platform(this) };
- if let Some(callback) = platform.0.lock().become_active.as_mut() {
- callback();
- }
-}
-
-extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
- let platform = unsafe { get_mac_platform(this) };
- if let Some(callback) = platform.0.lock().resign_active.as_mut() {
- callback();
- }
-}
-
-extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) {
- let platform = unsafe { get_mac_platform(this) };
- if let Some(callback) = platform.0.lock().quit.as_mut() {
- callback();
- }
-}
-
-extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) {
- let urls = unsafe {
- (0..urls.count())
- .filter_map(|i| {
- let url = urls.objectAtIndex(i);
- match CStr::from_ptr(url.absoluteString().UTF8String() as *mut c_char).to_str() {
- Ok(string) => Some(string.to_string()),
- Err(err) => {
- log::error!("error converting path to string: {}", err);
- None
- }
- }
- })
- .collect::<Vec<_>>()
- };
- let platform = unsafe { get_mac_platform(this) };
- if let Some(callback) = platform.0.lock().open_urls.as_mut() {
- callback(urls);
- }
-}
-
-extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
- unsafe {
- let platform = get_mac_platform(this);
- let mut platform = platform.0.lock();
- if let Some(mut callback) = platform.menu_command.take() {
- let tag: NSInteger = msg_send![item, tag];
- let index = tag as usize;
- if let Some(action) = platform.menu_actions.get(index) {
- callback(action.as_ref());
- }
- platform.menu_command = Some(callback);
- }
- }
-}
-
-extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool {
- unsafe {
- let mut result = false;
- let platform = get_mac_platform(this);
- let mut platform = platform.0.lock();
- if let Some(mut callback) = platform.validate_menu_command.take() {
- let tag: NSInteger = msg_send![item, tag];
- let index = tag as usize;
- if let Some(action) = platform.menu_actions.get(index) {
- result = callback(action.as_ref());
- }
- platform.validate_menu_command = Some(callback);
- }
- result
- }
-}
-
-extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) {
- unsafe {
- let platform = get_mac_platform(this);
- let mut platform = platform.0.lock();
- if let Some(mut callback) = platform.will_open_menu.take() {
- callback();
- platform.will_open_menu = Some(callback);
- }
- }
-}
-
-unsafe fn ns_string(string: &str) -> id {
- NSString::alloc(nil).init_str(string).autorelease()
-}
-
-unsafe fn ns_url_to_path(url: id) -> Result<PathBuf> {
- let path: *mut c_char = msg_send![url, fileSystemRepresentation];
- if path.is_null() {
- Err(anyhow!(
- "url is not a file path: {}",
- CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy()
- ))
- } else {
- Ok(PathBuf::from(OsStr::from_bytes(
- CStr::from_ptr(path).to_bytes(),
- )))
- }
-}
-
-mod security {
- #![allow(non_upper_case_globals)]
- use super::*;
-
- #[link(name = "Security", kind = "framework")]
- extern "C" {
- pub static kSecClass: CFStringRef;
- pub static kSecClassInternetPassword: CFStringRef;
- pub static kSecAttrServer: CFStringRef;
- pub static kSecAttrAccount: CFStringRef;
- pub static kSecValueData: CFStringRef;
- pub static kSecReturnAttributes: CFStringRef;
- pub static kSecReturnData: CFStringRef;
-
- pub fn SecItemAdd(attributes: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
- pub fn SecItemUpdate(query: CFDictionaryRef, attributes: CFDictionaryRef) -> OSStatus;
- pub fn SecItemDelete(query: CFDictionaryRef) -> OSStatus;
- pub fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
- }
-
- pub const errSecSuccess: OSStatus = 0;
- pub const errSecUserCanceled: OSStatus = -128;
- pub const errSecItemNotFound: OSStatus = -25300;
-}
-
-#[cfg(test)]
-mod tests {
- use crate::ClipboardItem;
-
- use super::*;
-
- #[test]
- fn test_clipboard() {
- let platform = build_platform();
- assert_eq!(platform.read_from_clipboard(), None);
-
- let item = ClipboardItem::new("1".to_string());
- platform.write_to_clipboard(item.clone());
- assert_eq!(platform.read_from_clipboard(), Some(item));
-
- let item = ClipboardItem::new("2".to_string()).with_metadata(vec![3, 4]);
- platform.write_to_clipboard(item.clone());
- assert_eq!(platform.read_from_clipboard(), Some(item));
-
- let text_from_other_app = "text from other app";
- unsafe {
- let bytes = NSData::dataWithBytes_length_(
- nil,
- text_from_other_app.as_ptr() as *const c_void,
- text_from_other_app.len() as u64,
- );
- platform
- .0
- .lock()
- .pasteboard
- .setData_forType(bytes, NSPasteboardTypeString);
- }
- assert_eq!(
- platform.read_from_clipboard(),
- Some(ClipboardItem::new(text_from_other_app.to_string()))
- );
- }
-
- fn build_platform() -> MacPlatform {
- let platform = MacPlatform::new();
- platform.0.lock().pasteboard = unsafe { NSPasteboard::pasteboardWithUniqueName(nil) };
- platform
- }
-}
@@ -1,1791 +0,0 @@
-use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange};
-use crate::{
- display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, DrawWindow, ExternalPaths,
- FileDropEvent, ForegroundExecutor, GlobalPixels, InputEvent, KeyDownEvent, Keystroke,
- Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
- Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
- PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
-};
-use block::ConcreteBlock;
-use cocoa::{
- appkit::{
- CGPoint, NSApplication, NSBackingStoreBuffered, NSEventModifierFlags,
- NSFilenamesPboardType, NSPasteboard, NSScreen, NSView, NSViewHeightSizable,
- NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior,
- NSWindowStyleMask, NSWindowTitleVisibility,
- },
- base::{id, nil},
- foundation::{
- NSArray, NSAutoreleasePool, NSDictionary, NSFastEnumeration, NSInteger, NSPoint, NSRect,
- NSSize, NSString, NSUInteger,
- },
-};
-use core_graphics::display::CGRect;
-use ctor::ctor;
-use foreign_types::ForeignTypeRef;
-use futures::channel::oneshot;
-use objc::{
- class,
- declare::ClassDecl,
- msg_send,
- runtime::{Class, Object, Protocol, Sel, BOOL, NO, YES},
- sel, sel_impl,
-};
-use parking_lot::Mutex;
-use smallvec::SmallVec;
-use std::{
- any::Any,
- cell::{Cell, RefCell},
- ffi::{c_void, CStr},
- mem,
- ops::Range,
- os::raw::c_char,
- path::PathBuf,
- ptr,
- rc::Rc,
- sync::{Arc, Weak},
- time::Duration,
-};
-use util::ResultExt;
-
-const WINDOW_STATE_IVAR: &str = "windowState";
-
-static mut WINDOW_CLASS: *const Class = ptr::null();
-static mut PANEL_CLASS: *const Class = ptr::null();
-static mut VIEW_CLASS: *const Class = ptr::null();
-
-#[allow(non_upper_case_globals)]
-const NSWindowStyleMaskNonactivatingPanel: NSWindowStyleMask =
- unsafe { NSWindowStyleMask::from_bits_unchecked(1 << 7) };
-#[allow(non_upper_case_globals)]
-const NSNormalWindowLevel: NSInteger = 0;
-#[allow(non_upper_case_globals)]
-const NSPopUpWindowLevel: NSInteger = 101;
-#[allow(non_upper_case_globals)]
-const NSTrackingMouseEnteredAndExited: NSUInteger = 0x01;
-#[allow(non_upper_case_globals)]
-const NSTrackingMouseMoved: NSUInteger = 0x02;
-#[allow(non_upper_case_globals)]
-const NSTrackingActiveAlways: NSUInteger = 0x80;
-#[allow(non_upper_case_globals)]
-const NSTrackingInVisibleRect: NSUInteger = 0x200;
-#[allow(non_upper_case_globals)]
-const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4;
-#[allow(non_upper_case_globals)]
-const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
-// https://developer.apple.com/documentation/appkit/nsdragoperation
-type NSDragOperation = NSUInteger;
-#[allow(non_upper_case_globals)]
-const NSDragOperationNone: NSDragOperation = 0;
-#[allow(non_upper_case_globals)]
-const NSDragOperationCopy: NSDragOperation = 1;
-
-#[ctor]
-unsafe fn build_classes() {
- ::util::gpui2_loaded();
-
- WINDOW_CLASS = build_window_class("GPUI2Window", class!(NSWindow));
- PANEL_CLASS = build_window_class("GPUI2Panel", class!(NSPanel));
- VIEW_CLASS = {
- let mut decl = ClassDecl::new("GPUI2View", class!(NSView)).unwrap();
- decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
-
- decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
-
- decl.add_method(
- sel!(performKeyEquivalent:),
- handle_key_equivalent as extern "C" fn(&Object, Sel, id) -> BOOL,
- );
- decl.add_method(
- sel!(keyDown:),
- handle_key_down as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(mouseDown:),
- handle_view_event as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(mouseUp:),
- handle_view_event as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(rightMouseDown:),
- handle_view_event as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(rightMouseUp:),
- handle_view_event as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(otherMouseDown:),
- handle_view_event as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(otherMouseUp:),
- handle_view_event as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(mouseMoved:),
- handle_view_event as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(mouseExited:),
- handle_view_event as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(mouseDragged:),
- handle_view_event as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(scrollWheel:),
- handle_view_event as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(flagsChanged:),
- handle_view_event as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(cancelOperation:),
- cancel_operation as extern "C" fn(&Object, Sel, id),
- );
-
- decl.add_method(
- sel!(makeBackingLayer),
- make_backing_layer as extern "C" fn(&Object, Sel) -> id,
- );
-
- decl.add_protocol(Protocol::get("CALayerDelegate").unwrap());
- decl.add_method(
- sel!(viewDidChangeBackingProperties),
- view_did_change_backing_properties as extern "C" fn(&Object, Sel),
- );
- decl.add_method(
- sel!(setFrameSize:),
- set_frame_size as extern "C" fn(&Object, Sel, NSSize),
- );
- decl.add_method(
- sel!(displayLayer:),
- display_layer as extern "C" fn(&Object, Sel, id),
- );
-
- decl.add_protocol(Protocol::get("NSTextInputClient").unwrap());
- decl.add_method(
- sel!(validAttributesForMarkedText),
- valid_attributes_for_marked_text as extern "C" fn(&Object, Sel) -> id,
- );
- decl.add_method(
- sel!(hasMarkedText),
- has_marked_text as extern "C" fn(&Object, Sel) -> BOOL,
- );
- decl.add_method(
- sel!(markedRange),
- marked_range as extern "C" fn(&Object, Sel) -> NSRange,
- );
- decl.add_method(
- sel!(selectedRange),
- selected_range as extern "C" fn(&Object, Sel) -> NSRange,
- );
- decl.add_method(
- sel!(firstRectForCharacterRange:actualRange:),
- first_rect_for_character_range as extern "C" fn(&Object, Sel, NSRange, id) -> NSRect,
- );
- decl.add_method(
- sel!(insertText:replacementRange:),
- insert_text as extern "C" fn(&Object, Sel, id, NSRange),
- );
- decl.add_method(
- sel!(setMarkedText:selectedRange:replacementRange:),
- set_marked_text as extern "C" fn(&Object, Sel, id, NSRange, NSRange),
- );
- decl.add_method(sel!(unmarkText), unmark_text as extern "C" fn(&Object, Sel));
- decl.add_method(
- sel!(attributedSubstringForProposedRange:actualRange:),
- attributed_substring_for_proposed_range
- as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id,
- );
- decl.add_method(
- sel!(viewDidChangeEffectiveAppearance),
- view_did_change_effective_appearance as extern "C" fn(&Object, Sel),
- );
-
- // Suppress beep on keystrokes with modifier keys.
- decl.add_method(
- sel!(doCommandBySelector:),
- do_command_by_selector as extern "C" fn(&Object, Sel, Sel),
- );
-
- decl.add_method(
- sel!(acceptsFirstMouse:),
- accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL,
- );
-
- decl.register()
- };
-}
-
-pub fn convert_mouse_position(position: NSPoint, window_height: Pixels) -> Point<Pixels> {
- point(
- px(position.x as f32),
- // MacOS screen coordinates are relative to bottom left
- window_height - px(position.y as f32),
- )
-}
-
-unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const Class {
- let mut decl = ClassDecl::new(name, superclass).unwrap();
- decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
- decl.add_method(sel!(dealloc), dealloc_window as extern "C" fn(&Object, Sel));
- decl.add_method(
- sel!(canBecomeMainWindow),
- yes as extern "C" fn(&Object, Sel) -> BOOL,
- );
- decl.add_method(
- sel!(canBecomeKeyWindow),
- yes as extern "C" fn(&Object, Sel) -> BOOL,
- );
- decl.add_method(
- sel!(windowDidResize:),
- window_did_resize as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(windowWillEnterFullScreen:),
- window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(windowWillExitFullScreen:),
- window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(windowDidMove:),
- window_did_move as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(windowDidBecomeKey:),
- window_did_change_key_status as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(windowDidResignKey:),
- window_did_change_key_status as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(windowShouldClose:),
- window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL,
- );
- decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
-
- decl.add_method(
- sel!(draggingEntered:),
- dragging_entered as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
- );
- decl.add_method(
- sel!(draggingUpdated:),
- dragging_updated as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
- );
- decl.add_method(
- sel!(draggingExited:),
- dragging_exited as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(performDragOperation:),
- perform_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL,
- );
- decl.add_method(
- sel!(concludeDragOperation:),
- conclude_drag_operation as extern "C" fn(&Object, Sel, id),
- );
-
- decl.register()
-}
-
-///Used to track what the IME does when we send it a keystroke.
-///This is only used to handle the case where the IME mysteriously
-///swallows certain keys.
-///
-///Basically a direct copy of the approach that WezTerm uses in:
-///github.com/wez/wezterm : d5755f3e : window/src/os/macos/window.rs
-enum ImeState {
- Continue,
- Acted,
- None,
-}
-
-struct InsertText {
- replacement_range: Option<Range<usize>>,
- text: String,
-}
-
-struct MacWindowState {
- handle: AnyWindowHandle,
- executor: ForegroundExecutor,
- native_window: id,
- renderer: MetalRenderer,
- draw: Option<DrawWindow>,
- kind: WindowKind,
- event_callback: Option<Box<dyn FnMut(InputEvent) -> bool>>,
- activate_callback: Option<Box<dyn FnMut(bool)>>,
- resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
- fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
- moved_callback: Option<Box<dyn FnMut()>>,
- should_close_callback: Option<Box<dyn FnMut() -> bool>>,
- close_callback: Option<Box<dyn FnOnce()>>,
- appearance_changed_callback: Option<Box<dyn FnMut()>>,
- input_handler: Option<Box<dyn PlatformInputHandler>>,
- pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
- last_key_equivalent: Option<KeyDownEvent>,
- synthetic_drag_counter: usize,
- last_fresh_keydown: Option<Keystroke>,
- traffic_light_position: Option<Point<Pixels>>,
- previous_modifiers_changed_event: Option<InputEvent>,
- // State tracking what the IME did after the last request
- ime_state: ImeState,
- // Retains the last IME Text
- ime_text: Option<String>,
-}
-
-impl MacWindowState {
- fn move_traffic_light(&self) {
- if let Some(traffic_light_position) = self.traffic_light_position {
- let titlebar_height = self.titlebar_height();
-
- unsafe {
- let close_button: id = msg_send![
- self.native_window,
- standardWindowButton: NSWindowButton::NSWindowCloseButton
- ];
- let min_button: id = msg_send![
- self.native_window,
- standardWindowButton: NSWindowButton::NSWindowMiniaturizeButton
- ];
- let zoom_button: id = msg_send![
- self.native_window,
- standardWindowButton: NSWindowButton::NSWindowZoomButton
- ];
-
- let mut close_button_frame: CGRect = msg_send![close_button, frame];
- let mut min_button_frame: CGRect = msg_send![min_button, frame];
- let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame];
- let mut origin = point(
- traffic_light_position.x,
- titlebar_height
- - traffic_light_position.y
- - px(close_button_frame.size.height as f32),
- );
- let button_spacing =
- px((min_button_frame.origin.x - close_button_frame.origin.x) as f32);
-
- close_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
- let _: () = msg_send![close_button, setFrame: close_button_frame];
- origin.x += button_spacing;
-
- min_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
- let _: () = msg_send![min_button, setFrame: min_button_frame];
- origin.x += button_spacing;
-
- zoom_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
- let _: () = msg_send![zoom_button, setFrame: zoom_button_frame];
- origin.x += button_spacing;
- }
- }
- }
-
- fn is_fullscreen(&self) -> bool {
- unsafe {
- let style_mask = self.native_window.styleMask();
- style_mask.contains(NSWindowStyleMask::NSFullScreenWindowMask)
- }
- }
-
- fn bounds(&self) -> WindowBounds {
- unsafe {
- if self.is_fullscreen() {
- return WindowBounds::Fullscreen;
- }
-
- let frame = self.frame();
- let screen_size = self.native_window.screen().visibleFrame().into();
- if frame.size == screen_size {
- WindowBounds::Maximized
- } else {
- WindowBounds::Fixed(frame)
- }
- }
- }
-
- fn frame(&self) -> Bounds<GlobalPixels> {
- unsafe {
- let frame = NSWindow::frame(self.native_window);
- display_bounds_from_native(mem::transmute::<NSRect, CGRect>(frame))
- }
- }
-
- fn content_size(&self) -> Size<Pixels> {
- let NSSize { width, height, .. } =
- unsafe { NSView::frame(self.native_window.contentView()) }.size;
- size(px(width as f32), px(height as f32))
- }
-
- fn scale_factor(&self) -> f32 {
- get_scale_factor(self.native_window)
- }
-
- fn titlebar_height(&self) -> Pixels {
- unsafe {
- let frame = NSWindow::frame(self.native_window);
- let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect];
- px((frame.size.height - content_layout_rect.size.height) as f32)
- }
- }
-
- fn to_screen_ns_point(&self, point: Point<Pixels>) -> NSPoint {
- unsafe {
- let point = NSPoint::new(
- point.x.into(),
- (self.content_size().height - point.y).into(),
- );
- msg_send![self.native_window, convertPointToScreen: point]
- }
- }
-}
-
-unsafe impl Send for MacWindowState {}
-
-pub struct MacWindow(Arc<Mutex<MacWindowState>>);
-
-impl MacWindow {
- pub fn open(
- handle: AnyWindowHandle,
- options: WindowOptions,
- draw: DrawWindow,
- executor: ForegroundExecutor,
- ) -> Self {
- unsafe {
- let pool = NSAutoreleasePool::new(nil);
-
- let mut style_mask;
- if let Some(titlebar) = options.titlebar.as_ref() {
- style_mask = NSWindowStyleMask::NSClosableWindowMask
- | NSWindowStyleMask::NSMiniaturizableWindowMask
- | NSWindowStyleMask::NSResizableWindowMask
- | NSWindowStyleMask::NSTitledWindowMask;
-
- if titlebar.appears_transparent {
- style_mask |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
- }
- } else {
- style_mask = NSWindowStyleMask::NSTitledWindowMask
- | NSWindowStyleMask::NSFullSizeContentViewWindowMask;
- }
-
- let native_window: id = match options.kind {
- WindowKind::Normal => msg_send![WINDOW_CLASS, alloc],
- WindowKind::PopUp => {
- style_mask |= NSWindowStyleMaskNonactivatingPanel;
- msg_send![PANEL_CLASS, alloc]
- }
- };
-
- let display = options
- .display_id
- .and_then(|display_id| MacDisplay::all().find(|display| display.id() == display_id))
- .unwrap_or_else(MacDisplay::primary);
-
- let mut target_screen = nil;
- let screens = NSScreen::screens(nil);
- let count: u64 = cocoa::foundation::NSArray::count(screens);
- for i in 0..count {
- let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
- let device_description = NSScreen::deviceDescription(screen);
- let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
- let screen_number = device_description.objectForKey_(screen_number_key);
- let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
- if screen_number as u32 == display.id().0 {
- target_screen = screen;
- break;
- }
- }
-
- let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
- NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)),
- style_mask,
- NSBackingStoreBuffered,
- NO,
- target_screen,
- );
- assert!(!native_window.is_null());
- let () = msg_send![
- native_window,
- registerForDraggedTypes:
- NSArray::arrayWithObject(nil, NSFilenamesPboardType)
- ];
-
- let screen = native_window.screen();
- match options.bounds {
- WindowBounds::Fullscreen => {
- native_window.toggleFullScreen_(nil);
- }
- WindowBounds::Maximized => {
- native_window.setFrame_display_(screen.visibleFrame(), YES);
- }
- WindowBounds::Fixed(bounds) => {
- let display_bounds = display.bounds();
- let frame = if bounds.intersects(&display_bounds) {
- display_bounds_to_native(bounds)
- } else {
- display_bounds_to_native(display_bounds)
- };
- native_window.setFrame_display_(mem::transmute::<CGRect, NSRect>(frame), YES);
- }
- }
-
- let native_view: id = msg_send![VIEW_CLASS, alloc];
- let native_view = NSView::init(native_view);
-
- assert!(!native_view.is_null());
-
- let window = Self(Arc::new(Mutex::new(MacWindowState {
- handle,
- executor,
- native_window,
- renderer: MetalRenderer::new(true),
- draw: Some(draw),
- kind: options.kind,
- event_callback: None,
- activate_callback: None,
- resize_callback: None,
- fullscreen_callback: None,
- moved_callback: None,
- should_close_callback: None,
- close_callback: None,
- appearance_changed_callback: None,
- input_handler: None,
- pending_key_down: None,
- last_key_equivalent: None,
- synthetic_drag_counter: 0,
- last_fresh_keydown: None,
- traffic_light_position: options
- .titlebar
- .as_ref()
- .and_then(|titlebar| titlebar.traffic_light_position),
- previous_modifiers_changed_event: None,
- ime_state: ImeState::None,
- ime_text: None,
- })));
-
- (*native_window).set_ivar(
- WINDOW_STATE_IVAR,
- Arc::into_raw(window.0.clone()) as *const c_void,
- );
- native_window.setDelegate_(native_window);
- (*native_view).set_ivar(
- WINDOW_STATE_IVAR,
- Arc::into_raw(window.0.clone()) as *const c_void,
- );
-
- if let Some(title) = options
- .titlebar
- .as_ref()
- .and_then(|t| t.title.as_ref().map(AsRef::as_ref))
- {
- native_window.setTitle_(NSString::alloc(nil).init_str(title));
- }
-
- native_window.setMovable_(options.is_movable as BOOL);
-
- if options
- .titlebar
- .map_or(true, |titlebar| titlebar.appears_transparent)
- {
- native_window.setTitlebarAppearsTransparent_(YES);
- native_window.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden);
- }
-
- native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
- native_view.setWantsBestResolutionOpenGLSurface_(YES);
-
- // From winit crate: On Mojave, views automatically become layer-backed shortly after
- // being added to a native_window. Changing the layer-backedness of a view breaks the
- // association between the view and its associated OpenGL context. To work around this,
- // on we explicitly make the view layer-backed up front so that AppKit doesn't do it
- // itself and break the association with its context.
- native_view.setWantsLayer(YES);
- let _: () = msg_send![
- native_view,
- setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
- ];
-
- native_window.setContentView_(native_view.autorelease());
- native_window.makeFirstResponder_(native_view);
-
- if options.center {
- native_window.center();
- }
-
- match options.kind {
- WindowKind::Normal => {
- native_window.setLevel_(NSNormalWindowLevel);
- native_window.setAcceptsMouseMovedEvents_(YES);
- }
- WindowKind::PopUp => {
- // Use a tracking area to allow receiving MouseMoved events even when
- // the window or application aren't active, which is often the case
- // e.g. for notification windows.
- let tracking_area: id = msg_send![class!(NSTrackingArea), alloc];
- let _: () = msg_send![
- tracking_area,
- initWithRect: NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.))
- options: NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect
- owner: native_view
- userInfo: nil
- ];
- let _: () =
- msg_send![native_view, addTrackingArea: tracking_area.autorelease()];
-
- native_window.setLevel_(NSPopUpWindowLevel);
- let _: () = msg_send![
- native_window,
- setAnimationBehavior: NSWindowAnimationBehaviorUtilityWindow
- ];
- native_window.setCollectionBehavior_(
- NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces |
- NSWindowCollectionBehavior::NSWindowCollectionBehaviorFullScreenAuxiliary
- );
- }
- }
- if options.focus {
- native_window.makeKeyAndOrderFront_(nil);
- } else if options.show {
- native_window.orderFront_(nil);
- }
-
- window.0.lock().move_traffic_light();
- pool.drain();
-
- window
- }
- }
-
- pub fn active_window() -> Option<AnyWindowHandle> {
- unsafe {
- let app = NSApplication::sharedApplication(nil);
- let main_window: id = msg_send![app, mainWindow];
- if msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
- let handle = get_window_state(&*main_window).lock().handle;
- Some(handle)
- } else {
- None
- }
- }
- }
-}
-
-impl Drop for MacWindow {
- fn drop(&mut self) {
- let this = self.0.lock();
- let window = this.native_window;
- this.executor
- .spawn(async move {
- unsafe {
- // todo!() this panic()s when you click the red close button
- // unless should_close returns false.
- // (luckliy in zed it always returns false)
- window.close();
- }
- })
- .detach();
- }
-}
-
-impl PlatformWindow for MacWindow {
- fn bounds(&self) -> WindowBounds {
- self.0.as_ref().lock().bounds()
- }
-
- fn content_size(&self) -> Size<Pixels> {
- self.0.as_ref().lock().content_size()
- }
-
- fn scale_factor(&self) -> f32 {
- self.0.as_ref().lock().scale_factor()
- }
-
- fn titlebar_height(&self) -> Pixels {
- self.0.as_ref().lock().titlebar_height()
- }
-
- fn appearance(&self) -> WindowAppearance {
- unsafe {
- let appearance: id = msg_send![self.0.lock().native_window, effectiveAppearance];
- WindowAppearance::from_native(appearance)
- }
- }
-
- fn display(&self) -> Rc<dyn PlatformDisplay> {
- unsafe {
- let screen = self.0.lock().native_window.screen();
- let device_description: id = msg_send![screen, deviceDescription];
- let screen_number: id = NSDictionary::valueForKey_(
- device_description,
- NSString::alloc(nil).init_str("NSScreenNumber"),
- );
-
- let screen_number: u32 = msg_send![screen_number, unsignedIntValue];
-
- Rc::new(MacDisplay(screen_number))
- }
- }
-
- fn mouse_position(&self) -> Point<Pixels> {
- let position = unsafe {
- self.0
- .lock()
- .native_window
- .mouseLocationOutsideOfEventStream()
- };
- convert_mouse_position(position, self.content_size().height)
- }
-
- fn modifiers(&self) -> Modifiers {
- unsafe {
- let modifiers: NSEventModifierFlags = msg_send![class!(NSEvent), modifierFlags];
-
- let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
- let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
- let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
- let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
- let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
-
- Modifiers {
- control,
- alt,
- shift,
- command,
- function,
- }
- }
- }
-
- fn as_any_mut(&mut self) -> &mut dyn Any {
- self
- }
-
- fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>) {
- self.0.as_ref().lock().input_handler = Some(input_handler);
- }
-
- fn clear_input_handler(&mut self) {
- self.0.as_ref().lock().input_handler = None;
- }
-
- fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize> {
- // macOs applies overrides to modal window buttons after they are added.
- // Two most important for this logic are:
- // * Buttons with "Cancel" title will be displayed as the last buttons in the modal
- // * Last button added to the modal via `addButtonWithTitle` stays focused
- // * Focused buttons react on "space"/" " keypresses
- // * Usage of `keyEquivalent`, `makeFirstResponder` or `setInitialFirstResponder` does not change the focus
- //
- // See also https://developer.apple.com/documentation/appkit/nsalert/1524532-addbuttonwithtitle#discussion
- // ```
- // By default, the first button has a key equivalent of Return,
- // any button with a title of “Cancel” has a key equivalent of Escape,
- // and any button with the title “Don’t Save” has a key equivalent of Command-D (but only if it’s not the first button).
- // ```
- //
- // To avoid situations when the last element added is "Cancel" and it gets the focus
- // (hence stealing both ESC and Space shortcuts), we find and add one non-Cancel button
- // last, so it gets focus and a Space shortcut.
- // This way, "Save this file? Yes/No/Cancel"-ish modals will get all three buttons mapped with a key.
- let latest_non_cancel_label = answers
- .iter()
- .enumerate()
- .rev()
- .find(|(_, &label)| label != "Cancel")
- .filter(|&(label_index, _)| label_index > 0);
-
- unsafe {
- let alert: id = msg_send![class!(NSAlert), alloc];
- let alert: id = msg_send![alert, init];
- let alert_style = match level {
- PromptLevel::Info => 1,
- PromptLevel::Warning => 0,
- PromptLevel::Critical => 2,
- };
- let _: () = msg_send![alert, setAlertStyle: alert_style];
- let _: () = msg_send![alert, setMessageText: ns_string(msg)];
-
- for (ix, answer) in answers
- .iter()
- .enumerate()
- .filter(|&(ix, _)| Some(ix) != latest_non_cancel_label.map(|(ix, _)| ix))
- {
- let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
- let _: () = msg_send![button, setTag: ix as NSInteger];
- }
- if let Some((ix, answer)) = latest_non_cancel_label {
- let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
- let _: () = msg_send![button, setTag: ix as NSInteger];
- }
-
- let (done_tx, done_rx) = oneshot::channel();
- let done_tx = Cell::new(Some(done_tx));
- let block = ConcreteBlock::new(move |answer: NSInteger| {
- if let Some(done_tx) = done_tx.take() {
- let _ = done_tx.send(answer.try_into().unwrap());
- }
- });
- let block = block.copy();
- let native_window = self.0.lock().native_window;
- let executor = self.0.lock().executor.clone();
- executor
- .spawn(async move {
- let _: () = msg_send![
- alert,
- beginSheetModalForWindow: native_window
- completionHandler: block
- ];
- })
- .detach();
-
- done_rx
- }
- }
-
- fn activate(&self) {
- let window = self.0.lock().native_window;
- let executor = self.0.lock().executor.clone();
- executor
- .spawn(async move {
- unsafe {
- let _: () = msg_send![window, makeKeyAndOrderFront: nil];
- }
- })
- .detach();
- }
-
- fn set_title(&mut self, title: &str) {
- unsafe {
- let app = NSApplication::sharedApplication(nil);
- let window = self.0.lock().native_window;
- let title = ns_string(title);
- let _: () = msg_send![app, changeWindowsItem:window title:title filename:false];
- let _: () = msg_send![window, setTitle: title];
- self.0.lock().move_traffic_light();
- }
- }
-
- fn set_edited(&mut self, edited: bool) {
- unsafe {
- let window = self.0.lock().native_window;
- msg_send![window, setDocumentEdited: edited as BOOL]
- }
-
- // Changing the document edited state resets the traffic light position,
- // so we have to move it again.
- self.0.lock().move_traffic_light();
- }
-
- fn show_character_palette(&self) {
- let this = self.0.lock();
- let window = this.native_window;
- this.executor
- .spawn(async move {
- unsafe {
- let app = NSApplication::sharedApplication(nil);
- let _: () = msg_send![app, orderFrontCharacterPalette: window];
- }
- })
- .detach();
- }
-
- fn minimize(&self) {
- let window = self.0.lock().native_window;
- unsafe {
- window.miniaturize_(nil);
- }
- }
-
- fn zoom(&self) {
- let this = self.0.lock();
- let window = this.native_window;
- this.executor
- .spawn(async move {
- unsafe {
- window.zoom_(nil);
- }
- })
- .detach();
- }
-
- fn toggle_full_screen(&self) {
- let this = self.0.lock();
- let window = this.native_window;
- this.executor
- .spawn(async move {
- unsafe {
- window.toggleFullScreen_(nil);
- }
- })
- .detach();
- }
-
- fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>) {
- self.0.as_ref().lock().event_callback = Some(callback);
- }
-
- fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
- self.0.as_ref().lock().activate_callback = Some(callback);
- }
-
- fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
- self.0.as_ref().lock().resize_callback = Some(callback);
- }
-
- fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
- self.0.as_ref().lock().fullscreen_callback = Some(callback);
- }
-
- fn on_moved(&self, callback: Box<dyn FnMut()>) {
- self.0.as_ref().lock().moved_callback = Some(callback);
- }
-
- fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
- self.0.as_ref().lock().should_close_callback = Some(callback);
- }
-
- fn on_close(&self, callback: Box<dyn FnOnce()>) {
- self.0.as_ref().lock().close_callback = Some(callback);
- }
-
- fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
- self.0.lock().appearance_changed_callback = Some(callback);
- }
-
- fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool {
- let self_borrow = self.0.lock();
- let self_handle = self_borrow.handle;
-
- unsafe {
- let app = NSApplication::sharedApplication(nil);
-
- // Convert back to screen coordinates
- let screen_point = self_borrow.to_screen_ns_point(position);
-
- let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:screen_point belowWindowWithWindowNumber:0];
- let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number];
-
- let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS];
- let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS];
- if is_panel == YES || is_window == YES {
- let topmost_window = get_window_state(&*top_most_window).lock().handle;
- topmost_window == self_handle
- } else {
- // Someone else's window is on top
- false
- }
- }
- }
-
- fn invalidate(&self) {
- let this = self.0.lock();
- unsafe {
- let _: () = msg_send![this.native_window.contentView(), setNeedsDisplay: YES];
- }
- }
-
- fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
- self.0.lock().renderer.sprite_atlas().clone()
- }
-}
-
-fn get_scale_factor(native_window: id) -> f32 {
- unsafe {
- let screen: id = msg_send![native_window, screen];
- NSScreen::backingScaleFactor(screen) as f32
- }
-}
-
-unsafe fn get_window_state(object: &Object) -> Arc<Mutex<MacWindowState>> {
- let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
- let rc1 = Arc::from_raw(raw as *mut Mutex<MacWindowState>);
- let rc2 = rc1.clone();
- mem::forget(rc1);
- rc2
-}
-
-unsafe fn drop_window_state(object: &Object) {
- let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
- Rc::from_raw(raw as *mut RefCell<MacWindowState>);
-}
-
-extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
- YES
-}
-
-extern "C" fn dealloc_window(this: &Object, _: Sel) {
- unsafe {
- drop_window_state(this);
- let _: () = msg_send![super(this, class!(NSWindow)), dealloc];
- }
-}
-
-extern "C" fn dealloc_view(this: &Object, _: Sel) {
- unsafe {
- drop_window_state(this);
- let _: () = msg_send![super(this, class!(NSView)), dealloc];
- }
-}
-
-extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL {
- handle_key_event(this, native_event, true)
-}
-
-extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) {
- handle_key_event(this, native_event, false);
-}
-
-extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL {
- let window_state = unsafe { get_window_state(this) };
- let mut lock = window_state.as_ref().lock();
-
- let window_height = lock.content_size().height;
- let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) };
-
- if let Some(InputEvent::KeyDown(event)) = event {
- // For certain keystrokes, macOS will first dispatch a "key equivalent" event.
- // If that event isn't handled, it will then dispatch a "key down" event. GPUI
- // makes no distinction between these two types of events, so we need to ignore
- // the "key down" event if we've already just processed its "key equivalent" version.
- if key_equivalent {
- lock.last_key_equivalent = Some(event.clone());
- } else if lock.last_key_equivalent.take().as_ref() == Some(&event) {
- return NO;
- }
-
- let keydown = event.keystroke.clone();
- let fn_modifier = keydown.modifiers.function;
- // Ignore events from held-down keys after some of the initially-pressed keys
- // were released.
- if event.is_held {
- if lock.last_fresh_keydown.as_ref() != Some(&keydown) {
- return YES;
- }
- } else {
- lock.last_fresh_keydown = Some(keydown);
- }
- lock.pending_key_down = Some((event, None));
- drop(lock);
-
- // Send the event to the input context for IME handling, unless the `fn` modifier is
- // being pressed.
- if !fn_modifier {
- unsafe {
- let input_context: id = msg_send![this, inputContext];
- let _: BOOL = msg_send![input_context, handleEvent: native_event];
- }
- }
-
- let mut handled = false;
- let mut lock = window_state.lock();
- let ime_text = lock.ime_text.clone();
- if let Some((event, insert_text)) = lock.pending_key_down.take() {
- let is_held = event.is_held;
- if let Some(mut callback) = lock.event_callback.take() {
- drop(lock);
-
- let is_composing =
- with_input_handler(this, |input_handler| input_handler.marked_text_range())
- .flatten()
- .is_some();
- if !is_composing {
- // if the IME has changed the key, we'll first emit an event with the character
- // generated by the IME system; then fallback to the keystroke if that is not
- // handled.
- // cases that we have working:
- // - " on a brazillian layout by typing <quote><space>
- // - ctrl-` on a brazillian layout by typing <ctrl-`>
- // - $ on a czech QWERTY layout by typing <alt-4>
- // - 4 on a czech QWERTY layout by typing <shift-4>
- // - ctrl-4 on a czech QWERTY layout by typing <ctrl-alt-4> (or <ctrl-shift-4>)
- if ime_text.is_some() && ime_text.as_ref() != Some(&event.keystroke.key) {
- let event_with_ime_text = KeyDownEvent {
- is_held: false,
- keystroke: Keystroke {
- // we match ctrl because some use-cases need it.
- // we don't match alt because it's often used to generate the optional character
- // we don't match shift because we're not here with letters (usually)
- // we don't match cmd/fn because they don't seem to use IME
- modifiers: Default::default(),
- key: ime_text.clone().unwrap(),
- ime_key: None, // todo!("handle IME key")
- },
- };
- handled = callback(InputEvent::KeyDown(event_with_ime_text));
- }
- if !handled {
- // empty key happens when you type a deadkey in input composition.
- // (e.g. on a brazillian keyboard typing quote is a deadkey)
- if !event.keystroke.key.is_empty() {
- handled = callback(InputEvent::KeyDown(event));
- }
- }
- }
-
- if !handled {
- if let Some(insert) = insert_text {
- handled = true;
- with_input_handler(this, |input_handler| {
- input_handler
- .replace_text_in_range(insert.replacement_range, &insert.text)
- });
- } else if !is_composing && is_held {
- if let Some(last_insert_text) = ime_text {
- //MacOS IME is a bit funky, and even when you've told it there's nothing to
- //inter it will still swallow certain keys (e.g. 'f', 'j') and not others
- //(e.g. 'n'). This is a problem for certain kinds of views, like the terminal
- with_input_handler(this, |input_handler| {
- if input_handler.selected_text_range().is_none() {
- handled = true;
- input_handler.replace_text_in_range(None, &last_insert_text)
- }
- });
- }
- }
- }
-
- window_state.lock().event_callback = Some(callback);
- }
- } else {
- handled = true;
- }
-
- handled as BOOL
- } else {
- NO
- }
-}
-
-extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
- let window_state = unsafe { get_window_state(this) };
- let weak_window_state = Arc::downgrade(&window_state);
- let mut lock = window_state.as_ref().lock();
- let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
-
- let window_height = lock.content_size().height;
- let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) };
-
- if let Some(mut event) = event {
- match &mut event {
- InputEvent::MouseDown(
- event @ MouseDownEvent {
- button: MouseButton::Left,
- modifiers: Modifiers { control: true, .. },
- ..
- },
- ) => {
- // On mac, a ctrl-left click should be handled as a right click.
- *event = MouseDownEvent {
- button: MouseButton::Right,
- modifiers: Modifiers {
- control: false,
- ..event.modifiers
- },
- click_count: 1,
- ..*event
- };
- }
-
- // Because we map a ctrl-left_down to a right_down -> right_up let's ignore
- // the ctrl-left_up to avoid having a mismatch in button down/up events if the
- // user is still holding ctrl when releasing the left mouse button
- InputEvent::MouseUp(
- event @ MouseUpEvent {
- button: MouseButton::Left,
- modifiers: Modifiers { control: true, .. },
- ..
- },
- ) => {
- *event = MouseUpEvent {
- button: MouseButton::Right,
- modifiers: Modifiers {
- control: false,
- ..event.modifiers
- },
- click_count: 1,
- ..*event
- };
- }
-
- _ => {}
- };
-
- match &event {
- InputEvent::MouseMove(
- event @ MouseMoveEvent {
- pressed_button: Some(_),
- ..
- },
- ) => {
- lock.synthetic_drag_counter += 1;
- let executor = lock.executor.clone();
- executor
- .spawn(synthetic_drag(
- weak_window_state,
- lock.synthetic_drag_counter,
- event.clone(),
- ))
- .detach();
- }
-
- InputEvent::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
-
- InputEvent::MouseUp(MouseUpEvent { .. }) => {
- lock.synthetic_drag_counter += 1;
- }
-
- InputEvent::ModifiersChanged(ModifiersChangedEvent { modifiers }) => {
- // Only raise modifiers changed event when they have actually changed
- if let Some(InputEvent::ModifiersChanged(ModifiersChangedEvent {
- modifiers: prev_modifiers,
- })) = &lock.previous_modifiers_changed_event
- {
- if prev_modifiers == modifiers {
- return;
- }
- }
-
- lock.previous_modifiers_changed_event = Some(event.clone());
- }
-
- _ => {}
- }
-
- if let Some(mut callback) = lock.event_callback.take() {
- drop(lock);
- callback(event);
- window_state.lock().event_callback = Some(callback);
- }
- }
-}
-
-// Allows us to receive `cmd-.` (the shortcut for closing a dialog)
-// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
-extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
- let window_state = unsafe { get_window_state(this) };
- let mut lock = window_state.as_ref().lock();
-
- let keystroke = Keystroke {
- modifiers: Default::default(),
- key: ".".into(),
- ime_key: None,
- };
- let event = InputEvent::KeyDown(KeyDownEvent {
- keystroke: keystroke.clone(),
- is_held: false,
- });
-
- lock.last_fresh_keydown = Some(keystroke);
- if let Some(mut callback) = lock.event_callback.take() {
- drop(lock);
- callback(event);
- window_state.lock().event_callback = Some(callback);
- }
-}
-
-extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
- let window_state = unsafe { get_window_state(this) };
- window_state.as_ref().lock().move_traffic_light();
-}
-
-extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
- window_fullscreen_changed(this, true);
-}
-
-extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
- window_fullscreen_changed(this, false);
-}
-
-fn window_fullscreen_changed(this: &Object, is_fullscreen: bool) {
- let window_state = unsafe { get_window_state(this) };
- let mut lock = window_state.as_ref().lock();
- if let Some(mut callback) = lock.fullscreen_callback.take() {
- drop(lock);
- callback(is_fullscreen);
- window_state.lock().fullscreen_callback = Some(callback);
- }
-}
-
-extern "C" fn window_did_move(this: &Object, _: Sel, _: id) {
- let window_state = unsafe { get_window_state(this) };
- let mut lock = window_state.as_ref().lock();
- if let Some(mut callback) = lock.moved_callback.take() {
- drop(lock);
- callback();
- window_state.lock().moved_callback = Some(callback);
- }
-}
-
-extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
- let window_state = unsafe { get_window_state(this) };
- let lock = window_state.lock();
- let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
-
- // When opening a pop-up while the application isn't active, Cocoa sends a spurious
- // `windowDidBecomeKey` message to the previous key window even though that window
- // isn't actually key. This causes a bug if the application is later activated while
- // the pop-up is still open, making it impossible to activate the previous key window
- // even if the pop-up gets closed. The only way to activate it again is to de-activate
- // the app and re-activate it, which is a pretty bad UX.
- // The following code detects the spurious event and invokes `resignKeyWindow`:
- // in theory, we're not supposed to invoke this method manually but it balances out
- // the spurious `becomeKeyWindow` event and helps us work around that bug.
- if selector == sel!(windowDidBecomeKey:) && !is_active {
- unsafe {
- let _: () = msg_send![lock.native_window, resignKeyWindow];
- return;
- }
- }
-
- let executor = lock.executor.clone();
- drop(lock);
- executor
- .spawn(async move {
- let mut lock = window_state.as_ref().lock();
- if let Some(mut callback) = lock.activate_callback.take() {
- drop(lock);
- callback(is_active);
- window_state.lock().activate_callback = Some(callback);
- };
- })
- .detach();
-}
-
-extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL {
- let window_state = unsafe { get_window_state(this) };
- let mut lock = window_state.as_ref().lock();
- if let Some(mut callback) = lock.should_close_callback.take() {
- drop(lock);
- let should_close = callback();
- window_state.lock().should_close_callback = Some(callback);
- should_close as BOOL
- } else {
- YES
- }
-}
-
-extern "C" fn close_window(this: &Object, _: Sel) {
- unsafe {
- let close_callback = {
- let window_state = get_window_state(this);
- window_state
- .as_ref()
- .try_lock()
- .and_then(|mut window_state| window_state.close_callback.take())
- };
-
- if let Some(callback) = close_callback {
- callback();
- }
-
- let _: () = msg_send![super(this, class!(NSWindow)), close];
- }
-}
-
-extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
- let window_state = unsafe { get_window_state(this) };
- let window_state = window_state.as_ref().lock();
- window_state.renderer.layer().as_ptr() as id
-}
-
-extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
- let window_state = unsafe { get_window_state(this) };
- let mut lock = window_state.as_ref().lock();
-
- unsafe {
- let scale_factor = lock.scale_factor() as f64;
- let size = lock.content_size();
- let drawable_size: NSSize = NSSize {
- width: f64::from(size.width) * scale_factor,
- height: f64::from(size.height) * scale_factor,
- };
-
- let _: () = msg_send![
- lock.renderer.layer(),
- setContentsScale: scale_factor
- ];
- let _: () = msg_send![
- lock.renderer.layer(),
- setDrawableSize: drawable_size
- ];
- }
-
- if let Some(mut callback) = lock.resize_callback.take() {
- let content_size = lock.content_size();
- let scale_factor = lock.scale_factor();
- drop(lock);
- callback(content_size, scale_factor);
- window_state.as_ref().lock().resize_callback = Some(callback);
- };
-}
-
-extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
- let window_state = unsafe { get_window_state(this) };
- let lock = window_state.as_ref().lock();
-
- if lock.content_size() == size.into() {
- return;
- }
-
- unsafe {
- let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
- }
-
- let scale_factor = lock.scale_factor() as f64;
- let drawable_size: NSSize = NSSize {
- width: size.width * scale_factor,
- height: size.height * scale_factor,
- };
-
- unsafe {
- let _: () = msg_send![
- lock.renderer.layer(),
- setDrawableSize: drawable_size
- ];
- }
-
- drop(lock);
- let mut lock = window_state.lock();
- if let Some(mut callback) = lock.resize_callback.take() {
- let content_size = lock.content_size();
- let scale_factor = lock.scale_factor();
- drop(lock);
- callback(content_size, scale_factor);
- window_state.lock().resize_callback = Some(callback);
- };
-}
-
-extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
- unsafe {
- let window_state = get_window_state(this);
- let mut draw = window_state.lock().draw.take().unwrap();
- let scene = draw().log_err();
- let mut window_state = window_state.lock();
- window_state.draw = Some(draw);
- if let Some(scene) = scene {
- window_state.renderer.draw(&scene);
- }
- }
-}
-
-extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
- unsafe { msg_send![class!(NSArray), array] }
-}
-
-extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL {
- with_input_handler(this, |input_handler| input_handler.marked_text_range())
- .flatten()
- .is_some() as BOOL
-}
-
-extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange {
- with_input_handler(this, |input_handler| input_handler.marked_text_range())
- .flatten()
- .map_or(NSRange::invalid(), |range| range.into())
-}
-
-extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange {
- with_input_handler(this, |input_handler| input_handler.selected_text_range())
- .flatten()
- .map_or(NSRange::invalid(), |range| range.into())
-}
-
-extern "C" fn first_rect_for_character_range(
- this: &Object,
- _: Sel,
- range: NSRange,
- _: id,
-) -> NSRect {
- let frame = unsafe {
- let window = get_window_state(this).lock().native_window;
- NSView::frame(window)
- };
- with_input_handler(this, |input_handler| {
- input_handler.bounds_for_range(range.to_range()?)
- })
- .flatten()
- .map_or(
- NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)),
- |bounds| {
- NSRect::new(
- NSPoint::new(
- frame.origin.x + bounds.origin.x.0 as f64,
- frame.origin.y + frame.size.height
- - bounds.origin.y.0 as f64
- - bounds.size.height.0 as f64,
- ),
- NSSize::new(bounds.size.width.0 as f64, bounds.size.height.0 as f64),
- )
- },
- )
-}
-
-extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) {
- unsafe {
- let window_state = get_window_state(this);
- let mut lock = window_state.lock();
- let pending_key_down = lock.pending_key_down.take();
- drop(lock);
-
- let is_attributed_string: BOOL =
- msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
- let text: id = if is_attributed_string == YES {
- msg_send![text, string]
- } else {
- text
- };
- let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
- .to_str()
- .unwrap();
- let replacement_range = replacement_range.to_range();
-
- window_state.lock().ime_text = Some(text.to_string());
- window_state.lock().ime_state = ImeState::Acted;
-
- let is_composing =
- with_input_handler(this, |input_handler| input_handler.marked_text_range())
- .flatten()
- .is_some();
-
- if is_composing || text.chars().count() > 1 || pending_key_down.is_none() {
- with_input_handler(this, |input_handler| {
- input_handler.replace_text_in_range(replacement_range, text)
- });
- } else {
- let mut pending_key_down = pending_key_down.unwrap();
- pending_key_down.1 = Some(InsertText {
- replacement_range,
- text: text.to_string(),
- });
- window_state.lock().pending_key_down = Some(pending_key_down);
- }
- }
-}
-
-extern "C" fn set_marked_text(
- this: &Object,
- _: Sel,
- text: id,
- selected_range: NSRange,
- replacement_range: NSRange,
-) {
- unsafe {
- let window_state = get_window_state(this);
- window_state.lock().pending_key_down.take();
-
- let is_attributed_string: BOOL =
- msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
- let text: id = if is_attributed_string == YES {
- msg_send![text, string]
- } else {
- text
- };
- let selected_range = selected_range.to_range();
- let replacement_range = replacement_range.to_range();
- let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
- .to_str()
- .unwrap();
-
- window_state.lock().ime_state = ImeState::Acted;
- window_state.lock().ime_text = Some(text.to_string());
-
- with_input_handler(this, |input_handler| {
- input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range);
- });
- }
-}
-
-extern "C" fn unmark_text(this: &Object, _: Sel) {
- unsafe {
- let state = get_window_state(this);
- let mut borrow = state.lock();
- borrow.ime_state = ImeState::Acted;
- borrow.ime_text.take();
- }
-
- with_input_handler(this, |input_handler| input_handler.unmark_text());
-}
-
-extern "C" fn attributed_substring_for_proposed_range(
- this: &Object,
- _: Sel,
- range: NSRange,
- _actual_range: *mut c_void,
-) -> id {
- with_input_handler(this, |input_handler| {
- let range = range.to_range()?;
- if range.is_empty() {
- return None;
- }
-
- let selected_text = input_handler.text_for_range(range)?;
- unsafe {
- let string: id = msg_send![class!(NSAttributedString), alloc];
- let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
- Some(string)
- }
- })
- .flatten()
- .unwrap_or(nil)
-}
-
-extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
- unsafe {
- let state = get_window_state(this);
- let mut borrow = state.lock();
- borrow.ime_state = ImeState::Continue;
- borrow.ime_text.take();
- }
-}
-
-extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
- unsafe {
- let state = get_window_state(this);
- let mut lock = state.as_ref().lock();
- if let Some(mut callback) = lock.appearance_changed_callback.take() {
- drop(lock);
- callback();
- state.lock().appearance_changed_callback = Some(callback);
- }
- }
-}
-
-extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
- unsafe {
- let state = get_window_state(this);
- let lock = state.as_ref().lock();
- if lock.kind == WindowKind::PopUp {
- YES
- } else {
- NO
- }
- }
-}
-
-extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
- let window_state = unsafe { get_window_state(this) };
- if send_new_event(&window_state, {
- let position = drag_event_position(&window_state, dragging_info);
- let paths = external_paths_from_event(dragging_info);
- InputEvent::FileDrop(FileDropEvent::Entered {
- position,
- files: paths,
- })
- }) {
- NSDragOperationCopy
- } else {
- NSDragOperationNone
- }
-}
-
-extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
- let window_state = unsafe { get_window_state(this) };
- let position = drag_event_position(&window_state, dragging_info);
- if send_new_event(
- &window_state,
- InputEvent::FileDrop(FileDropEvent::Pending { position }),
- ) {
- NSDragOperationCopy
- } else {
- NSDragOperationNone
- }
-}
-
-extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) {
- let window_state = unsafe { get_window_state(this) };
- send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited));
-}
-
-extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -> BOOL {
- let window_state = unsafe { get_window_state(this) };
- let position = drag_event_position(&window_state, dragging_info);
- if send_new_event(
- &window_state,
- InputEvent::FileDrop(FileDropEvent::Submit { position }),
- ) {
- YES
- } else {
- NO
- }
-}
-
-fn external_paths_from_event(dragging_info: *mut Object) -> ExternalPaths {
- let mut paths = SmallVec::new();
- let pasteboard: id = unsafe { msg_send![dragging_info, draggingPasteboard] };
- let filenames = unsafe { NSPasteboard::propertyListForType(pasteboard, NSFilenamesPboardType) };
- for file in unsafe { filenames.iter() } {
- let path = unsafe {
- let f = NSString::UTF8String(file);
- CStr::from_ptr(f).to_string_lossy().into_owned()
- };
- paths.push(PathBuf::from(path))
- }
- ExternalPaths(paths)
-}
-
-extern "C" fn conclude_drag_operation(this: &Object, _: Sel, _: id) {
- let window_state = unsafe { get_window_state(this) };
- send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited));
-}
-
-async fn synthetic_drag(
- window_state: Weak<Mutex<MacWindowState>>,
- drag_id: usize,
- event: MouseMoveEvent,
-) {
- loop {
- Timer::after(Duration::from_millis(16)).await;
- if let Some(window_state) = window_state.upgrade() {
- let mut lock = window_state.lock();
- if lock.synthetic_drag_counter == drag_id {
- if let Some(mut callback) = lock.event_callback.take() {
- drop(lock);
- callback(InputEvent::MouseMove(event.clone()));
- window_state.lock().event_callback = Some(callback);
- }
- } else {
- break;
- }
- }
- }
-}
-
-fn send_new_event(window_state_lock: &Mutex<MacWindowState>, e: InputEvent) -> bool {
- let window_state = window_state_lock.lock().event_callback.take();
- if let Some(mut callback) = window_state {
- callback(e);
- window_state_lock.lock().event_callback = Some(callback);
- true
- } else {
- false
- }
-}
-
-fn drag_event_position(window_state: &Mutex<MacWindowState>, dragging_info: id) -> Point<Pixels> {
- let drag_location: NSPoint = unsafe { msg_send![dragging_info, draggingLocation] };
- convert_mouse_position(drag_location, window_state.lock().content_size().height)
-}
-
-fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
-where
- F: FnOnce(&mut dyn PlatformInputHandler) -> R,
-{
- let window_state = unsafe { get_window_state(window) };
- let mut lock = window_state.as_ref().lock();
- if let Some(mut input_handler) = lock.input_handler.take() {
- drop(lock);
- let result = f(input_handler.as_mut());
- window_state.lock().input_handler = Some(input_handler);
- Some(result)
- } else {
- None
- }
-}
@@ -1,9 +0,0 @@
-mod dispatcher;
-mod display;
-mod platform;
-mod window;
-
-pub use dispatcher::*;
-pub use display::*;
-pub use platform::*;
-pub use window::*;
@@ -1,778 +0,0 @@
-use crate::{
- point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, Hsla, Pixels, Point,
- ScaledPixels, StackingOrder,
-};
-use collections::BTreeMap;
-use std::{fmt::Debug, iter::Peekable, mem, slice};
-
-// Exported to metal
-pub(crate) type PointF = Point<f32>;
-#[allow(non_camel_case_types, unused)]
-pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
-
-pub type LayerId = u32;
-
-pub type DrawOrder = u32;
-
-#[derive(Default)]
-pub(crate) struct SceneBuilder {
- last_order: Option<(StackingOrder, LayerId)>,
- layers_by_order: BTreeMap<StackingOrder, LayerId>,
- shadows: Vec<Shadow>,
- quads: Vec<Quad>,
- paths: Vec<Path<ScaledPixels>>,
- underlines: Vec<Underline>,
- monochrome_sprites: Vec<MonochromeSprite>,
- polychrome_sprites: Vec<PolychromeSprite>,
- surfaces: Vec<Surface>,
-}
-
-impl SceneBuilder {
- pub fn build(&mut self) -> Scene {
- let mut orders = vec![0; self.layers_by_order.len()];
- for (ix, layer_id) in self.layers_by_order.values().enumerate() {
- orders[*layer_id as usize] = ix as u32;
- }
- self.layers_by_order.clear();
- self.last_order = None;
-
- for shadow in &mut self.shadows {
- shadow.order = orders[shadow.order as usize];
- }
- self.shadows.sort_by_key(|shadow| shadow.order);
-
- for quad in &mut self.quads {
- quad.order = orders[quad.order as usize];
- }
- self.quads.sort_by_key(|quad| quad.order);
-
- for path in &mut self.paths {
- path.order = orders[path.order as usize];
- }
- self.paths.sort_by_key(|path| path.order);
-
- for underline in &mut self.underlines {
- underline.order = orders[underline.order as usize];
- }
- self.underlines.sort_by_key(|underline| underline.order);
-
- for monochrome_sprite in &mut self.monochrome_sprites {
- monochrome_sprite.order = orders[monochrome_sprite.order as usize];
- }
- self.monochrome_sprites.sort_by_key(|sprite| sprite.order);
-
- for polychrome_sprite in &mut self.polychrome_sprites {
- polychrome_sprite.order = orders[polychrome_sprite.order as usize];
- }
- self.polychrome_sprites.sort_by_key(|sprite| sprite.order);
-
- for surface in &mut self.surfaces {
- surface.order = orders[surface.order as usize];
- }
- self.surfaces.sort_by_key(|surface| surface.order);
-
- Scene {
- shadows: mem::take(&mut self.shadows),
- quads: mem::take(&mut self.quads),
- paths: mem::take(&mut self.paths),
- underlines: mem::take(&mut self.underlines),
- monochrome_sprites: mem::take(&mut self.monochrome_sprites),
- polychrome_sprites: mem::take(&mut self.polychrome_sprites),
- surfaces: mem::take(&mut self.surfaces),
- }
- }
-
- pub fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
- let primitive = primitive.into();
- let clipped_bounds = primitive
- .bounds()
- .intersect(&primitive.content_mask().bounds);
- if clipped_bounds.size.width <= ScaledPixels(0.)
- || clipped_bounds.size.height <= ScaledPixels(0.)
- {
- return;
- }
-
- let layer_id = self.layer_id_for_order(order);
- match primitive {
- Primitive::Shadow(mut shadow) => {
- shadow.order = layer_id;
- self.shadows.push(shadow);
- }
- Primitive::Quad(mut quad) => {
- quad.order = layer_id;
- self.quads.push(quad);
- }
- Primitive::Path(mut path) => {
- path.order = layer_id;
- path.id = PathId(self.paths.len());
- self.paths.push(path);
- }
- Primitive::Underline(mut underline) => {
- underline.order = layer_id;
- self.underlines.push(underline);
- }
- Primitive::MonochromeSprite(mut sprite) => {
- sprite.order = layer_id;
- self.monochrome_sprites.push(sprite);
- }
- Primitive::PolychromeSprite(mut sprite) => {
- sprite.order = layer_id;
- self.polychrome_sprites.push(sprite);
- }
- Primitive::Surface(mut surface) => {
- surface.order = layer_id;
- self.surfaces.push(surface);
- }
- }
- }
-
- fn layer_id_for_order(&mut self, order: &StackingOrder) -> u32 {
- if let Some((last_order, last_layer_id)) = self.last_order.as_ref() {
- if last_order == order {
- return *last_layer_id;
- }
- };
-
- let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) {
- *layer_id
- } else {
- let next_id = self.layers_by_order.len() as LayerId;
- self.layers_by_order.insert(order.clone(), next_id);
- next_id
- };
- self.last_order = Some((order.clone(), layer_id));
- layer_id
- }
-}
-
-pub struct Scene {
- pub shadows: Vec<Shadow>,
- pub quads: Vec<Quad>,
- pub paths: Vec<Path<ScaledPixels>>,
- pub underlines: Vec<Underline>,
- pub monochrome_sprites: Vec<MonochromeSprite>,
- pub polychrome_sprites: Vec<PolychromeSprite>,
- pub surfaces: Vec<Surface>,
-}
-
-impl Scene {
- pub fn paths(&self) -> &[Path<ScaledPixels>] {
- &self.paths
- }
-
- pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> {
- BatchIterator {
- shadows: &self.shadows,
- shadows_start: 0,
- shadows_iter: self.shadows.iter().peekable(),
- quads: &self.quads,
- quads_start: 0,
- quads_iter: self.quads.iter().peekable(),
- paths: &self.paths,
- paths_start: 0,
- paths_iter: self.paths.iter().peekable(),
- underlines: &self.underlines,
- underlines_start: 0,
- underlines_iter: self.underlines.iter().peekable(),
- monochrome_sprites: &self.monochrome_sprites,
- monochrome_sprites_start: 0,
- monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
- polychrome_sprites: &self.polychrome_sprites,
- polychrome_sprites_start: 0,
- polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
- surfaces: &self.surfaces,
- surfaces_start: 0,
- surfaces_iter: self.surfaces.iter().peekable(),
- }
- }
-}
-
-struct BatchIterator<'a> {
- shadows: &'a [Shadow],
- shadows_start: usize,
- shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
- quads: &'a [Quad],
- quads_start: usize,
- quads_iter: Peekable<slice::Iter<'a, Quad>>,
- paths: &'a [Path<ScaledPixels>],
- paths_start: usize,
- paths_iter: Peekable<slice::Iter<'a, Path<ScaledPixels>>>,
- underlines: &'a [Underline],
- underlines_start: usize,
- underlines_iter: Peekable<slice::Iter<'a, Underline>>,
- monochrome_sprites: &'a [MonochromeSprite],
- monochrome_sprites_start: usize,
- monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
- polychrome_sprites: &'a [PolychromeSprite],
- polychrome_sprites_start: usize,
- polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
- surfaces: &'a [Surface],
- surfaces_start: usize,
- surfaces_iter: Peekable<slice::Iter<'a, Surface>>,
-}
-
-impl<'a> Iterator for BatchIterator<'a> {
- type Item = PrimitiveBatch<'a>;
-
- fn next(&mut self) -> Option<Self::Item> {
- let mut orders_and_kinds = [
- (
- self.shadows_iter.peek().map(|s| s.order),
- PrimitiveKind::Shadow,
- ),
- (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
- (self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
- (
- self.underlines_iter.peek().map(|u| u.order),
- PrimitiveKind::Underline,
- ),
- (
- self.monochrome_sprites_iter.peek().map(|s| s.order),
- PrimitiveKind::MonochromeSprite,
- ),
- (
- self.polychrome_sprites_iter.peek().map(|s| s.order),
- PrimitiveKind::PolychromeSprite,
- ),
- (
- self.surfaces_iter.peek().map(|s| s.order),
- PrimitiveKind::Surface,
- ),
- ];
- orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
-
- let first = orders_and_kinds[0];
- let second = orders_and_kinds[1];
- let (batch_kind, max_order) = if first.0.is_some() {
- (first.1, second.0.unwrap_or(u32::MAX))
- } else {
- return None;
- };
-
- match batch_kind {
- PrimitiveKind::Shadow => {
- let shadows_start = self.shadows_start;
- let mut shadows_end = shadows_start + 1;
- self.shadows_iter.next();
- while self
- .shadows_iter
- .next_if(|shadow| shadow.order < max_order)
- .is_some()
- {
- shadows_end += 1;
- }
- self.shadows_start = shadows_end;
- Some(PrimitiveBatch::Shadows(
- &self.shadows[shadows_start..shadows_end],
- ))
- }
- PrimitiveKind::Quad => {
- let quads_start = self.quads_start;
- let mut quads_end = quads_start + 1;
- self.quads_iter.next();
- while self
- .quads_iter
- .next_if(|quad| quad.order < max_order)
- .is_some()
- {
- quads_end += 1;
- }
- self.quads_start = quads_end;
- Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
- }
- PrimitiveKind::Path => {
- let paths_start = self.paths_start;
- let mut paths_end = paths_start + 1;
- self.paths_iter.next();
- while self
- .paths_iter
- .next_if(|path| path.order < max_order)
- .is_some()
- {
- paths_end += 1;
- }
- self.paths_start = paths_end;
- Some(PrimitiveBatch::Paths(&self.paths[paths_start..paths_end]))
- }
- PrimitiveKind::Underline => {
- let underlines_start = self.underlines_start;
- let mut underlines_end = underlines_start + 1;
- self.underlines_iter.next();
- while self
- .underlines_iter
- .next_if(|underline| underline.order < max_order)
- .is_some()
- {
- underlines_end += 1;
- }
- self.underlines_start = underlines_end;
- Some(PrimitiveBatch::Underlines(
- &self.underlines[underlines_start..underlines_end],
- ))
- }
- PrimitiveKind::MonochromeSprite => {
- let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
- let sprites_start = self.monochrome_sprites_start;
- let mut sprites_end = sprites_start + 1;
- self.monochrome_sprites_iter.next();
- while self
- .monochrome_sprites_iter
- .next_if(|sprite| {
- sprite.order < max_order && sprite.tile.texture_id == texture_id
- })
- .is_some()
- {
- sprites_end += 1;
- }
- self.monochrome_sprites_start = sprites_end;
- Some(PrimitiveBatch::MonochromeSprites {
- texture_id,
- sprites: &self.monochrome_sprites[sprites_start..sprites_end],
- })
- }
- PrimitiveKind::PolychromeSprite => {
- let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
- let sprites_start = self.polychrome_sprites_start;
- let mut sprites_end = self.polychrome_sprites_start + 1;
- self.polychrome_sprites_iter.next();
- while self
- .polychrome_sprites_iter
- .next_if(|sprite| {
- sprite.order < max_order && sprite.tile.texture_id == texture_id
- })
- .is_some()
- {
- sprites_end += 1;
- }
- self.polychrome_sprites_start = sprites_end;
- Some(PrimitiveBatch::PolychromeSprites {
- texture_id,
- sprites: &self.polychrome_sprites[sprites_start..sprites_end],
- })
- }
- PrimitiveKind::Surface => {
- let surfaces_start = self.surfaces_start;
- let mut surfaces_end = surfaces_start + 1;
- self.surfaces_iter.next();
- while self
- .surfaces_iter
- .next_if(|surface| surface.order < max_order)
- .is_some()
- {
- surfaces_end += 1;
- }
- self.surfaces_start = surfaces_end;
- Some(PrimitiveBatch::Surfaces(
- &self.surfaces[surfaces_start..surfaces_end],
- ))
- }
- }
- }
-}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
-pub enum PrimitiveKind {
- Shadow,
- #[default]
- Quad,
- Path,
- Underline,
- MonochromeSprite,
- PolychromeSprite,
- Surface,
-}
-
-pub enum Primitive {
- Shadow(Shadow),
- Quad(Quad),
- Path(Path<ScaledPixels>),
- Underline(Underline),
- MonochromeSprite(MonochromeSprite),
- PolychromeSprite(PolychromeSprite),
- Surface(Surface),
-}
-
-impl Primitive {
- pub fn bounds(&self) -> &Bounds<ScaledPixels> {
- match self {
- Primitive::Shadow(shadow) => &shadow.bounds,
- Primitive::Quad(quad) => &quad.bounds,
- Primitive::Path(path) => &path.bounds,
- Primitive::Underline(underline) => &underline.bounds,
- Primitive::MonochromeSprite(sprite) => &sprite.bounds,
- Primitive::PolychromeSprite(sprite) => &sprite.bounds,
- Primitive::Surface(surface) => &surface.bounds,
- }
- }
-
- pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
- match self {
- Primitive::Shadow(shadow) => &shadow.content_mask,
- Primitive::Quad(quad) => &quad.content_mask,
- Primitive::Path(path) => &path.content_mask,
- Primitive::Underline(underline) => &underline.content_mask,
- Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
- Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
- Primitive::Surface(surface) => &surface.content_mask,
- }
- }
-}
-
-#[derive(Debug)]
-pub(crate) enum PrimitiveBatch<'a> {
- Shadows(&'a [Shadow]),
- Quads(&'a [Quad]),
- Paths(&'a [Path<ScaledPixels>]),
- Underlines(&'a [Underline]),
- MonochromeSprites {
- texture_id: AtlasTextureId,
- sprites: &'a [MonochromeSprite],
- },
- PolychromeSprites {
- texture_id: AtlasTextureId,
- sprites: &'a [PolychromeSprite],
- },
- Surfaces(&'a [Surface]),
-}
-
-#[derive(Default, Debug, Clone, Eq, PartialEq)]
-#[repr(C)]
-pub struct Quad {
- pub order: u32, // Initially a LayerId, then a DrawOrder.
- pub bounds: Bounds<ScaledPixels>,
- pub content_mask: ContentMask<ScaledPixels>,
- pub background: Hsla,
- pub border_color: Hsla,
- pub corner_radii: Corners<ScaledPixels>,
- pub border_widths: Edges<ScaledPixels>,
-}
-
-impl Ord for Quad {
- fn cmp(&self, other: &Self) -> std::cmp::Ordering {
- self.order.cmp(&other.order)
- }
-}
-
-impl PartialOrd for Quad {
- fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
- Some(self.cmp(other))
- }
-}
-
-impl From<Quad> for Primitive {
- fn from(quad: Quad) -> Self {
- Primitive::Quad(quad)
- }
-}
-
-#[derive(Debug, Clone, Eq, PartialEq)]
-#[repr(C)]
-pub struct Underline {
- pub order: u32,
- pub bounds: Bounds<ScaledPixels>,
- pub content_mask: ContentMask<ScaledPixels>,
- pub thickness: ScaledPixels,
- pub color: Hsla,
- pub wavy: bool,
-}
-
-impl Ord for Underline {
- fn cmp(&self, other: &Self) -> std::cmp::Ordering {
- self.order.cmp(&other.order)
- }
-}
-
-impl PartialOrd for Underline {
- fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
- Some(self.cmp(other))
- }
-}
-
-impl From<Underline> for Primitive {
- fn from(underline: Underline) -> Self {
- Primitive::Underline(underline)
- }
-}
-
-#[derive(Debug, Clone, Eq, PartialEq)]
-#[repr(C)]
-pub struct Shadow {
- pub order: u32,
- pub bounds: Bounds<ScaledPixels>,
- pub corner_radii: Corners<ScaledPixels>,
- pub content_mask: ContentMask<ScaledPixels>,
- pub color: Hsla,
- pub blur_radius: ScaledPixels,
-}
-
-impl Ord for Shadow {
- fn cmp(&self, other: &Self) -> std::cmp::Ordering {
- self.order.cmp(&other.order)
- }
-}
-
-impl PartialOrd for Shadow {
- fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
- Some(self.cmp(other))
- }
-}
-
-impl From<Shadow> for Primitive {
- fn from(shadow: Shadow) -> Self {
- Primitive::Shadow(shadow)
- }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-#[repr(C)]
-pub struct MonochromeSprite {
- pub order: u32,
- pub bounds: Bounds<ScaledPixels>,
- pub content_mask: ContentMask<ScaledPixels>,
- pub color: Hsla,
- pub tile: AtlasTile,
-}
-
-impl Ord for MonochromeSprite {
- fn cmp(&self, other: &Self) -> std::cmp::Ordering {
- match self.order.cmp(&other.order) {
- std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
- order => order,
- }
- }
-}
-
-impl PartialOrd for MonochromeSprite {
- fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
- Some(self.cmp(other))
- }
-}
-
-impl From<MonochromeSprite> for Primitive {
- fn from(sprite: MonochromeSprite) -> Self {
- Primitive::MonochromeSprite(sprite)
- }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-#[repr(C)]
-pub struct PolychromeSprite {
- pub order: u32,
- pub bounds: Bounds<ScaledPixels>,
- pub content_mask: ContentMask<ScaledPixels>,
- pub corner_radii: Corners<ScaledPixels>,
- pub tile: AtlasTile,
- pub grayscale: bool,
-}
-
-impl Ord for PolychromeSprite {
- fn cmp(&self, other: &Self) -> std::cmp::Ordering {
- match self.order.cmp(&other.order) {
- std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
- order => order,
- }
- }
-}
-
-impl PartialOrd for PolychromeSprite {
- fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
- Some(self.cmp(other))
- }
-}
-
-impl From<PolychromeSprite> for Primitive {
- fn from(sprite: PolychromeSprite) -> Self {
- Primitive::PolychromeSprite(sprite)
- }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct Surface {
- pub order: u32,
- pub bounds: Bounds<ScaledPixels>,
- pub content_mask: ContentMask<ScaledPixels>,
- pub image_buffer: media::core_video::CVImageBuffer,
-}
-
-impl Ord for Surface {
- fn cmp(&self, other: &Self) -> std::cmp::Ordering {
- self.order.cmp(&other.order)
- }
-}
-
-impl PartialOrd for Surface {
- fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
- Some(self.cmp(other))
- }
-}
-
-impl From<Surface> for Primitive {
- fn from(surface: Surface) -> Self {
- Primitive::Surface(surface)
- }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
-pub(crate) struct PathId(pub(crate) usize);
-
-#[derive(Debug)]
-pub struct Path<P: Clone + Default + Debug> {
- pub(crate) id: PathId,
- order: u32,
- pub(crate) bounds: Bounds<P>,
- pub(crate) content_mask: ContentMask<P>,
- pub(crate) vertices: Vec<PathVertex<P>>,
- pub(crate) color: Hsla,
- start: Point<P>,
- current: Point<P>,
- contour_count: usize,
-}
-
-impl Path<Pixels> {
- pub fn new(start: Point<Pixels>) -> Self {
- Self {
- id: PathId(0),
- order: 0,
- vertices: Vec::new(),
- start,
- current: start,
- bounds: Bounds {
- origin: start,
- size: Default::default(),
- },
- content_mask: Default::default(),
- color: Default::default(),
- contour_count: 0,
- }
- }
-
- pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
- Path {
- id: self.id,
- order: self.order,
- bounds: self.bounds.scale(factor),
- content_mask: self.content_mask.scale(factor),
- vertices: self
- .vertices
- .iter()
- .map(|vertex| vertex.scale(factor))
- .collect(),
- start: self.start.map(|start| start.scale(factor)),
- current: self.current.scale(factor),
- contour_count: self.contour_count,
- color: self.color,
- }
- }
-
- pub fn line_to(&mut self, to: Point<Pixels>) {
- self.contour_count += 1;
- if self.contour_count > 1 {
- self.push_triangle(
- (self.start, self.current, to),
- (point(0., 1.), point(0., 1.), point(0., 1.)),
- );
- }
- self.current = to;
- }
-
- pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
- self.contour_count += 1;
- if self.contour_count > 1 {
- self.push_triangle(
- (self.start, self.current, to),
- (point(0., 1.), point(0., 1.), point(0., 1.)),
- );
- }
-
- self.push_triangle(
- (self.current, ctrl, to),
- (point(0., 0.), point(0.5, 0.), point(1., 1.)),
- );
- self.current = to;
- }
-
- fn push_triangle(
- &mut self,
- xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
- st: (Point<f32>, Point<f32>, Point<f32>),
- ) {
- self.bounds = self
- .bounds
- .union(&Bounds {
- origin: xy.0,
- size: Default::default(),
- })
- .union(&Bounds {
- origin: xy.1,
- size: Default::default(),
- })
- .union(&Bounds {
- origin: xy.2,
- size: Default::default(),
- });
-
- self.vertices.push(PathVertex {
- xy_position: xy.0,
- st_position: st.0,
- content_mask: Default::default(),
- });
- self.vertices.push(PathVertex {
- xy_position: xy.1,
- st_position: st.1,
- content_mask: Default::default(),
- });
- self.vertices.push(PathVertex {
- xy_position: xy.2,
- st_position: st.2,
- content_mask: Default::default(),
- });
- }
-}
-
-impl Eq for Path<ScaledPixels> {}
-
-impl PartialEq for Path<ScaledPixels> {
- fn eq(&self, other: &Self) -> bool {
- self.order == other.order
- }
-}
-
-impl Ord for Path<ScaledPixels> {
- fn cmp(&self, other: &Self) -> std::cmp::Ordering {
- self.order.cmp(&other.order)
- }
-}
-
-impl PartialOrd for Path<ScaledPixels> {
- fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
- Some(self.cmp(other))
- }
-}
-
-impl From<Path<ScaledPixels>> for Primitive {
- fn from(path: Path<ScaledPixels>) -> Self {
- Primitive::Path(path)
- }
-}
-
-#[derive(Clone, Debug)]
-#[repr(C)]
-pub struct PathVertex<P: Clone + Default + Debug> {
- pub(crate) xy_position: Point<P>,
- pub(crate) st_position: Point<f32>,
- pub(crate) content_mask: ContentMask<P>,
-}
-
-impl PathVertex<Pixels> {
- pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
- PathVertex {
- xy_position: self.xy_position.scale(factor),
- st_position: self.st_position,
- content_mask: self.content_mask.scale(factor),
- }
- }
-}
-
-#[derive(Copy, Clone, Debug)]
-pub struct AtlasId(pub(crate) usize);
@@ -1,80 +0,0 @@
-use crate::{Entity, Subscription, TestAppContext, TestDispatcher};
-use futures::StreamExt as _;
-use rand::prelude::*;
-use smol::channel;
-use std::{
- env,
- panic::{self, RefUnwindSafe},
-};
-
-pub fn run_test(
- mut num_iterations: u64,
- max_retries: usize,
- test_fn: &mut (dyn RefUnwindSafe + Fn(TestDispatcher, u64)),
- on_fail_fn: Option<fn()>,
- _fn_name: String, // todo!("re-enable fn_name")
-) {
- let starting_seed = env::var("SEED")
- .map(|seed| seed.parse().expect("invalid SEED variable"))
- .unwrap_or(0);
- let is_randomized = num_iterations > 1;
- if let Ok(iterations) = env::var("ITERATIONS") {
- num_iterations = iterations.parse().expect("invalid ITERATIONS variable");
- }
-
- for seed in starting_seed..starting_seed + num_iterations {
- let mut retry = 0;
- loop {
- if is_randomized {
- eprintln!("seed = {seed}");
- }
- let result = panic::catch_unwind(|| {
- let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(seed));
- test_fn(dispatcher, seed);
- });
-
- match result {
- Ok(_) => break,
- Err(error) => {
- if retry < max_retries {
- println!("retrying: attempt {}", retry);
- retry += 1;
- } else {
- if is_randomized {
- eprintln!("failing seed: {}", seed);
- }
- on_fail_fn.map(|f| f());
- panic::resume_unwind(error);
- }
- }
- }
- }
- }
-}
-
-pub struct Observation<T> {
- rx: channel::Receiver<T>,
- _subscription: Subscription,
-}
-
-impl<T: 'static> futures::Stream for Observation<T> {
- type Item = T;
-
- fn poll_next(
- mut self: std::pin::Pin<&mut Self>,
- cx: &mut std::task::Context<'_>,
- ) -> std::task::Poll<Option<Self::Item>> {
- self.rx.poll_next_unpin(cx)
- }
-}
-
-pub fn observe<T: 'static>(entity: &impl Entity<T>, cx: &mut TestAppContext) -> Observation<()> {
- let (tx, rx) = smol::channel::unbounded();
- let _subscription = cx.update(|cx| {
- cx.observe(entity, move |_, _| {
- let _ = smol::block_on(tx.send(()));
- })
- });
-
- Observation { rx, _subscription }
-}
@@ -1,51 +0,0 @@
-#[cfg(any(test, feature = "test-support"))]
-use std::time::Duration;
-
-#[cfg(any(test, feature = "test-support"))]
-use futures::Future;
-
-#[cfg(any(test, feature = "test-support"))]
-use smol::future::FutureExt;
-
-pub use util::*;
-
-#[cfg(any(test, feature = "test-support"))]
-pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
-where
- F: Future<Output = T>,
-{
- let timer = async {
- smol::Timer::after(timeout).await;
- Err(())
- };
- let future = async move { Ok(f.await) };
- timer.race(future).await
-}
-
-#[cfg(any(test, feature = "test-support"))]
-pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace);
-
-#[cfg(any(test, feature = "test-support"))]
-impl<'a> std::fmt::Debug for CwdBacktrace<'a> {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- use backtrace::{BacktraceFmt, BytesOrWideString};
-
- let cwd = std::env::current_dir().unwrap();
- let cwd = cwd.parent().unwrap();
- let mut print_path = |fmt: &mut std::fmt::Formatter<'_>, path: BytesOrWideString<'_>| {
- std::fmt::Display::fmt(&path, fmt)
- };
- let mut fmt = BacktraceFmt::new(f, backtrace::PrintFmt::Full, &mut print_path);
- for frame in self.0.frames() {
- let mut formatted_frame = fmt.frame();
- if frame
- .symbols()
- .iter()
- .any(|s| s.filename().map_or(false, |f| f.starts_with(&cwd)))
- {
- formatted_frame.backtrace_frame(frame)?;
- }
- }
- fmt.finish()
- }
-}
@@ -14,5 +14,5 @@ test-support = []
smol.workspace = true
anyhow.workspace = true
log.workspace = true
-gpui = { path = "../gpui2", package = "gpui2" }
+gpui = { path = "../gpui" }
util = { path = "../util" }
@@ -10,7 +10,7 @@ doctest = false
[dependencies]
editor = { path = "../editor" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
util = { path = "../util" }
workspace = { path = "../workspace" }
settings = { path = "../settings" }
@@ -26,7 +26,7 @@ clock = { path = "../clock" }
collections = { path = "../collections" }
fuzzy = { path = "../fuzzy" }
git = { path = "../git" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
lsp = { path = "../lsp" }
rpc = { path = "../rpc" }
settings = { path = "../settings" }
@@ -63,7 +63,7 @@ pulldown-cmark = { version = "0.9.2", default-features = false }
[dev-dependencies]
client = { path = "../client", features = ["test-support"] }
collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
text = { path = "../text", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
@@ -12,7 +12,7 @@ doctest = false
editor = { path = "../editor" }
fuzzy = { path = "../fuzzy" }
language = { path = "../language" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
picker = { path = "../picker" }
project = { path = "../project" }
theme = { path = "../theme" }
@@ -16,7 +16,7 @@ theme = { path = "../theme" }
language = { path = "../language" }
project = { path = "../project" }
workspace = { path = "../workspace" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
ui = { path = "../ui" }
util = { path = "../util" }
lsp = { path = "../lsp" }
@@ -28,7 +28,7 @@ tree-sitter.workspace = true
[dev-dependencies]
client = { path = "../client", features = ["test-support"] }
editor = { path = "../editor", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
env_logger.workspace = true
unindent.workspace = true
@@ -23,7 +23,7 @@ test-support = [
[dependencies]
collections = { path = "../collections", optional = true }
-gpui = { package = "gpui2", path = "../gpui2", optional = true }
+gpui = { path = "../gpui", optional = true }
live_kit_server = { path = "../live_kit_server", optional = true }
media = { path = "../media" }
@@ -41,7 +41,7 @@ nanoid = { version ="0.4", optional = true}
[dev-dependencies]
collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
live_kit_server = { path = "../live_kit_server" }
media = { path = "../media" }
nanoid = "0.4"
@@ -13,7 +13,7 @@ test-support = ["async-pipe"]
[dependencies]
collections = { path = "../collections" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
util = { path = "../util" }
anyhow.workspace = true
@@ -29,7 +29,7 @@ serde_json.workspace = true
smol.workspace = true
[dev-dependencies]
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
@@ -9,5 +9,5 @@ path = "src/menu.rs"
doctest = false
[dependencies]
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
serde = { workspace = true }
@@ -24,7 +24,7 @@ client = { path = "../client" }
clock = { path = "../clock" }
collections = { path = "../collections" }
git = { path = "../git" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
language = { path = "../language" }
lsp = { path = "../lsp" }
rich_text = { path = "../rich_text" }
@@ -63,7 +63,7 @@ copilot = { path = "../copilot", features = ["test-support"] }
text = { path = "../text", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
@@ -23,7 +23,7 @@ clock = { path = "../clock" }
collections = { path = "../collections" }
db = { path = "../db" }
feature_flags = { path = "../feature_flags" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
rpc = { path = "../rpc" }
settings = { path = "../settings" }
sum_tree = { path = "../sum_tree" }
@@ -36,7 +36,7 @@ time.workspace = true
[dev-dependencies]
client = { path = "../client", features = ["test-support"] }
collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
rpc = { path = "../rpc", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
@@ -11,7 +11,7 @@ doctest = false
[dependencies]
editor = { path = "../editor" }
fuzzy = { path = "../fuzzy" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
ui = { path = "../ui" }
language = { path = "../language" }
picker = { path = "../picker" }
@@ -11,7 +11,7 @@ doctest = false
[dependencies]
editor = { path = "../editor" }
ui = { path = "../ui" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
menu = { path = "../menu" }
settings = { path = "../settings" }
util = { path = "../util" }
@@ -22,7 +22,7 @@ parking_lot.workspace = true
[dev-dependencies]
editor = { path = "../editor", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
serde_json.workspace = true
ctor.workspace = true
env_logger.workspace = true
@@ -15,7 +15,7 @@ test-support = []
client = { path = "../client" }
collections = { path = "../collections"}
language = { path = "../language" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
fs = { path = "../fs" }
lsp = { path = "../lsp" }
node_runtime = { path = "../node_runtime"}
@@ -31,5 +31,5 @@ parking_lot.workspace = true
[dev-dependencies]
language = { path = "../language", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
fs = { path = "../fs", features = ["test-support"] }
@@ -30,7 +30,7 @@ fs = { path = "../fs" }
fsevent = { path = "../fsevent" }
fuzzy = { path = "../fuzzy" }
git = { path = "../git" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
language = { path = "../language" }
lsp = { path = "../lsp" }
node_runtime = { path = "../node_runtime" }
@@ -73,7 +73,7 @@ client = { path = "../client", features = ["test-support"] }
collections = { path = "../collections", features = ["test-support"] }
db = { path = "../db", features = ["test-support"] }
fs = { path = "../fs", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
@@ -12,7 +12,7 @@ doctest = false
collections = { path = "../collections" }
db = { path = "../db" }
editor = { path = "../editor" }
-gpui = { path = "../gpui2", package = "gpui2" }
+gpui = { path = "../gpui" }
menu = { path = "../menu" }
project = { path = "../project" }
search = { path = "../search" }
@@ -36,6 +36,6 @@ unicase = "2.6"
client = { path = "../client", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
editor = { path = "../editor", features = ["test-support"] }
-gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
workspace = { path = "../workspace", features = ["test-support"] }
serde_json.workspace = true
@@ -11,7 +11,7 @@ doctest = false
[dependencies]
editor = { path = "../editor" }
fuzzy = { path = "../fuzzy" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
picker = { path = "../picker" }
project = { path = "../project" }
text = { path = "../text" }
@@ -29,7 +29,7 @@ smol.workspace = true
futures.workspace = true
editor = { path = "../editor", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
@@ -11,12 +11,12 @@ doctest = false
[dependencies]
assistant = { path = "../assistant" }
editor = { path = "../editor" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
search = { path = "../search" }
workspace = { path = "../workspace" }
ui = { path = "../ui" }
[dev-dependencies]
editor = { path = "../editor", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
workspace = { path = "../workspace", features = ["test-support"] }
@@ -11,7 +11,7 @@ doctest = false
[dependencies]
editor = { path = "../editor" }
fuzzy = { path = "../fuzzy" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
language = { path = "../language" }
picker = { path = "../picker" }
settings = { path = "../settings" }
@@ -16,7 +16,7 @@ test-support = [
[dependencies]
collections = { path = "../collections" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
sum_tree = { path = "../sum_tree" }
theme = { path = "../theme" }
language = { path = "../language" }
@@ -18,4 +18,4 @@ util = { path = "../util" }
[dev-dependencies]
rand.workspace = true
util = { path = "../util", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
@@ -15,7 +15,7 @@ test-support = ["collections/test-support", "gpui/test-support"]
[dependencies]
clock = { path = "../clock" }
collections = { path = "../collections" }
-gpui = { package = "gpui2", path = "../gpui2", optional = true }
+gpui = { path = "../gpui", optional = true }
util = { path = "../util" }
anyhow.workspace = true
async-lock = "2.4"
@@ -39,7 +39,7 @@ prost-build = "0.9"
[dev-dependencies]
collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
smol.workspace = true
tempdir.workspace = true
ctor.workspace = true
@@ -12,7 +12,7 @@ doctest = false
bitflags = "1"
collections = { path = "../collections" }
editor = { path = "../editor" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
language = { path = "../language" }
menu = { path = "../menu" }
project = { path = "../project" }
@@ -34,7 +34,7 @@ serde_json.workspace = true
[dev-dependencies]
client = { path = "../client", features = ["test-support"] }
editor = { path = "../editor", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
workspace = { path = "../workspace", features = ["test-support"] }
unindent.workspace = true
@@ -11,7 +11,7 @@ doctest = false
[dependencies]
ai = { path = "../ai" }
collections = { path = "../collections" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
language = { path = "../language" }
project = { path = "../project" }
workspace = { path = "../workspace" }
@@ -41,7 +41,7 @@ ndarray = { version = "0.15.0" }
[dev-dependencies]
ai = { path = "../ai", features = ["test-support"] }
collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
rpc = { path = "../rpc", features = ["test-support"] }
@@ -13,7 +13,7 @@ test-support = ["gpui/test-support", "fs/test-support"]
[dependencies]
collections = { path = "../collections" }
-gpui = {package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
sqlez = { path = "../sqlez" }
fs = { path = "../fs" }
feature_flags = { path = "../feature_flags" }
@@ -35,7 +35,7 @@ tree-sitter.workspace = true
tree-sitter-json = "*"
[dev-dependencies]
-gpui = {package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
fs = { path = "../fs", features = ["test-support"] }
indoc.workspace = true
pretty_assertions.workspace = true
@@ -7,6 +7,6 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
smallvec.workspace = true
itertools = {package = "itertools", version = "0.10"}
@@ -18,7 +18,7 @@ strum = { version = "0.25.0", features = ["derive"] }
dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
editor = { path = "../editor" }
fuzzy = { path = "../fuzzy" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
indoc.workspace = true
itertools = "0.11.0"
language = { path = "../language" }
@@ -35,4 +35,4 @@ util = { path = "../util" }
picker = { path = "../picker" }
[dev-dependencies]
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
@@ -10,7 +10,7 @@ doctest = false
[dependencies]
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
settings = { path = "../settings" }
db = { path = "../db" }
theme = { path = "../theme" }
@@ -11,7 +11,7 @@ doctest = false
[dependencies]
editor = { path = "../editor" }
language = { path = "../language" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
project = { path = "../project" }
# search = { path = "../search" }
settings = { path = "../settings" }
@@ -39,7 +39,7 @@ serde_derive.workspace = true
[dev-dependencies]
editor = { path = "../editor", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
client = { path = "../client", features = ["test-support"]}
project = { path = "../project", features = ["test-support"]}
workspace = { path = "../workspace", features = ["test-support"] }
@@ -30,7 +30,7 @@ regex.workspace = true
[dev-dependencies]
collections = { path = "../collections", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true
@@ -21,7 +21,7 @@ doctest = false
[dependencies]
anyhow.workspace = true
fs = { path = "../fs" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
indexmap = "1.6.2"
parking_lot.workspace = true
refineable.workspace = true
@@ -37,6 +37,6 @@ util = { path = "../util" }
itertools = { version = "0.11.0", optional = true }
[dev-dependencies]
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
fs = { path = "../fs", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
@@ -9,7 +9,7 @@ any_ascii = "0.3.2"
anyhow.workspace = true
clap = { version = "4.4", features = ["derive"] }
convert_case = "0.6.0"
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
indexmap = { version = "1.6.2", features = ["serde"] }
json_comments = "0.2.2"
log.workspace = true
@@ -14,7 +14,7 @@ editor = { path = "../editor" }
feature_flags = { path = "../feature_flags" }
fs = { path = "../fs" }
fuzzy = { path = "../fuzzy" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
picker = { path = "../picker" }
settings = { path = "../settings" }
theme = { path = "../theme" }
@@ -11,7 +11,7 @@ path = "src/ui.rs"
[dependencies]
anyhow.workspace = true
chrono = "0.4"
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
itertools = { version = "0.11.0", optional = true }
menu = { path = "../menu"}
serde.workspace = true
@@ -11,12 +11,6 @@ doctest = true
[features]
test-support = ["tempdir", "git2"]
-# Suppress a panic when both GPUI1 and GPUI2 are loaded.
-#
-# This is used in the `theme_importer` where we need to depend on both
-# GPUI1 and GPUI2 in order to convert Zed1 themes to Zed2 themes.
-allow-multiple-gpui-versions = []
-
[dependencies]
anyhow.workspace = true
backtrace = "0.3"
@@ -16,9 +16,6 @@ use std::{
task::{Context, Poll},
};
-#[cfg(not(feature = "allow-multiple-gpui-versions"))]
-use std::sync::atomic::AtomicU32;
-
pub use backtrace::Backtrace;
use futures::Future;
use rand::{seq::SliceRandom, Rng};
@@ -436,23 +433,6 @@ impl<T: Ord + Clone> RangeExt<T> for RangeInclusive<T> {
}
}
-#[cfg(not(feature = "allow-multiple-gpui-versions"))]
-static GPUI_LOADED: AtomicU32 = AtomicU32::new(0);
-
-pub fn gpui2_loaded() {
- #[cfg(not(feature = "allow-multiple-gpui-versions"))]
- if GPUI_LOADED.fetch_add(2, std::sync::atomic::Ordering::SeqCst) != 0 {
- panic!("=========\nYou are loading both GPUI1 and GPUI2 in the same build!\nFix Your Dependencies with cargo tree!\n=========")
- }
-}
-
-pub fn gpui1_loaded() {
- #[cfg(not(feature = "allow-multiple-gpui-versions"))]
- if GPUI_LOADED.fetch_add(1, std::sync::atomic::Ordering::SeqCst) != 0 {
- panic!("=========\nYou are loading both GPUI1 and GPUI2 in the same build!\nFix Your Dependencies with cargo tree!\n=========")
- }
-}
-
#[cfg(test)]
mod tests {
use super::*;
@@ -8,7 +8,7 @@ publish = false
[dependencies]
fuzzy = { path = "../fuzzy"}
fs = {path = "../fs"}
-gpui = {package = "gpui2", path = "../gpui2"}
+gpui = {path = "../gpui"}
picker = {path = "../picker"}
util = {path = "../util"}
ui = {path = "../ui"}
@@ -27,7 +27,7 @@ serde_json.workspace = true
collections = { path = "../collections" }
command_palette = { path = "../command_palette" }
editor = { path = "../editor" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
language = { path = "../language" }
search = { path = "../search" }
settings = { path = "../settings" }
@@ -43,7 +43,7 @@ parking_lot.workspace = true
futures.workspace = true
editor = { path = "../editor", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
@@ -15,7 +15,7 @@ client = { path = "../client" }
editor = { path = "../editor" }
fs = { path = "../fs" }
fuzzy = { path = "../fuzzy" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
ui = { path = "../ui" }
db = { path = "../db" }
install_cli = { path = "../install_cli" }
@@ -25,7 +25,7 @@ client = { path = "../client" }
collections = { path = "../collections" }
# context_menu = { path = "../context_menu" }
fs = { path = "../fs" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
install_cli = { path = "../install_cli" }
language = { path = "../language" }
#menu = { path = "../menu" }
@@ -56,7 +56,7 @@ uuid.workspace = true
[dev-dependencies]
call = { path = "../call", features = ["test-support"] }
client = { path = "../client", features = ["test-support"] }
-gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
fs = { path = "../fs", features = ["test-support"] }
@@ -40,7 +40,7 @@ search = { path = "../search" }
fs = { path = "../fs" }
fsevent = { path = "../fsevent" }
go_to_line = { path = "../go_to_line" }
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
install_cli = { path = "../install_cli" }
journal = { path = "../journal" }
language = { path = "../language" }
@@ -148,7 +148,7 @@ call = { path = "../call", features = ["test-support"] }
# client = { path = "../client", features = ["test-support"] }
# editor = { path = "../editor", features = ["test-support"] }
# gpui = { path = "../gpui", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
# lsp = { path = "../lsp", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
@@ -7,5 +7,5 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-gpui = { package = "gpui2", path = "../gpui2" }
+gpui = { path = "../gpui" }
serde.workspace = true
@@ -124,7 +124,7 @@ The following data is sent:
- `operation`: The app operation that was performed
- `first open`
- `open`
- - `close (only in GPUI2-powered Zed)`
+ - `close`
- `milliseconds_since_first_event`: Same as above
You can audit the metrics data that Zed has reported by running the command `zed: open telemetry log` from the command palette, or clicking `Help > View Telemetry Log` in the application menu.