diff --git a/Cargo.lock b/Cargo.lock index 425a2a0a4b..90a046e1a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index b4a5d83fd5..560abfa3af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/crates/activity_indicator/Cargo.toml b/crates/activity_indicator/Cargo.toml index c7c8e4a224..55672d0956 100644 --- a/crates/activity_indicator/Cargo.toml +++ b/crates/activity_indicator/Cargo.toml @@ -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" } diff --git a/crates/ai/Cargo.toml b/crates/ai/Cargo.toml index c4b5a9ac8e..6516e07cd4 100644 --- a/crates/ai/Cargo.toml +++ b/crates/ai/Cargo.toml @@ -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"] } diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index 8af5dd9f85..9588932c25 100644 --- a/crates/assistant/Cargo.toml +++ b/crates/assistant/Cargo.toml @@ -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" } diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml index de118f0d5b..8b38cdd103 100644 --- a/crates/audio/Cargo.toml +++ b/crates/audio/Cargo.toml @@ -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" } diff --git a/crates/auto_update/Cargo.toml b/crates/auto_update/Cargo.toml index 81c85e53b5..884ed2b7a0 100644 --- a/crates/auto_update/Cargo.toml +++ b/crates/auto_update/Cargo.toml @@ -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" } diff --git a/crates/breadcrumbs/Cargo.toml b/crates/breadcrumbs/Cargo.toml index e130b0ce5e..e8663f9bc7 100644 --- a/crates/breadcrumbs/Cargo.toml +++ b/crates/breadcrumbs/Cargo.toml @@ -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"] } diff --git a/crates/call/Cargo.toml b/crates/call/Cargo.toml index 2ddf7caf44..7d200a0d21 100644 --- a/crates/call/Cargo.toml +++ b/crates/call/Cargo.toml @@ -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"] } diff --git a/crates/channel/Cargo.toml b/crates/channel/Cargo.toml index 966c56e351..6bd177bed5 100644 --- a/crates/channel/Cargo.toml +++ b/crates/channel/Cargo.toml @@ -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"] } diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 962fb82ab6..c24cbca35b 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -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"] } diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 2607331ef8..7681a5e572 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -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" } diff --git a/crates/collab2/Cargo.toml b/crates/collab2/Cargo.toml new file mode 100644 index 0000000000..7681a5e572 --- /dev/null +++ b/crates/collab2/Cargo.toml @@ -0,0 +1,99 @@ +[package] +authors = ["Nathan Sobo "] +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"] diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index c6d57a7edd..8206d89dce 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -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"] } diff --git a/crates/command_palette/Cargo.toml b/crates/command_palette/Cargo.toml index b8a09f9b60..39ed4fd95e 100644 --- a/crates/command_palette/Cargo.toml +++ b/crates/command_palette/Cargo.toml @@ -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"] } diff --git a/crates/copilot/Cargo.toml b/crates/copilot/Cargo.toml index 8a512d1a12..588c747696 100644 --- a/crates/copilot/Cargo.toml +++ b/crates/copilot/Cargo.toml @@ -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"] } diff --git a/crates/copilot_button/Cargo.toml b/crates/copilot_button/Cargo.toml index eb7ab69854..63788f9d28 100644 --- a/crates/copilot_button/Cargo.toml +++ b/crates/copilot_button/Cargo.toml @@ -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" } diff --git a/crates/db/Cargo.toml b/crates/db/Cargo.toml index c3f4a73d3f..b49078e860 100644 --- a/crates/db/Cargo.toml +++ b/crates/db/Cargo.toml @@ -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 diff --git a/crates/diagnostics/Cargo.toml b/crates/diagnostics/Cargo.toml index 3ad94cef81..c393a41152 100644 --- a/crates/diagnostics/Cargo.toml +++ b/crates/diagnostics/Cargo.toml @@ -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"] } diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 688e402dd2..3c0269d410 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -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"] } diff --git a/crates/feature_flags/Cargo.toml b/crates/feature_flags/Cargo.toml index 277ad8de12..af273fe403 100644 --- a/crates/feature_flags/Cargo.toml +++ b/crates/feature_flags/Cargo.toml @@ -8,5 +8,5 @@ publish = false path = "src/feature_flags.rs" [dependencies] -gpui = { package = "gpui2", path = "../gpui2" } +gpui = { path = "../gpui" } anyhow.workspace = true diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index c516abd075..c6c8b9f4b2 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -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" } diff --git a/crates/file_finder/Cargo.toml b/crates/file_finder/Cargo.toml index 942ddec5f5..269d5790bc 100644 --- a/crates/file_finder/Cargo.toml +++ b/crates/file_finder/Cargo.toml @@ -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"] } diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index 13276564d2..11a34bcecb 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -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"] diff --git a/crates/fuzzy/Cargo.toml b/crates/fuzzy/Cargo.toml index c87880440f..553c0497a5 100644 --- a/crates/fuzzy/Cargo.toml +++ b/crates/fuzzy/Cargo.toml @@ -9,5 +9,5 @@ path = "src/fuzzy.rs" doctest = false [dependencies] -gpui = { package = "gpui2", path = "../gpui2" } +gpui = { path = "../gpui" } util = { path = "../util" } diff --git a/crates/go_to_line/Cargo.toml b/crates/go_to_line/Cargo.toml index d4ce64efe6..64ec7cebfd 100644 --- a/crates/go_to_line/Cargo.toml +++ b/crates/go_to_line/Cargo.toml @@ -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" } diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index bc896f7fac..dfdd6af416 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -1,27 +1,28 @@ [package] -authors = ["Nathan Sobo "] -edition = "2021" name = "gpui" version = "0.1.0" -description = "A GPU-accelerated UI framework" +edition = "2021" +authors = ["Nathan Sobo "] +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 diff --git a/crates/gpui/build.rs b/crates/gpui/build.rs index 095d5065d9..24e493cb81 100644 --- a/crates/gpui/build.rs +++ b/crates/gpui/build.rs @@ -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); -fn compile_metal_shaders() { - let shader_path = "./src/platform/mac/shaders/shaders.metal"; + 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={}", 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"); -} diff --git a/crates/gpui2/docs/contexts.md b/crates/gpui/docs/contexts.md similarity index 100% rename from crates/gpui2/docs/contexts.md rename to crates/gpui/docs/contexts.md diff --git a/crates/gpui2/docs/key_dispatch.md b/crates/gpui/docs/key_dispatch.md similarity index 100% rename from crates/gpui2/docs/key_dispatch.md rename to crates/gpui/docs/key_dispatch.md diff --git a/crates/gpui/examples/components.rs b/crates/gpui/examples/components.rs deleted file mode 100644 index d3ca0d1ecc..0000000000 --- a/crates/gpui/examples/components.rs +++ /dev/null @@ -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 { - 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 { - pub inactive: T, - pub active: T, - } - - impl Toggleable { - 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 { - active: bool, - style: Option>, - button: Button, - } - - impl ToggleableButton { - pub fn new(active: bool, on_click: F) -> Self - where - F: Fn(MouseClick, &mut V, &mut EventContext) + 'static, - { - Self { - active, - button: Button::new(on_click), - style: None, - } - } - - pub fn with_text(self, text: &str, style: impl Into) -> ToggleableButton { - ToggleableButton { - active: self.active, - style: self.style, - button: self.button.with_text(text, style), - } - } - - pub fn with_style(self, style: Toggleable) -> ToggleableButton { - ToggleableButton { - active: self.active, - style: Some(style), - button: self.button, - } - } - } - - impl StatefulComponent for ToggleableButton { - fn render(self, v: &mut V, cx: &mut gpui::ViewContext) -> gpui::AnyElement { - 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 = Box)>; - - pub struct Button { - click_handler: ClickHandler, - tag: TypeTag, - contents: Option>, - style: Option, - } - - impl Button { - pub fn new) + 'static>(handler: F) -> Self { - Self { - click_handler: Box::new(handler), - tag: TypeTag::new::(), - style: None, - contents: None, - } - } - - pub fn with_text(mut self, text: &str, style: impl Into) -> Self { - self.contents = Some(Label::new(text.to_string(), style).into_any()); - self - } - - pub fn _with_contents>(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 StatefulComponent for Button { - fn render(self, _: &mut V, cx: &mut ViewContext) -> AnyElement { - 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() - } - } -} diff --git a/crates/gpui/examples/corner_radii.rs b/crates/gpui/examples/corner_radii.rs deleted file mode 100644 index 75ea3aeec6..0000000000 --- a/crates/gpui/examples/corner_radii.rs +++ /dev/null @@ -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) -> AnyElement { - CornersElement.into_any() - } -} - -struct CornersElement; - -impl gpui::Element for CornersElement { - type LayoutState = (); - - type PaintState = (); - - fn layout( - &mut self, - constraint: gpui::SizeConstraint, - _: &mut V, - _: &mut gpui::ViewContext, - ) -> (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, - ) -> 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, - _: pathfinder_geometry::rect::RectF, - _: pathfinder_geometry::rect::RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &V, - _: &gpui::ViewContext, - ) -> Option { - unimplemented!() - } - - fn debug( - &self, - _: pathfinder_geometry::rect::RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &V, - _: &gpui::ViewContext, - ) -> serde_json::Value { - unimplemented!() - } -} diff --git a/crates/gpui/examples/text.rs b/crates/gpui/examples/text.rs deleted file mode 100644 index bc62d75ec2..0000000000 --- a/crates/gpui/examples/text.rs +++ /dev/null @@ -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) -> AnyElement { - 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::(view_id, ix, bounds).on_click::( - MouseButton::Left, - move |_, _, _| { - eprintln!("clicked link {ix}"); - }, - ), - ); - }) - .into_any() - } -} diff --git a/crates/gpui2/src/action.rs b/crates/gpui/src/action.rs similarity index 100% rename from crates/gpui2/src/action.rs rename to crates/gpui/src/action.rs diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index b732be7455..f8da622b53 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -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; +/// Temporary(?) wrapper around RefCell to help us debug any double borrows. +/// Strongly consider removing after stabilization. +pub struct AppCell { + app: RefCell, +} - fn release(&mut self, _: &mut AppContext) {} - fn app_will_quit( - &mut self, - _: &mut AppContext, - ) -> Option>>> { - None +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()) } } -pub trait View: Entity + Sized { - fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement; - fn focus_in(&mut self, _: AnyViewHandle, _: &mut ViewContext) {} - fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext) {} - fn ui_name() -> &'static str { - type_name::() - } - fn key_down(&mut self, _: &KeyDownEvent, _: &mut ViewContext) -> bool { - false - } - fn key_up(&mut self, _: &KeyUpEvent, _: &mut ViewContext) -> bool { - false - } - fn modifiers_changed(&mut self, _: &ModifiersChangedEvent, _: &mut ViewContext) -> bool { - false - } +#[derive(Deref, DerefMut)] +pub struct AppRef<'a>(Ref<'a, AppContext>); - fn update_keymap_context(&self, keymap: &mut keymap_matcher::KeymapContext, _: &AppContext) { - Self::reset_to_default_keymap_context(keymap); - } - - fn reset_to_default_keymap_context(keymap: &mut keymap_matcher::KeymapContext) { - keymap.clear(); - keymap.add_identifier(Self::ui_name()); - } - - fn debug_json(&self, _: &AppContext) -> serde_json::Value { - serde_json::Value::Null - } - - fn text_for_range(&self, _: Range, _: &AppContext) -> Option { - None - } - fn selected_text_range(&self, _: &AppContext) -> Option> { - None - } - fn marked_text_range(&self, _: &AppContext) -> Option> { - None - } - fn unmark_text(&mut self, _: &mut ViewContext) {} - fn replace_text_in_range( - &mut self, - _: Option>, - _: &str, - _: &mut ViewContext, - ) { - } - fn replace_and_mark_text_in_range( - &mut self, - _: Option>, - _: &str, - _: Option>, - _: &mut ViewContext, - ) { +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>(&self, f: F) -> T; - fn update T>(&mut self, f: F) -> T; +#[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 trait BorrowWindowContext { - type Result; - - fn read_window(&self, window: AnyWindowHandle, f: F) -> Self::Result - where - F: FnOnce(&WindowContext) -> T; - fn read_window_optional(&self, window: AnyWindowHandle, f: F) -> Option - where - F: FnOnce(&WindowContext) -> Option; - fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Self::Result - where - F: FnOnce(&mut WindowContext) -> T; - fn update_window_optional(&mut self, window: AnyWindowHandle, f: F) -> Option - where - F: FnOnce(&mut WindowContext) -> Option; -} - -#[derive(Clone)] -pub struct App(Rc>); +pub struct App(Rc); +/// 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 { - 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) -> 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 { - self.0.borrow().background().clone() - } - - pub fn on_become_active(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(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(&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(&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(&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(&mut self, mut callback: F) -> &mut Self - where - F: 'static + FnMut(Vec, &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(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 { - self.0.borrow().platform.clone() - } - - pub fn font_cache(&self) -> Arc { - self.0.borrow().font_cache.clone() - } - - fn update 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(&mut self, window: AnyWindowHandle, callback: F) -> Option + /// Register a handler to be invoked when the platform instructs the application + /// to open one or more URLs. + pub fn on_open_urls(&self, mut callback: F) -> &Self where - F: FnOnce(&mut WindowContext) -> T, + F: 'static + FnMut(Vec, &mut AppContext), { - let mut state = self.0.borrow_mut(); - let result = state.update_window(window, callback); - state.pending_notifications.clear(); - result + 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(&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 { + self.0.borrow().text_system.clone() } } -#[derive(Clone)] -pub struct AsyncAppContext(Rc>); +pub(crate) type FrameCallback = Box; +type Handler = Box bool + 'static>; +type Listener = Box bool + 'static>; +type KeystrokeObserver = Box; +type QuitHandler = Box LocalBoxFuture<'static, ()> + 'static>; +type ReleaseListener = Box; +type NewViewListener = Box; -impl AsyncAppContext { - pub fn spawn(&self, f: F) -> Task - where - F: FnOnce(AsyncAppContext) -> Fut, - Fut: 'static + Future, - T: 'static, - { - self.0.borrow().foreground.spawn(f(self.clone())) - } - - pub fn read T>(&self, callback: F) -> T { - callback(&*self.0.borrow()) - } - - pub fn update T>(&mut self, callback: F) -> T { - self.0.borrow_mut().update(callback) - } - - pub fn windows(&self) -> Vec { - self.0.borrow().windows().collect() - } - - pub fn add_model(&mut self, build_model: F) -> ModelHandle - where - T: Entity, - F: FnOnce(&mut ModelContext) -> T, - { - self.update(|cx| cx.add_model(build_model)) - } - - pub fn add_window( - &mut self, - window_options: WindowOptions, - build_root_view: F, - ) -> WindowHandle - where - T: View, - F: FnOnce(&mut ViewContext) -> T, - { - self.update(|cx| cx.add_window(window_options, build_root_view)) - } - - pub fn platform(&self) -> Arc { - self.0.borrow().platform().clone() - } - - pub fn foreground(&self) -> Rc { - self.0.borrow().foreground.clone() - } - - pub fn background(&self) -> Arc { - self.0.borrow().background.clone() - } -} - -impl BorrowAppContext for AsyncAppContext { - fn read_with T>(&self, f: F) -> T { - self.0.borrow().read_with(f) - } - - fn update T>(&mut self, f: F) -> T { - self.0.borrow_mut().update(f) - } -} - -impl BorrowWindowContext for AsyncAppContext { - type Result = Option; - - fn read_window(&self, window: AnyWindowHandle, f: F) -> Self::Result - where - F: FnOnce(&WindowContext) -> T, - { - self.0.borrow().read_with(|cx| cx.read_window(window, f)) - } - - fn read_window_optional(&self, window: AnyWindowHandle, f: F) -> Option - where - F: FnOnce(&WindowContext) -> Option, - { - self.0 - .borrow_mut() - .update(|cx| cx.read_window_optional(window, f)) - } - - fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Self::Result - where - F: FnOnce(&mut WindowContext) -> T, - { - self.0.borrow_mut().update(|cx| cx.update_window(window, f)) - } - - fn update_window_optional(&mut self, window: AnyWindowHandle, f: F) -> Option - where - F: FnOnce(&mut WindowContext) -> Option, - { - self.0 - .borrow_mut() - .update(|cx| cx.update_window_optional(window, f)) - } -} - -type ActionCallback = dyn FnMut(&mut dyn AnyView, &dyn Action, &mut WindowContext, usize); -type GlobalActionCallback = dyn FnMut(&dyn Action, &mut AppContext); - -type SubscriptionCallback = Box bool>; -type GlobalSubscriptionCallback = Box; -type ObservationCallback = Box bool>; -type GlobalObservationCallback = Box; -type FocusObservationCallback = Box bool>; -type ReleaseObservationCallback = Box; -type ActionObservationCallback = Box; -type WindowActivationCallback = Box bool>; -type WindowFullscreenCallback = Box bool>; -type WindowBoundsCallback = Box bool>; -type KeystrokeCallback = - Box>, &mut WindowContext) -> bool>; -type ActiveLabeledTasksCallback = Box bool>; -type DeserializeActionCallback = fn(json: serde_json::Value) -> anyhow::Result>; -type WindowShouldCloseSubscriptionCallback = Box bool>; +// struct FrameConsumer { +// next_frame_callbacks: Vec, +// task: Task<()>, +// display_linker +// } pub struct AppContext { - models: HashMap>, - views: HashMap<(AnyWindowHandle, usize), Box>, - views_metadata: HashMap<(AnyWindowHandle, usize), ViewMetadata>, - windows: HashMap, - globals: HashMap>, - element_states: HashMap>, - background: Arc, - ref_counts: Arc>, - - weak_self: Option>>, - platform: Arc, - foreground_platform: Rc, - pub asset_cache: Arc, - font_system: Arc, - pub font_cache: Arc, - pub image_cache: Arc, - action_deserializers: HashMap<&'static str, (TypeId, DeserializeActionCallback)>, - capture_actions: HashMap>>>, - // Entity Types -> { Action Types -> Action Handlers } - actions: HashMap>>>, - // Action Types -> Action Handlers - global_actions: HashMap>, - keystroke_matcher: KeymapMatcher, - next_id: usize, - // next_window: AnyWindowHandle, - next_subscription_id: usize, - frame_count: usize, - - subscriptions: CallbackCollection, - global_subscriptions: CallbackCollection, - observations: CallbackCollection, - global_observations: CallbackCollection, - focus_observations: CallbackCollection, - release_observations: CallbackCollection, - action_dispatch_observations: CallbackCollection<(), ActionObservationCallback>, - window_activation_observations: CallbackCollection, - window_fullscreen_observations: CallbackCollection, - window_bounds_observations: CallbackCollection, - keystroke_observations: CallbackCollection, - active_labeled_task_observations: CallbackCollection<(), ActiveLabeledTasksCallback>, - - foreground: Rc, - pending_effects: VecDeque, - pending_notifications: HashSet, - pending_global_notifications: HashSet, - pending_flushes: usize, + pub(crate) this: Weak, + pub(crate) platform: Rc, + app_metadata: AppMetadata, + text_system: Arc, flushing_effects: bool, - halt_action_dispatch: bool, - next_labeled_task_id: usize, - active_labeled_tasks: BTreeMap, + pending_updates: usize, + pub(crate) actions: Rc, + pub(crate) active_drag: Option, + pub(crate) active_tooltip: Option, + pub(crate) next_frame_callbacks: FxHashMap>, + pub(crate) frame_consumers: FxHashMap>, + pub(crate) background_executor: BackgroundExecutor, + pub(crate) foreground_executor: ForegroundExecutor, + pub(crate) svg_renderer: SvgRenderer, + asset_source: Arc, + pub(crate) image_cache: ImageCache, + pub(crate) text_style_stack: Vec, + pub(crate) globals_by_type: FxHashMap>, + pub(crate) entities: EntityMap, + pub(crate) new_view_observers: SubscriberSet, + pub(crate) windows: SlotMap>, + pub(crate) keymap: Arc>, + pub(crate) global_action_listeners: + FxHashMap>>, + pending_effects: VecDeque, + pub(crate) pending_notifications: FxHashSet, + pub(crate) pending_global_notifications: FxHashSet, + pub(crate) observers: SubscriberSet, + // TypeId is the type of the event that the listener callback expects + pub(crate) event_listeners: SubscriberSet, + pub(crate) keystroke_observers: SubscriberSet<(), KeystrokeObserver>, + pub(crate) release_listeners: SubscriberSet, + pub(crate) global_observers: SubscriberSet, + pub(crate) quit_observers: SubscriberSet<(), QuitHandler>, + pub(crate) layout_id_buffer: Vec, // We recycle this memory across layout requests. + pub(crate) propagate_event: bool, } impl AppContext { - fn new( - foreground: Rc, - background: Arc, - platform: Arc, - foreground_platform: Rc, - font_cache: Arc, + pub(crate) fn new( + platform: Rc, + asset_source: Arc, http_client: Arc, - 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, + ) -> Rc { + let executor = platform.background_executor(); + let foreground_executor = platform.foreground_executor(); + assert!( + executor.is_main_thread(), + "must construct App on main thread" + ); - 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(), - } + 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 } - pub fn background(&self) -> &Arc { - &self.background - } - - pub fn font_cache(&self) -> &Arc { - &self.font_cache - } - - pub fn platform(&self) -> &Arc { - &self.platform - } - - pub fn has_global(&self) -> bool { - self.globals.contains_key(&TypeId::of::()) - } - - pub fn global(&self) -> &T { - if let Some(global) = self.globals.get(&TypeId::of::()) { - global.downcast_ref().unwrap() - } else { - panic!("no global has been added for {}", type_name::()); - } - } - - pub fn optional_global(&self) -> Option<&T> { - if let Some(global) = self.globals.get(&TypeId::of::()) { - Some(global.downcast_ref().unwrap()) - } else { - None - } - } - - pub fn upgrade(&self) -> App { - App(self.weak_self.as_ref().unwrap().upgrade().unwrap()) - } - - 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::>() { - 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::>() { - 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 { - &self.foreground + pub fn quit(&mut self) { + self.platform.quit(); } - pub fn deserialize_action( - &self, - name: &str, - argument: Option, - ) -> Result> { - 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(&mut self, handler: F) - where - A: Action, - V: 'static, - F: 'static + FnMut(&mut V, &A, &mut ViewContext) -> 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 fn capture_action(&mut self, handler: F) - where - A: Action, - V: 'static, - F: 'static + FnMut(&mut V, &A, &mut ViewContext), - { - self.add_action_internal(handler, true) - } - - fn add_action_internal(&mut self, mut handler: F, capture: bool) - where - A: Action, - V: 'static, - F: 'static + FnMut(&mut V, &A, &mut ViewContext) -> R, - { - 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>, - action_id: TypeId, - view_id: TypeId, - handler: Box, - 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); + pub(crate) fn update(&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; } - inner( - self, - A::qualified_name(), - A::from_json_str, - TypeId::of::(), - TypeId::of::(), - handler, - capture, - ); - } - - pub fn add_async_action(&mut self, mut handler: F) - where - A: Action, - V: 'static, - F: 'static + FnMut(&mut V, &A, &mut ViewContext) -> Option>>, - { - self.add_action(move |view, action, cx| { - if let Some(task) = handler(view, action, cx) { - task.detach_and_log_err(cx); - } - }) - } - - pub fn add_global_action(&mut self, mut handler: F) - where - A: Action, - F: 'static + FnMut(&A, &mut AppContext), - { - let handler = Box::new(move |action: &dyn Action, cx: &mut AppContext| { - let action = action.as_any().downcast_ref().unwrap(); - handler(action, cx); - }); - - self.action_deserializers - .entry(A::qualified_name()) - .or_insert((TypeId::of::(), A::from_json_str)); - - if self - .global_actions - .insert(TypeId::of::(), handler) - .is_some() - { - panic!( - "registered multiple global handlers for {}", - type_name::() - ); - } - } - - pub fn view_ui_name(&self, window: AnyWindowHandle, view_id: usize) -> Option<&'static str> { - Some(self.views.get(&(window, view_id))?.ui_name()) - } - - pub fn view_type_id(&self, window: AnyWindowHandle, view_id: usize) -> Option { - self.views_metadata - .get(&(window, view_id)) - .map(|metadata| metadata.type_id) - } - - pub fn active_labeled_tasks<'a>( - &'a self, - ) -> impl DoubleEndedIterator + 'a { - self.active_labeled_tasks.values().cloned() - } - - pub(crate) fn start_frame(&mut self) { - self.frame_count += 1; - } - - pub fn update T>(&mut self, callback: F) -> T { - self.pending_flushes += 1; - let result = callback(self); - self.flush_effects(); + self.pending_updates -= 1; result } - fn read_window T>( - &self, - handle: AnyWindowHandle, - callback: F, - ) -> Option { - let window = self.windows.get(&handle)?; - let window_context = WindowContext::immutable(self, &window, handle); - Some(callback(&window_context)) - } - - pub fn update_active_window T>( + pub fn observe( &mut self, - callback: F, - ) -> Option { - self.active_window() - .and_then(|window| window.update(self, callback)) - } - - pub fn prompt_for_paths( - &self, - options: PathPromptOptions, - ) -> oneshot::Receiver>> { - self.foreground_platform.prompt_for_paths(options) - } - - pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver> { - self.foreground_platform.prompt_for_new_path(directory) - } - - pub fn reveal_path(&self, path: &Path) { - self.foreground_platform.reveal_path(path) - } - - pub fn emit_global(&mut self, payload: E) { - self.pending_effects.push_back(Effect::GlobalEvent { - payload: Box::new(payload), - }); - } - - pub fn subscribe(&mut self, handle: &H, mut callback: F) -> Subscription + entity: &E, + mut on_notify: impl FnMut(E, &mut AppContext) + 'static, + ) -> Subscription where - E: Entity, - E::Event: 'static, - H: Handle, - F: 'static + FnMut(H, &E::Event, &mut Self), + W: 'static, + E: Entity, { - self.subscribe_internal(handle, move |handle, event, cx| { - callback(handle, event, cx); + self.observe_internal(entity, move |e, cx| { + on_notify(e, cx); true }) } - pub fn subscribe_global(&mut self, mut callback: F) -> Subscription + pub fn observe_internal( + &mut self, + entity: &E, + mut on_notify: impl FnMut(E, &mut AppContext) -> bool + 'static, + ) -> Subscription where - E: Any, - F: 'static + FnMut(&E, &mut Self), + W: 'static, + E: Entity, { - let subscription_id = post_inc(&mut self.next_subscription_id); - let type_id = TypeId::of::(); - 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 observe(&mut self, handle: &H, mut callback: F) -> Subscription - where - E: Entity, - E::Event: 'static, - H: Handle, - F: 'static + FnMut(H, &mut Self), - { - self.observe_internal(handle, move |handle, cx| { - callback(handle, cx); - true - }) - } - - fn subscribe_internal(&mut self, handle: &H, mut callback: F) -> Subscription - where - E: Entity, - E::Event: 'static, - H: Handle, - 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 - } - }), - }); - Subscription::Subscription(self.subscriptions.subscribe(handle.id(), subscription_id)) - } - - fn observe_internal(&mut self, handle: &H, mut callback: F) -> Subscription - where - E: Entity, - E::Event: 'static, - H: Handle, - 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 { + let entity_id = entity.entity_id(); + let handle = entity.downgrade(); + let (subscription, activate) = self.observers.insert( entity_id, - subscription_id, - callback: Box::new(move |cx| { - if let Some(observed) = H::upgrade_from(&observed, cx) { - callback(observed, cx) + Box::new(move |cx| { + if let Some(handle) = E::upgrade_from(&handle) { + on_notify(handle, cx) } else { false } }), - }); - Subscription::Observation(self.observations.subscribe(entity_id, subscription_id)) - } - - fn observe_focus(&mut self, handle: &ViewHandle, mut callback: F) -> Subscription - where - V: 'static, - F: 'static + FnMut(ViewHandle, 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)) - } - - pub fn observe_global(&mut self, mut observe: F) -> Subscription - where - G: Any, - F: 'static + FnMut(&mut AppContext), - { - let type_id = TypeId::of::(); - let id = post_inc(&mut self.next_subscription_id); - - 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)) + self.defer(move |_| activate()); + subscription } - pub fn observe_default_global(&mut self, observe: F) -> Subscription + pub fn subscribe( + &mut self, + entity: &E, + mut on_event: impl FnMut(E, &Evt, &mut AppContext) + 'static, + ) -> Subscription where - G: Any + Default, - F: 'static + FnMut(&mut AppContext), + T: 'static + EventEmitter, + E: Entity, + Evt: 'static, { - if !self.has_global::() { - self.set_global(G::default()); - } - self.observe_global::(observe) + self.subscribe_internal(entity, move |entity, event, cx| { + on_event(entity, event, cx); + true + }) } - pub fn observe_release(&mut self, handle: &H, callback: F) -> Subscription + pub(crate) fn subscribe_internal( + &mut self, + entity: &E, + mut on_event: impl FnMut(E, &Evt, &mut AppContext) -> bool + 'static, + ) -> Subscription where - E: Entity, - E::Event: 'static, - H: Handle, - F: 'static + FnOnce(&E, &mut Self), + T: 'static + EventEmitter, + E: Entity, + Evt: 'static, { - 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) - } - }), + let entity_id = entity.entity_id(); + let entity = entity.downgrade(); + let (subscription, activate) = self.event_listeners.insert( + entity_id, + ( + TypeId::of::(), + 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 + } + }), + ), ); - Subscription::ReleaseObservation(self.release_observations.subscribe(handle.id(), id)) + self.defer(move |_| activate()); + subscription } - pub fn observe_actions(&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), - ) - } - - fn observe_active_labeled_tasks(&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), - ) - } - - 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, - }) - } - - 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 notify_model(&mut self, model_id: usize) { - if self.pending_notifications.insert(model_id) { - self.pending_effects - .push_back(Effect::ModelNotification { model_id }); - } - } - - 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 notify_global(&mut self, type_id: TypeId) { - if self.pending_global_notifications.insert(type_id) { - self.pending_effects - .push_back(Effect::GlobalNotification { type_id }); - } - } - - pub fn all_action_names<'a>(&'a self) -> impl Iterator + 'a { - self.action_deserializers.keys().copied() - } - - 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) - } - - fn actions_mut( - &mut self, - capture_phase: bool, - ) -> &mut HashMap>>> { - if capture_phase { - &mut self.capture_actions - } else { - &mut self.actions - } - } - - 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 - } - }) - } - - pub fn add_bindings>(&mut self, bindings: T) { - self.keystroke_matcher.add_bindings(bindings); - } - - pub fn clear_bindings(&mut self) { - self.keystroke_matcher.clear_bindings(); - } - - 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)) - } - - pub fn default_global(&mut self) -> &T { - let type_id = TypeId::of::(); - 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() - } - - pub fn set_global(&mut self, state: T) { - self.update(|this| { - let type_id = TypeId::of::(); - this.globals.insert(type_id, Box::new(state)); - this.notify_global(type_id); - }); - } - - pub fn update_default_global(&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)) - }) - } - - fn update_default_global_internal(this: &mut C, update: F) -> U - where - C: DerefMut, - T: 'static + Default, - F: FnOnce(&mut T, &mut C) -> U, - { - let type_id = TypeId::of::(); - 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 - } - - pub fn update_global(&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)) - }) - } - - fn update_global_internal(this: &mut C, update: F) -> U - where - C: DerefMut, - T: 'static, - F: FnOnce(&mut T, &mut C) -> U, - { - let type_id = TypeId::of::(); - 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::()); - } - } - - pub fn clear_globals(&mut self) { - self.globals.clear(); - } - - pub fn remove_global(&mut self) -> T { - *self - .globals - .remove(&TypeId::of::()) - .unwrap_or_else(|| panic!("no global added for {}", std::any::type_name::())) - .downcast() - .unwrap() - } - - pub fn add_model(&mut self, build_model: F) -> ModelHandle - where - T: Entity, - F: FnOnce(&mut ModelContext) -> 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(&self, handle: &ModelHandle) -> &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"); - } - } - - fn update_model( - &mut self, - handle: &ModelHandle, - update: &mut dyn FnMut(&mut T, &mut ModelContext) -> 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::()); - } - } - - fn upgrade_model_handle( - &self, - handle: &WeakModelHandle, - ) -> Option> { - if self.ref_counts.lock().is_entity_alive(handle.model_id) { - Some(ModelHandle::new(handle.model_id, &self.ref_counts)) - } else { - None - } - } - - fn model_handle_is_upgradable(&self, handle: &WeakModelHandle) -> bool { - self.ref_counts.lock().is_entity_alive(handle.model_id) - } - - fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option { - 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 - } - } - - pub fn add_window( - &mut self, - window_options: WindowOptions, - build_root_view: F, - ) -> WindowHandle - where - V: View, - F: FnOnce(&mut ViewContext) -> V, - { - self.update(|this| { - let handle = WindowHandle::::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 - }) - } - - pub fn add_status_bar_item(&mut self, build_root_view: F) -> WindowHandle - where - V: View, - F: FnOnce(&mut ViewContext) -> V, - { - self.update(|this| { - let handle = WindowHandle::::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 - }) - } - - pub fn build_window( - &mut self, - handle: AnyWindowHandle, - mut platform_window: Box, - build_root_view: F, - ) -> Window - where - V: View, - F: FnOnce(&mut ViewContext) -> 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)) - })); - } - - { - let mut app = self.upgrade(); - platform_window.on_resize(Box::new(move || { - app.update(|cx| cx.window_was_resized(handle)) - })); - } - - { - let mut app = self.upgrade(); - platform_window.on_moved(Box::new(move || { - app.update(|cx| cx.window_was_moved(handle)) - })); - } - - { - 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)) - })); - } - - { - let mut app = self.upgrade(); - platform_window.on_close(Box::new(move || { - app.update(|cx| cx.update_window(handle, |cx| cx.remove_window())); - })); - } - - { - let mut app = self.upgrade(); - platform_window - .on_appearance_changed(Box::new(move || app.update(|cx| cx.refresh_windows()))); - } - - platform_window.set_input_handler(Box::new(WindowInputHandler { - app: self.upgrade().0, - window: handle, - })); - - 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 windows(&self) -> Vec { + self.windows + .values() + .filter_map(|window| Some(window.as_ref()?.handle)) + .collect() } pub fn active_window(&self) -> Option { - self.platform.main_window() + self.platform.active_window() } - pub fn windows(&self) -> impl '_ + Iterator { - self.windows.keys().copied() - } - - pub fn read_view(&self, handle: &ViewHandle) -> &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::()); - } - } - - fn upgrade_view_handle(&self, handle: &WeakViewHandle) -> Option> { - if self.ref_counts.lock().is_entity_alive(handle.view_id) { - Some(ViewHandle::new( - handle.window, - handle.view_id, - &self.ref_counts, - )) - } else { - None - } - } - - fn upgrade_any_view_handle(&self, handle: &AnyWeakViewHandle) -> Option { - 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 - } - } - - 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() - { - break; - } - - 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 }); - } - - 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); - } - - self.pending_effects - .push_back(Effect::ViewRelease { view_id, view }); - } - - for key in dropped_element_states { - self.element_states.remove(&key); - } - } - } - - 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; - - let mut refreshing = false; - let mut updated_windows = HashSet::default(); - let mut focus_effects = HashMap::::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)) - } - - 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)); - } - - 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::>() { - 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::>(); - let new_ancestors = - cx.ancestors(focused_view_id).collect::>(); - - // 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( + /// 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( &mut self, - window: AnyWindowHandle, - keystroke: Keystroke, - handled_by: Option>, - 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) { - 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( - &mut self, - window: AnyWindowHandle, - keystroke: Keystroke, - handled_by: Option>, - 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 - }) - .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::>() { - 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::>() { - 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( - &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) { - self.pending_effects - .push_back(Effect::Focus(FocusEffect::View { - window, - view_id, - is_forced: false, - })); - } - - fn spawn_internal(&mut self, task_name: Option<&'static str>, f: F) -> Task - where - F: FnOnce(AsyncAppContext) -> Fut, - Fut: 'static + Future, - T: 'static, - { - 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); - } - cx.flush_effects(); - result + options: crate::WindowOptions, + build_root_view: impl FnOnce(&mut WindowContext) -> View, + ) -> WindowHandle { + 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 spawn_labeled(&mut self, task_name: &'static str, f: F) -> Task - where - F: FnOnce(AsyncAppContext) -> Fut, - Fut: 'static + Future, - T: 'static, - { - self.spawn_internal(Some(task_name), f) + /// 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 spawn(&mut self, f: F) -> Task - where - F: FnOnce(AsyncAppContext) -> Fut, - Fut: 'static + Future, - T: 'static, - { - self.spawn_internal(None, f) + pub fn hide(&self) { + self.platform.hide(); } - pub fn to_async(&self) -> AsyncAppContext { - AsyncAppContext(self.weak_self.as_ref().unwrap().upgrade().unwrap()) + pub fn hide_other_apps(&self) { + self.platform.hide_other_apps(); } - pub fn open_url(&self, url: &str) { - self.platform.open_url(url) + pub fn unhide_other_apps(&self) { + self.platform.unhide_other_apps(); } + /// Returns the list of currently active displays. + pub fn displays(&self) -> Vec> { + self.platform.displays() + } + + /// Writes data to the platform clipboard. pub fn write_to_clipboard(&self, item: ClipboardItem) { - self.platform.write_to_clipboard(item); + self.platform.write_to_clipboard(item) } + /// Reads data from the platform clipboard. pub fn read_from_clipboard(&self) -> Option { self.platform.read_from_clipboard() } - #[cfg(any(test, feature = "test-support"))] - pub fn leak_detector(&self) -> Arc> { - self.ref_counts.lock().leak_detector.clone() + /// 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) } -} -impl BorrowAppContext for AppContext { - fn read_with T>(&self, f: F) -> T { - f(self) + /// Reads credentials from the platform keychain. + pub fn read_credentials(&self, url: &str) -> Result)>> { + self.platform.read_credentials(url) } - fn update T>(&mut self, f: F) -> T { - f(self) + /// Deletes credentials from the platform keychain. + pub fn delete_credentials(&self, url: &str) -> Result<()> { + self.platform.delete_credentials(url) } -} -impl BorrowWindowContext for AppContext { - type Result = Option; - - fn read_window(&self, window: AnyWindowHandle, f: F) -> Self::Result - where - F: FnOnce(&WindowContext) -> T, - { - AppContext::read_window(self, window, f) - } - - fn read_window_optional(&self, window: AnyWindowHandle, f: F) -> Option - where - F: FnOnce(&WindowContext) -> Option, - { - AppContext::read_window(self, window, f).flatten() - } - - fn update_window(&mut self, handle: AnyWindowHandle, f: F) -> Self::Result - 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(&mut self, handle: AnyWindowHandle, f: F) -> Option - where - F: FnOnce(&mut WindowContext) -> Option, - { - 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, - pub removed: Vec, -} - -#[derive(Debug)] -pub enum FocusEffect { - View { - window: AnyWindowHandle, - view_id: Option, - 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, - } - } - - fn is_forced(&self) -> bool { - match self { - FocusEffect::View { is_forced, .. } => *is_forced, - FocusEffect::ViewParent { is_forced, .. } => *is_forced, - } - } - - fn force(&mut self) { - match self { - FocusEffect::View { is_forced, .. } => *is_forced = true, - FocusEffect::ViewParent { is_forced, .. } => *is_forced = true, - } - } -} - -pub enum Effect { - Subscription { - entity_id: usize, - subscription_id: usize, - callback: SubscriptionCallback, - }, - Event { - entity_id: usize, - payload: Box, - }, - GlobalSubscription { - type_id: TypeId, - subscription_id: usize, - callback: GlobalSubscriptionCallback, - }, - GlobalEvent { - payload: Box, - }, - Observation { - entity_id: usize, - subscription_id: usize, - callback: ObservationCallback, - }, - ModelNotification { - model_id: usize, - }, - ViewNotification { - window: AnyWindowHandle, - view_id: usize, - }, - Deferred { - callback: Box, - after_window_update: bool, - }, - GlobalNotification { - type_id: TypeId, - }, - ModelRelease { - model_id: usize, - model: Box, - }, - ViewRelease { - view_id: usize, - view: Box, - }, - Focus(FocusEffect), - FocusObservation { - view_id: usize, - subscription_id: usize, - callback: FocusObservationCallback, - }, - ResizeWindow { - window: AnyWindowHandle, - }, - MoveWindow { - window: AnyWindowHandle, - }, - 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>, - result: MatchResult, - }, - RefreshWindows, - ActionDispatchNotification { - action_id: TypeId, - }, - WindowShouldCloseSubscription { - window: AnyWindowHandle, - callback: WindowShouldCloseSubscriptionCallback, - }, - ActiveLabeledTasksChanged, - ActiveLabeledTasksObservation { - subscription_id: usize, - callback: ActiveLabeledTasksCallback, - }, -} - -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(), - } - } -} - -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>>>; -} - -impl AnyModel for T -where - T: Entity, -{ - 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>>> { - self.app_will_quit(cx) - } -} - -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>>>; - fn ui_name(&self) -> &'static str; - fn render(&mut self, cx: &mut WindowContext, view_id: usize) -> Box; - 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, cx: &WindowContext) -> Option; - fn selected_text_range(&self, cx: &WindowContext) -> Option>; - fn marked_text_range(&self, cx: &WindowContext) -> Option>; - fn unmark_text(&mut self, cx: &mut WindowContext, view_id: usize); - fn replace_text_in_range( - &mut self, - range: Option>, - text: &str, - cx: &mut WindowContext, - view_id: usize, - ); - fn replace_and_mark_text_in_range( - &mut self, - range: Option>, - new_text: &str, - new_selected_range: Option>, - 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(), - ) - } -} - -impl 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>>> { - 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 { - 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, cx: &WindowContext) -> Option { - View::text_for_range(self, range, cx) - } - - fn selected_text_range(&self, cx: &WindowContext) -> Option> { - View::selected_text_range(self, cx) - } - - fn marked_text_range(&self, cx: &WindowContext) -> Option> { - 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>, - 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>, - new_text: &str, - new_selected_range: Option>, - 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, - 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 { - &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(&mut self, build_model: F) -> ModelHandle - where - S: Entity, - F: FnOnce(&mut ModelContext) -> 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( - &mut self, - handle: &ModelHandle, - mut callback: F, - ) -> Subscription - where - S::Event: 'static, - F: 'static + FnMut(&mut T, ModelHandle, &S::Event, &mut ModelContext), - { - 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(&mut self, handle: &ModelHandle, mut callback: F) -> Subscription - where - S: Entity, - F: 'static + FnMut(&mut T, ModelHandle, &mut ModelContext), - { - 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(&mut self, mut callback: F) -> Subscription - where - G: Any, - F: 'static + FnMut(&mut T, &mut ModelContext), - { - let observer = self.weak_handle(); - self.app.observe_global::(move |cx| { - if let Some(observer) = observer.upgrade(cx) { - observer.update(cx, |observer, cx| callback(observer, cx)); - } - }) - } - - pub fn observe_release( - &mut self, - handle: &ModelHandle, - mut callback: F, - ) -> Subscription - where - S: Entity, - F: 'static + FnMut(&mut T, &S, &mut ModelContext), - { - 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 { - ModelHandle::new(self.model_id, &self.app.ref_counts) - } - - pub fn weak_handle(&self) -> WeakModelHandle { - WeakModelHandle::new(self.model_id) - } - - pub fn spawn(&mut self, f: F) -> Task - where - F: FnOnce(ModelHandle, AsyncAppContext) -> Fut, - Fut: 'static + Future, - S: 'static, - { - let handle = self.handle(); - self.app.spawn(|cx| f(handle, cx)) - } - - pub fn spawn_weak(&mut self, f: F) -> Task - where - F: FnOnce(WeakModelHandle, AsyncAppContext) -> Fut, - Fut: 'static + Future, - S: 'static, - { - let handle = self.weak_handle(); - self.app.spawn(|cx| f(handle, cx)) - } -} - -impl AsRef for ModelContext<'_, M> { - fn as_ref(&self) -> &AppContext { - &self.app - } -} - -impl AsMut for ModelContext<'_, M> { - fn as_mut(&mut self) -> &mut AppContext { - self.app - } -} - -impl BorrowAppContext for ModelContext<'_, M> { - fn read_with T>(&self, f: F) -> T { - self.app.read_with(f) - } - - fn update T>(&mut self, f: F) -> T { - self.app.update(f) - } -} - -impl Deref for ModelContext<'_, M> { - type Target = AppContext; - - fn deref(&self) -> &Self::Target { - self.app - } -} - -impl 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, -} - -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 { - ViewHandle::new( - self.window_handle, - self.view_id, - &self.window_context.ref_counts, - ) - } - - pub fn weak_handle(&self) -> WeakViewHandle { - 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 + /// 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 foreground(&self) -> &Rc { - self.window_context.foreground() + pub fn app_path(&self) -> Result { + self.platform.app_path() } - pub fn background_executor(&self) -> &Arc { - &self.window_context.background + pub fn path_for_auxiliary_executable(&self, name: &str) -> Result { + self.platform.path_for_auxiliary_executable(name) } - pub fn platform(&self) -> &Arc { - self.window_context.platform() + pub fn double_click_interval(&self) -> Duration { + self.platform.double_click_interval() } pub fn prompt_for_paths( &self, options: PathPromptOptions, ) -> oneshot::Receiver>> { - self.window_context.prompt_for_paths(options) + self.platform.prompt_for_paths(options) } pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver> { - self.window_context.prompt_for_new_path(directory) + self.platform.prompt_for_new_path(directory) } pub fn reveal_path(&self, path: &Path) { - self.window_context.reveal_path(path) + self.platform.reveal_path(path) } - pub fn focus(&mut self, handle: &AnyViewHandle) { - self.window_context.focus(Some(handle.view_id)); + pub fn should_auto_hide_scrollbars(&self) -> bool { + self.platform.should_auto_hide_scrollbars() } - pub fn focus_self(&mut self) { - let view_id = self.view_id; - self.window_context.focus(Some(view_id)); + pub fn restart(&self) { + self.platform.restart() } - pub fn is_self_focused(&self) -> bool { - self.window.focused_view_id == Some(self.view_id) + pub fn local_timezone(&self) -> UtcOffset { + self.platform.local_timezone() } - 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(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); } - pub fn blur(&mut self) { - self.window_context.focus(None); + /// 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::>() + { + self.update_window(window, |_, cx| cx.draw()).unwrap(); + } + + if self.pending_effects.is_empty() { + break; + } + } + } } - pub fn on_window_should_close(&mut self, mut callback: F) - where - F: 'static + FnMut(&mut V, &mut ViewContext) -> 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)) + /// 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 } - }) - .unwrap_or(true) - }), - }); - } - - pub fn subscribe(&mut self, handle: &H, mut callback: F) -> Subscription - where - E: Entity, - E::Event: 'static, - H: Handle, - F: 'static + FnMut(&mut V, H, &E::Event, &mut ViewContext), - { - 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(&mut self, handle: &H, mut callback: F) -> Subscription - where - E: Entity, - H: Handle, - F: 'static + FnMut(&mut V, H, &mut ViewContext), - { - 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 + if blur_window { + cx.blur(); } }) - .unwrap_or(false) - }) + .unwrap(); + } } - pub fn observe_global(&mut self, mut callback: F) -> Subscription - where - G: Any, - F: 'static + FnMut(&mut V, &mut ViewContext), - { - let window = self.window_handle; - let observer = self.weak_handle(); - self.window_context.observe_global::(move |cx| { - cx.update_window(window, |cx| { - if let Some(observer) = observer.upgrade(cx) { - observer.update(cx, |observer, cx| callback(observer, cx)); + 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) { + self.event_listeners + .clone() + .retain(&emitter, |(stored_type, handler)| { + if *stored_type == event_type { + handler(event.as_ref(), self) + } else { + true } }); - }) } - pub fn observe_focus(&mut self, handle: &ViewHandle, mut callback: F) -> Subscription - where - F: 'static + FnMut(&mut V, ViewHandle, bool, &mut ViewContext), - 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(&mut self, handle: &H, mut callback: F) -> Subscription - where - E: Entity, - H: Handle, - F: 'static + FnMut(&mut V, &E, &mut ViewContext), - { - 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(&mut self, mut callback: F) -> Subscription - where - F: 'static + FnMut(&mut V, TypeId, &mut ViewContext), - { - 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(&mut self, mut callback: F) -> Subscription - where - F: 'static + FnMut(&mut V, bool, &mut ViewContext), - { - 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(&mut self, mut callback: F) -> Subscription - where - F: 'static + FnMut(&mut V, bool, &mut ViewContext), - { - 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(&mut self, mut callback: F) -> Subscription - where - F: 'static - + FnMut( - &mut V, - &Keystroke, - Option<&Box>, - &MatchResult, - &mut ViewContext, - ) -> 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(&mut self, mut callback: F) -> Subscription - where - F: 'static + FnMut(&mut V, WindowBounds, Uuid, &mut ViewContext), - { - 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(&mut self, mut callback: F) -> Subscription - where - F: 'static + FnMut(&mut V, &mut ViewContext), - { - 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)) { - 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), - ) { - 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(&mut self, task_label: &'static str, f: F) -> Task - where - F: FnOnce(WeakViewHandle, AsyncAppContext) -> Fut, - Fut: 'static + Future, - S: 'static, - { - let handle = self.weak_handle(); - self.window_context - .spawn_labeled(task_label, |cx| f(handle, cx)) - } - - pub fn spawn(&mut self, f: F) -> Task - where - F: FnOnce(WeakViewHandle, AsyncAppContext) -> Fut, - Fut: 'static + Future, - S: 'static, - { - let handle = self.weak_handle(); - self.window_context.spawn(|cx| f(handle, cx)) - } - - pub fn mouse_state(&self, region_id: usize) -> MouseState { - self.mouse_state_dynamic(TypeTag::new::(), 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( - &mut self, - element_id: usize, - initial: T, - ) -> ElementStateHandle { - self.element_state_dynamic(TypeTag::new::(), element_id, initial) - } - - pub fn element_state_dynamic( - &mut self, - tag: TypeTag, - element_id: usize, - initial: T, - ) -> ElementStateHandle { - 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( - &mut self, - element_id: usize, - ) -> ElementStateHandle { - self.element_state::(element_id, T::default()) - } - - pub fn default_element_state_dynamic( - &mut self, - tag: TypeTag, - element_id: usize, - ) -> ElementStateHandle { - self.element_state_dynamic::(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> { - 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()); + fn apply_refresh_effect(&mut self) { + for window in self.windows.values_mut() { + if let Some(window) = window.as_mut() { + window.dirty = true; } } - - 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); + 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)); } - pub fn paint_layer(&mut self, clip_bounds: Option, f: F) -> R - where - F: FnOnce(&mut Self) -> R, - { - self.scene().push_layer(clip_bounds); - let result = f(self); - self.scene().pop_layer(); - result + fn apply_defer_effect(&mut self, callback: Box) { + callback(self); } -} -impl 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, - #[cfg(debug_assertions)] - tag_type_name: &'static str, -} - -impl TypeTag { - pub fn new() -> Self { - Self { - tag: TypeId::of::(), - composed: None, - #[cfg(debug_assertions)] - tag_type_name: std::any::type_name::(), + /// 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 dynamic(tag: TypeId, #[cfg(debug_assertions)] type_name: &'static str) -> Self { - Self { - tag, - composed: None, - #[cfg(debug_assertions)] - tag_type_name: type_name, - } + /// Obtains a reference to the executor, which can be used to spawn futures. + pub fn background_executor(&self) -> &BackgroundExecutor { + &self.background_executor } - pub fn compose(mut self, other: TypeTag) -> Self { - self.composed = Some(other.tag); - self + /// Obtains a reference to the executor, which can be used to spawn futures. + pub fn foreground_executor(&self) -> &ForegroundExecutor { + &self.foreground_executor } - #[cfg(debug_assertions)] - pub(crate) fn type_name(&self) -> &'static str { - self.tag_type_name - } -} - -impl BorrowAppContext for ViewContext<'_, '_, V> { - fn read_with T>(&self, f: F) -> T { - BorrowAppContext::read_with(&*self.window_context, f) - } - - fn update T>(&mut self, f: F) -> T { - BorrowAppContext::update(&mut *self.window_context, f) - } -} - -impl BorrowWindowContext for ViewContext<'_, '_, V> { - type Result = T; - - fn read_window T>(&self, window: AnyWindowHandle, f: F) -> T { - BorrowWindowContext::read_window(&*self.window_context, window, f) - } - - fn read_window_optional(&self, window: AnyWindowHandle, f: F) -> Option + /// 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(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where - F: FnOnce(&WindowContext) -> Option, + Fut: Future + 'static, + R: 'static, { - BorrowWindowContext::read_window_optional(&*self.window_context, window, f) + self.foreground_executor.spawn(f(self.to_async())) } - fn update_window T>( - &mut self, - window: AnyWindowHandle, - f: F, - ) -> T { - BorrowWindowContext::update_window(&mut *self.window_context, window, f) - } - - fn update_window_optional(&mut self, window: AnyWindowHandle, f: F) -> Option - where - F: FnOnce(&mut WindowContext) -> Option, - { - 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 DerefMut for EventContext<'_, '_, '_, V> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.view_context - } -} - -impl BorrowAppContext for EventContext<'_, '_, '_, V> { - fn read_with T>(&self, f: F) -> T { - BorrowAppContext::read_with(&*self.view_context, f) - } - - fn update T>(&mut self, f: F) -> T { - BorrowAppContext::update(&mut *self.view_context, f) - } -} - -impl BorrowWindowContext for EventContext<'_, '_, '_, V> { - type Result = T; - - fn read_window T>(&self, window: AnyWindowHandle, f: F) -> T { - BorrowWindowContext::read_window(&*self.view_context, window, f) - } - - fn read_window_optional(&self, window: AnyWindowHandle, f: F) -> Option - where - F: FnOnce(&WindowContext) -> Option, - { - BorrowWindowContext::read_window_optional(&*self.view_context, window, f) - } - - fn update_window T>( - &mut self, - window: AnyWindowHandle, - f: F, - ) -> T { - BorrowWindowContext::update_window(&mut *self.view_context, window, f) - } - - fn update_window_optional(&mut self, window: AnyWindowHandle, f: F) -> Option - where - F: FnOnce(&mut WindowContext) -> Option, - { - 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, - 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 { - 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 { - 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 - 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 { - any_handle: AnyModelHandle, - model_type: PhantomData, -} - -impl Deref for ModelHandle { - type Target = AnyModelHandle; - - fn deref(&self) -> &Self::Target { - &self.any_handle - } -} - -impl ModelHandle { - fn new(model_id: usize, ref_counts: &Arc>) -> Self { - Self { - any_handle: AnyModelHandle::new(model_id, TypeId::of::(), ref_counts.clone()), - model_type: PhantomData, - } - } - - pub fn downgrade(&self) -> WeakModelHandle { - 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(&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(&self, cx: &mut C, update: F) -> S - where - C: BorrowAppContext, - F: FnOnce(&mut T, &mut ModelContext) -> 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 Clone for ModelHandle { - fn clone(&self) -> Self { - Self::new(self.model_id, &self.ref_counts) - } -} - -impl PartialEq for ModelHandle { - fn eq(&self, other: &Self) -> bool { - self.model_id == other.model_id - } -} - -impl Eq for ModelHandle {} - -impl PartialEq> for ModelHandle { - fn eq(&self, other: &WeakModelHandle) -> bool { - self.model_id == other.model_id - } -} - -impl Hash for ModelHandle { - fn hash(&self, state: &mut H) { - self.model_id.hash(state); - } -} - -impl std::borrow::Borrow for ModelHandle { - fn borrow(&self) -> &usize { - &self.model_id - } -} - -impl Debug for ModelHandle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple(&format!("ModelHandle<{}>", type_name::())) - .field(&self.model_id) - .finish() - } -} - -unsafe impl Send for ModelHandle {} -unsafe impl Sync for ModelHandle {} - -impl Handle for ModelHandle { - type Weak = WeakModelHandle; - - 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 - where - Self: Sized, - { - weak.upgrade(cx) - } -} - -pub struct WeakModelHandle { - any_handle: AnyWeakModelHandle, - model_type: PhantomData, -} - -impl WeakModelHandle { - pub fn into_any(self) -> AnyWeakModelHandle { - self.any_handle - } -} - -impl Deref for WeakModelHandle { - type Target = AnyWeakModelHandle; - - fn deref(&self) -> &Self::Target { - &self.any_handle - } -} - -impl WeakHandle for WeakModelHandle { - fn id(&self) -> usize { - self.model_id - } -} - -unsafe impl Send for WeakModelHandle {} -unsafe impl Sync for WeakModelHandle {} - -impl WeakModelHandle { - fn new(model_id: usize) -> Self { - Self { - any_handle: AnyWeakModelHandle { - model_id, - model_type: TypeId::of::(), - }, - 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> { - cx.read_with(|cx| cx.upgrade_model_handle(self)) - } -} - -impl Hash for WeakModelHandle { - fn hash(&self, state: &mut H) { - self.model_id.hash(state) - } -} - -impl PartialEq for WeakModelHandle { - fn eq(&self, other: &Self) -> bool { - self.model_id == other.model_id - } -} - -impl Eq for WeakModelHandle {} - -impl PartialEq> for WeakModelHandle { - fn eq(&self, other: &ModelHandle) -> bool { - self.model_id == other.model_id - } -} - -impl Clone for WeakModelHandle { - fn clone(&self) -> Self { - Self { - any_handle: self.any_handle.clone(), - model_type: PhantomData, - } - } -} - -impl Copy for WeakModelHandle {} - -#[derive(Deref)] -pub struct WindowHandle { - #[deref] - any_handle: AnyWindowHandle, - root_view_type: PhantomData, -} - -impl Clone for WindowHandle { - fn clone(&self) -> Self { - Self { - any_handle: self.any_handle.clone(), - root_view_type: PhantomData, - } - } -} - -impl Copy for WindowHandle {} - -impl WindowHandle { - fn new(window_id: usize) -> Self { - WindowHandle { - any_handle: AnyWindowHandle::new(window_id, TypeId::of::()), - root_view_type: PhantomData, - } - } - - pub fn root(&self, cx: &C) -> C::Result> { - self.read_with(cx, |cx| cx.root_view().clone().downcast().unwrap()) - } - - pub fn read_root_with(&self, cx: &C, read: F) -> C::Result - where - C: BorrowWindowContext, - F: FnOnce(&V, &ViewContext) -> R, - { - self.read_with(cx, |cx| { - cx.root_view() - .downcast_ref::() - .unwrap() - .read_with(cx, read) - }) - } - - pub fn update_root(&self, cx: &mut C, update: F) -> C::Result - where - C: BorrowWindowContext, - F: FnOnce(&mut V, &mut ViewContext) -> R, - { - cx.update_window(self.any_handle, |cx| { - cx.root_view() - .clone() - .downcast::() - .unwrap() - .update(cx, update) - }) - } -} - -impl WindowHandle { - pub fn replace_root(&self, cx: &mut C, build_root: F) -> C::Result> - where - C: BorrowWindowContext, - F: FnOnce(&mut ViewContext) -> 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 Into for WindowHandle { - 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(&self, cx: &C, read: F) -> C::Result - where - C: BorrowWindowContext, - F: FnOnce(&WindowContext) -> R, - { - cx.read_window(*self, |cx| read(cx)) - } - - pub fn read_optional_with(&self, cx: &C, read: F) -> Option - where - C: BorrowWindowContext, - F: FnOnce(&WindowContext) -> Option, - { - cx.read_window_optional(*self, |cx| read(cx)) - } - - pub fn update(&self, cx: &mut C, update: F) -> C::Result - where - C: BorrowWindowContext, - F: FnOnce(&mut WindowContext) -> R, - { - cx.update_window(*self, update) - } - - pub fn update_optional(&self, cx: &mut C, update: F) -> Option - where - C: BorrowWindowContext, - F: FnOnce(&mut WindowContext) -> Option, - { - cx.update_window_optional(*self, update) - } - - pub fn add_view(&self, cx: &mut C, build_view: F) -> C::Result> - where - C: BorrowWindowContext, - U: View, - F: FnOnce(&mut ViewContext) -> U, - { - self.update(cx, |cx| cx.add_view(build_view)) - } - - pub fn downcast(self) -> Option> { - if self.root_view_type == TypeId::of::() { - Some(WindowHandle { - any_handle: self, - root_view_type: PhantomData, - }) - } else { - None - } - } - - pub fn root_is(&self) -> bool { - self.root_view_type == TypeId::of::() - } - - pub fn is_active(&self, cx: &C) -> C::Result { - self.read_with(cx, |cx| cx.window.is_active) - } - - pub fn remove(&self, cx: &mut C) -> C::Result<()> { - self.update(cx, |cx| cx.remove_window()) - } - - pub fn debug_elements(&self, cx: &C) -> Option { - 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(&mut self, cx: &mut C) -> C::Result<()> { - self.update(cx, |cx| cx.activate_window()) - } - - pub fn prompt( - &self, - level: PromptLevel, - msg: &str, - answers: &[&str], - cx: &mut C, - ) -> C::Result> { - self.update(cx, |cx| cx.prompt(level, msg, answers)) - } - - pub fn dispatch_action( - &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( - &self, - view_id: usize, - cx: &C, - ) -> C::Result, 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::>(); - - for window in other_windows { - cx.window_changed_active_status(window, false) - } - - cx.window_changed_active_status(*self, true) + /// 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 { + &self.asset_source + } + + /// Accessor for the text system. + pub fn text_system(&self) -> &Arc { + &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(&self) -> bool { + self.globals_by_type.contains_key(&TypeId::of::()) + } + + /// Access the global of the given type. Panics if a global for that type has not been assigned. + #[track_caller] + pub fn global(&self) -> &G { + self.globals_by_type + .get(&TypeId::of::()) + .map(|any_state| any_state.downcast_ref::().unwrap()) + .ok_or_else(|| anyhow!("no state of type {} exists", type_name::())) + .unwrap() + } + + /// Access the global of the given type if a value has been assigned. + pub fn try_global(&self) -> Option<&G> { + self.globals_by_type + .get(&TypeId::of::()) + .map(|any_state| any_state.downcast_ref::().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(&mut self) -> &mut G { + let global_type = TypeId::of::(); + self.push_effect(Effect::NotifyGlobalObservers { global_type }); + self.globals_by_type + .get_mut(&global_type) + .and_then(|any_state| any_state.downcast_mut::()) + .ok_or_else(|| anyhow!("no state of type {} exists", type_name::())) + .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(&mut self) -> &mut G { + let global_type = TypeId::of::(); + self.push_effect(Effect::NotifyGlobalObservers { global_type }); + self.globals_by_type + .entry(global_type) + .or_insert_with(|| Box::::default()) + .downcast_mut::() + .unwrap() + } + + /// Set the value of the global of the given type. + pub fn set_global(&mut self, global: G) { + let global_type = TypeId::of::(); + 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 simulate_deactivation(&self, cx: &mut TestAppContext) { - self.update(cx, |cx| { - cx.window_changed_active_status(*self, false); - }) - } -} - -#[repr(transparent)] -pub struct ViewHandle { - any_handle: AnyViewHandle, - view_type: PhantomData, -} - -impl Deref for ViewHandle { - type Target = AnyViewHandle; - - fn deref(&self) -> &Self::Target { - &self.any_handle - } -} - -impl ViewHandle { - fn new(window: AnyWindowHandle, view_id: usize, ref_counts: &Arc>) -> Self { - Self { - any_handle: AnyViewHandle::new(window, view_id, TypeId::of::(), ref_counts.clone()), - view_type: PhantomData, - } + pub fn clear_globals(&mut self) { + self.globals_by_type.drain(); } - pub fn downgrade(&self) -> WeakViewHandle { - WeakViewHandle::new(self.window, self.view_id) + /// Remove the global of the given type from the app context. Does not notify global observers. + pub fn remove_global(&mut self) -> G { + let global_type = TypeId::of::(); + *self + .globals_by_type + .remove(&global_type) + .unwrap_or_else(|| panic!("no global added for {}", std::any::type_name::())) + .downcast() + .unwrap() } - pub fn into_any(self) -> AnyViewHandle { - self.any_handle + /// 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(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R { + let mut global = self.lease_global::(); + let result = f(&mut global, self); + self.end_global_lease(global); + result } - pub fn window(&self) -> AnyWindowHandle { - self.window + /// Register a callback to be invoked when a global of the given type is updated. + pub fn observe_global( + &mut self, + mut f: impl FnMut(&mut Self) + 'static, + ) -> Subscription { + let (subscription, activate) = self.global_observers.insert( + TypeId::of::(), + Box::new(move |cx| { + f(cx); + true + }), + ); + self.defer(move |_| activate()); + subscription } - 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(&self, cx: &C, read: F) -> C::Result - where - C: BorrowWindowContext, - F: FnOnce(&V, &ViewContext) -> S, - { - cx.read_window(self.window, |cx| { - let cx = ViewContext::immutable(cx, self.view_id); - read(cx.read_view(self), &cx) - }) - } - - pub fn update(&self, cx: &mut C, update: F) -> C::Result - where - C: BorrowWindowContext, - F: FnOnce(&mut V, &mut ViewContext) -> 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 Clone for ViewHandle { - fn clone(&self) -> Self { - ViewHandle::new(self.window, self.view_id, &self.ref_counts) - } -} - -impl PartialEq for ViewHandle { - fn eq(&self, other: &Self) -> bool { - self.window == other.window && self.view_id == other.view_id - } -} - -impl PartialEq for ViewHandle { - fn eq(&self, other: &AnyViewHandle) -> bool { - self.window == other.window && self.view_id == other.view_id - } -} - -impl PartialEq> for ViewHandle { - fn eq(&self, other: &WeakViewHandle) -> bool { - self.window == other.window && self.view_id == other.view_id - } -} - -impl PartialEq> for WeakViewHandle { - fn eq(&self, other: &ViewHandle) -> bool { - self.window == other.window && self.view_id == other.view_id - } -} - -impl Eq for ViewHandle {} - -impl Hash for ViewHandle { - fn hash(&self, state: &mut H) { - self.window.hash(state); - self.view_id.hash(state); - } -} - -impl Debug for ViewHandle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct(&format!("ViewHandle<{}>", type_name::())) - .field("window_id", &self.window) - .field("view_id", &self.view_id) - .finish() - } -} - -impl Handle for ViewHandle { - type Weak = WeakViewHandle; - - 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 - where - Self: Sized, - { - weak.upgrade(cx) - } -} - -pub struct AnyViewHandle { - window: AnyWindowHandle, - view_id: usize, - view_type: TypeId, - ref_counts: Arc>, - - #[cfg(any(test, feature = "test-support"))] - handle_id: usize, -} - -impl AnyViewHandle { - fn new( - window: AnyWindowHandle, - view_id: usize, - view_type: TypeId, - ref_counts: Arc>, - ) -> 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(&self) -> bool { - TypeId::of::() == self.view_type - } - - pub fn downcast(self) -> Option> { - if self.is::() { - Some(ViewHandle { - any_handle: self, - view_type: PhantomData, - }) - } else { - None - } - } - - pub fn downcast_ref(&self) -> Option<&ViewHandle> { - if self.is::() { - 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(), + /// Move the global of the given type to the stack. + pub(crate) fn lease_global(&mut self) -> GlobalLease { + GlobalLease::new( + self.globals_by_type + .remove(&TypeId::of::()) + .ok_or_else(|| anyhow!("no global registered of type {}", type_name::())) + .unwrap(), ) } -} -impl PartialEq for AnyViewHandle { - fn eq(&self, other: &Self) -> bool { - self.window == other.window && self.view_id == other.view_id - } -} - -impl PartialEq> for AnyViewHandle { - fn eq(&self, other: &ViewHandle) -> 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>, - - #[cfg(any(test, feature = "test-support"))] - handle_id: usize, -} - -impl AnyModelHandle { - fn new(model_id: usize, model_type: TypeId, ref_counts: Arc>) -> 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, - } + /// Restore the global of the given type after it is moved to the stack. + pub(crate) fn end_global_lease(&mut self, lease: GlobalLease) { + let global_type = TypeId::of::(); + self.push_effect(Effect::NotifyGlobalObservers { global_type }); + self.globals_by_type.insert(global_type, lease.global); } - pub fn downcast(self) -> Option> { - if self.is::() { - Some(ModelHandle { - any_handle: self, - model_type: PhantomData, - }) - } else { - None - } + pub fn observe_new_views( + &mut self, + on_new: impl 'static + Fn(&mut V, &mut ViewContext), + ) -> Subscription { + let (subscription, activate) = self.new_view_observers.insert( + TypeId::of::(), + Box::new(move |any_view: AnyView, cx: &mut WindowContext| { + any_view + .downcast::() + .unwrap() + .update(cx, |view_state, cx| { + on_new(view_state, cx); + }) + }), + ); + activate(); + subscription } - pub fn downgrade(&self) -> AnyWeakModelHandle { - AnyWeakModelHandle { - model_id: self.model_id, - model_type: self.model_type, - } - } - - pub fn is(&self) -> bool { - self.model_type == TypeId::of::() - } - - 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 { - cx.read_with(|cx| cx.upgrade_any_model_handle(self)) - } - - pub fn model_type(&self) -> TypeId { - self.model_type - } - - fn is(&self) -> bool { - TypeId::of::() == self.model_type - } - - pub fn downcast(self) -> Option> { - if self.is::() { - let result = Some(WeakModelHandle { - any_handle: self, - model_type: PhantomData, - }); - - result - } else { - None - } - } -} - -pub struct WeakViewHandle { - any_handle: AnyWeakViewHandle, - view_type: PhantomData, -} - -impl Copy for WeakViewHandle {} - -impl Debug for WeakViewHandle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct(&format!("WeakViewHandle<{}>", type_name::())) - .field("any_handle", &self.any_handle) - .finish() - } -} - -impl WeakHandle for WeakViewHandle { - fn id(&self) -> usize { - self.view_id - } -} - -impl WeakViewHandle { - fn new(window: AnyWindowHandle, view_id: usize) -> Self { - Self { - any_handle: AnyWeakViewHandle { - window, - view_id, - view_type: TypeId::of::(), - }, - 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> { - cx.read_with(|cx| cx.upgrade_view_handle(self)) - } - - pub fn read_with( - &self, - cx: &AsyncAppContext, - read: impl FnOnce(&V, &ViewContext) -> T, - ) -> Result { - 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( - &self, - cx: &mut B, - update: impl FnOnce(&mut V, &mut ViewContext) -> T, - ) -> Result + pub fn observe_release( + &mut self, + handle: &E, + on_release: impl FnOnce(&mut T, &mut AppContext) + 'static, + ) -> Subscription where - B: BorrowWindowContext, - B::Result>: Flatten, + E: Entity, + T: 'static, { - cx.update_window(self.window(), |cx| { - cx.upgrade_view_handle(self) - .map(|handle| handle.update(cx, update)) + 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) { + 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(&mut self, listener: impl Fn(&A, &mut Self) + 'static) { + self.global_action_listeners + .entry(TypeId::of::()) + .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, + ) -> Result> { + self.actions.build_action(name, data) + } + + pub fn all_action_names(&self) -> &[SharedString] { + self.actions.all_action_names() + } + + pub fn on_app_quit( + &mut self, + mut on_quit: impl FnMut(&mut AppContext) -> Fut + 'static, + ) -> Subscription + where + Fut: 'static + Future, + { + 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) { + 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(&self) -> Option<&T> { + self.active_drag + .as_ref() + .and_then(|drag| drag.value.downcast_ref()) + } +} + +impl Context for AppContext { + type Result = 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( + &mut self, + build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T, + ) -> Model { + self.update(|cx| { + let slot = cx.entities.reserve(); + let entity = build_model(&mut ModelContext::new(cx, slot.downgrade())); + cx.entities.insert(slot, entity) }) - .flatten() - .ok_or_else(|| anyhow!("window was removed")) + } + + /// 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( + &mut self, + model: &Model, + 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(&mut self, handle: AnyWindowHandle, update: F) -> Result + 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( + &self, + handle: &Model, + read: impl FnOnce(&T, &AppContext) -> R, + ) -> Self::Result + where + T: 'static, + { + let entity = self.entities.read(handle); + read(entity, self) + } + + fn read_window( + &self, + window: &WindowHandle, + read: impl FnOnce(View, &AppContext) -> R, + ) -> Result + 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::() + .map_err(|_| anyhow!("root view's type has changed"))?; + + Ok(read(view, self)) } } -pub trait Flatten { - fn flatten(self) -> Option; +/// 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, + }, + Refresh, + NotifyGlobalObservers { + global_type: TypeId, + }, + Defer { + callback: Box, + }, } -impl Flatten for Option> { - fn flatten(self) -> Option { - self.flatten() +/// Wraps a global variable value during `update_global` while the value has been moved to the stack. +pub(crate) struct GlobalLease { + global: Box, + global_type: PhantomData, +} + +impl GlobalLease { + fn new(global: Box) -> Self { + GlobalLease { + global, + global_type: PhantomData, + } } } -impl Flatten for Option { - fn flatten(self) -> Option { - self - } -} - -impl Deref for WeakViewHandle { - type Target = AnyWeakViewHandle; +impl Deref for GlobalLease { + type Target = G; fn deref(&self) -> &Self::Target { - &self.any_handle + self.global.downcast_ref().unwrap() } } -impl Clone for WeakViewHandle { - fn clone(&self) -> Self { - Self { - any_handle: self.any_handle.clone(), - view_type: PhantomData, - } +impl DerefMut for GlobalLease { + fn deref_mut(&mut self) -> &mut Self::Target { + self.global.downcast_mut().unwrap() } } -impl PartialEq for WeakViewHandle { - fn eq(&self, other: &Self) -> bool { - self.window == other.window && self.view_id == other.view_id - } +/// 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, + pub cursor_offset: Point, } -impl Eq for WeakViewHandle {} - -impl Hash for WeakViewHandle { - fn hash(&self, state: &mut H) { - self.any_handle.hash(state); - } +#[derive(Clone)] +pub(crate) struct AnyTooltip { + pub view: AnyView, + pub cursor_offset: Point, } -#[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(&self) -> bool { - TypeId::of::() == self.view_type - } - - pub fn upgrade(&self, cx: &impl BorrowAppContext) -> Option { - cx.read_with(|cx| cx.upgrade_any_view_handle(self)) - } - - pub fn downcast(self) -> Option> { - if self.is::() { - Some(WeakViewHandle { - any_handle: self, - view_type: PhantomData, - }) - } else { - None - } - } -} - -impl Hash for AnyWeakViewHandle { - fn hash(&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 { - value_type: PhantomData, - id: ElementStateId, - ref_counts: Weak>, -} - -impl ElementStateHandle { - fn new(id: ElementStateId, frame_id: usize, ref_counts: &Arc>) -> 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(&self, cx: &mut C, f: impl FnOnce(&mut T, &mut C) -> R) -> R - where - C: DerefMut, - D: DerefMut, - { - 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 Drop for ElementStateHandle { - 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), - Observation(callback_collection::Subscription), - GlobalSubscription(callback_collection::Subscription), - GlobalObservation(callback_collection::Subscription), - FocusObservation(callback_collection::Subscription), - WindowActivationObservation( - callback_collection::Subscription, - ), - WindowFullscreenObservation( - callback_collection::Subscription, - ), - WindowBoundsObservation( - callback_collection::Subscription, - ), - KeystrokeObservation(callback_collection::Subscription), - ReleaseObservation(callback_collection::Subscription), - 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>, - events: Vec, - } - - impl Entity for Model { - type Event = usize; - } - - impl Model { - fn new(other: Option>, cx: &mut ModelContext) -> 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, - } - - 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, - } - - 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) -> AnyElement { - 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>, - events: Vec, - } - - impl Entity for View { - type Event = usize; - } - - impl super::View for View { - fn render(&mut self, _: &mut ViewContext) -> AnyElement { - Empty::new().into_any() - } - - fn ui_name() -> &'static str { - "View" - } - } - - impl View { - fn new(other: Option>, cx: &mut ViewContext) -> 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, - } - - impl Entity for View { - type Event = (); - } - - impl super::View for View { - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - enum Handler {} - let mouse_down_count = self.mouse_down_count.clone(); - MouseEventHandler::new::(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>, - } - - struct View { - released: Rc>, - } - - 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) -> AnyElement { - 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::({ - let observation_count = observation_count.clone(); - move |_| { - *observation_count.borrow_mut() += 1; - } - }); - - assert!(!cx.has_global::()); - assert_eq!(cx.default_global::(), &0); - assert_eq!(*observation_count.borrow(), 1); - assert!(cx.has_global::()); - assert_eq!( - cx.update_global::(|global, _| { - *global = 1; - "Update Result" - }), - "Update Result" - ); - assert_eq!(*observation_count.borrow(), 2); - assert_eq!(cx.global::(), &1); - - drop(subscription); - cx.update_global::(|global, _| { - *global = 2; - }); - assert_eq!(*observation_count.borrow(), 2); - - type OtherGlobal = f32; - - let observation_count = Rc::new(RefCell::new(0)); - cx.observe_global::({ - let observation_count = observation_count.clone(); - move |_| { - *observation_count.borrow_mut() += 1; - } - }) - .detach(); - - assert_eq!( - cx.update_default_global::(|global, _| { - assert_eq!(global, &0.0); - *global = 2.0; - "Default update result" - }), - "Default update result" - ); - assert_eq!(cx.global::(), &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::(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::(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) -> AnyElement { - 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>>, - child: Option, - } - - impl Entity for View { - type Event = (); - } - - impl super::View for View { - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - 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) { - if cx.handle().id() == focused.id() { - self.events.lock().push(format!("{} focused", &self.name)); - } - } - - fn focus_out(&mut self, blurred: AnyViewHandle, cx: &mut ViewContext) { - if cx.handle().id() == blurred.id() { - self.events.lock().push(format!("{} blurred", &self.name)); - } - } - } - - let view_events: Arc>> = 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>> = 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::().unwrap(), - &ComplexAction { - arg: "a".to_string(), - count: 5, - } - ); - assert_eq!( - action2.as_any().downcast_ref::().unwrap(), - &SimpleAction - ); - } - - #[crate::test(self)] - fn test_dispatch_action(cx: &mut TestAppContext) { - struct ViewA { - id: usize, - child: Option, - } - - impl Entity for ViewA { - type Event = (); - } - - impl View for ViewA { - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - 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, - } - - impl Entity for ViewB { - type Event = (); - } - - impl View for ViewB { - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - 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, - } - - impl Entity for View { - type Event = (); - } - - impl super::View for View { - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - 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, - } - 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) -> AnyElement { - ChildView::new(&self.child, cx).into_any() - } - fn ui_name() -> &'static str { - "View1" - } - } - impl super::View for View2 { - fn render(&mut self, _: &mut ViewContext) -> AnyElement { - 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)> { - cx.available_actions(window.into(), view_id) - .into_iter() - .map(|(action_name, _, bindings)| { - ( - action_name, - bindings - .iter() - .map(|binding| binding.keystrokes()[0].clone()) - .collect::>(), - ) - }) - .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) -> AnyElement { - 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::(), - Some(&ActionWithArg { arg: false }) - ); - assert_eq!( - actions[0] - .2 - .iter() - .map(|b| b.keystrokes()[0].clone()) - .collect::>(), - 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.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) -> AnyElement { - Empty::new().into_any() - } - } - - impl Counter { - fn inc(&mut self, cx: &mut ViewContext) { - 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) -> AnyElement { - 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) -> AnyElement { - Empty::new().into_any() - } - } - - let events = Rc::new(RefCell::new(Vec::new())); - let window_1 = cx.add_window(|cx: &mut ViewContext| { - 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| { - 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| { - 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>, - dropped: Rc>, - } - - 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) -> AnyElement { - self.rendered.set(true); - Empty::new().into_any() - } - } - - impl Drop for Child { - fn drop(&mut self) { - self.dropped.set(true); - } - } - - struct Parent { - child: Option>, - } - - 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) -> AnyElement { - 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, - } - - impl Entity for TestView { - type Event = String; - } - - impl View for TestView { - fn ui_name() -> &'static str { - "TestView" - } - - fn render(&mut self, _: &mut ViewContext) -> AnyElement { - Empty::new().into_any() - } - } +#[derive(Debug)] +pub struct KeystrokeEvent { + pub keystroke: Keystroke, + pub action: Option>, } diff --git a/crates/gpui/src/app/action.rs b/crates/gpui/src/app/action.rs deleted file mode 100644 index 23eb4da730..0000000000 --- a/crates/gpui/src/app/action.rs +++ /dev/null @@ -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; - 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> - 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> { - 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> { - Ok(Box::new($crate::serde_json::from_value::(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 { - Box::new(self.clone()) - } - - fn eq(&self, other: &dyn $crate::Action) -> bool { - if let Some(other) = other.as_any().downcast_ref::() { - self == other - } else { - false - } - } - - fn type_tag(&self) -> $crate::TypeTag { - $crate::TypeTag::new::() - } - - $from_json_fn - } - }; -} diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui/src/app/async_context.rs similarity index 100% rename from crates/gpui2/src/app/async_context.rs rename to crates/gpui/src/app/async_context.rs diff --git a/crates/gpui/src/app/callback_collection.rs b/crates/gpui/src/app/callback_collection.rs deleted file mode 100644 index 498c413d25..0000000000 --- a/crates/gpui/src/app/callback_collection.rs +++ /dev/null @@ -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 { - internal: Arc>>, -} - -pub struct Subscription { - key: K, - id: usize, - mapping: Option>>>, -} - -struct Mapping { - callbacks: HashMap>, - dropped_subscriptions: HashMap>, -} - -impl Mapping { - 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 Default for Mapping { - fn default() -> Self { - Self { - callbacks: Default::default(), - dropped_subscriptions: Default::default(), - } - } -} - -impl Clone for CallbackCollection { - fn clone(&self) -> Self { - Self { - internal: self.internal.clone(), - } - } -} - -impl Default for CallbackCollection { - fn default() -> Self { - CallbackCollection { - internal: Arc::new(Mutex::new(Default::default())), - } - } -} - -impl CallbackCollection { - #[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 { - 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(&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 Subscription { - pub fn id(&self) -> usize { - self.id - } - - pub fn detach(&mut self) { - self.mapping.take(); - } -} - -impl Drop for Subscription { - 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); - } - } -} diff --git a/crates/gpui2/src/app/entity_map.rs b/crates/gpui/src/app/entity_map.rs similarity index 100% rename from crates/gpui2/src/app/entity_map.rs rename to crates/gpui/src/app/entity_map.rs diff --git a/crates/gpui/src/app/menu.rs b/crates/gpui/src/app/menu.rs deleted file mode 100644 index 67531a8297..0000000000 --- a/crates/gpui/src/app/menu.rs +++ /dev/null @@ -1,99 +0,0 @@ -use crate::{platform::ForegroundPlatform, Action, App, AppContext}; - -pub struct Menu<'a> { - pub name: &'a str, - pub items: Vec>, -} - -pub enum MenuItem<'a> { - Separator, - Submenu(Menu<'a>), - Action { - name: &'a str, - action: Box, - os_action: Option, - }, -} - -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) { - 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); - } - })); -} diff --git a/crates/gpui2/src/app/model_context.rs b/crates/gpui/src/app/model_context.rs similarity index 100% rename from crates/gpui2/src/app/model_context.rs rename to crates/gpui/src/app/model_context.rs diff --git a/crates/gpui/src/app/ref_counts.rs b/crates/gpui/src/app/ref_counts.rs deleted file mode 100644 index 63905326fe..0000000000 --- a/crates/gpui/src/app/ref_counts.rs +++ /dev/null @@ -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, - element_state_counts: HashMap, - dropped_models: HashSet, - dropped_views: HashSet<(AnyWindowHandle, usize)>, - dropped_element_states: HashSet, - - #[cfg(any(test, feature = "test-support"))] - pub leak_detector: Arc>, -} - -impl RefCounts { - #[cfg(any(test, feature = "test-support"))] - pub fn new(leak_detector: Arc>) -> 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, - HashSet<(AnyWindowHandle, usize)>, - HashSet, - ) { - ( - 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>, - ), - >, -} - -#[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); - } -} diff --git a/crates/gpui/src/app/test_app_context.rs b/crates/gpui/src/app/test_app_context.rs deleted file mode 100644 index 0dc1d1eba4..0000000000 --- a/crates/gpui/src/app/test_app_context.rs +++ /dev/null @@ -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>, - foreground_platform: Rc, - condition_duration: Option, - pub function_name: String, - assertion_context: AssertionContextManager, -} - -impl TestAppContext { - pub fn new( - foreground_platform: Rc, - platform: Arc, - foreground: Rc, - background: Arc, - font_cache: Arc, - leak_detector: Arc>, - 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(&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, SmallVec<[Binding; 1]>)> { - self.read_window(window, |cx| cx.available_actions(view_id)) - .unwrap_or_default() - } - - pub fn dispatch_global_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>( - &self, - window: AnyWindowHandle, - callback: F, - ) -> Option { - self.cx.borrow().read_window(window, callback) - } - - pub fn update_window T>( - &mut self, - window: AnyWindowHandle, - callback: F, - ) -> Option { - self.cx.borrow_mut().update_window(window, callback) - } - - pub fn add_model(&mut self, build_model: F) -> ModelHandle - where - T: Entity, - F: FnOnce(&mut ModelContext) -> T, - { - self.cx.borrow_mut().add_model(build_model) - } - - pub fn add_window(&mut self, build_root_view: F) -> WindowHandle - where - V: View, - F: FnOnce(&mut ViewContext) -> V, - { - let window = self - .cx - .borrow_mut() - .add_window(Default::default(), build_root_view); - window.simulate_activation(self); - window - } - - pub fn observe_global(&mut self, callback: F) -> Subscription - where - E: Any, - F: 'static + FnMut(&mut AppContext), - { - self.cx.borrow_mut().observe_global::(callback) - } - - pub fn set_global(&mut self, state: T) { - self.cx.borrow_mut().set_global(state); - } - - pub fn subscribe_global(&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 { - self.cx.borrow().windows().collect() - } - - pub fn remove_all_windows(&mut self) { - self.update(|cx| cx.windows.clear()); - } - - pub fn read T>(&self, callback: F) -> T { - callback(&*self.cx.borrow()) - } - - pub fn update 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 { - self.cx.borrow().font_cache.clone() - } - - pub fn foreground_platform(&self) -> Rc { - self.foreground_platform.clone() - } - - pub fn platform(&self) -> Arc { - self.cx.borrow().platform.clone() - } - - pub fn foreground(&self) -> Rc { - self.cx.borrow().foreground().clone() - } - - pub fn background(&self) -> Arc { - self.cx.borrow().background().clone() - } - - pub fn spawn(&self, f: F) -> Task - where - F: FnOnce(AsyncAppContext) -> Fut, - Fut: 'static + Future, - 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) { - 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> { - 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>(&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) { - 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>(&self, f: F) -> T { - self.cx.borrow().read_with(f) - } - - fn update T>(&mut self, f: F) -> T { - self.cx.borrow_mut().update(f) - } -} - -impl BorrowWindowContext for TestAppContext { - type Result = T; - - fn read_window T>(&self, window: AnyWindowHandle, f: F) -> T { - self.cx - .borrow() - .read_window(window, f) - .expect("window was closed") - } - - fn read_window_optional(&self, window: AnyWindowHandle, f: F) -> Option - where - F: FnOnce(&WindowContext) -> Option, - { - BorrowWindowContext::read_window(self, window, f) - } - - fn update_window T>( - &mut self, - window: AnyWindowHandle, - f: F, - ) -> T { - self.cx - .borrow_mut() - .update_window(window, f) - .expect("window was closed") - } - - fn update_window_optional(&mut self, window: AnyWindowHandle, f: F) -> Option - where - F: FnOnce(&mut WindowContext) -> Option, - { - BorrowWindowContext::update_window(self, window, f) - } -} - -impl ModelHandle { - pub fn next_notification(&self, cx: &TestAppContext) -> impl Future { - 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 - 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 { - 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 { - 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::() - .unwrap(); - test_window - }) - } -} - -impl ViewHandle { - pub fn next_notification(&self, cx: &TestAppContext) -> impl Future { - 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 { - 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, - contexts: Arc>>, -} - -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); - } -} diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs similarity index 100% rename from crates/gpui2/src/app/test_context.rs rename to crates/gpui/src/app/test_context.rs diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs deleted file mode 100644 index 7ba1e85100..0000000000 --- a/crates/gpui/src/app/window.rs +++ /dev/null @@ -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, - pub(crate) root_view: Option, - pub(crate) focused_view_id: Option, - pub(crate) parents: HashMap, - pub(crate) is_active: bool, - pub(crate) is_fullscreen: bool, - inspector_enabled: bool, - pub(crate) invalidation: Option, - pub(crate) platform_window: Box, - pub(crate) rendered_views: HashMap>, - scene: SceneBuilder, - pub(crate) text_style_stack: Vec, - pub(crate) theme_stack: Vec>, - pub(crate) new_parents: HashMap, - pub(crate) views_to_notify_if_ancestors_change: HashMap>, - titlebar_height: f32, - appearance: Appearance, - cursor_regions: Vec, - mouse_regions: Vec<(MouseRegion, usize)>, - event_handlers: Vec, - last_mouse_moved_event: Option, - last_mouse_position: Vector2F, - pressed_buttons: HashSet, - pub(crate) hovered_region_ids: Vec, - pub(crate) clicked_region_ids: Vec, - pub(crate) clicked_region: Option<(MouseRegionId, MouseButton)>, - text_layout_cache: Arc, - refreshing: bool, -} - -impl Window { - pub fn new( - handle: AnyWindowHandle, - platform_window: Box, - cx: &mut AppContext, - build_view: F, - ) -> Self - where - V: View, - F: FnOnce(&mut ViewContext) -> 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 { - 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>(&self, f: F) -> T { - self.app_context.read_with(f) - } - - fn update T>(&mut self, f: F) -> T { - self.app_context.update(f) - } -} - -impl BorrowWindowContext for WindowContext<'_> { - type Result = T; - - fn read_window 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(&self, window: AnyWindowHandle, f: F) -> Option - where - F: FnOnce(&WindowContext) -> Option, - { - BorrowWindowContext::read_window(self, window, f) - } - - fn update_window 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(&mut self, handle: AnyWindowHandle, f: F) -> Option - where - F: FnOnce(&mut WindowContext) -> Option, - { - 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 { - 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 { - &self.window.text_layout_cache - } - - pub(crate) fn update_any_view(&mut self, view_id: usize, f: F) -> Option - 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( - &mut self, - handle: &ViewHandle, - update: &mut dyn FnMut(&mut V, &mut ViewContext) -> 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(&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(&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(&mut self, handle: &H, mut callback: F) -> Subscription - where - E: Entity, - E::Event: 'static, - H: Handle, - 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(&mut self, handle: &H, mut callback: F) -> Subscription - where - E: Entity, - E::Event: 'static, - H: Handle, - 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(&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(&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(&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(&mut self, callback: F) -> Subscription - where - F: 'static - + FnMut(&Keystroke, &MatchResult, Option<&Box>, &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, SmallVec<[Binding; 1]>)> { - let handle = self.window_handle; - let mut contexts = Vec::new(); - let mut handler_depths_by_action_id = HashMap::::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 = 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::>() { - 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::>() { - 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::>() { - 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> { - 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> { - 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 { - 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 { - 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) -> Option { - 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, 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 + '_ { - 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::>(); - - // 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 { - self.window.focused_view_id - } - - pub fn focus(&mut self, view_id: Option) { - 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 { - 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 { - self.window.platform_window.prompt(level, msg, answers) - } - - pub fn add_view(&mut self, build_view: F) -> ViewHandle - where - T: View, - F: FnOnce(&mut ViewContext) -> T, - { - self.add_option_view(|cx| Some(build_view(cx))).unwrap() - } - - pub fn add_option_view(&mut self, build_view: F) -> Option> - where - T: View, - F: FnOnce(&mut ViewContext) -> Option, - { - 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::(), - 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(&self) -> Arc { - self.window - .theme_stack - .iter() - .rev() - .find_map(|theme| { - let entry = Arc::clone(theme); - entry.downcast::().ok() - }) - .ok_or_else(|| anyhow!("no theme provided of type {}", type_name::())) - .unwrap() - } - - pub fn push_theme(&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(&mut self, style: LayoutStyle, children: C) -> Result - where - C: IntoIterator, - { - let children = children.into_iter().collect::>(); - if children.is_empty() { - Ok(self.0.new_leaf(style)?) - } else { - Ok(self.0.new_with_children(style, &children)?) - } - } - - pub fn add_measured_node(&mut self, style: LayoutStyle, measure: F) -> Result - where - F: Fn(MeasureParams) -> Size + 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 { - Ok(Layout::from(self.0.layout(node)?)) - } -} - -pub struct MeasureFn(F); - -impl Measurable for MeasureFn -where - F: Fn(MeasureParams) -> Size, -{ - fn measure( - &self, - known_dimensions: taffy::prelude::Size>, - available_space: taffy::prelude::Size, - ) -> taffy::prelude::Size { - (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>, - pub available_space: Size, -} - -#[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 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 { - 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 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 Element for ChildView { - type LayoutState = (); - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - _: &mut V, - cx: &mut ViewContext, - ) -> (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, - ) { - 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, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &V, - cx: &ViewContext, - ) -> Option { - 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, - ) -> 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) - } - }) - } -} diff --git a/crates/gpui/src/app/window_input_handler.rs b/crates/gpui/src/app/window_input_handler.rs deleted file mode 100644 index bdc871a802..0000000000 --- a/crates/gpui/src/app/window_input_handler.rs +++ /dev/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>, - pub window: AnyWindowHandle, -} - -impl WindowInputHandler { - fn read_focused_view(&self, f: F) -> Option - 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(&mut self, f: F) -> Option - 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) -> Option { - self.read_focused_view(|view, cx| view.text_for_range(range.clone(), cx)) - .flatten() - } - - fn selected_text_range(&self) -> Option> { - self.read_focused_view(|view, cx| view.selected_text_range(cx)) - .flatten() - } - - fn replace_text_in_range(&mut self, range: Option>, 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> { - 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>, - new_text: &str, - new_selected_range: Option>, - ) { - 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) -> Option { - self.window.read_optional_with(&*self.app.borrow(), |cx| { - cx.rect_for_text_range(range_utf16) - }) - } -} diff --git a/crates/gpui2/src/arena.rs b/crates/gpui/src/arena.rs similarity index 100% rename from crates/gpui2/src/arena.rs rename to crates/gpui/src/arena.rs diff --git a/crates/gpui/src/assets.rs b/crates/gpui/src/assets.rs index 2170d215af..39c8562b69 100644 --- a/crates/gpui/src/assets.rs +++ b/crates/gpui/src/assets.rs @@ -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>; - fn list(&self, path: &str) -> Vec>; + fn list(&self, path: &str) -> Result>; } impl AssetSource for () { @@ -17,49 +21,44 @@ impl AssetSource for () { )) } - fn list(&self, _: &str) -> Vec> { - vec![] + fn list(&self, _path: &str) -> Result> { + Ok(vec![]) } } -pub struct AssetCache { - source: Box, - svgs: RefCell>, - pngs: RefCell>>, +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct ImageId(usize); + +pub struct ImageData { + pub id: ImageId, + data: ImageBuffer, Vec>, } -impl AssetCache { - pub fn new(source: impl AssetSource) -> Self { +impl ImageData { + pub fn new(data: ImageBuffer, Vec>) -> 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 { - 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> { - 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 { + 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() } } diff --git a/crates/gpui/src/clipboard.rs b/crates/gpui/src/clipboard.rs deleted file mode 100644 index b7e4b600ee..0000000000 --- a/crates/gpui/src/clipboard.rs +++ /dev/null @@ -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, -} - -impl ClipboardItem { - pub fn new(text: String) -> Self { - Self { - text, - metadata: None, - } - } - - pub fn with_metadata(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(&self) -> Option - 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() - } -} diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index af56cad76c..df16d2c0b5 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -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; -use crate::json::ToJson; -use pathfinder_color::{ColorF, ColorU}; -use schemars::JsonSchema; -use serde::{ - de::{self, Unexpected}, - Deserialize, Deserializer, -}; -use serde_json::json; - -#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, JsonSchema)] -#[repr(transparent)] -pub struct Color(#[schemars(with = "String")] pub ColorU); - -pub fn color(rgba: u32) -> Color { - Color::from_u32(rgba) +pub fn rgb>(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 rgb(r: f32, g: f32, b: f32) -> Color { - Color(ColorF::new(r, g, b, 1.).to_u8()) +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 } } -pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Color { - Color(ColorF::new(r, g, b, a).to_u8()) +#[derive(PartialEq, Clone, Copy, Default)] +pub struct Rgba { + pub r: f32, + pub g: f32, + pub b: f32, + pub a: f32, } -pub fn transparent_black() -> Color { - Color(ColorU::transparent_black()) +impl fmt::Debug for Rgba { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "rgba({:#010x})", u32::from(*self)) + } } -pub fn black() -> Color { - Color(ColorU::black()) +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 white() -> Color { - Color(ColorU::white()) +impl From 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 red() -> Color { - color(0xff0000ff) +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(self, value: &str) -> Result { + Rgba::try_from(value).map_err(E::custom) + } } -pub fn green() -> Color { - color(0x00ff00ff) +impl<'de> Deserialize<'de> for Rgba { + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_str(RgbaVisitor) + } } -pub fn blue() -> Color { - color(0x0000ffff) +impl From 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 yellow() -> Color { - color(0xffff00ff) +impl TryFrom<&'_ str> for Rgba { + type Error = anyhow::Error; + + fn try_from(value: &'_ str) -> Result { + 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., + }) + } } -impl Color { - pub fn transparent_black() -> Self { - transparent_black() +#[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 { + // 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 { @@ -70,107 +228,230 @@ impl Color { white() } - pub fn red() -> Self { - Color::from_u32(0xff0000ff) - } - - pub fn green() -> Self { - Color::from_u32(0x00ff00ff) - } - - pub fn blue() -> Self { - Color::from_u32(0x0000ffff) - } - - pub fn yellow() -> Self { - Color::from_u32(0xffff00ff) - } - - pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { - Self(ColorU::new(r, g, b, a)) - } - - pub fn from_u32(rgba: u32) -> Self { - Self(ColorU::from_u32(rgba)) - } - - 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; - } - - let source = source.0.to_f32(); - let dest = dest.0.to_f32(); - - 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; - - Self(ColorF::new(r, g, b, a).to_u8()) - } - - 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; + pub fn transparent_black() -> Self { + transparent_black() } } -impl<'de> Deserialize<'de> for Color { +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 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 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(deserializer: D) -> Result where D: Deserializer<'de>, { - let literal: Cow = 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 for Hsla implementation to convert it + Ok(Hsla::from(rgba)) } } -impl From for Color { - fn from(value: u32) -> Self { - Self(ColorU::from_u32(value)) - } -} +#[cfg(test)] +mod tests { + use serde_json::json; -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 - )) - } -} + use super::*; -impl Deref for Color { - type Target = ColorU; - fn deref(&self) -> &Self::Target { - &self.0 - } -} + #[test] + fn test_deserialize_three_value_hex_to_rgba() { + let actual: Rgba = serde_json::from_value(json!("#f09")).unwrap(); -impl DerefMut for Color { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + 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_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)) } } diff --git a/crates/gpui/src/dispatch.rs b/crates/gpui/src/dispatch.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/crates/gpui/src/dispatch.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/gpui2/src/element.rs b/crates/gpui/src/element.rs similarity index 100% rename from crates/gpui2/src/element.rs rename to crates/gpui/src/element.rs diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs deleted file mode 100644 index cf01a1ddc1..0000000000 --- a/crates/gpui/src/elements.rs +++ /dev/null @@ -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: 'static { - type LayoutState; - type PaintState; - - fn view_name(&self) -> &'static str { - type_name::() - } - - fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (Vector2F, Self::LayoutState); - - fn paint( - &mut self, - bounds: RectF, - visible_bounds: RectF, - layout: &mut Self::LayoutState, - view: &mut V, - cx: &mut ViewContext, - ) -> Self::PaintState; - - fn rect_for_text_range( - &self, - range_utf16: Range, - bounds: RectF, - visible_bounds: RectF, - layout: &Self::LayoutState, - paint: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option; - - fn metadata(&self) -> Option<&dyn Any> { - None - } - - fn debug( - &self, - bounds: RectF, - layout: &Self::LayoutState, - paint: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> serde_json::Value; - - fn into_any(self) -> AnyElement - where - Self: 'static + Sized, - { - AnyElement { - state: Box::new(ElementState::Init { element: self }), - name: None, - } - } - - fn into_any_named(self, name: impl Into>) -> AnyElement - where - Self: 'static + Sized, - { - AnyElement { - state: Box::new(ElementState::Init { element: self }), - name: Some(name.into()), - } - } - - fn into_root_element(self, cx: &ViewContext) -> RootElement - where - Self: 'static + Sized, - { - RootElement { - element: self.into_any(), - view: cx.handle().downgrade(), - } - } - - fn constrained(self) -> ConstrainedBox - where - Self: 'static + Sized, - { - ConstrainedBox::new(self.into_any()) - } - - fn aligned(self) -> Align - where - Self: 'static + Sized, - { - Align::new(self.into_any()) - } - - fn clipped(self) -> Clipped - where - Self: 'static + Sized, - { - Clipped::new(self.into_any()) - } - - fn contained(self) -> Container - where - Self: 'static + Sized, - { - Container::new(self.into_any()) - } - - fn expanded(self) -> Expanded - where - Self: 'static + Sized, - { - Expanded::new(self.into_any()) - } - - fn flex(self, flex: f32, expanded: bool) -> FlexItem - where - Self: 'static + Sized, - { - FlexItem::new(self.into_any()).flex(flex, expanded) - } - - fn flex_float(self) -> FlexItem - where - Self: 'static + Sized, - { - FlexItem::new(self.into_any()).float() - } - - fn with_dynamic_tooltip( - self, - tag: TypeTag, - id: usize, - text: impl Into>, - action: Option>, - style: TooltipStyle, - cx: &mut ViewContext, - ) -> Tooltip - where - Self: 'static + Sized, - { - Tooltip::new_dynamic(tag, id, text, action, style, self.into_any(), cx) - } - fn with_tooltip( - self, - id: usize, - text: impl Into>, - action: Option>, - style: TooltipStyle, - cx: &mut ViewContext, - ) -> Tooltip - where - Self: 'static + Sized, - { - Tooltip::new::(id, text, action, style, self.into_any(), cx) - } - - /// Uses the the given element to calculate resizes for the given tag - fn provide_resize_bounds(self) -> BoundsProvider - 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( - self, - side: HandleSide, - size: f32, - on_resize: impl 'static + FnMut(&mut V, Option, &mut ViewContext), - ) -> Resizable - where - Self: 'static + Sized, - { - Resizable::new::(self.into_any(), side, size, on_resize) - } - - fn mouse(self, region_id: usize) -> MouseEventHandler - where - Self: Sized, - { - MouseEventHandler::for_child::(self.into_any(), region_id) - } - - fn component(self) -> StatelessElementAdapter - where - Self: Sized, - { - StatelessElementAdapter::new(self.into_any()) - } - - fn stateful_component(self) -> StatefulElementAdapter - where - Self: Sized, - { - StatefulElementAdapter::new(self.into_any()) - } - - fn styleable_component(self) -> StylableAdapter - where - Self: Sized, - { - StatelessElementAdapter::new(self.into_any()).stylable() - } -} - -trait AnyElementState { - fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> Vector2F; - - fn paint( - &mut self, - origin: Vector2F, - visible_bounds: RectF, - view: &mut V, - cx: &mut ViewContext, - ); - - fn rect_for_text_range( - &self, - range_utf16: Range, - view: &V, - cx: &ViewContext, - ) -> Option; - - fn debug(&self, view: &V, cx: &ViewContext) -> serde_json::Value; - - fn size(&self) -> Vector2F; - - fn metadata(&self) -> Option<&dyn Any>; -} - -enum ElementState> { - 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> AnyElementState for ElementState { - fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> 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, - ) { - *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, - view: &V, - cx: &ViewContext, - ) -> Option { - 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) -> 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 = - 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> Default for ElementState { - fn default() -> Self { - Self::Empty - } -} - -pub struct AnyElement { - state: Box>, - name: Option>, -} - -impl AnyElement { - pub fn name(&self) -> Option<&str> { - self.name.as_deref() - } - - pub fn metadata(&self) -> Option<&T> { - self.state - .metadata() - .and_then(|data| data.downcast_ref::()) - } - - pub fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> Vector2F { - self.state.layout(constraint, view, cx) - } - - pub fn paint( - &mut self, - origin: Vector2F, - visible_bounds: RectF, - view: &mut V, - cx: &mut ViewContext, - ) { - self.state.paint(origin, visible_bounds, view, cx); - } - - pub fn rect_for_text_range( - &self, - range_utf16: Range, - view: &V, - cx: &ViewContext, - ) -> Option { - 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) -> 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 = 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(&self, f: F) -> R - where - T: 'static, - F: FnOnce(Option<&T>) -> R, - { - f(self.state.metadata().and_then(|m| m.downcast_ref())) - } -} - -impl Element for AnyElement { - type LayoutState = (); - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (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, - ) -> Self::PaintState { - self.paint(bounds.origin(), visible_bounds, view, cx); - } - - fn rect_for_text_range( - &self, - range_utf16: Range, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - self.rect_for_text_range(range_utf16, view, cx) - } - - fn debug( - &self, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> serde_json::Value { - self.debug(view, cx) - } - - fn into_any(self) -> AnyElement - where - Self: Sized, - { - self - } -} - -impl Entity for AnyElement<()> { - type Event = (); -} - -// impl View for AnyElement<()> {} - -pub struct RootElement { - element: AnyElement, - view: WeakViewHandle, -} - -impl RootElement { - pub fn new(element: AnyElement, view: WeakViewHandle) -> Self { - Self { element, view } - } -} - -pub trait AnyRootElement { - fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result; - fn paint( - &mut self, - origin: Vector2F, - visible_bounds: RectF, - cx: &mut WindowContext, - ) -> Result<()>; - fn rect_for_text_range( - &self, - range_utf16: Range, - cx: &WindowContext, - ) -> Result>; - fn debug(&self, cx: &WindowContext) -> Result; - fn name(&self) -> Option<&str>; -} - -impl AnyRootElement for RootElement { - fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result { - 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, - cx: &WindowContext, - ) -> Result> { - 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 { - 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> + Sized { - fn add_children>(&mut self, children: impl IntoIterator) { - self.extend(children.into_iter().map(|child| child.into_any())); - } - - fn add_child>(&mut self, child: D) { - self.extend(Some(child.into_any())); - } - - fn with_children>(mut self, children: impl IntoIterator) -> Self { - self.extend(children.into_iter().map(|child| child.into_any())); - self - } - - fn with_child>(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>, -{ -} - -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()) - } -} diff --git a/crates/gpui/src/elements/align.rs b/crates/gpui/src/elements/align.rs deleted file mode 100644 index ba302f4094..0000000000 --- a/crates/gpui/src/elements/align.rs +++ /dev/null @@ -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 { - child: AnyElement, - alignment: Vector2F, -} - -impl Align { - pub fn new(child: AnyElement) -> 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 Element for Align { - type LayoutState = (); - type PaintState = (); - - fn layout( - &mut self, - mut constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (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, - ) -> 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, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - 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, - ) -> json::Value { - json!({ - "type": "Align", - "bounds": bounds.to_json(), - "alignment": self.alignment.to_json(), - "child": self.child.debug(view, cx), - }) - } -} diff --git a/crates/gpui/src/elements/canvas.rs b/crates/gpui/src/elements/canvas.rs index 494d9747c5..767417ae40 100644 --- a/crates/gpui/src/elements/canvas.rs +++ b/crates/gpui/src/elements/canvas.rs @@ -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(F, PhantomData); - -impl Canvas -where - F: FnMut(RectF, RectF, &mut V, &mut ViewContext), -{ - pub fn new(f: F) -> Self { - Self(f, PhantomData) +pub fn canvas(callback: impl 'static + FnOnce(&Bounds, &mut WindowContext)) -> Canvas { + Canvas { + paint_callback: Some(Box::new(callback)), + style: StyleRefinement::default(), } } -impl Element for Canvas -where - F: 'static + FnMut(RectF, RectF, &mut V, &mut ViewContext), -{ - type LayoutState = (); - type PaintState = (); +pub struct Canvas { + paint_callback: Option, &mut WindowContext)>>, + style: StyleRefinement, +} - fn layout( - &mut self, - constraint: crate::SizeConstraint, - _: &mut V, - _: &mut crate::ViewContext, - ) -> (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 paint( - &mut self, - bounds: RectF, - visible_bounds: RectF, - _: &mut Self::LayoutState, - view: &mut V, - cx: &mut ViewContext, - ) -> Self::PaintState { - self.0(bounds, visible_bounds, view, cx) - } - - fn rect_for_text_range( - &self, - _: std::ops::Range, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &V, - _: &ViewContext, - ) -> Option { + fn element_id(&self) -> Option { None } - fn debug( - &self, - bounds: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &V, - _: &ViewContext, - ) -> json::Value { - json!({"type": "Canvas", "bounds": bounds.to_json()}) + fn into_element(self) -> Self::Element { + self + } +} + +impl Element for Canvas { + type State = Style; + + fn request_layout( + &mut self, + _: Option, + 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, 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 } } diff --git a/crates/gpui/src/elements/clipped.rs b/crates/gpui/src/elements/clipped.rs deleted file mode 100644 index 3bd16306bc..0000000000 --- a/crates/gpui/src/elements/clipped.rs +++ /dev/null @@ -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 { - child: AnyElement, -} - -impl Clipped { - pub fn new(child: AnyElement) -> Self { - Self { child } - } -} - -impl Element for Clipped { - type LayoutState = (); - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (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, - ) -> 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, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - self.child.rect_for_text_range(range_utf16, view, cx) - } - - fn debug( - &self, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> json::Value { - json!({ - "type": "Clipped", - "child": self.child.debug(view, cx) - }) - } -} diff --git a/crates/gpui/src/elements/component.rs b/crates/gpui/src/elements/component.rs deleted file mode 100644 index 1d52ede456..0000000000 --- a/crates/gpui/src/elements/component.rs +++ /dev/null @@ -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(self, cx: &mut ViewContext) -> AnyElement; - - fn element(self) -> ComponentAdapter - where - Self: Sized, - { - ComponentAdapter::new(self) - } - - fn stylable(self) -> StylableAdapter - where - Self: Sized, - { - StylableAdapter::new(self) - } - - fn stateful(self) -> StatefulAdapter - 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 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 { - component: C, -} - -impl StylableAdapter { - pub fn new(component: C) -> Self { - Self { component } - } -} - -impl SafeStylable for StylableAdapter { - 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 { - fn render(self, v: &mut V, cx: &mut ViewContext) -> AnyElement; - - fn element(self) -> ComponentAdapter - where - Self: Sized, - { - ComponentAdapter::new(self) - } - - fn styleable(self) -> StatefulStylableAdapter - 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 StatefulComponent for C { - fn render(self, _: &mut V, cx: &mut ViewContext) -> AnyElement { - self.render(cx) - } -} - -/// Same as stylable, but generic over a view type -pub trait StatefulStylable: StatefulComponent { - type Style: Clone; - - fn with_style(self, style: Self::Style) -> Self; -} - -/// Same as SafeStylable, but generic over a view type -pub trait StatefulSafeStylable { - type Style: Clone; - type Output: StatefulComponent; - - fn with_style(self, style: Self::Style) -> Self::Output; -} - -/// Converting from stateless to stateful -impl StatefulSafeStylable 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 { - component: C, - phantom: std::marker::PhantomData, -} - -impl StatefulAdapter { - pub fn new(component: C) -> Self { - Self { - component, - phantom: std::marker::PhantomData, - } - } -} - -impl StatefulComponent for StatefulAdapter { - fn render(self, _: &mut V, cx: &mut ViewContext) -> AnyElement { - 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, V: 'static> { - component: C, - phantom: std::marker::PhantomData, -} - -impl, V: 'static> StatefulStylableAdapter { - pub fn new(component: C) -> Self { - Self { - component, - phantom: std::marker::PhantomData, - } - } -} - -impl, V: 'static> StatefulSafeStylable - for StatefulStylableAdapter -{ - 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, -} - -impl StatelessElementAdapter { - pub fn new(element: AnyElement) -> Self { - StatelessElementAdapter { - element: Box::new(element) as Box, - } - } -} - -impl Component for StatelessElementAdapter { - fn render(self, _: &mut ViewContext) -> AnyElement { - *self - .element - .downcast::>() - .expect("Don't move elements out of their view :(") - } -} - -// For converting elements into stateful components -pub struct StatefulElementAdapter { - element: AnyElement, - _phantom: std::marker::PhantomData, -} - -impl StatefulElementAdapter { - pub fn new(element: AnyElement) -> Self { - Self { - element, - _phantom: std::marker::PhantomData, - } - } -} - -impl StatefulComponent for StatefulElementAdapter { - fn render(self, _: &mut V, _: &mut ViewContext) -> AnyElement { - self.element - } -} - -/// A convenient shorthand for creating an empty component. -impl Component for () { - fn render(self, _: &mut ViewContext) -> AnyElement { - 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 { - component: Option, - element: Option>, - phantom: PhantomData, -} - -impl ComponentAdapter { - pub fn new(e: E) -> Self { - Self { - component: Some(e), - element: None, - phantom: PhantomData, - } - } -} - -impl + 'static> Element for ComponentAdapter { - type LayoutState = (); - - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (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, - ) -> 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, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - 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, - ) -> serde_json::Value { - serde_json::json!({ - "type": "ComponentAdapter", - "component": std::any::type_name::(), - "child": self.element.as_ref().map(|el| el.debug(view, cx)), - }) - } -} diff --git a/crates/gpui/src/elements/constrained_box.rs b/crates/gpui/src/elements/constrained_box.rs deleted file mode 100644 index 0b49b0951d..0000000000 --- a/crates/gpui/src/elements/constrained_box.rs +++ /dev/null @@ -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 { - child: AnyElement, - constraint: Constraint, -} - -pub enum Constraint { - Static(SizeConstraint), - Dynamic(Box) -> SizeConstraint>), -} - -impl ToJson for Constraint { - fn to_json(&self) -> serde_json::Value { - match self { - Constraint::Static(constraint) => constraint.to_json(), - Constraint::Dynamic(_) => "dynamic".into(), - } - } -} - -impl ConstrainedBox { - pub fn new(child: impl Element) -> 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) -> 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, - ) -> SizeConstraint { - match &mut self.constraint { - Constraint::Static(constraint) => *constraint, - Constraint::Dynamic(compute_constraint) => { - compute_constraint(input_constraint, view, cx) - } - } - } -} - -impl Element for ConstrainedBox { - type LayoutState = (); - type PaintState = (); - - fn layout( - &mut self, - mut parent_constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (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, - ) -> 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, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - self.child.rect_for_text_range(range_utf16, view, cx) - } - - fn debug( - &self, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> json::Value { - json!({"type": "ConstrainedBox", "assigned_constraint": self.constraint.to_json(), "child": self.child.debug(view, cx)}) - } -} diff --git a/crates/gpui/src/elements/container.rs b/crates/gpui/src/elements/container.rs deleted file mode 100644 index c2d8fd38bd..0000000000 --- a/crates/gpui/src/elements/container.rs +++ /dev/null @@ -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, - #[serde(rename = "overlay")] - pub overlay_color: Option, - #[serde(default)] - pub border: Border, - #[serde(default)] - #[serde(alias = "corner_radius")] - pub corner_radii: CornerRadii, - #[serde(default)] - pub shadow: Option, - #[serde(default)] - pub cursor: Option, -} - -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 { - child: AnyElement, - style: ContainerStyle, -} - -impl Container { - pub fn new(child: AnyElement) -> 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 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(deserializer: D) -> Result - 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 Element for Container { - type LayoutState = (); - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (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, - ) -> 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, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - self.child.rect_for_text_range(range_utf16, view, cx) - } - - fn debug( - &self, - bounds: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> 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(deserializer: D) -> Result - 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(deserializer: D) -> Result - 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::")] - 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() - }) - } -} diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui/src/elements/div.rs similarity index 100% rename from crates/gpui2/src/elements/div.rs rename to crates/gpui/src/elements/div.rs diff --git a/crates/gpui/src/elements/empty.rs b/crates/gpui/src/elements/empty.rs deleted file mode 100644 index 4344199278..0000000000 --- a/crates/gpui/src/elements/empty.rs +++ /dev/null @@ -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 Element for Empty { - type LayoutState = (); - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - _: &mut V, - _: &mut ViewContext, - ) -> (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, - ) -> Self::PaintState { - } - - fn rect_for_text_range( - &self, - _: Range, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &V, - _: &ViewContext, - ) -> Option { - None - } - - fn debug( - &self, - bounds: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &V, - _: &ViewContext, - ) -> serde_json::Value { - json!({ - "type": "Empty", - "bounds": bounds.to_json(), - }) - } -} diff --git a/crates/gpui/src/elements/expanded.rs b/crates/gpui/src/elements/expanded.rs deleted file mode 100644 index 0cafa3f119..0000000000 --- a/crates/gpui/src/elements/expanded.rs +++ /dev/null @@ -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 { - child: AnyElement, - full_width: bool, - full_height: bool, -} - -impl Expanded { - pub fn new(child: impl Element) -> 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 Element for Expanded { - type LayoutState = (); - type PaintState = (); - - fn layout( - &mut self, - mut constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (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, - ) -> Self::PaintState { - self.child.paint(bounds.origin(), visible_bounds, view, cx); - } - - fn rect_for_text_range( - &self, - range_utf16: Range, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - self.child.rect_for_text_range(range_utf16, view, cx) - } - - fn debug( - &self, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> json::Value { - json!({ - "type": "Expanded", - "full_width": self.full_width, - "full_height": self.full_height, - "child": self.child.debug(view, cx) - }) - } -} diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs deleted file mode 100644 index f4b8578d17..0000000000 --- a/crates/gpui/src/elements/flex.rs +++ /dev/null @@ -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>, - scroll_position: Cell, - type_tag: TypeTag, -} - -pub struct Flex { - axis: Axis, - children: Vec>, - scroll_state: Option<(ElementStateHandle>, usize)>, - child_alignment: f32, - spacing: f32, -} - -impl Flex { - 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( - mut self, - element_id: usize, - scroll_to: Option, - cx: &mut ViewContext, - ) -> 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::>( - element_id, - Rc::new(ScrollState { - type_tag: TypeTag::new::(), - 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, - ) { - let cross_axis = self.axis.invert(); - for child in self.children.iter_mut() { - if let Some(metadata) = child.metadata::() { - 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 Extend> for Flex { - fn extend>>(&mut self, children: T) { - self.children.extend(children); - } -} - -impl Element for Flex { - type LayoutState = f32; - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (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::(); - 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, - ) -> 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::() { - 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, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - 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, - ) -> 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::>() - }) - } -} - -struct FlexParentData { - flex: Option<(f32, bool)>, - float: bool, -} - -pub struct FlexItem { - metadata: FlexParentData, - child: AnyElement, -} - -impl FlexItem { - pub fn new(child: impl Element) -> 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 Element for FlexItem { - type LayoutState = (); - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (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, - ) -> Self::PaintState { - self.child.paint(bounds.origin(), visible_bounds, view, cx) - } - - fn rect_for_text_range( - &self, - range_utf16: Range, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - 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, - ) -> Value { - json!({ - "type": "Flexible", - "flex": self.metadata.flex, - "child": self.child.debug(view, cx) - }) - } -} diff --git a/crates/gpui/src/elements/hook.rs b/crates/gpui/src/elements/hook.rs deleted file mode 100644 index 8797899f03..0000000000 --- a/crates/gpui/src/elements/hook.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::ops::Range; - -use crate::{ - geometry::{rect::RectF, vector::Vector2F}, - json::json, - AnyElement, Element, SizeConstraint, ViewContext, -}; - -pub struct Hook { - child: AnyElement, - after_layout: Option)>>, -} - -impl Hook { - pub fn new(child: impl Element) -> Self { - Self { - child: child.into_any(), - after_layout: None, - } - } - - pub fn on_after_layout( - mut self, - f: impl 'static + FnMut(Vector2F, &mut ViewContext), - ) -> Self { - self.after_layout = Some(Box::new(f)); - self - } -} - -impl Element for Hook { - type LayoutState = (); - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (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, - ) { - self.child.paint(bounds.origin(), visible_bounds, view, cx); - } - - fn rect_for_text_range( - &self, - range_utf16: Range, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - self.child.rect_for_text_range(range_utf16, view, cx) - } - - fn debug( - &self, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> serde_json::Value { - json!({ - "type": "Hooks", - "child": self.child.debug(view, cx), - }) - } -} diff --git a/crates/gpui/src/elements/image.rs b/crates/gpui/src/elements/image.rs deleted file mode 100644 index 7e0c7d5daa..0000000000 --- a/crates/gpui/src/elements/image.rs +++ /dev/null @@ -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), -} - -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, - #[serde(default)] - pub width: Option, - #[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) -> Self { - Self { - source: ImageSource::Data(data), - style: Default::default(), - } - } - - pub fn with_style(mut self, style: ImageStyle) -> Self { - self.style = style; - self - } -} - -impl Element for Image { - type LayoutState = Option>; - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - _: &mut V, - cx: &mut ViewContext, - ) -> (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, - ) -> 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, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &V, - _: &ViewContext, - ) -> Option { - None - } - - fn debug( - &self, - bounds: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &V, - _: &ViewContext, - ) -> serde_json::Value { - json!({ - "type": "Image", - "bounds": bounds.to_json(), - }) - } -} diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui/src/elements/img.rs similarity index 100% rename from crates/gpui2/src/elements/img.rs rename to crates/gpui/src/elements/img.rs diff --git a/crates/gpui/src/elements/keystroke_label.rs b/crates/gpui/src/elements/keystroke_label.rs deleted file mode 100644 index 5ebb9ea688..0000000000 --- a/crates/gpui/src/elements/keystroke_label.rs +++ /dev/null @@ -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, - container_style: ContainerStyle, - text_style: TextStyle, - view_id: usize, -} - -impl KeystrokeLabel { - pub fn new( - view_id: usize, - action: Box, - container_style: ContainerStyle, - text_style: TextStyle, - ) -> Self { - Self { - view_id, - action, - container_style, - text_style, - } - } -} - -impl Element for KeystrokeLabel { - type LayoutState = AnyElement; - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (Vector2F, AnyElement) { - 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, - view: &mut V, - cx: &mut ViewContext, - ) { - element.paint(bounds.origin(), visible_bounds, view, cx); - } - - fn rect_for_text_range( - &self, - _: Range, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &V, - _: &ViewContext, - ) -> Option { - None - } - - fn debug( - &self, - _: RectF, - element: &AnyElement, - _: &(), - view: &V, - cx: &ViewContext, - ) -> serde_json::Value { - json!({ - "type": "KeystrokeLabel", - "action": self.action.name(), - "child": element.debug(view, cx) - }) - } -} diff --git a/crates/gpui/src/elements/label.rs b/crates/gpui/src/elements/label.rs deleted file mode 100644 index d8e6bd3ea6..0000000000 --- a/crates/gpui/src/elements/label.rs +++ /dev/null @@ -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, -} - -#[derive(Clone, Debug, Deserialize, Default, JsonSchema)] -pub struct LabelStyle { - pub text: TextStyle, - pub highlight_text: Option, -} - -impl From 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>>(text: I, style: impl Into) -> Self { - Self { - text: text.into(), - highlight_indices: Default::default(), - style: style.into(), - } - } - - pub fn with_highlights(mut self, indices: Vec) -> 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 Element for Label { - type LayoutState = Line; - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - _: &mut V, - cx: &mut ViewContext, - ) -> (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, - ) -> 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, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &V, - _: &ViewContext, - ) -> Option { - None - } - - fn debug( - &self, - bounds: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &V, - _: &ViewContext, - ) -> 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), - ] - ); - } -} diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 58409aca42..364b610eee 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -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 { - state: ListState, +pub fn list(state: ListState) -> List { + List { + state, + style: StyleRefinement::default(), + } } -pub struct ListState(Rc>>); +pub struct List { + state: ListState, + style: StyleRefinement, +} + +#[derive(Clone)] +pub struct ListState(Rc>); + +struct StateInner { + last_layout_bounds: Option>, + render_item: Box AnyElement>, + items: SumTree, + logical_scroll_top: Option, + alignment: ListAlignment, + overdraw: Pixels, + #[allow(clippy::type_complexity)] + scroll_handler: Option>, +} #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Orientation { +pub enum ListAlignment { Top, Bottom, } -struct StateInner { - last_layout_width: Option, - render_item: Box) -> AnyElement>, - rendered_range: Range, - items: SumTree>, - logical_scroll_top: Option, - orientation: Orientation, - overdraw: f32, - #[allow(clippy::type_complexity)] - scroll_handler: Option, usize, &mut V, &mut ViewContext)>>, +pub struct ListScrollEvent { + pub visible_range: Range, + pub count: usize, } -#[derive(Clone, Copy, Debug, Default, PartialEq)] -pub struct ListOffset { - pub item_ix: usize, - pub offset_in_item: f32, -} - -enum ListItem { +#[derive(Clone)] +enum ListItem { Unrendered, - Rendered(Rc>>), - Removed(f32), -} - -impl Clone for ListItem { - fn clone(&self) -> Self { - match self { - Self::Unrendered => Self::Unrendered, - Self::Rendered(element) => Self::Rendered(element.clone()), - Self::Removed(height) => Self::Removed(*height), - } - } -} - -impl Debug for ListItem { - 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(), - } - } + 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); +struct Height(Pixels); -impl List { - pub fn new(state: ListState) -> Self { - Self { state } - } -} - -impl Element for List { - type LayoutState = ListOffset; - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (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(); - - // Render items after the scroll top, including those in the trailing overdraw. - let mut cursor = old_items.cursor::(); - 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::(); - - 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, - ) { - 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::(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, - bounds: RectF, - _: RectF, - scroll_top: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - 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::(); - 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, - ) -> 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::>(); - 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 ListState { - pub fn new( +impl ListState { + pub fn new( element_count: usize, - orientation: Orientation, - overdraw: f32, - mut render_item: F, + orientation: ListAlignment, + overdraw: Pixels, + render_item: F, ) -> Self where - D: Element, - F: 'static + FnMut(&mut V, usize, &mut ViewContext) -> 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 ListState { 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 ListState { { 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::(); 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 ListState { } pub fn set_scroll_handler( - &mut self, - handler: impl FnMut(Range, usize, &mut V, &mut ViewContext) + 'static, + &self, + handler: impl FnMut(&ListScrollEvent, &mut WindowContext) + 'static, ) { self.0.borrow_mut().scroll_handler = Some(Box::new(handler)) } @@ -434,37 +149,72 @@ impl ListState { 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 Clone for ListState { - 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 StateInner { - fn render_item( - &mut self, - ix: usize, - existing_element: Option<&ListItem>, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> Option>>> { - 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::(); + 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); } - fn visible_range(&self, height: f32, scroll_top: &ListOffset) -> Range { + /// Get the bounds for the given item in window coordinates. + pub fn bounds_for_item(&self, ix: usize) -> Option> { + 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 { let mut cursor = self.items.cursor::(); cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); let start_y = cursor.start().height + scroll_top.offset_in_item; @@ -472,53 +222,19 @@ impl StateInner { scroll_top.item_ix..cursor.start().count + 1 } - fn visible_elements<'a>( - &'a self, - bounds: RectF, - scroll_top: &ListOffset, - ) -> impl Iterator>>, Vector2F)> + 'a { - let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item); - let mut cursor = self.items.cursor::(); - 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; - } - - 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); - } - - cursor.next(&()); - } - - None - }) - } - fn scroll( &mut self, scroll_top: &ListOffset, - height: f32, - mut delta: Vector2F, - precise: bool, - view: &mut V, - cx: &mut ViewContext, + height: Pixels, + delta: Point, + 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::(); @@ -534,9 +250,10 @@ impl StateInner { 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 StateInner { 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::(); cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right, &()); cursor.start().height + logical_scroll_top.offset_in_item } } -impl ListItem { - 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() + } } } } -impl sum_tree::Item for ListItem { +#[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, + 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, + _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::(); + 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::(); + 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 { + 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 { @@ -584,18 +500,12 @@ impl sum_tree::Item for ListItem { 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::>(), - )); - 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::>(); - 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) -> AnyElement { - 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 Element for TestElement { - type LayoutState = (); - type PaintState = (); - - fn layout( - &mut self, - _: SizeConstraint, - _: &mut V, - _: &mut ViewContext, - ) -> (Vector2F, ()) { - (self.size, ()) - } - - fn paint(&mut self, _: RectF, _: RectF, _: &mut (), _: &mut V, _: &mut ViewContext) { - unimplemented!() - } - - fn rect_for_text_range( - &self, - _: Range, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &V, - _: &ViewContext, - ) -> Option { - unimplemented!() - } - - fn debug(&self, _: RectF, _: &(), _: &(), _: &V, _: &ViewContext) -> serde_json::Value { - self.id.into() - } - - fn metadata(&self) -> Option<&dyn std::any::Any> { - Some(&self.id) - } - } -} diff --git a/crates/gpui2/src/elements/mod.rs b/crates/gpui/src/elements/mod.rs similarity index 100% rename from crates/gpui2/src/elements/mod.rs rename to crates/gpui/src/elements/mod.rs diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs deleted file mode 100644 index 0a632ba382..0000000000 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ /dev/null @@ -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 { - child: AnyElement, - region_id: usize, - cursor_style: Option, - 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 MouseEventHandler { - pub fn for_child(child: impl Element, 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::(), - } - } - - pub fn new( - region_id: usize, - cx: &mut ViewContext, - render_child: impl FnOnce(&mut MouseState, &mut ViewContext) -> E, - ) -> Self - where - E: Element, - { - let mut mouse_state = cx.mouse_state_dynamic(TypeTag::new::(), 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::(), - } - } - - pub fn new_dynamic( - tag: TypeTag, - region_id: usize, - cx: &mut ViewContext, - render_child: impl FnOnce(&mut MouseState, &mut ViewContext) -> AnyElement, - ) -> 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( - region_id: usize, - cx: &mut ViewContext, - render_child: impl FnOnce(&mut MouseState, &mut ViewContext) -> D, - ) -> Self - where - D: Element, - { - let mut handler = Self::new::(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) + '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) + '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) + '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) + '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) + '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) + '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) + '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) + '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) + '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) + 'static, - ) -> Self { - self.handlers = self.handlers.on_hover(handler); - self - } - - pub fn on_scroll( - mut self, - handler: impl Fn(MouseScrollWheel, &mut V, &mut EventContext) + '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) { - 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 Element for MouseEventHandler { - type LayoutState = (); - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (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, - ) -> 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, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - self.child.rect_for_text_range(range_utf16, view, cx) - } - - fn debug( - &self, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> serde_json::Value { - json!({ - "type": "MouseEventHandler", - "child": self.child.debug(view, cx), - }) - } -} diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index 11956bc211..eab3ee60b4 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -1,33 +1,187 @@ -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 { - child: AnyElement, - anchor_position: Option, - anchor_corner: AnchorCorner, - fit_mode: OverlayFitMode, - position_mode: OverlayPositionMode, - hoverable: bool, - z_index: Option, +pub struct OverlayState { + child_layout_ids: SmallVec<[LayoutId; 4]>, } -#[derive(Copy, Clone)] +pub struct Overlay { + children: SmallVec<[AnyElement; 2]>, + anchor_corner: AnchorCorner, + fit_mode: OverlayFitMode, + // todo!(); + anchor_position: Option>, + // 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) -> 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, + cx: &mut WindowContext, + ) -> (crate::LayoutId, Self::State) { + let child_layout_ids = self + .children + .iter_mut() + .map(|child| child.request_layout(cx)) + .collect::>(); + + 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, + 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 = (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 { + None + } + + fn into_element(self) -> Self::Element { + self + } +} + +enum Axis { + Horizontal, + Vertical, +} + +#[derive(Copy, Clone, PartialEq)] pub enum OverlayFitMode { SnapToWindow, SwitchAnchor, - None, -} - -#[derive(Copy, Clone, PartialEq, Eq)] -pub enum OverlayPositionMode { - Window, - Local, } #[derive(Clone, Copy, PartialEq, Eq)] @@ -39,18 +193,32 @@ pub enum AnchorCorner { } impl AnchorCorner { - fn get_bounds(&self, anchor_position: Vector2F, size: Vector2F) -> RectF { + fn get_bounds(&self, origin: Point, size: Size) -> Bounds { + 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) -> Point { 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), + Self::TopLeft => bounds.origin, + Self::TopRight => bounds.upper_right(), + Self::BottomLeft => bounds.lower_left(), + Self::BottomRight => bounds.lower_right(), } } @@ -71,190 +239,3 @@ impl AnchorCorner { } } } - -impl Overlay { - pub fn new(child: impl Element) -> 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; - self - } - - pub fn with_position_mode(mut self, position_mode: OverlayPositionMode) -> Self { - self.position_mode = position_mode; - self - } - - pub fn with_hoverable(mut self, hoverable: bool) -> Self { - self.hoverable = hoverable; - self - } - - pub fn with_z_index(mut self, z_index: usize) -> Self { - self.z_index = Some(z_index); - self - } -} - -impl Element for Overlay { - type LayoutState = Vector2F; - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (Vector2F, Self::LayoutState) { - let constraint = if self.anchor_position.is_some() { - SizeConstraint::new(Vector2F::zero(), cx.window_size()) - } else { - constraint - }; - let size = self.child.layout(constraint, view, cx); - (Vector2F::zero(), size) - } - - fn paint( - &mut self, - bounds: RectF, - _: RectF, - size: &mut Self::LayoutState, - view: &mut V, - cx: &mut ViewContext, - ) { - 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) - } - }; - - 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); - } - - // 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); - } - } - 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 bounds.max_y() > cx.window_size().y() { - anchor_corner = anchor_corner.switch_axis(Axis::Vertical); - } - - if bounds.min_x() < 0. { - anchor_corner = anchor_corner.switch_axis(Axis::Horizontal) - } - - if bounds.min_y() < 0. { - anchor_corner = anchor_corner.switch_axis(Axis::Vertical) - } - - // Update bounds if needed - if anchor_corner != self.anchor_corner { - bounds = anchor_corner.get_bounds(anchor_position, *size) - } - } - OverlayFitMode::None => {} - } - - 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::( - 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 rect_for_text_range( - &self, - range_utf16: Range, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - self.child.rect_for_text_range(range_utf16, view, cx) - } - - fn debug( - &self, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> serde_json::Value { - json!({ - "type": "Overlay", - "abs_position": self.anchor_position.to_json(), - "child": self.child.debug(view, cx), - }) - } -} diff --git a/crates/gpui/src/elements/resizable.rs b/crates/gpui/src/elements/resizable.rs deleted file mode 100644 index 123225cc64..0000000000 --- a/crates/gpui/src/elements/resizable.rs +++ /dev/null @@ -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::() - .and_then(|map| map.0.get(&tag)) -} - -pub struct Resizable { - child: AnyElement, - tag: TypeTag, - handle_side: HandleSide, - handle_size: f32, - on_resize: Rc, &mut ViewContext)>>, -} - -const DEFAULT_HANDLE_SIZE: f32 = 4.0; - -impl Resizable { - pub fn new( - child: AnyElement, - handle_side: HandleSide, - size: f32, - on_resize: impl 'static + FnMut(&mut V, Option, &mut ViewContext), - ) -> 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::(), - 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 Element for Resizable { - type LayoutState = SizeConstraint; - type PaintState = (); - - fn layout( - &mut self, - constraint: crate::SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (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, - ) -> 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::(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, - _bounds: pathfinder_geometry::rect::RectF, - _visible_bounds: pathfinder_geometry::rect::RectF, - _layout: &Self::LayoutState, - _paint: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - 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, - ) -> serde_json::Value { - json!({ - "child": self.child.debug(view, cx), - }) - } -} - -#[derive(Debug, Default)] -struct ProviderMap(HashMap); - -pub struct BoundsProvider { - child: AnyElement, - phantom: std::marker::PhantomData

, -} - -impl BoundsProvider { - pub fn new(child: AnyElement) -> Self { - Self { - child, - phantom: std::marker::PhantomData, - } - } -} - -impl Element for BoundsProvider { - type LayoutState = (); - - type PaintState = (); - - fn layout( - &mut self, - constraint: crate::SizeConstraint, - view: &mut V, - cx: &mut crate::ViewContext, - ) -> (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, - ) -> Self::PaintState { - cx.update_default_global::(|map, _| { - map.0.insert(TypeTag::new::

(), (bounds, visible_bounds)); - }); - - self.child.paint(bounds.origin(), visible_bounds, view, cx) - } - - fn rect_for_text_range( - &self, - range_utf16: std::ops::Range, - _: pathfinder_geometry::rect::RectF, - _: pathfinder_geometry::rect::RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &crate::ViewContext, - ) -> Option { - 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, - ) -> serde_json::Value { - serde_json::json!({ - "type": "Provider", - "providing": format!("{:?}", TypeTag::new::

()), - "child": self.child.debug(view, cx), - }) - } -} diff --git a/crates/gpui/src/elements/stack.rs b/crates/gpui/src/elements/stack.rs deleted file mode 100644 index ad5080907b..0000000000 --- a/crates/gpui/src/elements/stack.rs +++ /dev/null @@ -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 { - children: Vec>, -} - -impl Default for Stack { - fn default() -> Self { - Self { - children: Vec::new(), - } - } -} - -impl Stack { - pub fn new() -> Self { - Self::default() - } -} - -impl Element for Stack { - type LayoutState = (); - type PaintState = (); - - fn layout( - &mut self, - mut constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (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, - ) -> 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, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - 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, - ) -> json::Value { - json!({ - "type": "Stack", - "bounds": bounds.to_json(), - "children": self.children.iter().map(|child| child.debug(view, cx)).collect::>() - }) - } -} - -impl Extend> for Stack { - fn extend>>(&mut self, children: T) { - self.children.extend(children) - } -} diff --git a/crates/gpui/src/elements/svg.rs b/crates/gpui/src/elements/svg.rs index 7f67719d8a..5ea7f9bd78 100644 --- a/crates/gpui/src/elements/svg.rs +++ b/crates/gpui/src/elements/svg.rs @@ -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, +} + +pub fn svg() -> Svg { + Svg { + interactivity: Interactivity::default(), + path: None, + } } impl Svg { - pub fn new(path: impl Into>) -> Self { - Self { - path: path.into(), - color: Color::black(), - } - } - - pub fn for_style(style: SvgStyle) -> impl Element { - Self::new(style.asset) - .with_color(style.color) - .constrained() - .with_width(style.dimensions.width) - .with_height(style.dimensions.height) - } - - pub fn with_color(mut self, color: Color) -> Self { - self.color = color; + pub fn path(mut self, path: impl Into) -> Self { + self.path = Some(path.into()); self } } -impl Element for Svg { - type LayoutState = Option; - type PaintState = (); +impl Element for Svg { + type State = InteractiveElementState; - fn layout( + fn request_layout( &mut self, - constraint: SizeConstraint, - _: &mut V, - cx: &mut ViewContext, - ) -> (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, + 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, - ) { - if let Some(svg) = svg.clone() { - cx.scene().push_icon(scene::Icon { - bounds, - svg, - path: self.path.clone(), - color: self.color, - }); - } - } - - fn rect_for_text_range( - &self, - _: Range, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &V, - _: &ViewContext, - ) -> Option { - None - } - - fn debug( - &self, - bounds: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &V, - _: &ViewContext, - ) -> serde_json::Value { - json!({ - "type": "Svg", - "bounds": bounds.to_json(), - "path": self.path, - "color": self.color.to_json(), - }) + bounds: Bounds, + 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(); + } + }) } } -#[derive(Clone, Deserialize, Default, JsonSchema)] -pub struct SvgStyle { - pub color: Color, - pub asset: String, - pub dimensions: Dimensions, -} +impl IntoElement for Svg { + type Element = Self; -#[derive(Clone, Deserialize, Default, JsonSchema)] -pub struct Dimensions { - pub width: f32, - pub height: f32, -} + fn element_id(&self) -> Option { + self.interactivity.element_id.clone() + } -impl Dimensions { - pub fn to_vec(&self) -> Vector2F { - vec2f(self.width, self.height) + fn into_element(self) -> Self::Element { + self } } -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 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 + } } diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index c823840692..29c93fd19e 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -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}; +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; -pub struct Text { - text: Cow<'static, str>, - style: TextStyle, - soft_wrap: bool, - highlights: Option, HighlightStyle)]>>, - custom_runs: Option<( - Box<[Range]>, - Box, - )>, -} +impl Element for &'static str { + type State = TextState; -pub struct LayoutState { - shaped_lines: Vec, - wrap_boundaries: Vec>, - line_height: f32, -} - -impl Text { - pub fn new>>(text: I, style: TextStyle) -> Self { - Self { - text: text.into(), - style, - soft_wrap: true, - highlights: None, - custom_runs: None, - } + fn request_layout( + &mut self, + _: Option, + cx: &mut WindowContext, + ) -> (LayoutId, Self::State) { + let mut state = TextState::default(); + let layout_id = state.layout(SharedString::from(*self), 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, state: &mut TextState, cx: &mut WindowContext) { + state.paint(bounds, self, cx) + } +} + +impl IntoElement for &'static str { + type Element = Self; + + fn element_id(&self) -> Option { + None + } + + fn into_element(self) -> Self::Element { self } +} + +impl Element for SharedString { + type State = TextState; + + fn request_layout( + &mut self, + _: Option, + 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, 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 { + 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>, +} + +impl StyledText { + pub fn new(text: impl Into) -> Self { + StyledText { + text: text.into(), + runs: None, + } + } pub fn with_highlights( mut self, - runs: impl Into, HighlightStyle)]>>, + default_style: &TextStyle, + highlights: impl IntoIterator, HighlightStyle)>, ) -> Self { - self.highlights = Some(runs.into()); - self - } - - pub fn with_custom_runs( - mut self, - runs: impl Into]>>, - callback: impl 'static + FnMut(usize, RectF, &mut WindowContext), - ) -> Self { - self.custom_runs = Some((runs.into(), Box::new(callback))); - self - } - - pub fn with_soft_wrap(mut self, soft_wrap: bool) -> Self { - self.soft_wrap = soft_wrap; + 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 Text { - type LayoutState = LayoutState; - type PaintState = (); +impl Element for StyledText { + type State = TextState; - fn layout( + fn request_layout( &mut self, - constraint: SizeConstraint, - _: &mut V, - cx: &mut ViewContext, - ) -> (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; - } else { - warn!( - "Highlight out of text range. Text len: {}, Highlight range: {}..{}", - self.text.len(), - range.start, - range.end - ); - result = None; - } - } else if offset < self.text.len() { - result = Some((&self.text[offset..], None)); - offset = self.text.len(); - } else { - result = None; - } - 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::>(); - line_count += boundaries.len() + 1; - wrap_boundaries.push(boundaries); - } else { - line_count += 1; - } - max_line_width = max_line_width.max(shaped_line.width()); - } - - 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, - }, - ) + _: Option, + 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: RectF, - visible_bounds: RectF, - layout: &mut Self::LayoutState, - _: &mut V, - cx: &mut ViewContext, - ) -> 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; - } - 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); - } - } - - // 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); - } - - // 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)); - } - } - } - - // 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)); - } - } - } - - // 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(); - } - } - } - } - - offset = end_offset + 1; - origin.set_y(boundaries.max_y()); - } + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + state.paint(bounds, &self.text, cx) } +} - fn rect_for_text_range( - &self, - _: Range, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &V, - _: &ViewContext, - ) -> Option { +impl IntoElement for StyledText { + type Element = Self; + + fn element_id(&self) -> Option { None } - fn debug( - &self, - bounds: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - _: &V, - _: &ViewContext, - ) -> Value { - json!({ - "type": "Text", - "bounds": bounds.to_json(), - "text": &self.text, - "style": self.style.to_json(), - }) + fn into_element(self) -> Self::Element { + self } } -/// Perform text layout on a series of highlighted chunks of text. -pub fn layout_highlighted_chunks<'a>( - chunks: impl Iterator)>, - text_style: &TextStyle, - text_layout_cache: &TextLayoutCache, - font_cache: &Arc, - max_line_len: usize, - max_line_count: usize, -) -> Vec { - 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; - } - } +#[derive(Default, Clone)] +pub struct TextState(Arc>>); - 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)) +struct TextStateInner { + lines: SmallVec<[WrappedLine; 1]>, + line_height: Pixels, + wrap_width: Option, + size: Option>, +} + +impl TextState { + fn lock(&self) -> MutexGuard> { + self.0.lock() + } + + fn layout( + &mut self, + text: SharedString, + runs: Option>, + 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 { - Cow::Borrowed(text_style) + None }; - 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; + 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(); } - 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, - }, - )); + 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 = 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, 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, position: Point) -> Option { + 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); } } - } - 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 - }); - } - - struct TestView; - - impl Entity for TestView { - type Event = (); - } - - impl View for TestView { - fn ui_name() -> &'static str { - "TestView" - } - - fn render(&mut self, _: &mut ViewContext) -> AnyElement { - Empty::new().into_any() - } + None + } +} + +pub struct InteractiveText { + element_id: ElementId, + text: StyledText, + click_listener: + Option], InteractiveTextClickEvent, &mut WindowContext<'_>)>>, + clickable_ranges: Vec>, +} + +struct InteractiveTextClickEvent { + mouse_down_index: usize, + mouse_up_index: usize, +} + +pub struct InteractiveTextState { + text_state: TextState, + mouse_down_index: Rc>>, +} + +impl InteractiveText { + pub fn new(id: impl Into, text: StyledText) -> Self { + Self { + element_id: id.into(), + text, + click_listener: None, + clickable_ranges: Vec::new(), + } + } + + pub fn on_click( + mut self, + ranges: Vec>, + 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, + 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, 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 { + Some(self.element_id.clone()) + } + + fn into_element(self) -> Self::Element { + self } } diff --git a/crates/gpui/src/elements/tooltip.rs b/crates/gpui/src/elements/tooltip.rs deleted file mode 100644 index 59892b6279..0000000000 --- a/crates/gpui/src/elements/tooltip.rs +++ /dev/null @@ -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 { - child: AnyElement, - tooltip: Option>, - _state: ElementStateHandle>, -} - -#[derive(Default)] -struct TooltipState { - visible: Cell, - position: Cell, - debounce: RefCell>>, -} - -#[derive(Clone, Deserialize, Default, JsonSchema)] -pub struct TooltipStyle { - #[serde(flatten)] - pub container: ContainerStyle, - pub text: TextStyle, - keystroke: KeystrokeStyle, - pub max_text_width: Option, -} - -#[derive(Clone, Deserialize, Default, JsonSchema)] -pub struct KeystrokeStyle { - #[serde(flatten)] - container: ContainerStyle, - #[serde(flatten)] - text: TextStyle, -} - -impl Tooltip { - pub fn new( - id: usize, - text: impl Into>, - action: Option>, - style: TooltipStyle, - child: AnyElement, - cx: &mut ViewContext, - ) -> Self { - Self::new_dynamic(TypeTag::new::(), id, text, action, style, child, cx) - } - - pub fn new_dynamic( - mut tag: TypeTag, - id: usize, - text: impl Into>, - action: Option>, - style: TooltipStyle, - child: AnyElement, - cx: &mut ViewContext, - ) -> Self { - tag = tag.compose(TypeTag::new::()); - - let focused_view_id = cx.focused_view_id(); - - let state_handle = cx.default_element_state_dynamic::>(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, - text: impl Into>, - style: TooltipStyle, - action: Option>, - measure: bool, - ) -> impl Element { - 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 Element for Tooltip { - type LayoutState = (); - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (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, - ) { - 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, - _: RectF, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - self.child.rect_for_text_range(range, view, cx) - } - - fn debug( - &self, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> serde_json::Value { - json!({ - "child": self.child.debug(view, cx), - "tooltip": self.tooltip.as_ref().map(|t| t.debug(view, cx)), - }) - } -} diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 037b003b20..ffa678e9e5 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -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}; +use taffy::style::Overflow; -#[derive(Clone, Default)] -pub struct UniformListState(Rc>); +/// 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( + view: View, + id: I, + item_count: usize, + f: impl 'static + Fn(&mut V, Range, &mut ViewContext) -> Vec, +) -> UniformList +where + I: Into, + R: IntoElement, + V: Render, +{ + let id = id.into(); + let mut base_style = StyleRefinement::default(); + base_style.overflow.y = Some(Overflow::Scroll); -#[derive(Debug)] -pub enum ScrollTarget { - Show(usize), - Center(usize), + 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, + } } -impl UniformListState { - pub fn scroll_to(&self, scroll_to: ScrollTarget) { - self.0.borrow_mut().scroll_to = Some(scroll_to); +pub struct UniformList { + id: ElementId, + item_count: usize, + item_to_measure_index: usize, + render_items: + Box Fn(Range, &'a mut WindowContext) -> SmallVec<[AnyElement; 64]>>, + interactivity: Interactivity, + scroll_handle: Option, +} + +#[derive(Clone, Default)] +pub struct UniformListScrollHandle(Rc>>); + +#[derive(Clone, Debug)] +struct ScrollHandleState { + item_height: Pixels, + list_height: Pixels, + scroll_offset: Rc>>, +} + +impl UniformListScrollHandle { + pub fn new() -> Self { + Self(Rc::new(RefCell::new(None))) } - pub fn scroll_top(&self) -> f32 { - self.0.borrow().scroll_top + 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)] -struct StateInner { - scroll_top: f32, - scroll_to: Option, +pub struct UniformListState { + interactive: InteractiveElementState, + item_size: Size, } -pub struct UniformListLayoutState { - scroll_max: f32, - item_height: f32, - items: Vec>, -} +impl Element for UniformList { + type State = UniformListState; -pub struct UniformList { - state: UniformListState, - item_count: usize, - #[allow(clippy::type_complexity)] - append_items: Box, &mut Vec>, &mut ViewContext)>, - padding_top: f32, - padding_bottom: f32, - get_width_from_item: Option, - view_id: usize, -} - -impl UniformList { - pub fn new( - state: UniformListState, - item_count: usize, - cx: &mut ViewContext, - append_items: F, - ) -> Self - where - F: 'static + Fn(&mut V, Range, &mut Vec>, &mut ViewContext), - { - 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) -> 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, - ) -> bool { - if !precise { - delta *= 20.; - } - - let mut state = state.0.borrow_mut(); - state.scroll_top = (state.scroll_top - delta.y()).max(0.0).min(scroll_max); - cx.notify(); - - true - } - - 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; - } - } - } - - if state.scroll_top > scroll_max { - state.scroll_top = scroll_max; - } - } - - fn scroll_top(&self) -> f32 { - self.state.0.borrow().scroll_top - } -} - -impl Element for UniformList { - type LayoutState = UniformListLayoutState; - type PaintState = (); - - fn layout( + fn request_layout( &mut self, - constraint: SizeConstraint, - view: &mut V, - cx: &mut ViewContext, - ) -> (Vector2F, Self::LayoutState) { - if constraint.max.y().is_infinite() { - unimplemented!( - "UniformList does not support being rendered with an unconstrained height" - ); - } + state: Option, + 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 no_items = ( - constraint.min, - UniformListLayoutState { - item_height: 0., - scroll_max: 0., - items: Default::default(), - }, - ); + 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 + } + }); - if self.item_count == 0 { - return no_items; - } + let height = match available_space.height { + AvailableSpace::Definite(height) => desired_height.min(height), + AvailableSpace::MinContent | AvailableSpace::MaxContent => { + desired_height + } + }; + size(width, height) + }, + ) + }); - 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; - } - } - - let item_constraint = SizeConstraint { - min: item_size, - max: vec2f(constraint.max.x(), item_size.y()), + let element_state = UniformListState { + interactive, + item_size, }; - 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 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); - - 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, - ); - - if (start..end).contains(&sample_item_ix) { - if sample_item_ix > start { - (self.append_items)(view, start..sample_item_ix, &mut items, cx); - } - - items.push(sample_item); - - 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); - } - - 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()); - } - } - - ( - size, - UniformListLayoutState { - item_height, - scroll_max, - items, - }, - ) + (layout_id, element_state) } fn paint( &mut self, - bounds: RectF, - visible_bounds: RectF, - layout: &mut Self::LayoutState, - view: &mut V, - cx: &mut ViewContext, - ) -> Self::PaintState { - let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default(); + bounds: Bounds, + 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()); - cx.scene().push_layer(Some(visible_bounds)); - - cx.scene().push_mouse_region( - MouseRegion::new::(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(); - } - } - }), + 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 mut item_origin = bounds.origin() - - vec2f( - 0., - (self.state.scroll_top() - self.padding_top) % layout.item_height, - ); + 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, + }; - for item in &mut layout.items { - item.paint(item_origin, visible_bounds, view, cx); - item_origin += vec2f(0.0, layout.item_height); - } + 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(); + } + } - cx.scene().pop_layer(); - } + Rc::default() + }) + .clone(); - fn rect_for_text_range( - &self, - range: Range, - _: RectF, - _: RectF, - layout: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> Option { - layout - .items - .iter() - .find_map(|child| child.rect_for_text_range(range.clone(), view, cx)) - } + let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height; - fn debug( - &self, - bounds: RectF, - layout: &Self::LayoutState, - _: &Self::PaintState, - view: &V, - cx: &ViewContext, - ) -> 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::>() + 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 { + Some(self.id.clone()) + } + + fn into_element(self) -> Self::Element { + self + } +} + +impl UniformList { + pub fn with_width_from_item(mut self, item_index: Option) -> Self { + self.item_to_measure_index = item_index.unwrap_or(0); + self + } + + fn measure_item(&self, list_width: Option, cx: &mut WindowContext) -> Size { + 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 } } diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index a7d81e5a4d..589493b4bb 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -1,822 +1,244 @@ -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, - _not_send_or_sync: PhantomData>, - }, - #[cfg(any(test, feature = "test-support"))] - Deterministic { - cx_id: usize, - executor: Arc, - }, +#[derive(Clone)] +pub struct BackgroundExecutor { + dispatcher: Arc, } -pub enum Background { - #[cfg(any(test, feature = "test-support"))] - Deterministic { executor: Arc }, - Production { - executor: Arc>, - _stop: channel::Sender<()>, - }, +#[derive(Clone)] +pub struct ForegroundExecutor { + dispatcher: Arc, + not_send: PhantomData>, } -type AnyLocalFuture = Pin>>>; -type AnyFuture = Pin>>>; -type AnyTask = async_task::Task>; -type AnyLocalTask = async_task::Task>; - #[must_use] +#[derive(Debug)] pub enum Task { Ready(Option), - Local { - any_task: AnyLocalTask, - result_type: PhantomData, - }, - Send { - any_task: AnyTask, - result_type: PhantomData, - }, + Spawned(async_task::Task), } -unsafe impl Send for Task {} - -#[cfg(any(test, feature = "test-support"))] -struct DeterministicState { - rng: rand::prelude::StdRng, - seed: u64, - scheduled_from_foreground: collections::HashMap>, - scheduled_from_background: Vec, - forbid_parking: bool, - block_on_ticks: std::ops::RangeInclusive, - now: std::time::Instant, - next_timer_id: usize, - pending_timers: Vec<(usize, std::time::Instant, postage::barrier::Sender)>, - waiting_backtrace: Option, - next_runnable_id: usize, - poll_history: Vec, - previous_poll_history: Option>, - enable_runnable_backtraces: bool, - runnable_backtraces: collections::HashMap, -} - -#[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, -} - -#[cfg(any(test, feature = "test-support"))] -struct BackgroundRunnable { - id: usize, - runnable: Runnable, -} - -#[cfg(any(test, feature = "test-support"))] -pub struct Deterministic { - state: Arc>, - parker: parking_lot::Mutex, -} - -#[must_use] -pub enum Timer { - Production(smol::Timer), - #[cfg(any(test, feature = "test-support"))] - Deterministic(DeterministicTimer), -} - -#[cfg(any(test, feature = "test-support"))] -pub struct DeterministicTimer { - rx: postage::barrier::Receiver, - id: usize, - state: Arc>, -} - -#[cfg(any(test, feature = "test-support"))] -impl Deterministic { - pub fn new(seed: u64) -> Arc { - use rand::prelude::*; - - 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 Task { + pub fn ready(val: T) -> Self { + Task::Ready(Some(val)) } - pub fn execution_history(&self) -> Vec { - self.state.lock().poll_history.clone() - } - - pub fn set_previous_execution_history(&self, history: Option>) { - self.state.lock().previous_poll_history = history; - } - - pub fn enable_runnable_backtrace(&self) { - self.state.lock().enable_runnable_backtraces = true; - } - - pub fn runnable_backtrace(&self, runnable_id: usize) -> backtrace::Backtrace { - let mut backtrace = self.state.lock().runnable_backtraces[&runnable_id].clone(); - backtrace.resolve(); - backtrace - } - - pub fn build_background(self: &Arc) -> Arc { - Arc::new(Background::Deterministic { - executor: self.clone(), - }) - } - - pub fn build_foreground(self: &Arc, id: usize) -> Rc { - Rc::new(Foreground::Deterministic { - cx_id: id, - executor: self.clone(), - }) - } - - fn spawn_from_foreground( - &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(); - }); - runnable.schedule(); - 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()); - } - } - - 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 { - 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 run_until_parked(&self) { - use std::sync::atomic::AtomicBool; - let woken = Arc::new(AtomicBool::new(false)); - self.run_internal(woken, None); - } - - fn run_internal( - &self, - woken: Arc, - mut main_task: Option<&mut AnyLocalTask>, - ) -> Option> { - 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(); - }); - - 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); - } - } - - 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::>(); - 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); - } - } - } - } - } - } - - fn block(&self, future: &mut F, max_ticks: usize) -> Option - where - F: Unpin + Future, - { - 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(); - - 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::>(); - drop(state); - drop(timers_to_wake); - continue; - } - } - - 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()); - } - } - } -} - -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 { - 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, - } - } - 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 { + pub fn detach(self) { match self { - ExecutorEvent::PollRunnable { id } => *id, - ExecutorEvent::EnqueueRunnable { id } => *id, + Task::Ready(_) => {} + Task::Spawned(task) => task.detach(), } } } -impl Foreground { - pub fn platform(dispatcher: Arc) -> Result { - if dispatcher.is_main_thread() { - Ok(Self::Platform { - dispatcher, - _not_send_or_sync: PhantomData, - }) - } else { - Err(anyhow!("must be constructed on main thread")) - } +impl Task> +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(); } +} - pub fn spawn(&self, future: impl Future + 'static) -> Task { - 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, - ) -> 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) - } +impl Future for Task { + type Output = T; - #[cfg(any(test, feature = "test-support"))] - pub fn run(&self, future: impl Future) -> T { - let future = async move { Box::new(future.await) as Box }.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() - } - - #[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"), - } - } - - #[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"), - } - } - - #[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"), - } - } - - #[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"), - } - } - - #[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"), - } - } - - #[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"), - } - } - - #[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"), - } - } - - #[cfg(any(test, feature = "test-support"))] - pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive) { - match self { - Self::Deterministic { executor, .. } => executor.state.lock().block_on_ticks = range, - _ => panic!("this method can only be called on a deterministic executor"), + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + match unsafe { self.get_unchecked_mut() } { + Task::Ready(val) => Poll::Ready(val.take().unwrap()), + Task::Spawned(task) => task.poll(cx), } } } -impl Background { +#[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 { - let executor = Arc::new(Executor::new()); - let stop = channel::unbounded::<()>(); + static NEXT_TASK_LABEL: AtomicUsize = AtomicUsize::new(1); + Self(NEXT_TASK_LABEL.fetch_add(1, SeqCst).try_into().unwrap()) + } +} - 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(); - } +type AnyLocalFuture = Pin>>; - Self::Production { - executor, - _stop: stop.0, - } +type AnyFuture = Pin>>; + +impl BackgroundExecutor { + pub fn new(dispatcher: Arc) -> Self { + Self { dispatcher } } - pub fn num_cpus(&self) -> usize { - num_cpus::get() - } - - pub fn spawn(&self, future: F) -> Task + /// Enqueues the given future to be run to completion on a background thread. + pub fn spawn(&self, future: impl Future + Send + 'static) -> Task where - T: 'static + Send, - F: Send + Future + 'static, + R: Send + '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) + self.spawn_internal::(Box::pin(future), None) } - pub fn block(&self, future: F) -> T - where - F: Future, - { - 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() - } - } - } - - pub fn block_with_timeout( + /// 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( &self, - timeout: Duration, - future: F, - ) -> Result> + label: TaskLabel, + future: impl Future + Send + 'static, + ) -> Task where - T: 'static, - F: 'static + Unpin + Future, + R: Send + 'static, { - 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()); - } - } - Err(async { *future.await.downcast().unwrap() }) + self.spawn_internal::(Box::pin(future), Some(label)) } - pub async fn scoped<'scope, F>(self: &Arc, scheduler: F) + fn spawn_internal( + &self, + future: AnyFuture, + label: Option, + ) -> Task { + 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(&self, future: impl Future) -> R { + if let Ok(value) = self.block_internal(false, future, usize::MAX) { + value + } else { + unreachable!() + } + } + + pub fn block(&self, future: impl Future) -> R { + if let Ok(value) = self.block_internal(true, future, usize::MAX) { + value + } else { + unreachable!() + } + } + + #[track_caller] + pub(crate) fn block_internal( + &self, + background_only: bool, + future: impl Future, + mut max_ticks: usize, + ) -> Result { + 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( + &self, + duration: Duration, + future: impl Future, + ) -> Result> { + 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>), { @@ -831,86 +253,120 @@ impl Background { } } - 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 { - 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") - } - } + 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) { - match self { - Self::Deterministic { executor, .. } => executor.start_waiting(), - _ => panic!("this method can only be called on a deterministic executor"), - } + 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 { + 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) { + self.dispatcher.as_test().unwrap().set_block_on_ticks(range); } } -impl Default for Background { - fn default() -> Self { - Self::new() +impl ForegroundExecutor { + pub fn new(dispatcher: Arc) -> 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(&self, future: impl Future + 'static) -> Task + where + R: 'static, + { + let dispatcher = self.dispatcher.clone(); + fn inner( + dispatcher: Arc, + future: AnyLocalFuture, + ) -> Task { + let (runnable, task) = async_task::spawn_local(future, move |runnable| { + dispatcher.dispatch_on_main_thread(runnable) + }); + runnable.schedule(); + Task::Spawned(task) + } + inner::(dispatcher, Box::pin(future)) } } pub struct Scope<'a> { - executor: Arc, + executor: BackgroundExecutor, futures: Vec + Send + 'static>>>, tx: Option>, rx: mpsc::Receiver<()>, - _phantom: PhantomData<&'a ()>, + lifetime: PhantomData<&'a ()>, } impl<'a> Scope<'a> { - fn new(executor: Arc) -> 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, } } @@ -944,88 +400,3 @@ impl<'a> Drop for Scope<'a> { self.executor.block(self.rx.next()); } } - -impl Task { - pub fn ready(value: T) -> Self { - Self::Ready(Some(value)) - } - - fn local(any_task: AnyLocalTask) -> Self { - Self::Local { - any_task, - result_type: PhantomData, - } - } - - pub fn detach(self) { - match self { - Task::Ready(_) => {} - Task::Local { any_task, .. } => any_task.detach(), - Task::Send { any_task, .. } => any_task.detach(), - } - } -} - -impl Task> { - #[track_caller] - pub fn detach_and_log_err(self, cx: &mut AppContext) { - let caller = Location::caller(); - cx.spawn(|_| async move { - if let Err(err) = self.await { - log::error!("{}:{}: {:#}", caller.file(), caller.line(), err); - } - }) - .detach(); - } -} - -impl Task { - fn send(any_task: AnyTask) -> Self { - Self::Send { - any_task, - result_type: PhantomData, - } - } -} - -impl fmt::Debug for Task { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Task::Ready(value) => value.fmt(f), - Task::Local { any_task, .. } => any_task.fmt(f), - Task::Send { any_task, .. } => any_task.fmt(f), - } - } -} - -impl Future for Task { - type Output = T; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match unsafe { self.get_unchecked_mut() } { - Task::Ready(value) => Poll::Ready(value.take().unwrap()), - Task::Local { any_task, .. } => { - any_task.poll(cx).map(|value| *value.downcast().unwrap()) - } - Task::Send { any_task, .. } => { - any_task.poll(cx).map(|value| *value.downcast().unwrap()) - } - } - } -} - -fn any_future(future: F) -> AnyFuture -where - T: 'static + Send, - F: Future + Send + 'static, -{ - async { Box::new(future.await) as Box }.boxed() -} - -fn any_local_future(future: F) -> AnyLocalFuture -where - T: 'static, - F: Future + 'static, -{ - async { Box::new(future.await) as Box }.boxed_local() -} diff --git a/crates/gpui/src/font_cache.rs b/crates/gpui/src/font_cache.rs deleted file mode 100644 index b2dc79c87b..0000000000 --- a/crates/gpui/src/font_cache.rs +++ /dev/null @@ -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, - font_features: Features, - font_ids: Vec, -} - -pub struct FontCache(RwLock); - -pub struct FontCacheState { - font_system: Arc, - families: Vec, - default_family: Option, - font_selections: HashMap>, - metrics: HashMap, - wrapper_pool: HashMap<(FontId, OrderedFloat), Vec>, -} - -pub struct LineWrapperHandle { - wrapper: Option, - font_cache: Arc, -} - -unsafe impl Send for FontCache {} - -impl FontCache { - pub fn new(fonts: Arc) -> 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> { - 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 { - 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::>() - .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 { - 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(&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, 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); - } -} diff --git a/crates/gpui/src/fonts.rs b/crates/gpui/src/fonts.rs deleted file mode 100644 index f360ef933f..0000000000 --- a/crates/gpui/src/fonts.rs +++ /dev/null @@ -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, - pub case: Option, - pub cpsp: Option, - pub frac: Option, - pub liga: Option, - pub onum: Option, - pub ordn: Option, - pub pnum: Option, - pub ss01: Option, - pub ss02: Option, - pub ss03: Option, - pub ss04: Option, - pub ss05: Option, - pub ss06: Option, - pub ss07: Option, - pub ss08: Option, - pub ss09: Option, - pub ss10: Option, - pub ss11: Option, - pub ss12: Option, - pub ss13: Option, - pub ss14: Option, - pub ss15: Option, - pub ss16: Option, - pub ss17: Option, - pub ss18: Option, - pub ss19: Option, - pub ss20: Option, - pub subs: Option, - pub sups: Option, - pub swsh: Option, - pub titl: Option, - pub tnum: Option, - pub zero: Option, -} - -#[derive(Clone, Debug, JsonSchema)] -pub struct TextStyle { - pub color: Color, - pub font_family_name: Arc, - 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, - pub font_family: Option>, - pub font_size: Option, - pub font_weight: Option, - pub font_style: Option