diff --git a/CHANGELOG.md b/CHANGELOG.md index 35e5ca9..4fe5879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,18 @@ ### Added +- Show an avatar in each panel's tab, using the repository owner's profile picture when set and a pixel avatar otherwise + ### Changed +- Migrate the GPUI foundation to the published `gpui-pre` crates and GPUI Kit 0.6, off the zed and gpui-component git pins +- Use the pixel avatar as the single fallback for a missing picture, sized and rounded to match the other avatars +- Redesign the dock tab bar, using muted grey active tab, added close buttons, double-click to zoom, and removed panel toolbar + ### Fixed +- Render every avatar at one consistent size, where a surrounding border had shrunk pictures by two pixels and the pixel avatar ignored an explicit size + ### Removed ### Deprecated diff --git a/Cargo.lock b/Cargo.lock index 8a40acd..f29569b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,8 +288,8 @@ name = "assets" version = "0.1.0-alpha" dependencies = [ "anyhow", - "gpui", "gpui-component", + "gpui-pre", "rust-embed", "serde_json", ] @@ -651,15 +651,6 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efbd3e1070bbdf4cd88a75264e18e8a26f7cb5c6949eadf0ceb85fb159cf08f8" -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bindgen" version = "0.72.1" @@ -1219,16 +1210,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "collections" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" -dependencies = [ - "gpui_util", - "indexmap", - "rustc-hash 2.1.3", -] - [[package]] name = "color_quant" version = "1.1.0" @@ -1691,16 +1672,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "derive_refineable" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "diffy" version = "0.5.2" @@ -1733,13 +1704,34 @@ dependencies = [ "ctutils", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", +] + [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys", + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", ] [[package]] @@ -1750,7 +1742,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.5.2", "windows-sys 0.61.2", ] @@ -1796,9 +1788,9 @@ dependencies = [ name = "dock" version = "0.1.0-alpha" dependencies = [ - "gpui", "gpui-base", "gpui-component", + "gpui-pre", "signed_ui", ] @@ -2055,17 +2047,6 @@ dependencies = [ "zune-inflate", ] -[[package]] -name = "fancy-regex" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" -dependencies = [ - "bit-set 0.8.0", - "regex-automata", - "regex-syntax", -] - [[package]] name = "faster-hex" version = "0.10.0" @@ -3536,90 +3517,22 @@ dependencies = [ "bitflags 2.13.2", ] -[[package]] -name = "gpui" -version = "0.2.2" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" -dependencies = [ - "accesskit", - "anyhow", - "async-channel", - "async-task", - "backtrace", - "bindgen", - "bitflags 2.13.2", - "chrono", - "collections", - "core-video", - "ctor", - "derive_more", - "embed-resource", - "etagere", - "futures", - "futures-concurrency", - "getrandom 0.3.4", - "gpui_macros", - "gpui_shared_string", - "gpui_util", - "heapless 0.9.3", - "http_client", - "image", - "inventory", - "itertools 0.14.0", - "log", - "lyon", - "num_cpus", - "parking", - "parking_lot", - "pin-project", - "pollster 0.4.0", - "postage", - "profiling", - "proptest", - "rand 0.9.5", - "raw-window-handle", - "refineable", - "regex", - "resvg 0.46.0", - "scheduler", - "schemars", - "seahash", - "serde", - "serde_json", - "slotmap", - "smallvec", - "spin 0.10.1", - "strum", - "sum_tree", - "taffy", - "thiserror 2.0.20", - "tracing", - "ttf-parser", - "url", - "usvg 0.46.0", - "util_macros", - "uuid", - "waker-fn", - "web-time", - "windows 0.62.2", - "zed-font-kit", - "zed-scap", - "ztracing", -] - [[package]] name = "gpui-base" -version = "0.5.2" -source = "git+https://github.com/longbridge/gpui-component?rev=39c2c86dbee7ad445591462f8675f74082e10828#39c2c86dbee7ad445591462f8675f74082e10828" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e904ee0d351f4e85b967e26ae986d3aa195a8d9d35fcd18184a2c40989694e" dependencies = [ "aho-corasick", "anyhow", + "ashpd", "async-channel", "chrono", + "data-url", "futures", - "gpui", - "gpui_macros", - "gpui_platform", + "gpui-pre", + "gpui-pre-macros", + "gpui-pre-sum-tree", "html5ever", "instant", "lsp-types", @@ -3636,27 +3549,28 @@ dependencies = [ "serde_json", "smallvec", "smol", - "syntect", "tracing", "unicode-segmentation", "web-time", - "zed-sum-tree", + "windows 0.58.0", ] [[package]] name = "gpui-component" -version = "0.5.2" -source = "git+https://github.com/longbridge/gpui-component?rev=39c2c86dbee7ad445591462f8675f74082e10828#39c2c86dbee7ad445591462f8675f74082e10828" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cf6c72516b44bb2e66a1bd2ccf77a122ecd1b54a75559ac299bb296bfcb5bb7" dependencies = [ "anyhow", "chrono", "core-text", "enum-iterator", - "gpui", "gpui-base", - "gpui-component-assets", "gpui-component-macros", - "gpui_macros", + "gpui-kit-assets", + "gpui-pre", + "gpui-pre-macros", + "gpui-pre-sum-tree", "instant", "itertools 0.13.0", "log", @@ -3670,6 +3584,7 @@ dependencies = [ "once_cell", "paste", "raw-window-handle", + "regex", "resvg 0.45.1", "ropey", "rust-i18n", @@ -3717,49 +3632,123 @@ dependencies = [ "tree-sitter-zig", "uuid", "windows 0.58.0", - "zed-sum-tree", -] - -[[package]] -name = "gpui-component-assets" -version = "0.5.1" -source = "git+https://github.com/longbridge/gpui-component?rev=39c2c86dbee7ad445591462f8675f74082e10828#39c2c86dbee7ad445591462f8675f74082e10828" -dependencies = [ - "anyhow", - "gpui", - "log", - "rust-embed", - "wasm-bindgen", - "wasm-bindgen-futures", - "zed-reqwest 0.12.15-zed (git+https://github.com/zed-industries/reqwest.git?rev=c15662463bda39148ba154100dd44d3fba5873a4)", ] [[package]] name = "gpui-component-macros" -version = "0.5.1" -source = "git+https://github.com/longbridge/gpui-component?rev=39c2c86dbee7ad445591462f8675f74082e10828#39c2c86dbee7ad445591462f8675f74082e10828" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3867545508c7cf6a5f6da586f60d894a3f8e7cce94b7fe82df7fad5f868ec20e" dependencies = [ + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.119", ] [[package]] -name = "gpui_apple" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" +name = "gpui-kit-assets" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111d3d03a68454318a193f137419864ccebdc7d3dc0194b0b0b3f818a6bc3d55" +dependencies = [ + "anyhow", + "gpui-pre", + "gpui-pre-reqwest", + "log", + "rust-embed", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "gpui-pre" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0437c0b83e636a92bd1a39fa1d05fb632ae671289537497b35871ffbe231b84" +dependencies = [ + "accesskit", + "anyhow", + "async-channel", + "async-task", + "backtrace", + "bindgen", + "bitflags 2.13.2", + "chrono", + "core-video", + "ctor", + "derive_more", + "embed-resource", + "etagere", + "futures", + "futures-concurrency", + "getrandom 0.3.4", + "gpui-pre-collections", + "gpui-pre-http-client", + "gpui-pre-macros", + "gpui-pre-refineable", + "gpui-pre-scheduler", + "gpui-pre-shared-string", + "gpui-pre-sum-tree", + "gpui-pre-util", + "gpui-pre-util-macros", + "heapless 0.9.3", + "image", + "inventory", + "itertools 0.14.0", + "log", + "lyon", + "num_cpus", + "objc2-core-foundation", + "objc2-core-video", + "parking", + "parking_lot", + "pin-project", + "pollster 0.4.0", + "postage", + "profiling", + "proptest", + "rand 0.9.5", + "raw-window-handle", + "regex", + "resvg 0.46.0", + "schemars", + "seahash", + "serde", + "serde_json", + "slotmap", + "smallvec", + "spin 0.10.1", + "strum", + "taffy", + "thiserror 2.0.20", + "ttf-parser", + "url", + "usvg 0.46.0", + "uuid", + "waker-fn", + "web-time", + "windows 0.62.2", + "zed-font-kit", + "zed-scap", +] + +[[package]] +name = "gpui-pre-apple" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af534f0746eb9e014114bcc0ee00dc250c4b8d75df2e437180c403aa65649c1" dependencies = [ "anyhow", "block", "cbindgen", "cocoa 0.26.0", - "collections", "core-foundation 0.10.1", "core-video", "derive_more", "etagere", "foreign-types", - "gpui", + "gpui-pre", "image", "log", "metal", @@ -3769,9 +3758,65 @@ dependencies = [ ] [[package]] -name = "gpui_linux" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" +name = "gpui-pre-collections" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e7a65204039187d1f4b3d4c2e0233eeac54d4acb0b13f9dc1a3b5adad26c1f" +dependencies = [ + "gpui-pre-util", + "indexmap", + "rustc-hash 2.1.3", +] + +[[package]] +name = "gpui-pre-derive-refineable" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79dfb4a05f3cd9893d7768dbf5dba1bbbe99cab7394e89b360ccb3b2bcb6b34e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui-pre-http-client" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd5a7542c77887a5aff997d78c95884119c8463409569b63fcf060956da1682" +dependencies = [ + "anyhow", + "async-compression", + "bytes", + "derive_more", + "futures", + "http", + "http-body", + "log", + "parking_lot", + "serde", + "serde_json", + "serde_urlencoded", + "url", +] + +[[package]] +name = "gpui-pre-http-client-tls" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72aed510adb5e7723f8f57b9751007e3451d46a84edb59b92227178cf59cc19c" +dependencies = [ + "log", + "rustls", + "rustls-platform-verifier", + "webpki-roots 1.0.9", +] + +[[package]] +name = "gpui-pre-linux" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aab655cda1d6dcd549ac869e8678babc84cf573ab6794a1fe1a39c6c70c60b48" dependencies = [ "accesskit", "accesskit_unix", @@ -3782,13 +3827,13 @@ dependencies = [ "bytemuck", "calloop", "calloop-wayland-source", - "collections", "filedescriptor", "futures", - "gpui", - "gpui_util", - "gpui_wgpu", - "http_client", + "gpui-pre", + "gpui-pre-collections", + "gpui-pre-http-client", + "gpui-pre-util", + "gpui-pre-wgpu", "libc", "log", "notify-rust", @@ -3815,18 +3860,17 @@ dependencies = [ ] [[package]] -name = "gpui_macos" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" +name = "gpui-pre-macos" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951d17e41a72067ad2d35c60e08a62e8ad7fa9400b41c802e26cfb459f6c05da" dependencies = [ "accesskit", "accesskit_macos", "anyhow", "async-task", - "block", "block2 0.6.2", "cocoa 0.26.0", - "collections", "core-foundation 0.10.1", "core-foundation-sys", "core-graphics 0.24.0", @@ -3835,19 +3879,20 @@ dependencies = [ "dispatch2", "foreign-types", "futures", - "gpui", - "gpui_apple", - "gpui_util", + "gpui-pre", + "gpui-pre-apple", + "gpui-pre-collections", + "gpui-pre-util", "image", "itertools 0.14.0", "libc", "log", "mach2", - "media", - "metal", "objc", "objc2 0.6.4", "objc2-app-kit 0.3.2", + "objc2-core-graphics", + "objc2-core-media", "objc2-foundation 0.3.2", "objc2-screen-capture-kit", "objc2-user-notifications", @@ -3862,33 +3907,142 @@ dependencies = [ ] [[package]] -name = "gpui_macros" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" +name = "gpui-pre-macros" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6324092b40ea8ec7c327a4a28427e366a489b4d5d23b5d1bd0243fc2f4ca7a9e" dependencies = [ "heck 0.5.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.119", ] [[package]] -name = "gpui_platform" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" +name = "gpui-pre-perf" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94bff26ccd8105f6206144f8dab16a2dc02e7a68f39dada02cdf492f0afd940" dependencies = [ - "console_error_panic_hook", - "gpui", - "gpui_linux", - "gpui_macos", - "gpui_web", - "gpui_windows", + "gpui-pre-collections", + "serde", + "serde_json", ] [[package]] -name = "gpui_shared_string" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" +name = "gpui-pre-platform" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8423d1e0a693d2f9f16c0326bb01b5e81ec0531854d596a18476934ac6880478" +dependencies = [ + "console_error_panic_hook", + "gpui-pre", + "gpui-pre-linux", + "gpui-pre-macos", + "gpui-pre-web", + "gpui-pre-windows", +] + +[[package]] +name = "gpui-pre-refineable" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93b4b0a8d296a3ee5beb1136bc48f8167a6028eb1e243b56851dcd8c9fc6d7a1" +dependencies = [ + "gpui-pre-derive-refineable", +] + +[[package]] +name = "gpui-pre-reqwest" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05be23908e707966824f8c51a609904b7f30739e8d915e120a53c1b995ba964c" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-rustls", + "tokio-socks", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry 0.4.0", +] + +[[package]] +name = "gpui-pre-reqwest-client" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f32ccea5d1a7c8d2868d7b346c2c1f76aa3f86ec9e0575c4e7340af3afd9e9d" +dependencies = [ + "anyhow", + "bytes", + "futures", + "gpui-pre-http-client", + "gpui-pre-http-client-tls", + "gpui-pre-reqwest", + "gpui-pre-util", + "log", + "regex", + "tokio", +] + +[[package]] +name = "gpui-pre-scheduler" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff7b10b3d2fc3fda24847a04436eca9531612c8414874a37ddc005f20e521e12" +dependencies = [ + "async-task", + "backtrace", + "chrono", + "flume 0.12.0", + "futures", + "parking_lot", + "rand 0.9.5", + "wasm_thread", + "web-time", +] + +[[package]] +name = "gpui-pre-shared-string" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe17f81bd98a0253140ea79f7f76442d7499bd8d17db302861af8e46c11d38df" dependencies = [ "schemars", "serde", @@ -3896,9 +4050,23 @@ dependencies = [ ] [[package]] -name = "gpui_util" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" +name = "gpui-pre-sum-tree" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6d35e998a51d24248dad731de1f09d386cbbf46a04edde3b67f469b2ce3f16" +dependencies = [ + "gpui-pre-ztracing", + "heapless 0.9.3", + "log", + "rayon", + "tracing", +] + +[[package]] +name = "gpui-pre-util" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a026a30c578930d20974fd8c3d77de36223ac7ba193bd4259e7238929bc8806" dependencies = [ "anyhow", "log", @@ -3906,21 +4074,33 @@ dependencies = [ ] [[package]] -name = "gpui_web" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" +name = "gpui-pre-util-macros" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cbf79627a6afc626d4e887dad9d2d92bb069413e8cc692ba3e90bff219eb7a9" +dependencies = [ + "gpui-pre-perf", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui-pre-web" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34be433b9fcb370b1fe9a1c7fe911f0eb6058791ef8167dcf6c5a5deb624975c" dependencies = [ "anyhow", "console_error_panic_hook", "futures", - "gpui", - "gpui_wgpu", - "http_client", + "gpui-pre", + "gpui-pre-http-client", + "gpui-pre-scheduler", + "gpui-pre-wgpu", "js-sys", "log", "parking_lot", "raw-window-handle", - "scheduler", "smallvec", "unicode-properties", "unicode-script", @@ -3934,17 +4114,18 @@ dependencies = [ ] [[package]] -name = "gpui_wgpu" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" +name = "gpui-pre-wgpu" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05515b32799b1767d69300921c1b562cd53104965211a0e8905acdc7aa18e693" dependencies = [ "anyhow", "bytemuck", - "collections", "cosmic-text", "etagere", - "gpui", - "gpui_util", + "gpui-pre", + "gpui-pre-collections", + "gpui-pre-util", "itertools 0.14.0", "log", "parking_lot", @@ -3960,19 +4141,20 @@ dependencies = [ ] [[package]] -name = "gpui_windows" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" +name = "gpui-pre-windows" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27662e65bfcf4445d2cccfad91f1570ec07f4b1b5de98400a9f207fd19c464cf" dependencies = [ "accesskit", "accesskit_windows", "anyhow", - "collections", "dunce", "etagere", "futures", - "gpui", - "gpui_util", + "gpui-pre", + "gpui-pre-collections", + "gpui-pre-util", "image", "itertools 0.14.0", "log", @@ -3987,6 +4169,36 @@ dependencies = [ "windows-registry 0.6.1", ] +[[package]] +name = "gpui-pre-zlog" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a5af0b734a391a84872eb770b3ee9830091ec2487b79d06371fe14eefb2b5d" +dependencies = [ + "anyhow", + "chrono", + "gpui-pre-collections", + "log", +] + +[[package]] +name = "gpui-pre-ztracing" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a24cb19f78c5bc3aab8ddf1e8e0bd6b402fbcb9c7f112f64ddbedef8f8768a59" +dependencies = [ + "gpui-pre-zlog", + "gpui-pre-ztracing-macro", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "gpui-pre-ztracing-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1bb25850ad31b88bbdef454afb93ec4cdb81f5e200ba5b3f1a0368111780d2c" + [[package]] name = "granit-parser" version = "1.2.1" @@ -4263,37 +4475,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "http_client" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" -dependencies = [ - "anyhow", - "async-compression", - "bytes", - "derive_more", - "futures", - "http", - "http-body", - "log", - "parking_lot", - "serde", - "serde_json", - "serde_urlencoded", - "url", -] - -[[package]] -name = "http_client_tls" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" -dependencies = [ - "log", - "rustls", - "rustls-platform-verifier", - "webpki-roots 1.0.9", -] - [[package]] name = "httparse" version = "1.10.1" @@ -5240,20 +5421,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "media" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" -dependencies = [ - "anyhow", - "bindgen", - "core-foundation 0.10.1", - "core-video", - "foreign-types", - "metal", - "objc", -] - [[package]] name = "memchr" version = "2.8.3" @@ -5882,6 +6049,28 @@ dependencies = [ "objc2-foundation 0.3.2", ] +[[package]] +name = "objc2-core-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2" +dependencies = [ + "dispatch2", + "objc2 0.6.4", + "objc2-core-audio-types", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-core-audio-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.4", +] + [[package]] name = "objc2-core-data" version = "0.2.2" @@ -5961,6 +6150,21 @@ dependencies = [ "objc2-foundation 0.3.2", ] +[[package]] +name = "objc2-core-media" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ec576860167a15dd9fce7fbee7512beb4e31f532159d3482d1f9c6caedf31d" +dependencies = [ + "bitflags 2.13.2", + "dispatch2", + "objc2 0.6.4", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-video", +] + [[package]] name = "objc2-core-text" version = "0.3.2" @@ -6085,8 +6289,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74b7c5390f477482f001bc354d6571a70db7e4f8d5288e860c45521fbce11394" dependencies = [ "block2 0.6.2", + "dispatch2", "objc2 0.6.4", "objc2-core-graphics", + "objc2-core-media", "objc2-foundation 0.3.2", ] @@ -6298,7 +6504,7 @@ dependencies = [ name = "paths" version = "0.1.0-alpha" dependencies = [ - "dirs", + "dirs 6.0.0", ] [[package]] @@ -6327,16 +6533,6 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "perf" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" -dependencies = [ - "collections", - "serde", - "serde_json", -] - [[package]] name = "phf" version = "0.11.3" @@ -6679,8 +6875,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.10.0" -source = "git+https://github.com/proptest-rs/proptest?rev=3dca198a8fef1b32e3a66f1e1897c955b4dc5b5b#3dca198a8fef1b32e3a66f1e1897c955b4dc5b5b" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set 0.8.0", "bit-vec 0.8.0", @@ -6699,7 +6896,8 @@ dependencies = [ [[package]] name = "proptest-macro" version = "0.5.0" -source = "git+https://github.com/proptest-rs/proptest?rev=3dca198a8fef1b32e3a66f1e1897c955b4dc5b5b#3dca198a8fef1b32e3a66f1e1897c955b4dc5b5b" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efaa288b896cb2b345da7b7f2110ab19e51565b83495b56fcec98a62f8b1f33e" dependencies = [ "convert_case 0.11.0", "proc-macro2", @@ -7084,6 +7282,17 @@ dependencies = [ "bitflags 2.13.2", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -7115,14 +7324,6 @@ dependencies = [ "syn 3.0.5", ] -[[package]] -name = "refineable" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" -dependencies = [ - "derive_refineable", -] - [[package]] name = "regex" version = "1.13.1" @@ -7198,23 +7399,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "reqwest_client" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" -dependencies = [ - "anyhow", - "bytes", - "futures", - "gpui_util", - "http_client", - "http_client_tls", - "log", - "regex", - "tokio", - "zed-reqwest 0.12.15-zed (git+https://github.com/zed-industries/reqwest.git?rev=33bc764aa15ff7b200bf7c93bd96e24878d53e14)", -] - [[package]] name = "resvg" version = "0.45.1" @@ -7576,22 +7760,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "scheduler" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" -dependencies = [ - "async-task", - "backtrace", - "chrono", - "flume 0.12.0", - "futures", - "parking_lot", - "rand 0.9.5", - "wasm_thread", - "web-time", -] - [[package]] name = "schemars" version = "1.2.2" @@ -7867,7 +8035,7 @@ name = "settings" version = "0.1.0-alpha" dependencies = [ "anyhow", - "gpui", + "gpui-pre", "log", "paths", "serde", @@ -7938,7 +8106,7 @@ version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" dependencies = [ - "dirs", + "dirs 6.0.0", ] [[package]] @@ -7969,11 +8137,11 @@ version = "0.1.0-alpha" dependencies = [ "assets", "dock", - "gpui", "gpui-component", - "gpui_platform", + "gpui-pre", + "gpui-pre-platform", + "gpui-pre-reqwest-client", "paths", - "reqwest_client", "settings", "signed_state", "tracing", @@ -8028,7 +8196,7 @@ dependencies = [ "flume 0.11.1", "futures", "gix", - "gpui", + "gpui-pre", "log", "nostr", "nostr-connect", @@ -8049,9 +8217,9 @@ version = "0.1.0-alpha" dependencies = [ "assets", "futures", - "gpui", "gpui-base", "gpui-component", + "gpui-pre", "log", "signed_core", ] @@ -8292,18 +8460,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "sum_tree" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" -dependencies = [ - "heapless 0.9.3", - "log", - "rayon", - "tracing", - "ztracing", -] - [[package]] name = "sval" version = "2.22.0" @@ -8471,24 +8627,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "syntect" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925" -dependencies = [ - "bincode", - "fancy-regex", - "flate2", - "fnv", - "once_cell", - "regex-syntax", - "serde", - "serde_derive", - "thiserror 2.0.20", - "walkdir", -] - [[package]] name = "sys-locale" version = "0.3.2" @@ -8523,17 +8661,6 @@ dependencies = [ "system-configuration-sys", ] -[[package]] -name = "system-configuration" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501336eb7ba9e417300a6a0fa985721065467aa83a6dcf0422a8e43e4c0328fa" -dependencies = [ - "bitflags 2.13.2", - "core-foundation 0.10.1", - "system-configuration-sys", -] - [[package]] name = "system-configuration-sys" version = "0.6.0" @@ -9667,16 +9794,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "util_macros" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" -dependencies = [ - "perf", - "quote", - "syn 2.0.119", -] - [[package]] name = "utils" version = "0.1.0-alpha" @@ -9896,7 +10013,8 @@ dependencies = [ [[package]] name = "wasm_thread" version = "0.3.3" -source = "git+https://github.com/zed-industries/wasm_thread?rev=0cf96c7708dfb97ccf3da50347e25edcf75d6937#0cf96c7708dfb97ccf3da50347e25edcf75d6937" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7516db7f32decdadb1c3b8deb1b7d78b9df7606c5cc2f6241737c2ab3a0258e" dependencies = [ "futures", "js-sys", @@ -10632,6 +10750,15 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -10659,6 +10786,21 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -10719,6 +10861,12 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -10731,6 +10879,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -10743,6 +10897,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -10767,6 +10927,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -10779,6 +10945,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -10791,6 +10963,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -10803,6 +10981,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -10867,9 +11051,9 @@ dependencies = [ "dock", "futures", "gix", - "gpui", "gpui-base", "gpui-component", + "gpui-pre", "log", "nostr", "paths", @@ -10881,12 +11065,6 @@ dependencies = [ "utils", ] -[[package]] -name = "workspace-hack" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beffa227304dbaea3ad6a06ac674f9bc83a3dec3b7f63eeb442de37e7cb6bb01" - [[package]] name = "writeable" version = "0.6.4" @@ -10954,15 +11132,17 @@ checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" [[package]] name = "xim-ctext" version = "0.3.0" -source = "git+https://github.com/zed-industries/xim-rs.git?rev=16f35a2c881b815a2b6cdfd6687988e84f8447d8#16f35a2c881b815a2b6cdfd6687988e84f8447d8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac61a7062c40f3c37b6e82eeeef835d5cc7824b632a72784a89b3963c33284c" dependencies = [ "encoding_rs", ] [[package]] name = "xim-parser" -version = "0.2.1" -source = "git+https://github.com/zed-industries/xim-rs.git?rev=16f35a2c881b815a2b6cdfd6687988e84f8447d8#16f35a2c881b815a2b6cdfd6687988e84f8447d8" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcee45f89572d5a65180af3a84e7ddb24f5ea690a6d3aa9de231281544dd7b7" dependencies = [ "bitflags 2.13.2", ] @@ -11163,14 +11343,15 @@ dependencies = [ [[package]] name = "zed-font-kit" version = "0.14.1-zed" -source = "git+https://github.com/zed-industries/font-kit?rev=94b0f28166665e8fd2f53ff6d268a14955c82269#94b0f28166665e8fd2f53ff6d268a14955c82269" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3898e450f36f852edda72e3f985c34426042c4951790b23b107f93394f9bff5" dependencies = [ "bitflags 2.13.2", "byteorder", "core-foundation 0.10.1", "core-graphics 0.24.0", "core-text", - "dirs", + "dirs 5.0.1", "dwrote", "float-ord", "freetype-sys", @@ -11184,108 +11365,11 @@ dependencies = [ "yeslogic-fontconfig-sys", ] -[[package]] -name = "zed-reqwest" -version = "0.12.15-zed" -source = "git+https://github.com/zed-industries/reqwest.git?rev=33bc764aa15ff7b200bf7c93bd96e24878d53e14#33bc764aa15ff7b200bf7c93bd96e24878d53e14" -dependencies = [ - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "mime_guess", - "once_cell", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-native-certs", - "rustls-pemfile", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration 0.8.0", - "tokio", - "tokio-rustls", - "tokio-socks", - "tokio-util", - "tower", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "windows-registry 0.4.0", -] - -[[package]] -name = "zed-reqwest" -version = "0.12.15-zed" -source = "git+https://github.com/zed-industries/reqwest.git?rev=c15662463bda39148ba154100dd44d3fba5873a4#c15662463bda39148ba154100dd44d3fba5873a4" -dependencies = [ - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "mime_guess", - "once_cell", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-native-certs", - "rustls-pemfile", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration 0.6.1", - "tokio", - "tokio-rustls", - "tokio-socks", - "tokio-util", - "tower", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "windows-registry 0.4.0", -] - [[package]] name = "zed-scap" version = "0.0.8-zed" -source = "git+https://github.com/zed-industries/scap?rev=4afea48c3b002197176fb19cd0f9b180dd36eaac#4afea48c3b002197176fb19cd0f9b180dd36eaac" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b338d705ae33a43ca00287c11129303a7a0aa57b101b72a1c08c863f698ac8" dependencies = [ "anyhow", "cocoa 0.25.0", @@ -11303,22 +11387,11 @@ dependencies = [ "xcb", ] -[[package]] -name = "zed-sum-tree" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d490156d0d7311855564d6e1d6dccab992405a0c0e15e1c8ef18920c02177e35" -dependencies = [ - "arrayvec", - "log", - "rayon", - "workspace-hack", -] - [[package]] name = "zed-xim" version = "0.4.0-zed" -source = "git+https://github.com/zed-industries/xim-rs.git?rev=16f35a2c881b815a2b6cdfd6687988e84f8447d8#16f35a2c881b815a2b6cdfd6687988e84f8447d8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0b46ed118eba34d9ba53d94ddc0b665e0e06a2cf874cfa2dd5dec278148642" dependencies = [ "ahash", "hashbrown 0.14.5", @@ -11434,39 +11507,12 @@ version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" -[[package]] -name = "zlog" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" -dependencies = [ - "anyhow", - "chrono", - "collections", - "log", -] - [[package]] name = "zmij" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" -[[package]] -name = "ztracing" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" -dependencies = [ - "tracing", - "tracing-subscriber", - "zlog", - "ztracing_macro", -] - -[[package]] -name = "ztracing_macro" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7960b2a7c9568e90fbe0727332149e5b2a5fd57a" - [[package]] name = "zune-core" version = "0.4.12" diff --git a/Cargo.toml b/Cargo.toml index 359ca4e..77fe390 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,15 +9,13 @@ edition = "2024" publish = false [workspace.dependencies] -# GPUI -gpui = { git = "https://github.com/zed-industries/zed" } -gpui_platform = { git = "https://github.com/zed-industries/zed", features = ["font-kit", "x11", "wayland"] } -gpui_tokio = { git = "https://github.com/zed-industries/zed" } -reqwest_client = { git = "https://github.com/zed-industries/zed" } +gpui = { package = "gpui-pre", version = "0.3.6" } +gpui_platform = { package = "gpui-pre-platform", version = "0.3.6", features = ["font-kit", "x11", "wayland"] } +reqwest_client = { package = "gpui-pre-reqwest-client", version = "0.3.6" } # GPUI Kit -gpui-component = { git = "https://github.com/longbridge/gpui-component", rev = "39c2c86dbee7ad445591462f8675f74082e10828", features = ["tree-sitter-languages"], } -gpui-base = { git = "https://github.com/longbridge/gpui-component", rev = "39c2c86dbee7ad445591462f8675f74082e10828" } +gpui-component = { version = "0.6.6", features = ["tree-sitter-languages"] } +gpui-base = "0.6.6" dock = { path = "crates/dock" } settings = { path = "crates/settings" } diff --git a/crates/assets/assets/themes/signed.json b/crates/assets/assets/themes/signed.json index 3c10784..de082cf 100644 --- a/crates/assets/assets/themes/signed.json +++ b/crates/assets/assets/themes/signed.json @@ -77,9 +77,9 @@ "success.foreground": "#052E16", "switch.background": "#CECECE", "switch.thumb.background": "#FFFFFF", - "tab.background": "#00000000", - "tab.active.background": "#EBFFC1", - "tab.active.foreground": "#3F6212", + "tab.background": "#E8E8E8", + "tab.active.background": "#F5F5F5", + "tab.active.foreground": "#18181B", "tab.foreground": "#646464", "tab_bar.background": "#F0F0F0", "tab_bar.segmented.background": "#F0F0F0", @@ -286,9 +286,9 @@ "success.foreground": "#052E16", "switch.background": "#484848", "switch.thumb.background": "#0A0A0A", - "tab.background": "#00000000", - "tab.active.background": "#19200A", - "tab.active.foreground": "#C6FF4D", + "tab.background": "#2E2E2E", + "tab.active.background": "#2A2A2A", + "tab.active.foreground": "#FAFAFA", "tab.foreground": "#B4B4B4", "tab_bar.background": "#191919", "tab_bar.segmented.background": "#191919", diff --git a/crates/dock/src/dock_area.rs b/crates/dock/src/dock_area.rs index de017bd..92060ad 100644 --- a/crates/dock/src/dock_area.rs +++ b/crates/dock/src/dock_area.rs @@ -11,22 +11,19 @@ use gpui::{ }; use gpui_base::dock::{ DockArea, DockAreaRenderer, DockContext, DockEvent, DockPlacement, NodeId, PanelState, - PanelView, TabGroupRenderer, TilesRenderer, + PanelView, TabGroupRenderer, }; use gpui_base::resize_handle; -use gpui_component::scroll::ScrollbarMode; use gpui_component::{ActiveTheme as _, Side}; use crate::invalid_panel::InvalidPanel; +use crate::panel_handle; use crate::tab_panel::SignedTabGroupSkin; -use crate::tiles::SignedTilesSkin; -use crate::{TAB_BAR_HEIGHT, panel_handle}; /// State the skin shares with its per-container renderers. pub(crate) struct SkinShared { area: WeakEntity, toggle_button_visible: Cell, - tiles_scrollbar_mode: Cell>, /// The dock whose resize handle is being dragged, if any. Only one can be. resizing_dock: Cell>, } @@ -40,10 +37,6 @@ impl SkinShared { self.toggle_button_visible.get() } - pub(crate) fn tiles_scrollbar_mode(&self) -> Option { - self.tiles_scrollbar_mode.get() - } - pub(crate) fn resizing_dock(&self) -> &Cell> { &self.resizing_dock } @@ -73,7 +66,6 @@ impl SignedDockSkin { shared: Rc::new(SkinShared { area: cx.weak_entity(), toggle_button_visible: Cell::new(true), - tiles_scrollbar_mode: Cell::new(None), resizing_dock: Cell::new(None), }), }) @@ -92,16 +84,6 @@ impl SignedDockSkin { self.shared.toggle_button_visible.set(visible); self.shared.notify(cx); } - - /// When a tiles canvas shows its scrollbar. `None` follows the theme. - pub fn tiles_scrollbar_mode(&self) -> Option { - self.shared.tiles_scrollbar_mode() - } - - pub fn set_tiles_scrollbar_mode(&self, mode: Option, cx: &mut App) { - self.shared.tiles_scrollbar_mode.set(mode); - self.shared.notify(cx); - } } /// Payload a dock's resize handle drags. @@ -154,16 +136,10 @@ impl DockAreaRenderer for SignedDockSkin { window: &mut Window, cx: &mut App, ) -> AnyElement { - let placement = dock.placement(); - div() .flex() .size_full() .relative() - // A closed bottom dock keeps a strip, and that strip is the tab bar. - .when(!dock.is_open() && placement.is_bottom(), |this| { - this.h(TAB_BAR_HEIGHT) - }) .child(content) .child(self.render_resize_handle(dock, window, cx)) .child(DockResizeTracker { @@ -190,10 +166,6 @@ impl DockAreaRenderer for SignedDockSkin { fn tab_group_renderer(&self) -> Rc { Rc::new(SignedTabGroupSkin::new(self.shared().clone())) } - - fn tiles_renderer(&self) -> Rc { - Rc::new(SignedTilesSkin::new(self.shared().clone())) - } } impl SignedDockSkin { diff --git a/crates/dock/src/lib.rs b/crates/dock/src/lib.rs index 005b376..f073e8f 100644 --- a/crates/dock/src/lib.rs +++ b/crates/dock/src/lib.rs @@ -6,7 +6,6 @@ use gpui_base::dock::PanelView; mod dock_area; mod invalid_panel; mod tab_panel; -mod tiles; mod window_controls; pub use dock_area::SignedDockSkin; diff --git a/crates/dock/src/tab_panel.rs b/crates/dock/src/tab_panel.rs index 774fd9c..85e8f27 100644 --- a/crates/dock/src/tab_panel.rs +++ b/crates/dock/src/tab_panel.rs @@ -5,9 +5,9 @@ use std::time::Duration; use gpui::prelude::FluentBuilder as _; use gpui::{ - Anchor, Animation, AnimationExt as _, AnyElement, AnyView, App, AppContext as _, Bounds, - Context, Div, Empty, InteractiveElement as _, IntoElement, ParentElement as _, Pixels, Point, - Render, ScrollHandle, SharedString, Stateful, StatefulInteractiveElement as _, StyleRefinement, + Animation, AnimationExt as _, AnyElement, AnyView, App, AppContext as _, Bounds, Context, Div, + Empty, InteractiveElement as _, IntoElement, ParentElement as _, Pixels, Point, Render, + ScrollHandle, SharedString, Stateful, StatefulInteractiveElement as _, StyleRefinement, Styled as _, Window, div, px, size, }; use gpui_base::dock::{ @@ -18,17 +18,22 @@ use gpui_base::{ElementExt, InteractiveElementExt, Tab, Tabs}; use gpui_component::animation::{Lerp as _, ease_out_cubic}; use gpui_component::button::{Button, ButtonVariants as _}; use gpui_component::dock::{ClosePanel, PanelControl, PanelHandle, ToggleZoom}; -use gpui_component::menu::DropdownMenu as _; -use gpui_component::{ - ActiveTheme as _, Disableable as _, IconName, Selectable as _, Sizable as _, h_flex, v_flex, -}; +use gpui_component::{ActiveTheme as _, Disableable as _, IconName, Sizable as _, h_flex, v_flex}; use signed_ui::title_bar_drag_handlers; use crate::dock_area::SkinShared; use crate::{TAB_BAR_HEIGHT, t, window_controls}; -/// The drag preview's size, reported to base for the drop placeholder. +/// A rough size for the drag preview, reported to base so a drop placeholder +/// knows where to fly in from. +/// +/// The preview itself fits its content, so this is an estimate used only to +/// place the placeholder. const DRAG_PREVIEW_SIZE: gpui::Size = size(px(96.), px(30.)); +const COLLAPSED_TAB_BAR_SELECTOR: &str = "signed-dock-collapsed-tab-bar"; +const ACTIVE_TAB_SELECTOR: &str = "signed-dock-active-tab"; +const ACTIVE_TAB_CLOSE_SELECTOR: &str = "signed-dock-active-tab-close"; +const TAB_HOVER_GROUP: &str = "signed-dock-tab"; /// A panel's title, or its registered name when the panel has no handle. pub(crate) fn panel_title( @@ -43,24 +48,23 @@ pub(crate) fn panel_title( } /// The preview that follows the cursor while a panel is dragged. +/// /// Base's `DragPanel` is the payload and draws nothing, this is the appearance half. +/// It fits its content rather than a fixed width, so a long title is never cut off. struct DragPanelPreview { panel: Arc, } impl Render for DragPanelPreview { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - div() + h_flex() .id("drag-panel") .cursor_grab() - .py_1() - .px_3() - .w_24() - .overflow_hidden() + .p_1() .whitespace_nowrap() .border_1() .border_color(cx.theme().border) - .rounded(cx.theme().radius) + .rounded(cx.theme().radius_lg) .text_color(cx.theme().tab_foreground) .bg(cx.theme().tokens.tab_active) .opacity(0.75) @@ -69,9 +73,14 @@ impl Render for DragPanelPreview { } /// The zoom affordance for the group's displayed panel, if it offers one. -/// The panel must offer a control and be zoomable, base refuses a zoom otherwise. fn zoom_control(group: &TabGroupContext, cx: &App) -> Option { - let panel = group.active_panel()?; + panel_zoom_control(group.active_panel()?, cx) +} + +/// The zoom affordance `panel` offers, if any. +/// +/// The panel must offer a control and be zoomable, base refuses a zoom otherwise. +fn panel_zoom_control(panel: &Arc, cx: &App) -> Option { panel .zoomable(cx) .then(|| PanelHandle::of(panel).and_then(|handle| handle.zoom_control(cx))) @@ -79,16 +88,17 @@ fn zoom_control(group: &TabGroupContext, cx: &App) -> Option { } /// The left-most, top-most tab group in a container. +/// /// A left dock's collapse button lives in this group. fn left_top_group(node: &PaneNode) -> Option { match node.kind() { PaneRef::Tabs { .. } => Some(node.id()), PaneRef::Split { children, .. } => children.first().and_then(left_top_group), - PaneRef::Tiles { .. } => None, } } /// The right-most, top-most tab group. +/// /// A vertical split picks its first child, a horizontal split picks its last. fn right_top_group(node: &PaneNode) -> Option { match node.kind() { @@ -98,7 +108,53 @@ fn right_top_group(node: &PaneNode) -> Option { gpui::Axis::Horizontal => children.last(), } .and_then(right_top_group), - PaneRef::Tiles { .. } => None, + } +} + +/// The bottom or right dock whose root tab group is this one, if any. +/// +/// Base keeps a dock's last group, so the skin removes these docks as a whole. +fn dock_root_group( + shared: &SkinShared, + group: &TabGroupContext, + cx: &App, +) -> Option { + let area = shared.area().upgrade()?; + let area = area.read(cx); + + [DockPlacement::Bottom, DockPlacement::Right] + .into_iter() + .find(|placement| { + area.layout(*placement) + .is_some_and(|tree| tree.root().id() == group.node()) + }) +} + +/// Close `panel`, removing its dock instead when base would refuse an empty one. +/// +/// A bottom or right dock's only panel is that case, so the whole dock goes. +fn close_panel( + shared: &SkinShared, + group: &TabGroupContext, + panel: &Arc, + window: &mut Window, + cx: &mut App, +) { + if !panel.closable(cx) { + return; + } + + let dock = (group.panels().len() <= 1) + .then(|| dock_root_group(shared, group, cx)) + .flatten(); + + match dock { + Some(placement) => { + _ = shared.area().update(cx, |area, cx| { + area.remove_dock(placement, window, cx); + }); + } + None => group.close(panel.panel_id(cx), window, cx), } } @@ -144,26 +200,14 @@ impl SignedTabGroupSkin { left == group.node() && group.panels().len() == 1 } - /// The bottom or right dock whose root tab group is this one, if any. - /// Base keeps a dock's last group, so the skin removes these docks as a whole. - fn is_dock_root_group(&self, group: &TabGroupContext, cx: &App) -> Option { - let area = self.shared.area().upgrade()?; - let area = area.read(cx); - [DockPlacement::Bottom, DockPlacement::Right] - .into_iter() - .find(|placement| { - area.layout(*placement) - .is_some_and(|tree| tree.root().id() == group.node()) - }) - } - /// The tab's drag payload, or `None` when the group must not be rearranged. + /// /// A locked group never is, a bottom or right dock root always is. fn tab_drag(&self, group: &TabGroupContext, ix: usize, cx: &App) -> Option { if group.is_locked() { return None; } - if !group.is_draggable() && self.is_dock_root_group(group, cx).is_none() { + if !group.is_draggable() && dock_root_group(&self.shared, group, cx).is_none() { return None; } group.drag_panel(ix, cx) @@ -275,95 +319,6 @@ impl SignedTabGroupSkin { ) } - /// The trailing controls, the panel's own buttons, zoom and the ellipsis menu. - fn render_toolbar( - &self, - group: &TabGroupContext, - window: &mut Window, - cx: &mut App, - ) -> impl IntoElement { - if group.is_collapsed() { - return div(); - } - - let zoomed = group.is_zoomed(); - let handle = group.active_panel().and_then(PanelHandle::of); - let control = zoom_control(group, cx); - let toolbar_zoom = control.is_some_and(|control| control.toolbar_visible()); - let menu_zoom = control.is_some_and(|control| control.menu_visible()); - // A bottom or right dock's only panel cannot close through the group. - // The close item is offered, the skin removes the whole dock instead. - let closable = group.is_closable() - || (self.is_dock_root_group(group, cx).is_some() - && group.active_panel().is_some_and(|panel| panel.closable(cx))); - let buttons = handle.and_then(|handle| handle.toolbar_buttons(window, cx)); - let panel = handle.map(|handle| handle.panel()); - - h_flex() - .gap_1() - .occlude() - .when_some(buttons, |this, buttons| { - this.children( - buttons - .into_iter() - .map(|button| button.small().ghost().tab_stop(false)), - ) - }) - .map(|this| { - let value = if zoomed { - Some(("zoom-out", IconName::Minimize, t("Dock.Zoom Out"))) - } else if toolbar_zoom { - Some(("zoom-in", IconName::Maximize, t("Dock.Zoom In"))) - } else { - None - }; - - if let Some((id, icon, tooltip)) = value { - this.child( - Button::new(id) - .icon(icon) - .small() - .ghost() - .tab_stop(false) - .tooltip_with_action(tooltip, &ToggleZoom, None) - .selected(zoomed) - .on_click({ - let group = group.clone(); - move |_, window, cx| group.toggle_zoom(window, cx) - }), - ) - } else { - this - } - }) - .child( - Button::new("menu") - .icon(IconName::Ellipsis) - .small() - .ghost() - .tab_stop(false) - .dropdown_menu(move |menu, window, cx| { - menu.when_some(panel.clone(), |menu, panel| { - panel.dropdown_menu(menu, window, cx) - }) - .separator() - .menu_with_disabled( - if zoomed { - t("Dock.Zoom Out") - } else { - t("Dock.Zoom In") - }, - Box::new(ToggleZoom), - !menu_zoom, - ) - .when(closable, |menu| { - menu.separator().menu(t("Dock.Close"), Box::new(ClosePanel)) - }) - }) - .anchor(Anchor::TopRight), - ) - } - /// One tab of the pill strip. /// While collapsed, tabs lose the active style and all interactions. /// The strip is also how a closed bottom dock is opened again. @@ -382,25 +337,73 @@ impl SignedTabGroupSkin { let droppable = group.is_droppable(); let drag = self.tab_drag(group, ix, cx); let handle = PanelHandle::of(&panel); + let id = SharedString::from(format!("close-tab:{}:{ix}", group.node().as_u64())); + + let closable = !collapsed + && (group.is_closable() + || (dock_root_group(&self.shared, group, cx).is_some() && panel.closable(cx))); + + // Whether a double-click zooms this tab in. + let can_zoom_in = panel_zoom_control(&panel, cx).is_some(); Tab::new(ix) - .h_6() - .px_3() - .text_sm() - .whitespace_nowrap() + .group(TAB_HOVER_GROUP) + .when(active, |this| { + this.debug_selector(|| ACTIVE_TAB_SELECTOR.to_string()) + }) .flex() .items_center() - .justify_center() - .gap_1() .flex_shrink_0() + .h_7() + .min_w_40() + .gap_1() + .px_1p5() + .text_sm() .overflow_hidden() .rounded(cx.theme().radius) - .text_color(cx.theme().foreground) - .map(|this| match handle.and_then(|handle| handle.tab_name(cx)) { - Some(tab_name) => this.child(tab_name), - None => this.child(panel_title(&panel, window, cx)), + .text_color(cx.theme().tab_foreground) + .child( + h_flex() + .flex_1() + .h_6() + .items_center() + .overflow_hidden() + .whitespace_nowrap() + .map(|this| match handle.and_then(|handle| handle.tab_name(cx)) { + Some(tab_name) => this.child(tab_name), + None => this.child(panel_title(&panel, window, cx)), + }), + ) + .when(closable, |this| { + this.child( + div() + .flex_shrink_0() + .when(active, |this| { + this.debug_selector(|| ACTIVE_TAB_CLOSE_SELECTOR.to_string()) + }) + .when(!active, |this| { + this.invisible() + .group_hover(TAB_HOVER_GROUP, |this| this.visible()) + }) + .child( + Button::new(id) + .icon(IconName::Close) + .tooltip(t("Dock.Close")) + .ghost() + .xsmall() + .tab_stop(false) + .on_click({ + let group = group.clone(); + let shared = self.shared.clone(); + let panel = panel.clone(); + move |_, window, cx| { + cx.stop_propagation(); + close_panel(&shared, &group, &panel, window, cx); + } + }), + ), + ) }) - // Pill style, the selected tab is the filled pill, others show only on hover. .styles(|styles| { styles.selected(|style| { style @@ -412,15 +415,15 @@ impl SignedTabGroupSkin { if active { this } else { - this.text_color(cx.theme().secondary_foreground) - .bg(cx.theme().secondary_hover) + this.text_color(cx.theme().tab_foreground) + .bg(cx.theme().tab) } }) .selected(active) .on_click({ let group = group.clone(); let area = self.shared.area().clone(); - move |_, window, cx| { + move |event, window, cx| { group.select_tab(ix, window, cx); // Clicking the strip of a collapsed bottom dock reopens it. @@ -428,7 +431,15 @@ impl SignedTabGroupSkin { _ = area.update(cx, |area, cx| { area.toggle_dock(DockPlacement::Bottom, window, cx); }); + return; } + + // A double-click zooms the tab's panel + if event.click_count() < 2 || (!group.is_zoomed() && !can_zoom_in) { + return; + } + + group.toggle_zoom(window, cx); } }) .when(!collapsed, |this| { @@ -531,10 +542,6 @@ impl TabGroupRenderer for SignedTabGroupSkin { if group.panels().is_empty() { return div().id("tab-panel"); } - // Base refuses an empty dock, so closing its only panel removes the dock. - let dock_to_remove = (group.panels().len() <= 1) - .then(|| self.is_dock_root_group(group, cx)) - .flatten(); let shared = self.shared.clone(); // `v_flex`, a plain `div` ignores `flex_grow` and the content would collapse. @@ -543,14 +550,10 @@ impl TabGroupRenderer for SignedTabGroupSkin { .size_full() .overflow_hidden() .bg(cx.theme().tokens.background) - // A collapsed group has no content, so these actions are not registered. .when(!group.is_collapsed(), |this| { this.on_action({ let group = group.clone(); move |_: &ToggleZoom, window, cx| { - // A panel with no zoom control is not zoomed in by the keybinding. - // Zooming out is never refused. - // Otherwise a zoomed panel that lost its control would strand the user. if !group.is_zoomed() && control.is_none() { return; } @@ -564,18 +567,7 @@ impl TabGroupRenderer for SignedTabGroupSkin { let Some(panel) = group.active_panel() else { return; }; - if !panel.closable(cx) { - return; - } - let panel = panel.panel_id(cx); - match dock_to_remove { - Some(placement) => { - _ = shared.area().update(cx, |area, cx| { - area.remove_dock(placement, window, cx); - }); - } - None => group.close(panel, window, cx), - } + close_panel(&shared, &group, panel, window, cx); } }) }) @@ -613,17 +605,23 @@ impl TabGroupRenderer for SignedTabGroupSkin { let right_dock_button = self.dock_toggle_button(DockPlacement::Right, group, cx); let is_bottom_dock = bottom_dock_button.is_some(); - // On macOS the traffic lights overlay the window's top-left corner. - // Only the tab bar that sits under them reserves the space. - // That is the center's top-left group when the left dock is closed or absent. + // On macOS the traffic lights overlay the window's top-left corner, so the + // tab bar under them reserves the space. That is the center's top-left group + // when the left dock is closed or absent, and the zoomed group otherwise: + // a zoom fills the whole area, hiding the docks the frame would draw. let needs_traffic_light_padding = cfg!(target_os = "macos") && self.shared.area().upgrade().is_some_and(|area| { let area = area.read(cx); - !area.is_dock_open(DockPlacement::Left) - && area - .layout(DockPlacement::Center) - .and_then(|tree| left_top_group(tree.root())) - == Some(group.node()) + match area.zoomed_group() { + Some(zoomed) => zoomed == group.node(), + None => { + !area.is_dock_open(DockPlacement::Left) + && area + .layout(DockPlacement::Center) + .and_then(|tree| left_top_group(tree.root())) + == Some(group.node()) + } + } }); // Bring a newly displayed tab into view. @@ -709,7 +707,16 @@ impl TabGroupRenderer for SignedTabGroupSkin { .child( Tabs::new("tab-bar") .px(px(-1.)) - .h(TAB_BAR_HEIGHT) + // A collapsed group stands in for a closed bottom dock, + // which base gives only its reserved strip. The bar keeps + // its own height there rather than the title-bar height, + // so it fits instead of being clipped away. + .when(!collapsed, |this| this.h(TAB_BAR_HEIGHT)) + // Only a collapsed (bottom) dock tags its bar, so the + // selector is unique when a test measures it. + .when(collapsed, |this| { + this.debug_selector(|| COLLAPSED_TAB_BAR_SELECTOR.to_string()) + }) .flex() .items_center() .text_color(cx.theme().tab_foreground) @@ -763,7 +770,6 @@ impl TabGroupRenderer for SignedTabGroupSkin { .and_then(PanelHandle::of) .and_then(|handle| handle.title_suffix(window, cx)), ) - .child(self.render_toolbar(group, window, cx)) .children(right_dock_button), ) }), @@ -833,3 +839,112 @@ impl TabGroupRenderer for SignedTabGroupSkin { ) } } + +#[cfg(test)] +mod tests { + use gpui::{ + App, AppContext as _, Context, Empty, EventEmitter, FocusHandle, Focusable, IntoElement, + Render, TestAppContext, Window, px, + }; + use gpui_base::dock::{DockArea, DockLayout, DockPlacement, PanelEvent}; + + use super::COLLAPSED_TAB_BAR_SELECTOR; + use crate::{BasePanel, Panel, SignedDockSkin, panel_handle}; + + struct Probe { + focus_handle: FocusHandle, + } + + impl Probe { + fn new(cx: &mut Context) -> Self { + Self { + focus_handle: cx.focus_handle(), + } + } + } + + impl BasePanel for Probe { + fn panel_name(&self) -> &'static str { + "Probe" + } + } + + impl Panel for Probe { + fn title(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + "Probe" + } + } + + impl EventEmitter for Probe {} + + impl Focusable for Probe { + fn focus_handle(&self, _: &App) -> FocusHandle { + self.focus_handle.clone() + } + } + + impl Render for Probe { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + Empty + } + } + + /// Base reserves only `gpui_base::dock::CLOSED_BOTTOM_STRIP` for a closed + /// bottom dock, so the tab bar drawn in place of one has to shrink to that + /// height; at the full title-bar height its bottom was clipped away. + #[gpui::test] + fn a_collapsed_bottom_dock_bar_fits_the_reserved_strip(cx: &mut TestAppContext) { + cx.update(|cx| { + gpui_component::init(cx); + }); + let (area, cx) = cx.add_window_view(|window, cx| { + let skin = SignedDockSkin::new(cx); + DockArea::new("test", None, window, cx).with_renderer(skin) + }); + + cx.update(|window, cx| { + let center = cx.new(Probe::new); + let bottom = cx.new(Probe::new); + area.update(cx, |area, cx| { + area.set_center( + DockLayout::tabs().panel_view(panel_handle(center), cx), + window, + cx, + ); + area.set_dock( + DockPlacement::Bottom, + DockLayout::tabs().panel_view(panel_handle(bottom), cx), + window, + cx, + ); + }); + }); + cx.run_until_parked(); + cx.update(|window, cx| window.draw(cx).clear(cx)); + + // Open, nothing is collapsed, so no bar carries the selector. + assert!(cx.debug_bounds(COLLAPSED_TAB_BAR_SELECTOR).is_none()); + + cx.update(|window, cx| { + area.update(cx, |area, cx| { + area.toggle_dock(DockPlacement::Bottom, window, cx); + }); + }); + cx.run_until_parked(); + cx.update(|window, cx| window.draw(cx).clear(cx)); + + let bounds = cx + .debug_bounds(COLLAPSED_TAB_BAR_SELECTOR) + .expect("a collapsed bottom dock draws its stand-in tab bar"); + // `gpui_base::dock::CLOSED_BOTTOM_STRIP`, which the engine does not + // re-export. The bar used to be `TAB_BAR_HEIGHT` here and its bottom was + // clipped away; it now has to fit inside what base reserves. + let reserved_strip = px(29.); + assert!( + bounds.size.height <= reserved_strip, + "the collapsed bar is {}px tall, more than the {}px strip base reserves", + bounds.size.height, + reserved_strip, + ); + } +} diff --git a/crates/dock/src/tiles.rs b/crates/dock/src/tiles.rs deleted file mode 100644 index 39e6000..0000000 --- a/crates/dock/src/tiles.rs +++ /dev/null @@ -1,388 +0,0 @@ -use std::rc::Rc; - -use gpui::prelude::FluentBuilder as _; -use gpui::{ - AnyElement, App, AppContext as _, Context, Div, DragMoveEvent, Empty, InteractiveElement as _, - IntoElement, MouseButton, MouseDownEvent, ParentElement as _, Pixels, Render, ScrollHandle, - Size, Stateful, StatefulInteractiveElement as _, Styled as _, Window, div, px, -}; -use gpui_base::dock::{ - DRAG_BAR_HEIGHT, HANDLE_SIZE, NodeId, ResizeSide, TileContext, TilesRenderer, -}; -use gpui_component::button::{Button, ButtonVariants as _}; -use gpui_component::dock::PanelHandle; -use gpui_component::menu::{DropdownMenu as _, PopupMenuItem}; -use gpui_component::scroll::Scrollbar; -use gpui_component::{ - ActiveTheme as _, Icon, IconName, Selectable as _, Sizable as _, h_flex, v_flex, -}; - -use crate::dock_area::SkinShared; -use crate::t; -use crate::tab_panel::panel_title; - -/// How far a resize handle sticks out past the tile's edge. -const HANDLE_OFFSET: Pixels = px(-4.); - -/// The payload a tile drag carries, so one canvas ignores another's drags. -#[derive(Clone)] -struct DragMoving(NodeId); - -impl Render for DragMoving { - fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { - Empty - } -} - -/// The payload a tile resize carries, for the same reason. -#[derive(Clone)] -struct DragResizing(NodeId); - -impl Render for DragResizing { - fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { - Empty - } -} - -/// One tiles canvas's appearance. -/// Built once per container, so its scroll position belongs to the canvas it scrolls. -pub(crate) struct SignedTilesSkin { - shared: Rc, - scroll_handle: ScrollHandle, -} - -impl SignedTilesSkin { - pub(crate) fn new(shared: Rc) -> Self { - Self { - shared, - scroll_handle: ScrollHandle::default(), - } - } - - fn resize_handle( - &self, - tile: &TileContext, - id: &'static str, - side: ResizeSide, - build: impl FnOnce(Stateful
) -> Stateful
, - ) -> Stateful
{ - let node = tile.node(); - - build(div().id(id).absolute()) - .on_mouse_down(MouseButton::Left, { - let tile = tile.clone(); - move |event: &MouseDownEvent, window, cx| { - tile.begin_resize(side, event.position, window, cx); - cx.stop_propagation(); - } - }) - .on_drag(DragResizing(node), |drag, _, _, cx| { - cx.stop_propagation(); - cx.new(|_| drag.clone()) - }) - .on_drag_move({ - let tile = tile.clone(); - move |event: &DragMoveEvent, window, cx| { - if event.drag(cx).0 != node { - return; - } - tile.resize_to(event.event.position, window, cx); - } - }) - } - - /// The trailing controls of a tile's title bar, zoom, close and the ellipsis menu. - /// They use click handlers, the zoom and close actions target a focused tab group. - fn render_tile_controls( - &self, - tile: &TileContext, - window: &mut Window, - cx: &mut App, - ) -> impl IntoElement { - let handle = PanelHandle::of(tile.panel()); - let control = handle.and_then(|handle| handle.zoom_control(cx)); - let zoomed = tile.is_zoomed(); - let toolbar_zoom = - tile.is_zoomable() && control.is_some_and(|control| control.toolbar_visible()); - let menu_zoom = tile.is_zoomable() && control.is_some_and(|control| control.menu_visible()); - let closable = tile.is_closable(); - let buttons = handle.and_then(|handle| handle.toolbar_buttons(window, cx)); - let panel = handle.map(|handle| handle.panel()); - - h_flex() - .gap_1() - .flex_shrink_0() - .occlude() - .when_some(buttons, |this, buttons| { - this.children( - buttons - .into_iter() - .map(|button| button.xsmall().ghost().tab_stop(false)), - ) - }) - .when_some( - match (zoomed, toolbar_zoom) { - (true, _) => Some(("zoom-out", IconName::Minimize, t("Dock.Zoom Out"))), - (false, true) => Some(("zoom-in", IconName::Maximize, t("Dock.Zoom In"))), - (false, false) => None, - }, - |this, (id, icon, tooltip)| { - this.child( - Button::new(id) - .icon(icon) - .xsmall() - .ghost() - .tab_stop(false) - .tooltip(tooltip) - .selected(zoomed) - .on_click({ - let tile = tile.clone(); - move |_, window, cx| tile.toggle_zoom(window, cx) - }), - ) - }, - ) - .child( - Button::new("menu") - .icon(IconName::Ellipsis) - .xsmall() - .ghost() - .tab_stop(false) - .dropdown_menu({ - let tile = tile.clone(); - move |menu, window, cx| { - menu.when_some(panel.clone(), |menu, panel| { - panel.dropdown_menu(menu, window, cx) - }) - .separator() - .item( - PopupMenuItem::new(match zoomed { - true => t("Dock.Zoom Out"), - false => t("Dock.Zoom In"), - }) - .disabled(!menu_zoom && !zoomed) - .on_click({ - let tile = tile.clone(); - move |_, window, cx| tile.toggle_zoom(window, cx) - }), - ) - .when(closable, |menu| { - menu.separator() - .item(PopupMenuItem::new(t("Dock.Close")).on_click({ - let tile = tile.clone(); - move |_, window, cx| tile.close(window, cx) - })) - }) - } - }) - .anchor(gpui::Anchor::TopRight), - ) - } -} - -impl TilesRenderer for SignedTilesSkin { - fn frame(&self, _: &mut Window, cx: &mut App) -> Stateful
{ - div() - .id("tiles") - .relative() - .size_full() - .bg(cx.theme().tokens.tiles) - .track_scroll(&self.scroll_handle) - .overflow_scroll() - } - - fn tile_frame(&self, tile: &TileContext, _: &mut Window, cx: &mut App) -> Stateful
{ - v_flex() - .id(("tile", tile.panel_id().as_u64())) - .occlude() - .overflow_hidden() - .bg(cx.theme().tokens.background) - .border_1() - .border_color(cx.theme().border) - .rounded(cx.theme().tile_radius) - // Room for the title bar, which overlays the top padding. - // Base draws the panel as a plain child, this keeps them apart. - .pt(DRAG_BAR_HEIGHT) - // Base stores no bounds on a zoomed tile, the skin decides how it fills the dock. - .when(tile.is_zoomed(), |this| this.size_full()) - .on_mouse_down(MouseButton::Left, { - let tile = tile.clone(); - move |_, window, cx| tile.bring_to_front(window, cx) - }) - // A gesture can end anywhere, so both mouse-up hooks run. - // Each is a no-op unless this tile is the one that moved. - .on_mouse_up(MouseButton::Left, { - let tile = tile.clone(); - move |_, window, cx| { - tile.end_move(window, cx); - tile.end_resize(window, cx); - } - }) - .on_mouse_up_out(MouseButton::Left, { - let tile = tile.clone(); - move |_, window, cx| { - tile.end_move(window, cx); - tile.end_resize(window, cx); - } - }) - } - - fn render_drag_bar(&self, tile: &TileContext, window: &mut Window, cx: &mut App) -> AnyElement { - let node = tile.node(); - let handle = PanelHandle::of(tile.panel()); - let title_style = handle.and_then(|handle| handle.title_style(cx)); - - h_flex() - .id("drag-bar") - .absolute() - .top_0() - .left_0() - .w_full() - .h(DRAG_BAR_HEIGHT) - .items_center() - .gap_1() - .pl_3() - .pr_2() - .when_some(title_style, |this, style| { - this.bg(style.background).text_color(style.foreground) - }) - .child( - div() - .flex_1() - .min_w_16() - .overflow_hidden() - .text_ellipsis() - .whitespace_nowrap() - .child(panel_title(tile.panel(), window, cx)), - ) - .children(handle.and_then(|handle| handle.title_suffix(window, cx))) - .child(self.render_tile_controls(tile, window, cx)) - // A zoomed tile is not at its stored bounds, so moving it would mean nothing. - .when(!tile.is_zoomed(), |this| { - this.cursor_grab() - .on_mouse_down(MouseButton::Left, { - let tile = tile.clone(); - move |event: &MouseDownEvent, window, cx| { - tile.begin_move(event.position, window, cx); - } - }) - .on_drag(DragMoving(node), |drag, _, _, cx| { - cx.stop_propagation(); - cx.new(|_| drag.clone()) - }) - .on_drag_move({ - let tile = tile.clone(); - move |event: &DragMoveEvent, window, cx| { - if event.drag(cx).0 != node { - return; - } - tile.move_to(event.event.position, window, cx); - } - }) - }) - .into_any_element() - } - - fn render_resize_handles( - &self, - tile: &TileContext, - _: &mut Window, - cx: &mut App, - ) -> AnyElement { - let bounds = tile.bounds(); - - // A passive full-tile box, so handles sit against the tile, not its flow neighbours. - // It registers no interaction, so it does not shadow the panel underneath. - div() - .absolute() - .top_0() - .left_0() - .size_full() - .child( - self.resize_handle(tile, "left-resize-handle", ResizeSide::Left, |this| { - this.cursor_ew_resize() - .top_0() - .left(HANDLE_OFFSET) - .w(HANDLE_SIZE) - .h(bounds.size.height) - }), - ) - .child( - self.resize_handle(tile, "right-resize-handle", ResizeSide::Right, |this| { - this.cursor_ew_resize() - .top_0() - .right(HANDLE_OFFSET) - .w(HANDLE_SIZE) - .h(bounds.size.height) - }), - ) - .child( - self.resize_handle(tile, "top-resize-handle", ResizeSide::Top, |this| { - this.cursor_ns_resize() - .left_0() - .top(HANDLE_OFFSET) - .w(bounds.size.width) - .h(HANDLE_SIZE) - }), - ) - .child( - self.resize_handle(tile, "bottom-resize-handle", ResizeSide::Bottom, |this| { - this.cursor_ns_resize() - .left_0() - .bottom(HANDLE_OFFSET) - .w(bounds.size.width) - .h(HANDLE_SIZE) - }), - ) - .child( - Icon::new(IconName::ResizeCorner) - .size_3() - .absolute() - .right(px(1.)) - .bottom(px(1.)) - .text_color(cx.theme().muted_foreground.opacity(0.5)), - ) - .child(self.resize_handle( - tile, - "corner-resize-handle", - ResizeSide::BottomRight, - |this| { - this.cursor_nwse_resize() - .right(HANDLE_OFFSET) - .bottom(HANDLE_OFFSET) - .size_3() - }, - )) - .into_any_element() - } - - /// Gives the tile's panel `size_full`, base draws it as a plain child otherwise. - fn panel_frame(&self, tile: &TileContext, _: &mut Window, _: &mut App) -> Stateful
{ - h_flex() - .id(("tile-panel", tile.panel_id().as_u64())) - .overflow_hidden() - .size_full() - } - - /// The canvas scrollbar, as an overlay. - /// Placed inside the frame it would end up underneath every tile. - fn render_overlay( - &self, - content: Size, - _: &mut Window, - _: &mut App, - ) -> Option { - Some( - Scrollbar::new(&self.scroll_handle) - .scroll_size(content) - .when_some(self.shared.tiles_scrollbar_mode(), |this, mode| { - this.mode(mode) - }) - .into_any_element(), - ) - } - - fn grid_size(&self, cx: &App) -> Pixels { - cx.theme().tile_grid_size - } -} diff --git a/crates/signed_ui/src/user_avatar.rs b/crates/signed_ui/src/avatar.rs similarity index 51% rename from crates/signed_ui/src/user_avatar.rs rename to crates/signed_ui/src/avatar.rs index c080a56..a866b33 100644 --- a/crates/signed_ui/src/user_avatar.rs +++ b/crates/signed_ui/src/avatar.rs @@ -1,21 +1,26 @@ use gpui::prelude::*; use gpui::{App, SharedString, StyleRefinement, Window}; -use gpui_component::avatar::Avatar; +use gpui_base::{Avatar as BaseAvatar, AvatarFallback, AvatarImage}; use gpui_component::{ActiveTheme, Sizable, Size, StyledExt}; -/// A small user avatar from gpui-component [`Avatar`], rounded with the theme radius. -/// It shows the user's picture or falls back to name initials. +use crate::pixel_avatar::{PixelAvatar, side_length}; + +/// A user avatar built on the unstyled [`gpui_base::Avatar`]. +/// +/// It shows the user's picture when one is set, and falls back to the +/// deterministic pixel avatar seeded from the name otherwise. #[derive(IntoElement)] -pub struct UserAvatar { +pub struct Avatar { name: SharedString, picture: Option, size: Size, style: StyleRefinement, } -impl UserAvatar { +impl Avatar { /// Create an avatar for `name`. - /// The name seeds the initials fallback shown when no picture is set. + /// + /// The name seeds the pixel fallback shown when no picture is set. pub fn new(name: impl Into) -> Self { Self { name: name.into(), @@ -32,26 +37,35 @@ impl UserAvatar { } } -impl Sizable for UserAvatar { +impl Sizable for Avatar { fn with_size(mut self, size: impl Into) -> Self { self.size = size.into(); self } } -impl Styled for UserAvatar { +impl Styled for Avatar { fn style(&mut self) -> &mut StyleRefinement { &mut self.style } } -impl RenderOnce for UserAvatar { +impl RenderOnce for Avatar { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { - Avatar::new() - .name(self.name) - .when_some(self.picture, |this, url| this.src(url)) + let fallback = AvatarFallback::new() + .size_full() + .child(PixelAvatar::new(self.name.clone()).size_full()); + + BaseAvatar::new() + .size(side_length(self.size)) + .flex_shrink_0() .rounded(cx.theme().radius) + .overflow_hidden() + .bg(cx.theme().secondary) + .when_some(self.picture, |this, url| { + this.image(AvatarImage::new(url).size_full().rounded(cx.theme().radius)) + }) + .fallback(fallback) .refine_style(&self.style) - .with_size(self.size) } } diff --git a/crates/signed_ui/src/lib.rs b/crates/signed_ui/src/lib.rs index 7c63981..bad4e4d 100644 --- a/crates/signed_ui/src/lib.rs +++ b/crates/signed_ui/src/lib.rs @@ -1,3 +1,4 @@ +mod avatar; mod dropdown_button; mod nav_item; mod pixel_avatar; @@ -8,11 +9,11 @@ mod setting; mod status_badge; mod title_bar; mod tree_row; -mod user_avatar; pub mod copy_row; pub mod util; +pub use avatar::Avatar; pub use copy_row::{copy_row, menu_copy_row}; pub use dropdown_button::DropdownButton; pub use nav_item::NavItem; @@ -24,5 +25,4 @@ pub use setting::{SelectOption, setting_block, setting_row}; pub use status_badge::status_badge; pub use title_bar::title_bar_drag_handlers; pub use tree_row::tree_row; -pub use user_avatar::UserAvatar; pub use util::middle_truncate; diff --git a/crates/signed_ui/src/pixel_avatar.rs b/crates/signed_ui/src/pixel_avatar.rs index 18a56f0..22dad72 100644 --- a/crates/signed_ui/src/pixel_avatar.rs +++ b/crates/signed_ui/src/pixel_avatar.rs @@ -5,7 +5,10 @@ use gpui_component::{ActiveTheme, Colorize, Sizable, Size}; /// Number of rows and columns in the pixel grid. const GRID_SIZE: usize = 8; -/// Probability that a cell in the left half is filled. +/// Empty cells kept between the pattern and the avatar edge, so the art +/// gathers in the center instead of filling the whole avatar. +const MARGIN: usize = 1; +/// Probability that a cell in the left half of the pattern area is filled. const FILL_PROBABILITY: f32 = 0.42; /// Probability that a filled cell uses the accent shade instead of the main color. const ACCENT_PROBABILITY: f32 = 0.25; @@ -22,12 +25,10 @@ pub struct PixelAvatar { impl PixelAvatar { /// Create an avatar seeded from `seed`. - /// - /// The seed should be a stable string unique to the entity the avatar represents. pub fn new(seed: impl AsRef) -> Self { Self { seed: fnv1a(seed.as_ref().as_bytes()), - size: Size::XSmall, + size: Size::Small, style: StyleRefinement::default(), } } @@ -79,19 +80,21 @@ impl RenderOnce for PixelAvatar { } div() - .refine_style(&self.style) .grid() .grid_cols(GRID_SIZE as u16) .grid_rows(GRID_SIZE as u16) .size(side_length(self.size)) .flex_shrink_0() + .rounded(theme.radius) .overflow_hidden() .bg(main.opacity(0.16)) .children(cells) + .refine_style(&self.style) } } -fn side_length(size: Size) -> Pixels { +/// The rendered side length of an avatar at `size`, shared with [`Avatar`]. +pub(crate) fn side_length(size: Size) -> Pixels { match size { Size::XSmall => px(16.), Size::Small => px(24.), @@ -106,8 +109,13 @@ fn pattern(seed: u64) -> [u8; GRID_SIZE * GRID_SIZE] { let mut pattern = [0u8; GRID_SIZE * GRID_SIZE]; let mut filled = 0usize; - for row in 0..GRID_SIZE { - for col in 0..GRID_SIZE / 2 { + // Only the inner rows and the inner left half are candidates; mirroring + // then keeps the art within the same inset, leaving the outer ring empty. + let art_rows = GRID_SIZE - 2 * MARGIN; + let art_columns = GRID_SIZE / 2 - MARGIN; + + for row in MARGIN..GRID_SIZE - MARGIN { + for col in MARGIN..GRID_SIZE / 2 { if rng.chance(FILL_PROBABILITY) { let accent = rng.chance(ACCENT_PROBABILITY); set_cell(&mut pattern, row, col, if accent { 2 } else { 1 }); @@ -117,17 +125,17 @@ fn pattern(seed: u64) -> [u8; GRID_SIZE * GRID_SIZE] { } if filled < MIN_FILLED { - let half = GRID_SIZE * GRID_SIZE / 2; - let start = (rng.next() % half as u64) as usize; + let total = art_rows * art_columns; + let start = (rng.next() % total as u64) as usize; - for offset in 0..half { + for offset in 0..total { if filled >= MIN_FILLED { break; } - let ix = (start + offset) % half; - let row = ix / (GRID_SIZE / 2); - let col = ix % (GRID_SIZE / 2); + let ix = (start + offset) % total; + let row = MARGIN + ix / art_columns; + let col = MARGIN + ix % art_columns; if pattern[row * GRID_SIZE + col] == 0 { set_cell(&mut pattern, row, col, 1); diff --git a/crates/workspace/src/views/commit_diff/mod.rs b/crates/workspace/src/views/commit_diff/mod.rs index 98ad225..b907adb 100644 --- a/crates/workspace/src/views/commit_diff/mod.rs +++ b/crates/workspace/src/views/commit_diff/mod.rs @@ -18,10 +18,12 @@ use gpui_component::{ ActiveTheme, Sizable, StyledExt, VirtualListScrollHandle, h_flex, v_flex, v_virtual_list, }; use signed_git::{CommitDiff, DiffHunk, DiffLine, DiffLineKind, DiffStatus, FileCommit, FileDiff}; +use signed_state::RepoStore; use signed_ui::{placeholder, tree_row}; use utils::relative_time_secs; use crate::views::tree::{build_tree_items, tree_items}; +use crate::views::{repo_tab_avatar, tab_title}; const TREE_WIDTH: f32 = 260.; @@ -293,6 +295,7 @@ impl Render for DiffPane { pub struct CommitDiffView { focus_handle: FocusHandle, + store: Entity, worktree: PathBuf, repo_name: SharedString, commit: FileCommit, @@ -304,6 +307,7 @@ pub struct CommitDiffView { impl CommitDiffView { pub fn new( + store: Entity, worktree: PathBuf, repo_name: SharedString, commit_id: String, @@ -319,6 +323,7 @@ impl CommitDiffView { Self { focus_handle: cx.focus_handle(), + store, worktree, repo_name, commit: FileCommit { @@ -465,11 +470,11 @@ impl BasePanel for CommitDiffView { } impl Panel for CommitDiffView { - fn title(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { - div().text_sm().child(SharedString::from(format!( - "{}/{}", - self.repo_name, self.commit.id - ))) + fn title(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let avatar = repo_tab_avatar(self.store.read(cx), cx); + let label = SharedString::from(format!("{}/{}", self.repo_name, self.commit.id)); + + tab_title(avatar, label) } } diff --git a/crates/workspace/src/views/discussion.rs b/crates/workspace/src/views/discussion.rs index 8f52a56..771c44e 100644 --- a/crates/workspace/src/views/discussion.rs +++ b/crates/workspace/src/views/discussion.rs @@ -7,7 +7,7 @@ use gpui_component::tag::Tag; use gpui_component::{ActiveTheme, Icon, Sizable, StyledExt, h_flex, v_flex}; use nostr::prelude::{Event, EventId, PublicKey}; use signed_state::{ProfileStore, RepoStore}; -use signed_ui::UserAvatar; +use signed_ui::Avatar; use utils::relative_time; pub(crate) fn issue_roots(store: &RepoStore) -> &[Event] { @@ -71,7 +71,7 @@ pub(crate) fn sidebar_section( h_flex() .gap_1() .items_center() - .child(UserAvatar::new(name.clone()).picture(picture)) + .child(Avatar::new(name.clone()).picture(picture)) .child(div().text_sm().truncate().text_ellipsis().child(name)) .into_any_element() })), @@ -137,7 +137,7 @@ pub(crate) fn comments_section(store: &Entity, root: EventId, cx: &Ap .child( h_flex() .gap_1() - .child(UserAvatar::new(author.clone()).picture(picture)) + .child(Avatar::new(author.clone()).picture(picture)) .child(author), ) .child( diff --git a/crates/workspace/src/views/inbox.rs b/crates/workspace/src/views/inbox.rs index ad96522..38fe63a 100644 --- a/crates/workspace/src/views/inbox.rs +++ b/crates/workspace/src/views/inbox.rs @@ -15,10 +15,10 @@ use signed_core::{COVER_NOTE_KIND, InboxItem, InboxReadState, RepoAddr, filters} use signed_state::{ Backend, BackendEvent, ProfileStore, RefreshGate, RefreshRequest, RepoListStore, query_inbox, }; -use signed_ui::{CountBadge, UserAvatar}; +use signed_ui::{Avatar, CountBadge}; use utils::relative_time; -use super::{RepoItem, open_repo_item}; +use super::{RepoItem, open_repo_item, panel_avatar, tab_title}; const LIST_OVERDRAW: Pixels = px(400.); const MAX_SUB_ACTIVITIES: usize = 5; @@ -553,7 +553,7 @@ fn sub_activity(event: &Event, me: Option, cx: &App) -> AnyElement { .items_center() .text_xs() .child( - UserAvatar::new(name.clone()) + Avatar::new(name.clone()) .picture(profile.picture()) .xsmall(), ) @@ -619,7 +619,7 @@ impl BasePanel for InboxView { impl Panel for InboxView { fn title(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { - div().text_sm().child(SharedString::from("Inbox")) + tab_title(panel_avatar("inbox"), SharedString::from("Inbox")) } } diff --git a/crates/workspace/src/views/issues/detail.rs b/crates/workspace/src/views/issues/detail.rs index ecb78cb..a401252 100644 --- a/crates/workspace/src/views/issues/detail.rs +++ b/crates/workspace/src/views/issues/detail.rs @@ -10,10 +10,11 @@ use gpui_component::{ActiveTheme, StyledExt, h_flex, v_flex}; use nostr::prelude::EventId; use signed_core::activity_subject; use signed_state::{ProfileStore, RepoStore}; -use signed_ui::{UserAvatar, placeholder, status_badge}; +use signed_ui::{Avatar, placeholder, status_badge}; use utils::relative_time; use crate::views::discussion::{comment_form, comments_section, issue_roots, sidebar_section}; +use crate::views::{repo_tab_avatar, tab_title}; pub struct IssueDetailView { focus_handle: FocusHandle, @@ -65,7 +66,9 @@ impl Panel for IssueDetailView { }) .unwrap_or_else(|| SharedString::from("Issue")); - div().text_sm().child(short_id) + let avatar = repo_tab_avatar(self.store.read(cx), cx); + + tab_title(avatar, short_id) } } @@ -150,9 +153,7 @@ impl Render for IssueDetailView { .child( h_flex() .gap_1() - .child( - UserAvatar::new(&author).picture(picture), - ) + .child(Avatar::new(&author).picture(picture)) .child(author), ) .child( diff --git a/crates/workspace/src/views/issues/mod.rs b/crates/workspace/src/views/issues/mod.rs index 5100175..b082548 100644 --- a/crates/workspace/src/views/issues/mod.rs +++ b/crates/workspace/src/views/issues/mod.rs @@ -18,13 +18,14 @@ use gpui_component::{ use nostr::prelude::EventId; use signed_core::{RepoStatus, activity_subject}; use signed_state::{ProfileStore, RepoStore}; -use signed_ui::{SegmentButton, UserAvatar, placeholder, status_badge}; +use signed_ui::{Avatar, SegmentButton, placeholder, status_badge}; use utils::relative_time; pub(super) mod detail; use self::detail::IssueDetailView; use super::status_list::{StatusCounts, filter_by_status}; +use crate::views::{repo_tab_avatar, tab_title}; const ISSUE_ROW_HEIGHT: f32 = 73.; @@ -188,7 +189,7 @@ impl IssuesView { .child( h_flex() .gap_1() - .child(UserAvatar::new(author.clone()).picture(picture)) + .child(Avatar::new(author.clone()).picture(picture)) .child(div().child(author)), ) .child(SharedString::from("opened")) @@ -333,8 +334,11 @@ impl BasePanel for IssuesView { } impl Panel for IssuesView { - fn title(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { - div().child(SharedString::from(format!("{}/issues", self.repo_name))) + fn title(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let avatar = repo_tab_avatar(self.store.read(cx), cx); + let label = SharedString::from(format!("{}/issues", self.repo_name)); + + tab_title(avatar, label) } } diff --git a/crates/workspace/src/views/mod.rs b/crates/workspace/src/views/mod.rs index 6c8caa1..b1ae8a1 100644 --- a/crates/workspace/src/views/mod.rs +++ b/crates/workspace/src/views/mod.rs @@ -11,8 +11,53 @@ pub(crate) mod sidebar; mod status_list; pub(crate) mod tree; +use gpui::prelude::*; +use gpui::{AnyElement, App, div}; +use gpui_component::{Sizable as _, h_flex}; pub use inbox::InboxView; pub use repo::RepoDetailView; pub(crate) use repo::{RepoItem, open_repo_item, open_repo_panel}; pub use repo_list::RepoListView; pub use sidebar::SidebarPanel; +use signed_state::{ProfileStore, RepoStore}; +use signed_ui::{Avatar, PixelAvatar}; + +pub(crate) fn tab_title(avatar: AnyElement, label: impl IntoElement) -> impl IntoElement { + h_flex() + .gap_1p5() + .items_center() + .child(div().flex_shrink_0().child(avatar)) + .child(label) +} + +pub(crate) fn panel_avatar(seed: impl AsRef) -> AnyElement { + PixelAvatar::new(seed).xsmall().into_any_element() +} + +pub(crate) fn repo_tab_avatar(store: &RepoStore, cx: &App) -> AnyElement { + let seed = store + .announcement + .as_ref() + .map(|announcement| format!("{}:{}", announcement.owner, announcement.id)) + .or_else(|| store.addr().map(|addr| addr.to_string())) + .or_else(|| { + store + .path + .as_ref() + .map(|path| path.to_string_lossy().into_owned()) + }) + .unwrap_or_default(); + + let owner = store + .announcement + .as_ref() + .map(|announcement| ProfileStore::global(cx).read(cx).get(&announcement.owner)); + + match owner.and_then(|profile| profile.picture().map(|picture| (profile.name(), picture))) { + Some((name, picture)) => Avatar::new(name) + .picture(Some(picture)) + .xsmall() + .into_any_element(), + None => PixelAvatar::new(seed).xsmall().into_any_element(), + } +} diff --git a/crates/workspace/src/views/pull_requests/detail.rs b/crates/workspace/src/views/pull_requests/detail.rs index 0a45185..89dd0e4 100644 --- a/crates/workspace/src/views/pull_requests/detail.rs +++ b/crates/workspace/src/views/pull_requests/detail.rs @@ -27,11 +27,12 @@ use signed_core::{ }; use signed_git::{FileCommit, patch_commits, patch_diffs}; use signed_state::{Backend, ProfileStore, RepoStore, ensure_repo_mirror}; -use signed_ui::{CountBadge, UserAvatar, placeholder, status_badge}; +use signed_ui::{Avatar, CountBadge, placeholder, status_badge}; use utils::{relative_time, relative_time_secs}; use crate::views::commit_diff::{CommitDiffView, DiffPane}; use crate::views::discussion::{comment_form, comments_section, pr_roots, sidebar_section}; +use crate::views::{repo_tab_avatar, tab_title}; const ROW_HEIGHT: f32 = 37.; @@ -343,6 +344,7 @@ impl PullRequestDetailView { let panel = cx.new(|cx| { CommitDiffView::new( + self.store.clone(), worktree, self.repo_name.clone(), commit_id.into(), @@ -451,7 +453,7 @@ impl PullRequestDetailView { h_flex() .gap_1() .child( - UserAvatar::new(author.clone()) + Avatar::new(author.clone()) .picture(picture), ) .child(author), @@ -788,15 +790,17 @@ impl BasePanel for PullRequestDetailView { } impl Panel for PullRequestDetailView { - fn title(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { + fn title(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let avatar = repo_tab_avatar(self.store.read(cx), cx); let hex = self.pr_id.to_hex(); let id = SharedString::from(&hex[..8]); - let title = if self.repo_name.is_empty() { + let label = if self.repo_name.is_empty() { id } else { SharedString::from(format!("{}/{}", self.repo_name, id)) }; - div().text_sm().child(title) + + tab_title(avatar, label) } } diff --git a/crates/workspace/src/views/pull_requests/mod.rs b/crates/workspace/src/views/pull_requests/mod.rs index f27af31..b14bf99 100644 --- a/crates/workspace/src/views/pull_requests/mod.rs +++ b/crates/workspace/src/views/pull_requests/mod.rs @@ -16,7 +16,7 @@ use gpui_component::{ use nostr::prelude::{EventId, Kind}; use signed_core::{RepoStatus, activity_subject}; use signed_state::{ProfileStore, RepoStore}; -use signed_ui::{DropdownButton, SegmentButton, UserAvatar, placeholder, status_badge}; +use signed_ui::{Avatar, DropdownButton, SegmentButton, placeholder, status_badge}; use utils::relative_time; pub(super) mod detail; @@ -27,6 +27,7 @@ use self::new::open_new_pull_panel; use super::send_patch::open_send_patch_panel; use super::status_list::{StatusCounts, filter_by_status}; use crate::views::repo::RepoAction; +use crate::views::{repo_tab_avatar, tab_title}; const ROW_HEIGHT: f32 = 73.; @@ -202,7 +203,7 @@ impl PullRequestsView { .child( h_flex() .gap_1() - .child(UserAvatar::new(author.clone()).picture(picture)) + .child(Avatar::new(author.clone()).picture(picture)) .child(div().child(author)), ) .child(SharedString::from("opened")) @@ -333,11 +334,11 @@ impl BasePanel for PullRequestsView { } impl Panel for PullRequestsView { - fn title(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { - div().child(SharedString::from(format!( - "{}/pull-requests", - self.repo_name - ))) + fn title(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let avatar = repo_tab_avatar(self.store.read(cx), cx); + let label = SharedString::from(format!("{}/pulls", self.repo_name)); + + tab_title(avatar, label) } } diff --git a/crates/workspace/src/views/pull_requests/new.rs b/crates/workspace/src/views/pull_requests/new.rs index 5a5f238..a3e3ef8 100644 --- a/crates/workspace/src/views/pull_requests/new.rs +++ b/crates/workspace/src/views/pull_requests/new.rs @@ -32,6 +32,7 @@ use signed_state::{ use signed_ui::{CountBadge, placeholder, ref_selector_trigger}; use crate::views::commit_diff::{COMMIT_ROW_HEIGHT, CommitDiffView, DiffPane, commit_row}; +use crate::views::{repo_tab_avatar, tab_title}; pub struct NewPullRequestView { focus_handle: FocusHandle, @@ -924,6 +925,7 @@ impl NewPullRequestView { let panel = cx.new(|cx| { CommitDiffView::new( + self.store.clone(), repo_path, self.repo_name.clone(), commit_id.into(), @@ -1323,11 +1325,11 @@ impl BasePanel for NewPullRequestView { } impl Panel for NewPullRequestView { - fn title(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { - div().child(SharedString::from(format!( - "{}/new-pull-request", - self.repo_name - ))) + fn title(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let avatar = repo_tab_avatar(self.store.read(cx), cx); + let label = SharedString::from(format!("{}/new-pull-request", self.repo_name)); + + tab_title(avatar, label) } } diff --git a/crates/workspace/src/views/repo/about.rs b/crates/workspace/src/views/repo/about.rs index 3b8b7e4..bfd6fbc 100644 --- a/crates/workspace/src/views/repo/about.rs +++ b/crates/workspace/src/views/repo/about.rs @@ -5,7 +5,7 @@ use gpui_component::{ActiveTheme, StyledExt, WindowExt, h_flex, v_flex}; use nostr::prelude::PublicKey; use signed_core::Announcement; use signed_state::ProfileStore; -use signed_ui::{UserAvatar, middle_truncate}; +use signed_ui::{Avatar, middle_truncate}; pub(super) fn open_about_dialog(announcement: Announcement, window: &mut Window, cx: &mut App) { window.open_dialog(cx, move |dialog, _window, cx| { @@ -173,7 +173,7 @@ fn maintainers(maintainers: &[PublicKey], cx: &App) -> AnyElement { .gap_2() .items_center() .min_w_0() - .child(UserAvatar::new(name.clone()).picture(picture)) + .child(Avatar::new(name.clone()).picture(picture)) .child( div() .flex_1() diff --git a/crates/workspace/src/views/repo/history.rs b/crates/workspace/src/views/repo/history.rs index 4ec8db5..21360ed 100644 --- a/crates/workspace/src/views/repo/history.rs +++ b/crates/workspace/src/views/repo/history.rs @@ -106,8 +106,10 @@ impl RepoHistoryView { // Same display name as the repo detail panel's title. let repo_name = repo_display_name(self.store.read(cx)); - let panel = - cx.new(|cx| CommitDiffView::new(worktree, repo_name, commit_id.into(), window, cx)); + let store = self.store.clone(); + let panel = cx.new(|cx| { + CommitDiffView::new(store, worktree, repo_name, commit_id.into(), window, cx) + }); dock_area.update(cx, |dock_area, cx| { add_center_panel(dock_area, panel_handle(panel), window, cx); diff --git a/crates/workspace/src/views/repo/mod.rs b/crates/workspace/src/views/repo/mod.rs index 9007ea3..8161042 100644 --- a/crates/workspace/src/views/repo/mod.rs +++ b/crates/workspace/src/views/repo/mod.rs @@ -31,7 +31,7 @@ use signed_state::{ pr_proposes_checkout, }; use signed_ui::{ - CountBadge, DropdownButton, PixelAvatar, UserAvatar, copy_row, menu_copy_row, middle_truncate, + Avatar, CountBadge, DropdownButton, PixelAvatar, copy_row, menu_copy_row, middle_truncate, ref_selector_trigger, }; @@ -55,6 +55,7 @@ use crate::views::pull_requests::new::open_new_pull_panel; use crate::views::repo::about::open_about_dialog; use crate::views::send_patch::open_send_patch_panel; use crate::views::tree::{TreeItemSeed, build_tree_items, sorted_worktree_paths}; +use crate::views::{repo_tab_avatar, tab_title}; #[derive(Clone, Copy, PartialEq, Eq)] enum RefKind { @@ -1490,7 +1491,7 @@ impl RepoDetailView { .child( h_flex() .gap_1() - .child(UserAvatar::new(owner_name.clone()).picture(owner_picture)) + .child(Avatar::new(owner_name.clone()).picture(owner_picture)) .child(div().text_xs().whitespace_nowrap().child(owner_name)), ) .when(!rest.is_empty(), |this| { @@ -2031,7 +2032,7 @@ fn bound_repo_label(binding: &Nip34Binding, cx: &App) -> AnyElement { h_flex() .gap_1() .text_color(cx.theme().foreground) - .child(UserAvatar::new(profile.name()).picture(profile.picture())) + .child(Avatar::new(profile.name()).picture(profile.picture())) .child(profile.name()), ) }) @@ -2054,7 +2055,11 @@ impl BasePanel for RepoDetailView { impl Panel for RepoDetailView { fn title(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - repo_display_name(self.store.read(cx)) + let store = self.store.read(cx); + let name = repo_display_name(store); + let avatar = repo_tab_avatar(store, cx); + + tab_title(avatar, name) } } diff --git a/crates/workspace/src/views/repo_list.rs b/crates/workspace/src/views/repo_list.rs index ed61ab1..2fde423 100644 --- a/crates/workspace/src/views/repo_list.rs +++ b/crates/workspace/src/views/repo_list.rs @@ -16,10 +16,10 @@ use gpui_component::{ }; use signed_core::Announcement; use signed_state::{ProfileStore, RepoListStore, Timestamp}; -use signed_ui::{SegmentButton, UserAvatar}; +use signed_ui::{Avatar, SegmentButton}; use utils::relative_time; -use super::open_repo_panel; +use super::{open_repo_panel, panel_avatar, tab_title}; const COLUMNS: usize = 2; const CARD_HEIGHT: f32 = 40. + 64. + 48. + 2. + 6.; @@ -289,7 +289,7 @@ impl RepoListView { h_flex() .gap_2() .items_center() - .child(UserAvatar::new(owner.name()).picture(owner.picture())) + .child(Avatar::new(owner.name()).picture(owner.picture())) .child( div() .text_xs() @@ -389,7 +389,7 @@ impl BasePanel for RepoListView { impl Panel for RepoListView { fn title(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { - div().text_sm().child(SharedString::from("Explore")) + tab_title(panel_avatar("explore"), SharedString::from("Explore")) } } diff --git a/crates/workspace/src/views/send_patch.rs b/crates/workspace/src/views/send_patch.rs index ff5958e..e174458 100644 --- a/crates/workspace/src/views/send_patch.rs +++ b/crates/workspace/src/views/send_patch.rs @@ -11,6 +11,8 @@ use gpui_component::spinner::Spinner; use gpui_component::{ActiveTheme, Icon, IconName, Sizable, h_flex, v_flex}; use signed_state::RepoStore; +use crate::views::{repo_tab_avatar, tab_title}; + pub struct SendPatchView { focus_handle: FocusHandle, dock_area: WeakEntity, @@ -205,8 +207,11 @@ impl BasePanel for SendPatchView { } impl Panel for SendPatchView { - fn title(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { - div().child(SharedString::from(format!("{}/send-patch", self.repo_name))) + fn title(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let avatar = repo_tab_avatar(self.store.read(cx), cx); + let label = SharedString::from(format!("{}/send-patch", self.repo_name)); + + tab_title(avatar, label) } } diff --git a/crates/workspace/src/views/sidebar/mod.rs b/crates/workspace/src/views/sidebar/mod.rs index 4f0804d..fa4eaf9 100644 --- a/crates/workspace/src/views/sidebar/mod.rs +++ b/crates/workspace/src/views/sidebar/mod.rs @@ -23,7 +23,7 @@ use signed_state::{ Backend, BackendEvent, CheckoutsStore, LocalReposStore, Nip34Binding, Nip34Kind, Profile, ProfileStore, RepoListStore, ResolvedLocalRepo, resolve_local_repos, }; -use signed_ui::{NavItem, PixelAvatar, UserAvatar, title_bar_drag_handlers}; +use signed_ui::{Avatar, NavItem, PixelAvatar, title_bar_drag_handlers}; use super::{InboxView, RepoDetailView, RepoListView, open_repo_panel}; @@ -447,7 +447,9 @@ impl SidebarPanel { cx: &mut Context, ) -> impl IntoElement { let name = announcement.name().map(SharedString::from); - let avatar = PixelAvatar::new(format!("{}:{}", announcement.owner, announcement.id)); + // The sidebar rows are compact, so the avatar matches the 16px nav icons. + let avatar = + PixelAvatar::new(format!("{}:{}", announcement.owner, announcement.id)).xsmall(); let announcement = announcement.clone(); let unpushed = self @@ -552,7 +554,7 @@ impl SidebarPanel { Button::new("user").text().dropdown_caret(true).child( h_flex() .gap_1() - .child(UserAvatar::new(name.clone()).picture(picture)) + .child(Avatar::new(name.clone()).picture(picture)) .child(div().text_xs().font_semibold().child(name)), ), ), @@ -654,7 +656,8 @@ pub(super) fn server_host(relay: &RelayUrl) -> SharedString { /// The repository's pixel avatar, with the bound owner's avatar at its bottom right. fn local_avatar(entry: &ResolvedLocalRepo, cx: &App) -> AnyElement { - let avatar = PixelAvatar::new(entry.path.to_string_lossy()); + // Matches the other sidebar rows, and the badge below sits at its corner. + let avatar = PixelAvatar::new(entry.path.to_string_lossy()).xsmall(); let Some(owner) = entry.nip34.as_ref().and_then(|binding| binding.owner) else { return avatar.into_any_element(); @@ -668,7 +671,7 @@ fn local_avatar(entry: &ResolvedLocalRepo, cx: &App) -> AnyElement { .child(avatar) .child( div().absolute().bottom_neg_0p5().right_neg_0p5().child( - UserAvatar::new(profile.name()) + Avatar::new(profile.name()) .picture(profile.picture()) .size(px(14.)), ),