diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3226997656..617fdaf00e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,7 +99,7 @@ jobs: - name: Build other binaries and features run: cargo build --workspace --bins --all-features; cargo check -p gpui --features "macos-blade" - # todo!(linux): Actually run the tests + # todo(linux): Actually run the tests linux_tests: name: (Linux) Run Clippy and tests runs-on: ubuntu-latest @@ -126,7 +126,7 @@ jobs: - name: Build Zed run: cargo build -p zed - # todo!(windows): Actually run the tests + # todo(windows): Actually run the tests windows_tests: name: (Windows) Run Clippy and tests runs-on: windows-latest diff --git a/Cargo.lock b/Cargo.lock index dc7126a608..23d00be39e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -404,9 +404,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c" +checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5" dependencies = [ "flate2", "futures-core", @@ -681,9 +681,9 @@ checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", @@ -2447,18 +2447,18 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29a6391a9172a93f413370fa561c6bca786e06c89cf85f23f02f6345b1c8ee34" +checksum = "9515fcc42b6cb5137f76b84c1a6f819782d0cf12473d145d3bc5cd67eedc8bc2" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "409c6cbb326604a53ec47eb6341fc85128f24c81012a014b4c728ed24f6e9350" +checksum = "1ad827c6071bfe6d22de1bc331296a29f9ddc506ff926d8415b435ec6a6efce0" dependencies = [ "bumpalo", "cranelift-bforest", @@ -2477,33 +2477,33 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff55e100130995b9ad9ac6b03a24ed5da3c1a1261dcdeb8a7a0292656994fb3" +checksum = "10e6b36237a9ca2ce2fb4cc7741d418a080afa1327402138412ef85d5367bef1" dependencies = [ "cranelift-codegen-shared", ] [[package]] name = "cranelift-codegen-shared" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1446e2eb395fc7b3019a36dccb7eccea923f6caf581b903c8e7e751b6d214a7" +checksum = "c36bf4bfb86898a94ccfa773a1f86e8a5346b1983ff72059bdd2db4600325251" [[package]] name = "cranelift-control" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24076ecf69cbf8b9e1e532ae8e7ac01d850a1c2e127058a26eb3245f9d5b89d1" +checksum = "7cbf36560e7a6bd1409ca91e7b43b2cc7ed8429f343d7605eadf9046e8fac0d0" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f40df95180ad317c60459bb90dd87803d35e538f4c54376d8b26c851f6f0a1b" +checksum = "a71e11061a75b1184c09bea97c026a88f08b59ade96a7bb1f259d4ea0df2e942" dependencies = [ "serde", "serde_derive", @@ -2511,9 +2511,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c3974cc665b699b626742775dae1c1cdea5170f5028ab1f3eb61a7a9a6e2979" +checksum = "af5d4da63143ee3485c7bcedde0a818727d737d1083484a0ceedb8950c89e495" dependencies = [ "cranelift-codegen", "log", @@ -2523,15 +2523,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99543f92b9c361f3c54a29e945adb5b9ef1318feaa5944453cabbfcb3c495919" +checksum = "457a9832b089e26f5eea70dcf49bed8ec6edafed630ce7c83161f24d46ab8085" [[package]] name = "cranelift-native" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c0d84dc7d9b3f73ad565eacc4ab36525c407ef5150893b4b94d5f5f904eb48a" +checksum = "9b490d579df1ce365e1ea359e24ed86d82289fa785153327c2f6a69a59a731e4" dependencies = [ "cranelift-codegen", "libc", @@ -2540,9 +2540,9 @@ dependencies = [ [[package]] name = "cranelift-wasm" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53781039219944d59c6d3ec57e6cae31a1a33db71573a945d84ba6d875d0a743" +checksum = "8cd747ed7f9a461dda9c388415392f6bb95d1a6ef3b7694d17e0817eb74b7798" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -7599,9 +7599,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.2.0" +version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f" +checksum = "fb78f46d0066053d16d4ca7b898e9343bc3530f71c61d5ad84cd404ada068745" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -7610,9 +7610,9 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.2.0" +version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16" +checksum = "b91ac2a3c6c0520a3fb3dd89321177c3c692937c4eb21893378219da10c44fc8" dependencies = [ "proc-macro2", "quote", @@ -7623,9 +7623,9 @@ dependencies = [ [[package]] name = "rust-embed-utils" -version = "8.2.0" +version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665" +checksum = "86f69089032567ffff4eada41c573fc43ff466c7db7c5688b2e7969584345581" dependencies = [ "globset", "sha2 0.10.7", @@ -8346,9 +8346,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" [[package]] name = "signal-hook" @@ -9922,7 +9922,7 @@ dependencies = [ [[package]] name = "tree-sitter-gitcommit" version = "0.3.3" -source = "git+https://github.com/gbprod/tree-sitter-gitcommit#7c01af8d227b5344f62aade2ff00f19bd0c458ca" +source = "git+https://github.com/gbprod/tree-sitter-gitcommit#e8d9eda4e5ea0b08aa39d48dab0f6553058fbe0f" dependencies = [ "cc", "tree-sitter", @@ -10798,9 +10798,9 @@ dependencies = [ [[package]] name = "wasmtime" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06f80b13fdeba0ea5267813d0f06af822309f7125fc8db6094bcd485f0a4ae7" +checksum = "4c843b8bc4dd4f3a76173ba93405c71111d570af0d90ea5f6299c705d0c2add2" dependencies = [ "anyhow", "bincode", @@ -10828,18 +10828,18 @@ dependencies = [ [[package]] name = "wasmtime-asm-macros" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d7395b475c6f858c7edfce375f00d8282a32fbf5d1ebc93eddfac5c2458a52" +checksum = "86b9d329c718b3a18412a6a017c912b539baa8fe1210d21b651f6b4dbafed743" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "wasmtime-c-api-impl" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c09ac0c18464f8ef0b554c12defc94e3fc082b62309a3da229de60d47cf75a" +checksum = "cc93587c24d8e3cb28912eb7abf95f7e350380656faccc46cff04c0821ec58c2" dependencies = [ "anyhow", "log", @@ -10851,9 +10851,9 @@ dependencies = [ [[package]] name = "wasmtime-c-api-macros" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864c4a337294fe690f02b39f2b3f45414447d9321d0ed24d3dc7696bf291e789" +checksum = "2e571a71eba52dfe81ef653a3a336888141f00fc2208a9962722e036fe2a34be" dependencies = [ "proc-macro2", "quote", @@ -10861,9 +10861,9 @@ dependencies = [ [[package]] name = "wasmtime-cranelift" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "974d9455611e26c97d31705e19545de58fa8867416592bd93b7a54a7fc37cedb" +checksum = "31ca62f519225492bd555d0ec85a2dacb0c10315db3418c8b9aeb3824bf54a24" dependencies = [ "anyhow", "cfg-if 1.0.0", @@ -10886,9 +10886,9 @@ dependencies = [ [[package]] name = "wasmtime-cranelift-shared" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40667ba458634db703aea3bd960e80bc9352c21d5e765b69f43e3b0c964eb611" +checksum = "fd5f2071f42e61490bf7cb95b9acdbe6a29dd577a398019304a960585f28b844" dependencies = [ "anyhow", "cranelift-codegen", @@ -10902,9 +10902,9 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8da991421528c2767053cb0cfa70b5d28279100dbcf70ed7f74b51abe1656ef" +checksum = "82bf1a47f384610da19f58b0fd392ca6a3b720974315c08afb0392c0f3951fed" dependencies = [ "anyhow", "bincode", @@ -10923,9 +10923,9 @@ dependencies = [ [[package]] name = "wasmtime-jit-icache-coherence" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3346431a41fbb0c5af0081c2322361b00289f2902e54ee7b115e9b2ad32b156b" +checksum = "33f4121cb29dda08139b2824a734dd095d83ce843f2d613a84eb580b9cfc17ac" dependencies = [ "cfg-if 1.0.0", "libc", @@ -10934,9 +10934,9 @@ dependencies = [ [[package]] name = "wasmtime-runtime" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a489353aa297b46a66cde8da48cab8e1e967e7f4b0ae3d9889a0550bf274810b" +checksum = "4e517f2b996bb3b0e34a82a2bce194f850d9bcfc25c08328ef5fb71b071066b8" dependencies = [ "anyhow", "cc", @@ -10961,9 +10961,9 @@ dependencies = [ [[package]] name = "wasmtime-types" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12c56e31fd7fa707fbd7720b2b29ac42ccfb092fe9d85c98f1d3988f9a1d4558" +checksum = "54a327d7a0ef57bd52a507d28b4561a74126c7a8535a2fc6f2025716bc6a52e8" dependencies = [ "cranelift-entity", "serde", @@ -10974,9 +10974,9 @@ dependencies = [ [[package]] name = "wasmtime-versioned-export-macros" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b0300976c36a9427d184e3ecf7c121c2cb3f030844faf9fcb767821e9d4c382" +checksum = "8ef32eea9fc7035a55159a679d1e89b43ece5ae45d24eed4808e6a92c99a0da4" dependencies = [ "proc-macro2", "quote", @@ -10985,9 +10985,9 @@ dependencies = [ [[package]] name = "wasmtime-wmemcheck" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdf5b8da6ebf7549dad0cd32ca4a3a0461449ef4feec9d0d8450d8da9f51f9b" +checksum = "7f4cbfb052d66f03603a9b77f18171ea245c7805714caad370a549a6344bf86b" [[package]] name = "wayland-backend" diff --git a/Cargo.toml b/Cargo.toml index 2757fed05d..5f87027887 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -310,7 +310,7 @@ pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "e4fc split-debuginfo = "unpacked" debug = "limited" -# todo!(linux) - Remove this +# todo(linux) - Remove this [profile.dev.package.blade-graphics] split-debuginfo = "off" debug = "full" diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 4af201b9e3..8f78a5dd09 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -73,7 +73,7 @@ "ctrl-n": "editor::MoveDown", "ctrl-b": "editor::MoveLeft", "ctrl-f": "editor::MoveRight", - "ctrl-shift-l": "editor::NextScreen", // todo!(linux): What is this + "ctrl-shift-l": "editor::NextScreen", // todo(linux): What is this "alt-left": "editor::MoveToPreviousWordStart", "alt-b": "editor::MoveToPreviousWordStart", "alt-right": "editor::MoveToNextWordEnd", @@ -388,7 +388,7 @@ } }, // Bindings from Sublime Text - // todo!(linux) make sure these match linux bindings or remove above comment? + // todo(linux) make sure these match linux bindings or remove above comment? { "context": "Editor", "bindings": { @@ -412,7 +412,7 @@ } }, // Bindings from Atom - // todo!(linux) make sure these match linux bindings or remove above comment? + // todo(linux) make sure these match linux bindings or remove above comment? { "context": "Pane", "bindings": { diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index cbe9edf8e5..189c54b0e0 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -31,9 +31,9 @@ use fs::Fs; use futures::StreamExt; use gpui::{ canvas, div, point, relative, rems, uniform_list, Action, AnyElement, AppContext, - AsyncAppContext, AsyncWindowContext, AvailableSpace, ClipboardItem, Context, EventEmitter, - FocusHandle, FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement, - IntoElement, Model, ModelContext, ParentElement, Pixels, PromptLevel, Render, SharedString, + AsyncAppContext, AsyncWindowContext, ClipboardItem, Context, EventEmitter, FocusHandle, + FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement, IntoElement, Model, + ModelContext, ParentElement, Pixels, PromptLevel, Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext, }; @@ -774,7 +774,7 @@ impl AssistantPanel { } else { editor.highlight_background::( background_ranges, - |theme| theme.editor_active_line_background, // todo!("use the appropriate color") + |theme| theme.editor_active_line_background, // todo("use the appropriate color") cx, ); } @@ -1277,25 +1277,25 @@ impl Render for AssistantPanel { let view = cx.view().clone(); let scroll_handle = self.saved_conversations_scroll_handle.clone(); let conversation_count = self.saved_conversations.len(); - canvas(move |bounds, cx| { - uniform_list( - view, - "saved_conversations", - conversation_count, - |this, range, cx| { - range - .map(|ix| this.render_saved_conversation(ix, cx)) - .collect() - }, - ) - .track_scroll(scroll_handle) - .into_any_element() - .draw( - bounds.origin, - bounds.size.map(AvailableSpace::Definite), - cx, - ); - }) + canvas( + move |bounds, cx| { + let mut list = uniform_list( + view, + "saved_conversations", + conversation_count, + |this, range, cx| { + range + .map(|ix| this.render_saved_conversation(ix, cx)) + .collect() + }, + ) + .track_scroll(scroll_handle) + .into_any_element(); + list.layout(bounds.origin, bounds.size.into(), cx); + list + }, + |_bounds, mut list, cx| list.paint(cx), + ) .size_full() .into_any_element() }), diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index e4750eb352..e6ec1e559d 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -156,7 +156,7 @@ mod linux { } } -// todo!("windows") +// todo("windows") #[cfg(target_os = "windows")] mod windows { use std::path::Path; diff --git a/crates/collab/src/main.rs b/crates/collab/src/main.rs index e2b04bb8ac..ac086ea89c 100644 --- a/crates/collab/src/main.rs +++ b/crates/collab/src/main.rs @@ -130,7 +130,7 @@ async fn main() -> Result<()> { }) .await?; - // todo!("windows") + // todo("windows") #[cfg(windows)] unimplemented!(); } diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index c644ca5ed0..2349b9d481 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -551,7 +551,6 @@ impl ChatPanel { .child( div() .absolute() - .z_index(1) .right_0() .w_6() .bg(background) @@ -788,7 +787,7 @@ impl Render for ChatPanel { .size_full() .on_action(cx.listener(Self::send)) .child( - h_flex().z_index(1).child( + h_flex().child( TabBar::new("chat_header").child( h_flex() .w_full() diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index da3301ecc9..a325328824 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -993,7 +993,6 @@ impl CollabPanel { .children(has_channel_buffer_changed.then(|| { div() .w_1p5() - .z_index(1) .absolute() .right(px(2.)) .top(px(2.)) @@ -1026,7 +1025,6 @@ impl CollabPanel { .children(has_messages_notification.then(|| { div() .w_1p5() - .z_index(1) .absolute() .right(px(2.)) .top(px(4.)) @@ -1052,7 +1050,7 @@ impl CollabPanel { .indent_step_size(px(20.)) .selected(is_selected) .on_click(cx.listener(move |_this, _, _cx| { - // todo!() + // todo() })) .start_slot( h_flex() @@ -1531,7 +1529,7 @@ impl CollabPanel { id: _id, name: _name, } => { - // todo!() + // todo() } ListEntry::OutgoingRequest(_) => {} @@ -2614,7 +2612,6 @@ impl CollabPanel { .children(has_notes_notification.then(|| { div() .w_1p5() - .z_index(1) .absolute() .right(px(-1.)) .top(px(-1.)) @@ -2629,49 +2626,44 @@ impl CollabPanel { ), ) .child( - h_flex() - .absolute() - .right(rems(0.)) - .z_index(1) - .h_full() - .child( - h_flex() - .h_full() - .gap_1() - .px_1() - .child( - IconButton::new("channel_chat", IconName::MessageBubbles) - .style(ButtonStyle::Filled) - .shape(ui::IconButtonShape::Square) - .icon_size(IconSize::Small) - .icon_color(if has_messages_notification { - Color::Default - } else { - Color::Muted - }) - .on_click(cx.listener(move |this, _, cx| { - this.join_channel_chat(channel_id, cx) - })) - .tooltip(|cx| Tooltip::text("Open channel chat", cx)) - .visible_on_hover(""), - ) - .child( - IconButton::new("channel_notes", IconName::File) - .style(ButtonStyle::Filled) - .shape(ui::IconButtonShape::Square) - .icon_size(IconSize::Small) - .icon_color(if has_notes_notification { - Color::Default - } else { - Color::Muted - }) - .on_click(cx.listener(move |this, _, cx| { - this.open_channel_notes(channel_id, cx) - })) - .tooltip(|cx| Tooltip::text("Open channel notes", cx)) - .visible_on_hover(""), - ), - ), + h_flex().absolute().right(rems(0.)).h_full().child( + h_flex() + .h_full() + .gap_1() + .px_1() + .child( + IconButton::new("channel_chat", IconName::MessageBubbles) + .style(ButtonStyle::Filled) + .shape(ui::IconButtonShape::Square) + .icon_size(IconSize::Small) + .icon_color(if has_messages_notification { + Color::Default + } else { + Color::Muted + }) + .on_click(cx.listener(move |this, _, cx| { + this.join_channel_chat(channel_id, cx) + })) + .tooltip(|cx| Tooltip::text("Open channel chat", cx)) + .visible_on_hover(""), + ) + .child( + IconButton::new("channel_notes", IconName::File) + .style(ButtonStyle::Filled) + .shape(ui::IconButtonShape::Square) + .icon_size(IconSize::Small) + .icon_color(if has_notes_notification { + Color::Default + } else { + Color::Muted + }) + .on_click(cx.listener(move |this, _, cx| { + this.open_channel_notes(channel_id, cx) + })) + .tooltip(|cx| Tooltip::text("Open channel notes", cx)) + .visible_on_hover(""), + ), + ), ) .tooltip({ let channel_store = self.channel_store.clone(); @@ -2717,31 +2709,34 @@ fn render_tree_branch(is_last: bool, overdraw: bool, cx: &mut WindowContext) -> let thickness = px(1.); let color = cx.theme().colors().text; - canvas(move |bounds, cx| { - let start_x = (bounds.left() + bounds.right() - thickness) / 2.; - let start_y = (bounds.top() + bounds.bottom() - thickness) / 2.; - let right = bounds.right(); - let top = bounds.top(); + canvas( + |_, _| {}, + move |bounds, _, cx| { + let start_x = (bounds.left() + bounds.right() - thickness) / 2.; + let start_y = (bounds.top() + bounds.bottom() - thickness) / 2.; + let right = bounds.right(); + let top = bounds.top(); - cx.paint_quad(fill( - Bounds::from_corners( - point(start_x, top), - point( - start_x + thickness, - if is_last { - start_y - } else { - bounds.bottom() + if overdraw { px(1.) } else { px(0.) } - }, + cx.paint_quad(fill( + Bounds::from_corners( + point(start_x, top), + point( + start_x + thickness, + if is_last { + start_y + } else { + bounds.bottom() + if overdraw { px(1.) } else { px(0.) } + }, + ), ), - ), - color, - )); - cx.paint_quad(fill( - Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)), - color, - )); - }) + color, + )); + cx.paint_quad(fill( + Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)), + color, + )); + }, + ) .w(width) .h(line_height) } diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 854809994b..6ccb11ff02 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -329,24 +329,27 @@ impl Render for CollabTitlebarItem { } } -fn render_color_ribbon(color: Hsla) -> gpui::Canvas { - canvas(move |bounds, cx| { - let height = bounds.size.height; - let horizontal_offset = height; - let vertical_offset = px(height.0 / 2.0); - let mut path = Path::new(bounds.lower_left()); - path.curve_to( - bounds.origin + point(horizontal_offset, vertical_offset), - bounds.origin + point(px(0.0), vertical_offset), - ); - path.line_to(bounds.upper_right() + point(-horizontal_offset, vertical_offset)); - path.curve_to( - bounds.lower_right(), - bounds.upper_right() + point(px(0.0), vertical_offset), - ); - path.line_to(bounds.lower_left()); - cx.paint_path(path, color); - }) +fn render_color_ribbon(color: Hsla) -> impl Element { + canvas( + move |_, _| {}, + move |bounds, _, cx| { + let height = bounds.size.height; + let horizontal_offset = height; + let vertical_offset = px(height.0 / 2.0); + let mut path = Path::new(bounds.lower_left()); + path.curve_to( + bounds.origin + point(horizontal_offset, vertical_offset), + bounds.origin + point(px(0.0), vertical_offset), + ); + path.line_to(bounds.upper_right() + point(-horizontal_offset, vertical_offset)); + path.curve_to( + bounds.lower_right(), + bounds.upper_right() + point(px(0.0), vertical_offset), + ); + path.line_to(bounds.lower_left()); + cx.paint_path(path, color); + }, + ) .h_1() .w_full() } diff --git a/crates/collab_ui/src/face_pile.rs b/crates/collab_ui/src/face_pile.rs index 71d15eb155..fab4aec266 100644 --- a/crates/collab_ui/src/face_pile.rs +++ b/crates/collab_ui/src/face_pile.rs @@ -27,10 +27,7 @@ impl RenderOnce for FacePile { let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| { let isnt_last = ix < player_count - 1; - div() - .z_index((player_count - ix) as u16) - .when(isnt_last, |div| div.neg_mr_1()) - .child(player) + div().when(isnt_last, |div| div.neg_mr_1()).child(player) }); self.base.children(player_list) } diff --git a/crates/color/src/color.rs b/crates/color/src/color.rs index 43bbc1c032..307b211e61 100644 --- a/crates/color/src/color.rs +++ b/crates/color/src/color.rs @@ -197,7 +197,7 @@ pub struct ColorStates { /// Returns a set of colors for different states of an element. /// -/// todo!("This should take a theme and use appropriate colors from it") +/// todo("This should take a theme and use appropriate colors from it") pub fn states_for_color(color: RGBAColor, is_light: bool) -> ColorStates { let adjustment_factor = if is_light { 0.1 } else { -0.1 }; let hover_adjustment = 1.0 - adjustment_factor; diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index d50daebb8a..ce67fea1a6 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -1593,20 +1593,18 @@ mod tests { let name: SharedString = match block { TransformBlock::Custom(block) => cx.with_element_context({ |cx| -> Option { - block - .render(&mut BlockContext { - context: cx, - anchor_x: px(0.), - gutter_dimensions: &GutterDimensions::default(), - line_height: px(0.), - em_width: px(0.), - max_width: px(0.), - block_id: ix, - editor_style: &editor::EditorStyle::default(), - }) - .inner_id()? - .try_into() - .ok() + let mut element = block.render(&mut BlockContext { + context: cx, + anchor_x: px(0.), + gutter_dimensions: &GutterDimensions::default(), + line_height: px(0.), + em_width: px(0.), + max_width: px(0.), + block_id: ix, + editor_style: &editor::EditorStyle::default(), + }); + let element = element.downcast_mut::
().unwrap(); + element.interactivity().element_id.clone()?.try_into().ok() } })?, diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 344abeda65..1fc3685bd7 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -28,7 +28,10 @@ use crate::{hover_links::InlayHighlight, movement::TextLayoutDetails, InlayId}; pub use block_map::{BlockMap, BlockPoint}; use collections::{BTreeMap, HashMap, HashSet}; use fold_map::FoldMap; -use gpui::{Font, HighlightStyle, Hsla, LineLayout, Model, ModelContext, Pixels, UnderlineStyle}; +use gpui::{ + Font, HighlightStyle, Hsla, LineLayout, Model, ModelContext, Pixels, UnderlineStyle, + WindowContext, +}; use inlay_map::InlayMap; use language::{ language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription, @@ -593,13 +596,13 @@ impl DisplaySnapshot { &self, display_row: u32, TextLayoutDetails { - text_system, editor_style, rem_size, scroll_anchor: _, visible_rows: _, vertical_scroll_margin: _, }: &TextLayoutDetails, + cx: &WindowContext, ) -> Arc { let mut runs = Vec::new(); let mut line = String::new(); @@ -628,7 +631,7 @@ impl DisplaySnapshot { } let font_size = editor_style.text.font_size.to_pixels(*rem_size); - text_system + cx.text_system() .layout_line(&line, font_size, &runs) .expect("we expect the font to be loaded because it's rendered by the editor") } @@ -637,8 +640,9 @@ impl DisplaySnapshot { &self, display_point: DisplayPoint, text_layout_details: &TextLayoutDetails, + cx: &WindowContext, ) -> Pixels { - let line = self.layout_row(display_point.row(), text_layout_details); + let line = self.layout_row(display_point.row(), text_layout_details, cx); line.x_for_index(display_point.column() as usize) } @@ -647,8 +651,9 @@ impl DisplaySnapshot { display_row: u32, x: Pixels, details: &TextLayoutDetails, + cx: &WindowContext, ) -> u32 { - let layout_line = self.layout_row(display_row, details); + let layout_line = self.layout_row(display_row, details, cx); layout_line.closest_index_for_x(x) as u32 } @@ -1336,7 +1341,8 @@ pub mod tests { DisplayPoint::new(0, 7) ); - let x = snapshot.x_for_display_point(DisplayPoint::new(1, 10), &text_layout_details); + let x = + snapshot.x_for_display_point(DisplayPoint::new(1, 10), &text_layout_details, cx); assert_eq!( movement::up( &snapshot, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b3adf64a94..f2e8d850d0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -51,7 +51,7 @@ pub use display_map::DisplayPoint; use display_map::*; pub use editor_settings::EditorSettings; use element::LineWithInvisibles; -pub use element::{Cursor, EditorElement, HighlightedRange, HighlightedRangeLine}; +pub use element::{CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine}; use futures::FutureExt; use fuzzy::{StringMatch, StringMatchCandidate}; use git::diff_hunk_to_display; @@ -3173,7 +3173,6 @@ impl Editor { pub fn text_layout_details(&self, cx: &WindowContext) -> TextLayoutDetails { TextLayoutDetails { - text_system: cx.text_system().clone(), editor_style: self.style.clone().unwrap(), rem_size: cx.rem_size(), scroll_anchor: self.scroll_manager.anchor(), @@ -4099,7 +4098,7 @@ impl Editor { _line_height: Pixels, _gutter_margin: Pixels, editor_view: View, - ) -> Vec> { + ) -> Vec> { fold_data .iter() .enumerate() @@ -4126,6 +4125,7 @@ impl Editor { .selected(fold_status == FoldStatus::Folded) .selected_icon(ui::IconName::ChevronRight) .size(ui::ButtonSize::None) + .into_any_element() }) }) .flatten() @@ -5258,7 +5258,7 @@ impl Editor { head = display_map.clip_point(head, Bias::Right); let goal = SelectionGoal::HorizontalPosition( display_map - .x_for_display_point(head, &text_layout_details) + .x_for_display_point(head, &text_layout_details, cx) .into(), ); selection.collapse_to(head, goal); @@ -6297,8 +6297,8 @@ impl Editor { let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone(); let range = oldest_selection.display_range(&display_map).sorted(); - let start_x = display_map.x_for_display_point(range.start, &text_layout_details); - let end_x = display_map.x_for_display_point(range.end, &text_layout_details); + let start_x = display_map.x_for_display_point(range.start, &text_layout_details, cx); + let end_x = display_map.x_for_display_point(range.end, &text_layout_details, cx); let positions = start_x.min(end_x)..start_x.max(end_x); selections.clear(); @@ -6337,16 +6337,17 @@ impl Editor { let range = selection.display_range(&display_map).sorted(); debug_assert_eq!(range.start.row(), range.end.row()); let mut row = range.start.row(); - let positions = - if let SelectionGoal::HorizontalRange { start, end } = selection.goal { - px(start)..px(end) - } else { - let start_x = - display_map.x_for_display_point(range.start, &text_layout_details); - let end_x = - display_map.x_for_display_point(range.end, &text_layout_details); - start_x.min(end_x)..start_x.max(end_x) - }; + let positions = if let SelectionGoal::HorizontalRange { start, end } = + selection.goal + { + px(start)..px(end) + } else { + let start_x = + display_map.x_for_display_point(range.start, &text_layout_details, cx); + let end_x = + display_map.x_for_display_point(range.end, &text_layout_details, cx); + start_x.min(end_x)..start_x.max(end_x) + }; while row != end_row { if above { @@ -7033,7 +7034,7 @@ impl Editor { let display_point = point.to_display_point(display_snapshot); let goal = SelectionGoal::HorizontalPosition( display_snapshot - .x_for_display_point(display_point, &text_layout_details) + .x_for_display_point(display_point, &text_layout_details, cx) .into(), ); (display_point, goal) @@ -10137,7 +10138,7 @@ impl ViewInputHandler for Editor { let scroll_left = scroll_position.x * em_width; let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot); - let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left + let x = snapshot.x_for_display_point(start, &text_layout_details, cx) - scroll_left + self.gutter_width; let y = line_height * (start.row() as f32 - scroll_position.y); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 0f3ee3ec62..3dd402f90b 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -5,9 +5,7 @@ use crate::{ }, editor_settings::ShowScrollbar, git::{diff_hunk_to_display, DisplayDiffHunk}, - hover_popover::{ - self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, - }, + hover_popover::{self, hover_at, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT}, items::BufferSearchHighlights, mouse_context_menu, scroll::scroll_amount::ScrollAmount, @@ -22,11 +20,11 @@ use git::diff::DiffHunkStatus; use gpui::{ div, fill, outline, overlay, point, px, quad, relative, size, transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ContentMask, Corners, CursorStyle, - DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla, InteractiveBounds, - InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent, - MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, - SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, - TextStyle, View, ViewContext, WindowContext, + DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla, InteractiveElement, + IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, + ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, + StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View, + ViewContext, WindowContext, }; use itertools::Itertools; use language::language_settings::ShowWhitespaceSetting; @@ -50,7 +48,7 @@ use std::{ use sum_tree::Bias; use theme::{ActiveTheme, PlayerColor}; use ui::prelude::*; -use ui::{h_flex, ButtonLike, ButtonStyle, IconButton, Tooltip}; +use ui::{h_flex, ButtonLike, ButtonStyle, Tooltip}; use util::ResultExt; use workspace::item::Item; @@ -343,29 +341,29 @@ impl EditorElement { &self, cx: &mut ElementContext, text_bounds: Bounds, - layout: &LayoutState, + layout: &mut AfterEditorLayout, ) { - let position_map = layout.position_map.clone(); - let stacking_order = cx.stacking_order().clone(); - cx.on_key_event({ - let editor = self.editor.clone(); - move |event: &ModifiersChangedEvent, phase, cx| { - if phase != DispatchPhase::Bubble { - return; - } + // let position_map = layout.position_map.clone(); + // let stacking_order = cx.stacking_order().clone(); + // cx.on_key_event({ + // let editor = self.editor.clone(); + // move |event: &ModifiersChangedEvent, phase, cx| { + // if phase != DispatchPhase::Bubble { + // return; + // } - editor.update(cx, |editor, cx| { - Self::modifiers_changed( - editor, - event, - &position_map, - text_bounds, - &stacking_order, - cx, - ) - }) - } - }); + // editor.update(cx, |editor, cx| { + // Self::modifiers_changed( + // editor, + // event, + // &position_map, + // text_bounds, + // &stacking_order, + // cx, + // ) + // }) + // } + // }); } fn modifiers_changed( @@ -376,19 +374,19 @@ impl EditorElement { stacking_order: &StackingOrder, cx: &mut ViewContext, ) { - let mouse_position = cx.mouse_position(); - if !text_bounds.contains(&mouse_position) - || !cx.was_top_layer(&mouse_position, stacking_order) - { - return; - } + // let mouse_position = cx.mouse_position(); + // if !text_bounds.contains(&mouse_position) + // || !cx.was_top_layer(&mouse_position, stacking_order) + // { + // return; + // } - editor.update_hovered_link( - position_map.point_for_position(text_bounds, mouse_position), - &position_map.snapshot, - event.modifiers, - cx, - ) + // editor.update_hovered_link( + // position_map.point_for_position(text_bounds, mouse_position), + // &position_map.snapshot, + // event.modifiers, + // cx, + // ) } fn mouse_left_down( @@ -400,50 +398,50 @@ impl EditorElement { stacking_order: &StackingOrder, cx: &mut ViewContext, ) { - let mut click_count = event.click_count; - let modifiers = event.modifiers; + // let mut click_count = event.click_count; + // let modifiers = event.modifiers; - if cx.default_prevented() { - return; - } else if gutter_bounds.contains(&event.position) { - click_count = 3; // Simulate triple-click when clicking the gutter to select lines - } else if !text_bounds.contains(&event.position) { - return; - } - if !cx.was_top_layer(&event.position, stacking_order) { - return; - } + // if cx.default_prevented() { + // return; + // } else if gutter_bounds.contains(&event.position) { + // click_count = 3; // Simulate triple-click when clicking the gutter to select lines + // } else if !text_bounds.contains(&event.position) { + // return; + // } + // if !cx.was_top_layer(&event.position, stacking_order) { + // return; + // } - let point_for_position = position_map.point_for_position(text_bounds, event.position); - let position = point_for_position.previous_valid; - if modifiers.shift && modifiers.alt { - editor.select( - SelectPhase::BeginColumnar { - position, - goal_column: point_for_position.exact_unclipped.column(), - }, - cx, - ); - } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.command { - editor.select( - SelectPhase::Extend { - position, - click_count, - }, - cx, - ); - } else { - editor.select( - SelectPhase::Begin { - position, - add: modifiers.alt, - click_count, - }, - cx, - ); - } + // let point_for_position = position_map.point_for_position(text_bounds, event.position); + // let position = point_for_position.previous_valid; + // if modifiers.shift && modifiers.alt { + // editor.select( + // SelectPhase::BeginColumnar { + // position, + // goal_column: point_for_position.exact_unclipped.column(), + // }, + // cx, + // ); + // } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.command { + // editor.select( + // SelectPhase::Extend { + // position, + // click_count, + // }, + // cx, + // ); + // } else { + // editor.select( + // SelectPhase::Begin { + // position, + // add: modifiers.alt, + // click_count, + // }, + // cx, + // ); + // } - cx.stop_propagation(); + // cx.stop_propagation(); } fn mouse_right_down( @@ -471,30 +469,30 @@ impl EditorElement { event: &MouseUpEvent, position_map: &PositionMap, text_bounds: Bounds, - interactive_bounds: &InteractiveBounds, + // interactive_bounds: &InteractiveBounds, stacking_order: &StackingOrder, cx: &mut ViewContext, ) { - let end_selection = editor.has_pending_selection(); - let pending_nonempty_selections = editor.has_pending_nonempty_selection(); + // let end_selection = editor.has_pending_selection(); + // let pending_nonempty_selections = editor.has_pending_nonempty_selection(); - if end_selection { - editor.select(SelectPhase::End, cx); - } + // if end_selection { + // editor.select(SelectPhase::End, cx); + // } - if interactive_bounds.visibly_contains(&event.position, cx) - && !pending_nonempty_selections - && event.modifiers.command - && text_bounds.contains(&event.position) - && cx.was_top_layer(&event.position, stacking_order) - { - let point = position_map.point_for_position(text_bounds, event.position); - editor.handle_click_hovered_link(point, event.modifiers, cx); + // if interactive_bounds.visibly_contains(&event.position, cx) + // && !pending_nonempty_selections + // && event.modifiers.command + // && text_bounds.contains(&event.position) + // && cx.was_top_layer(&event.position, stacking_order) + // { + // let point = position_map.point_for_position(text_bounds, event.position); + // editor.handle_click_hovered_link(point, event.modifiers, cx); - cx.stop_propagation(); - } else if end_selection { - cx.stop_propagation(); - } + // cx.stop_propagation(); + // } else if end_selection { + // cx.stop_propagation(); + // } } fn mouse_dragged( @@ -551,30 +549,30 @@ impl EditorElement { stacking_order: &StackingOrder, cx: &mut ViewContext, ) { - let modifiers = event.modifiers; - let text_hovered = text_bounds.contains(&event.position); - let gutter_hovered = gutter_bounds.contains(&event.position); - let was_top = cx.was_top_layer(&event.position, stacking_order); + // let modifiers = event.modifiers; + // let text_hovered = text_bounds.contains(&event.position); + // let gutter_hovered = gutter_bounds.contains(&event.position); + // let was_top = cx.was_top_layer(&event.position, stacking_order); - editor.set_gutter_hovered(gutter_hovered, cx); + // editor.set_gutter_hovered(gutter_hovered, cx); - // Don't trigger hover popover if mouse is hovering over context menu - if text_hovered && was_top { - let point_for_position = position_map.point_for_position(text_bounds, event.position); + // // Don't trigger hover popover if mouse is hovering over context menu + // if text_hovered && was_top { + // let point_for_position = position_map.point_for_position(text_bounds, event.position); - editor.update_hovered_link(point_for_position, &position_map.snapshot, modifiers, cx); + // editor.update_hovered_link(point_for_position, &position_map.snapshot, modifiers, cx); - if let Some(point) = point_for_position.as_valid() { - hover_at(editor, Some(point), cx); - Self::update_visible_cursor(editor, point, position_map, cx); - } - } else { - editor.hide_hovered_link(cx); - hover_at(editor, None, cx); - if gutter_hovered && was_top { - cx.stop_propagation(); - } - } + // if let Some(point) = point_for_position.as_valid() { + // hover_at(editor, Some(point), cx); + // Self::update_visible_cursor(editor, point, position_map, cx); + // } + // } else { + // editor.hide_hovered_link(cx); + // hover_at(editor, None, cx); + // if gutter_hovered && was_top { + // cx.stop_propagation(); + // } + // } } fn update_visible_cursor( @@ -626,7 +624,7 @@ impl EditorElement { &self, gutter_bounds: Bounds, text_bounds: Bounds, - layout: &LayoutState, + layout: &AfterEditorLayout, cx: &mut ElementContext, ) { let bounds = gutter_bounds.union(&text_bounds); @@ -709,7 +707,7 @@ impl EditorElement { fn paint_gutter( &mut self, bounds: Bounds, - layout: &mut LayoutState, + layout: &mut AfterEditorLayout, cx: &mut ElementContext, ) { let line_height = layout.position_map.line_height; @@ -717,10 +715,7 @@ impl EditorElement { let scroll_position = layout.position_map.snapshot.scroll_position(); let scroll_top = scroll_position.y * line_height; - if bounds.contains(&cx.mouse_position()) { - let stacking_order = cx.stacking_order().clone(); - cx.set_cursor_style(CursorStyle::Arrow, stacking_order); - } + cx.set_cursor_style(CursorStyle::Arrow, todo!()); let show_git_gutter = matches!( ProjectSettings::get_global(cx).git.git_gutter, @@ -745,55 +740,60 @@ impl EditorElement { } } - cx.with_z_index(1, |cx| { - for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() { - if let Some(fold_indicator) = fold_indicator { - debug_assert!(gutter_settings.folds); - let mut fold_indicator = fold_indicator.into_any_element(); - let available_space = size( - AvailableSpace::MinContent, - AvailableSpace::Definite(line_height * 0.55), - ); - let fold_indicator_size = fold_indicator.measure(available_space, cx); - - let position = point( - bounds.size.width - layout.gutter_dimensions.right_padding, - ix as f32 * line_height - (scroll_top % line_height), - ); - let centering_offset = point( - (layout.gutter_dimensions.right_padding + layout.gutter_dimensions.margin - - fold_indicator_size.width) - / 2., - (line_height - fold_indicator_size.height) / 2., - ); - let origin = bounds.origin + position + centering_offset; - fold_indicator.draw(origin, available_space, cx); - } - } - - if let Some(indicator) = layout.code_actions_indicator.take() { - debug_assert!(gutter_settings.code_actions); - let mut button = indicator.button.into_any_element(); + for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() { + if let Some(fold_indicator) = fold_indicator { + debug_assert!(gutter_settings.folds); + let mut fold_indicator = fold_indicator.into_any_element(); let available_space = size( AvailableSpace::MinContent, - AvailableSpace::Definite(line_height), + AvailableSpace::Definite(line_height * 0.55), ); - let indicator_size = button.measure(available_space, cx); + let fold_indicator_size = fold_indicator.measure(available_space, cx); - let mut x = Pixels::ZERO; - let mut y = indicator.row as f32 * line_height - scroll_top; - // Center indicator. - x += (layout.gutter_dimensions.margin + layout.gutter_dimensions.left_padding - - indicator_size.width) - / 2.; - y += (line_height - indicator_size.height) / 2.; - - button.draw(bounds.origin + point(x, y), available_space, cx); + let position = point( + bounds.size.width - layout.gutter_dimensions.right_padding, + ix as f32 * line_height - (scroll_top % line_height), + ); + let centering_offset = point( + (layout.gutter_dimensions.right_padding + layout.gutter_dimensions.margin + - fold_indicator_size.width) + / 2., + (line_height - fold_indicator_size.height) / 2., + ); + let origin = bounds.origin + position + centering_offset; + // TODO: commit to fold indicator bounds + fold_indicator.paint(cx); } - }); + } + + if let Some(indicator) = layout.code_actions_indicator.take() { + debug_assert!(gutter_settings.code_actions); + let mut button = indicator.button; + let available_space = size( + AvailableSpace::MinContent, + AvailableSpace::Definite(line_height), + ); + let indicator_size = button.measure(available_space, cx); + + let mut x = Pixels::ZERO; + let mut y = indicator.row as f32 * line_height - scroll_top; + // Center indicator. + x += (layout.gutter_dimensions.margin + layout.gutter_dimensions.left_padding + - indicator_size.width) + / 2.; + y += (line_height - indicator_size.height) / 2.; + // todo!("do the above logic during layout") + button.paint(cx); + + // button.draw(bounds.origin + point(x, y), available_space, cx); + } } - fn paint_diff_hunks(bounds: Bounds, layout: &LayoutState, cx: &mut ElementContext) { + fn paint_diff_hunks( + bounds: Bounds, + layout: &AfterEditorLayout, + cx: &mut ElementContext, + ) { let line_height = layout.position_map.line_height; let scroll_position = layout.position_map.snapshot.scroll_position(); @@ -896,312 +896,213 @@ impl EditorElement { fn paint_text( &mut self, text_bounds: Bounds, - layout: &mut LayoutState, + layout: &mut AfterEditorLayout, cx: &mut ElementContext, ) { - let start_row = layout.visible_display_row_range.start; - // Offset the content_bounds from the text_bounds by the gutter margin (which is roughly half a character wide) to make hit testing work more like how we want. - let content_origin = - text_bounds.origin + point(layout.gutter_dimensions.margin, Pixels::ZERO); - let line_end_overshoot = 0.15 * layout.position_map.line_height; - let whitespace_setting = self - .editor - .read(cx) - .buffer - .read(cx) - .settings_at(0, cx) - .show_whitespaces; + // let start_row = layout.visible_display_row_range.start; + // // Offset the content_bounds from the text_bounds by the gutter margin (which is roughly half a character wide) to make hit testing work more like how we want. + // let content_origin = + // text_bounds.origin + point(layout.gutter_dimensions.margin, Pixels::ZERO); + // let line_end_overshoot = 0.15 * layout.position_map.line_height; + // let whitespace_setting = self + // .editor + // .read(cx) + // .buffer + // .read(cx) + // .settings_at(0, cx) + // .show_whitespaces; - cx.with_content_mask( - Some(ContentMask { - bounds: text_bounds, - }), - |cx| { - let interactive_text_bounds = InteractiveBounds { - bounds: text_bounds, - stacking_order: cx.stacking_order().clone(), - }; - if text_bounds.contains(&cx.mouse_position()) { - if self - .editor - .read(cx) - .hovered_link_state - .as_ref() - .is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty()) - { - cx.set_cursor_style( - CursorStyle::PointingHand, - interactive_text_bounds.stacking_order.clone(), - ); - } else { - cx.set_cursor_style( - CursorStyle::IBeam, - interactive_text_bounds.stacking_order.clone(), - ); - } - } + // cx.with_content_mask( + // Some(ContentMask { + // bounds: text_bounds, + // }), + // |cx| { + // let interactive_text_bounds = InteractiveBounds { + // bounds: text_bounds, + // stacking_order: cx.stacking_order().clone(), + // }; + // if text_bounds.contains(&cx.mouse_position()) { + // if self + // .editor + // .read(cx) + // .hovered_link_state + // .as_ref() + // .is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty()) + // { + // cx.set_cursor_style(CursorStyle::PointingHand, todo!()); + // } else { + // cx.set_cursor_style(CursorStyle::IBeam, todo!()); + // } + // } - let fold_corner_radius = 0.15 * layout.position_map.line_height; - cx.with_element_id(Some("folds"), |cx| { - let snapshot = &layout.position_map.snapshot; + // let fold_corner_radius = 0.15 * layout.position_map.line_height; + // cx.with_element_id(Some("folds"), |cx| { + // let snapshot = &layout.position_map.snapshot; - for fold in snapshot.folds_in_range(layout.visible_anchor_range.clone()) { - let fold_range = fold.range.clone(); - let display_range = fold.range.start.to_display_point(&snapshot) - ..fold.range.end.to_display_point(&snapshot); - debug_assert_eq!(display_range.start.row(), display_range.end.row()); - let row = display_range.start.row(); - debug_assert!(row < layout.visible_display_row_range.end); - let Some(line_layout) = &layout - .position_map - .line_layouts - .get((row - layout.visible_display_row_range.start) as usize) - .map(|l| &l.line) - else { - continue; - }; + // for fold in snapshot.folds_in_range(layout.visible_anchor_range.clone()) { + // let fold_range = fold.range.clone(); + // let display_range = fold.range.start.to_display_point(&snapshot) + // ..fold.range.end.to_display_point(&snapshot); + // debug_assert_eq!(display_range.start.row(), display_range.end.row()); + // let row = display_range.start.row(); + // debug_assert!(row < layout.visible_display_row_range.end); + // let Some(line_layout) = &layout + // .position_map + // .line_layouts + // .get((row - layout.visible_display_row_range.start) as usize) + // .map(|l| &l.line) + // else { + // continue; + // }; - let start_x = content_origin.x - + line_layout.x_for_index(display_range.start.column() as usize) - - layout.position_map.scroll_position.x; - let start_y = content_origin.y - + row as f32 * layout.position_map.line_height - - layout.position_map.scroll_position.y; - let end_x = content_origin.x - + line_layout.x_for_index(display_range.end.column() as usize) - - layout.position_map.scroll_position.x; + // let start_x = content_origin.x + // + line_layout.x_for_index(display_range.start.column() as usize) + // - layout.position_map.scroll_position.x; + // let start_y = content_origin.y + // + row as f32 * layout.position_map.line_height + // - layout.position_map.scroll_position.y; + // let end_x = content_origin.x + // + line_layout.x_for_index(display_range.end.column() as usize) + // - layout.position_map.scroll_position.x; - let fold_bounds = Bounds { - origin: point(start_x, start_y), - size: size(end_x - start_x, layout.position_map.line_height), - }; + // let fold_bounds = Bounds { + // origin: point(start_x, start_y), + // size: size(end_x - start_x, layout.position_map.line_height), + // }; - let fold_background = cx.with_z_index(1, |cx| { - div() - .id(fold.id) - .size_full() - .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation()) - .on_click(cx.listener_for( - &self.editor, - move |editor: &mut Editor, _, cx| { - editor.unfold_ranges( - [fold_range.start..fold_range.end], - true, - false, - cx, - ); - cx.stop_propagation(); - }, - )) - .draw_and_update_state( - fold_bounds.origin, - fold_bounds.size, - cx, - |fold_element_state, cx| { - if fold_element_state.is_active() { - cx.theme().colors().ghost_element_active - } else if fold_bounds.contains(&cx.mouse_position()) { - cx.theme().colors().ghost_element_hover - } else { - cx.theme().colors().ghost_element_background - } - }, - ) - }); + // let fold_background = { + // // let _fold_frame_state = div() + // // .id(fold.id) + // // .size_full() + // // .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation()) + // // .on_click(cx.listener_for( + // // &self.editor, + // // move |editor: &mut Editor, _, cx| { + // // editor.unfold_ranges( + // // [fold_range.start..fold_range.end], + // // true, + // // false, + // // cx, + // // ); + // // cx.stop_propagation(); + // // }, + // // )) + // // .draw(fold_bounds.origin, fold_bounds.size, cx); - self.paint_highlighted_range( - display_range.clone(), - fold_background, - fold_corner_radius, - fold_corner_radius * 2., - layout, - content_origin, - text_bounds, - cx, - ); - } - }); + // todo!("FOLD FRAME STATE, RE-ENABLE ME!!!") + // // if fold_frame_state.is_active() { + // // cx.theme().colors().ghost_element_active + // // } else if fold_bounds.contains(&cx.mouse_position()) { + // // cx.theme().colors().ghost_element_hover + // // } else { + // // cx.theme().colors().ghost_element_background + // // } + // }; - for (range, color) in &layout.highlighted_ranges { - self.paint_highlighted_range( - range.clone(), - *color, - Pixels::ZERO, - line_end_overshoot, - layout, - content_origin, - text_bounds, - cx, - ); - } + // self.paint_highlighted_range( + // display_range.clone(), + // fold_background, + // fold_corner_radius, + // fold_corner_radius * 2., + // layout, + // content_origin, + // text_bounds, + // cx, + // ); + // } + // }); - let mut cursors = SmallVec::<[Cursor; 32]>::new(); - let corner_radius = 0.15 * layout.position_map.line_height; - let mut invisible_display_ranges = SmallVec::<[Range; 32]>::new(); + // for (range, color) in &layout.highlighted_ranges { + // self.paint_highlighted_range( + // range.clone(), + // *color, + // Pixels::ZERO, + // line_end_overshoot, + // layout, + // content_origin, + // text_bounds, + // cx, + // ); + // } - for (participant_ix, (player_color, selections)) in - layout.selections.iter().enumerate() - { - for selection in selections.into_iter() { - self.paint_highlighted_range( - selection.range.clone(), - player_color.selection, - corner_radius, - corner_radius * 2., - layout, - content_origin, - text_bounds, - cx, - ); + // let corner_radius = 0.15 * layout.position_map.line_height; + // let mut invisible_display_ranges = SmallVec::<[Range; 32]>::new(); - if selection.is_local && !selection.range.is_empty() { - invisible_display_ranges.push(selection.range.clone()); - } + // for (participant_ix, (player_color, selections)) in + // layout.selections.iter().enumerate() + // { + // for selection in selections.into_iter() { + // self.paint_highlighted_range( + // selection.range.clone(), + // player_color.selection, + // corner_radius, + // corner_radius * 2., + // layout, + // content_origin, + // text_bounds, + // cx, + // ); - if !selection.is_local || self.editor.read(cx).show_local_cursors(cx) { - let cursor_position = selection.head; - if layout - .visible_display_row_range - .contains(&cursor_position.row()) - { - let cursor_row_layout = &layout.position_map.line_layouts - [(cursor_position.row() - start_row) as usize] - .line; - let cursor_column = cursor_position.column() as usize; + // if selection.is_local && !selection.range.is_empty() { + // invisible_display_ranges.push(selection.range.clone()); + // } + // } + // } - let cursor_character_x = - cursor_row_layout.x_for_index(cursor_column); - let mut block_width = cursor_row_layout - .x_for_index(cursor_column + 1) - - cursor_character_x; - if block_width == Pixels::ZERO { - block_width = layout.position_map.em_width; - } - let block_text = if let CursorShape::Block = selection.cursor_shape - { - layout - .position_map - .snapshot - .chars_at(cursor_position) - .next() - .and_then(|(character, _)| { - let text = if character == '\n' { - SharedString::from(" ") - } else { - SharedString::from(character.to_string()) - }; - let len = text.len(); - cx.text_system() - .shape_line( - text, - cursor_row_layout.font_size, - &[TextRun { - len, - font: self.style.text.font(), - color: self.style.background, - background_color: None, - strikethrough: None, - underline: None, - }], - ) - .log_err() - }) - } else { - None - }; + // for (ix, line_with_invisibles) in + // layout.position_map.line_layouts.iter().enumerate() + // { + // let row = start_row + ix as u32; + // line_with_invisibles.draw( + // layout, + // row, + // content_origin, + // whitespace_setting, + // &invisible_display_ranges, + // cx, + // ) + // } - let x = cursor_character_x - layout.position_map.scroll_position.x; - let y = cursor_position.row() as f32 - * layout.position_map.line_height - - layout.position_map.scroll_position.y; - if selection.is_newest { - self.editor.update(cx, |editor, _| { - editor.pixel_position_of_newest_cursor = Some(point( - text_bounds.origin.x + x + block_width / 2., - text_bounds.origin.y - + y - + layout.position_map.line_height / 2., - )) - }); - } + // self.paint_redactions(text_bounds, &layout, cx); - cursors.push(Cursor { - color: player_color.cursor, - block_width, - origin: point(x, y), - line_height: layout.position_map.line_height, - shape: selection.cursor_shape, - block_text, - cursor_name: selection.user_name.clone().map(|name| { - CursorName { - string: name, - color: self.style.background, - is_top_row: cursor_position.row() == 0, - z_index: (participant_ix % 256).try_into().unwrap(), - } - }), - }); - } - } - } - } - - for (ix, line_with_invisibles) in - layout.position_map.line_layouts.iter().enumerate() - { - let row = start_row + ix as u32; - line_with_invisibles.draw( - layout, - row, - content_origin, - whitespace_setting, - &invisible_display_ranges, - cx, - ) - } - - cx.with_z_index(0, |cx| self.paint_redactions(text_bounds, &layout, cx)); - - cx.with_z_index(1, |cx| { - for cursor in cursors { - cursor.paint(content_origin, cx); - } - }); - }, - ) + // for cursor in &mut layout.cursors { + // cursor.paint(content_origin, cx); + // } + // }, + // ) } fn paint_redactions( &mut self, text_bounds: Bounds, - layout: &LayoutState, + layout: &AfterEditorLayout, cx: &mut ElementContext, ) { - let content_origin = - text_bounds.origin + point(layout.gutter_dimensions.margin, Pixels::ZERO); - let line_end_overshoot = layout.line_end_overshoot(); + // let content_origin = + // text_bounds.origin + point(layout.gutter_dimensions.margin, Pixels::ZERO); + // let line_end_overshoot = layout.line_end_overshoot(); - // A softer than perfect black - let redaction_color = gpui::rgb(0x0e1111); + // // A softer than perfect black + // let redaction_color = gpui::rgb(0x0e1111); - for range in layout.redacted_ranges.iter() { - self.paint_highlighted_range( - range.clone(), - redaction_color.into(), - Pixels::ZERO, - line_end_overshoot, - layout, - content_origin, - text_bounds, - cx, - ); - } + // for range in layout.redacted_ranges.iter() { + // self.paint_highlighted_range( + // range.clone(), + // redaction_color.into(), + // Pixels::ZERO, + // line_end_overshoot, + // layout, + // content_origin, + // text_bounds, + // cx, + // ); + // } } fn paint_overlays( &mut self, text_bounds: Bounds, - layout: &mut LayoutState, + layout: &mut AfterEditorLayout, cx: &mut ElementContext, ) { let content_origin = @@ -1230,8 +1131,10 @@ impl EditorElement { if list_origin.y + list_height > text_bounds.lower_right().y { list_origin.y -= layout.position_map.line_height + list_height; } + // cx.break_content_mask(|cx| context_menu.draw(list_origin, available_space, cx)); - cx.break_content_mask(|cx| context_menu.draw(list_origin, available_space, cx)); + // todo("do the above logic when committing the root") + context_menu.paint(cx); } if let Some((position, mut hover_popovers)) = layout.hover_popovers.take() { @@ -1259,54 +1162,49 @@ impl EditorElement { // There is enough space above. Render popovers above the hovered point let mut current_y = hovered_point.y; for mut hover_popover in hover_popovers { - let size = hover_popover.measure(available_space, cx); - let mut popover_origin = point(hovered_point.x, current_y - size.height); + todo!() + // let size = hover_popover.measure(available_space, cx); + // let mut popover_origin = point(hovered_point.x, current_y - size.height); - let x_out_of_bounds = - text_bounds.upper_right().x - (popover_origin.x + size.width); - if x_out_of_bounds < Pixels::ZERO { - popover_origin.x = popover_origin.x + x_out_of_bounds; - } + // let x_out_of_bounds = + // text_bounds.upper_right().x - (popover_origin.x + size.width); + // if x_out_of_bounds < Pixels::ZERO { + // popover_origin.x = popover_origin.x + x_out_of_bounds; + // } - if cx.was_top_layer(&popover_origin, cx.stacking_order()) { - cx.break_content_mask(|cx| { - hover_popover.draw(popover_origin, available_space, cx) - }); - } + // if cx.was_top_layer(&popover_origin, cx.stacking_order()) { + // // cx.break_content_mask(|cx| { + // // hover_popover.draw(popover_origin, available_space, cx) + // // }); + // // todo!("do the above logic when committing root") + // hover_popover.paint(cx) + // } - current_y = popover_origin.y - HOVER_POPOVER_GAP; + // current_y = popover_origin.y - HOVER_POPOVER_GAP; } } else { // There is not enough space above. Render popovers below the hovered point let mut current_y = hovered_point.y + layout.position_map.line_height; for mut hover_popover in hover_popovers { - let size = hover_popover.measure(available_space, cx); - let mut popover_origin = point(hovered_point.x, current_y); + todo!() + // let size = hover_popover.measure(available_space, cx); + // let mut popover_origin = point(hovered_point.x, current_y); - let x_out_of_bounds = - text_bounds.upper_right().x - (popover_origin.x + size.width); - if x_out_of_bounds < Pixels::ZERO { - popover_origin.x = popover_origin.x + x_out_of_bounds; - } + // let x_out_of_bounds = + // text_bounds.upper_right().x - (popover_origin.x + size.width); + // if x_out_of_bounds < Pixels::ZERO { + // popover_origin.x = popover_origin.x + x_out_of_bounds; + // } - hover_popover.draw(popover_origin, available_space, cx); + // hover_popover.draw(popover_origin, available_space, cx); - current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP; + // current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP; } } } - if let Some(mouse_context_menu) = self.editor.read(cx).mouse_context_menu.as_ref() { - let element = overlay() - .position(mouse_context_menu.position) - .child(mouse_context_menu.context_menu.clone()) - .anchor(AnchorCorner::TopLeft) - .snap_to_window(); - element.into_any().draw( - gpui::Point::default(), - size(AvailableSpace::MinContent, AvailableSpace::MinContent), - cx, - ); + if let Some(mouse_context_menu) = layout.mouse_context_menu.as_mut() { + mouse_context_menu.paint(cx); } } @@ -1317,7 +1215,7 @@ impl EditorElement { fn paint_scrollbar( &mut self, bounds: Bounds, - layout: &mut LayoutState, + layout: &mut AfterEditorLayout, cx: &mut ElementContext, ) { if layout.mode != EditorMode::Full { @@ -1558,97 +1456,94 @@ impl EditorElement { )); } - let interactive_track_bounds = InteractiveBounds { - bounds: track_bounds, - stacking_order: cx.stacking_order().clone(), - }; - let mut mouse_position = cx.mouse_position(); - if track_bounds.contains(&mouse_position) { - cx.set_cursor_style( - CursorStyle::Arrow, - interactive_track_bounds.stacking_order.clone(), - ); - } + // let interactive_track_bounds = InteractiveBounds { + // bounds: track_bounds, + // stacking_order: cx.stacking_order().clone(), + // }; + // let mut mouse_position = cx.mouse_position(); + // if track_bounds.contains(&mouse_position) { + // cx.set_cursor_style(CursorStyle::Arrow, todo!()); + // } - cx.on_mouse_event({ - let editor = self.editor.clone(); - move |event: &MouseMoveEvent, phase, cx| { - if phase == DispatchPhase::Capture { - return; - } + // cx.on_mouse_event({ + // let editor = self.editor.clone(); + // move |event: &MouseMoveEvent, phase, cx| { + // if phase == DispatchPhase::Capture { + // return; + // } - editor.update(cx, |editor, cx| { - if event.pressed_button == Some(MouseButton::Left) - && editor.scroll_manager.is_dragging_scrollbar() - { - let y = mouse_position.y; - let new_y = event.position.y; - if (track_bounds.top()..track_bounds.bottom()).contains(&y) { - let mut position = editor.scroll_position(cx); - position.y += (new_y - y) * (max_row as f32) / height; - if position.y < 0.0 { - position.y = 0.0; - } - editor.set_scroll_position(position, cx); - } + // editor.update(cx, |editor, cx| { + // if event.pressed_button == Some(MouseButton::Left) + // && editor.scroll_manager.is_dragging_scrollbar() + // { + // let y = mouse_position.y; + // let new_y = event.position.y; + // if (track_bounds.top()..track_bounds.bottom()).contains(&y) { + // let mut position = editor.scroll_position(cx); + // position.y += (new_y - y) * (max_row as f32) / height; + // if position.y < 0.0 { + // position.y = 0.0; + // } + // editor.set_scroll_position(position, cx); + // } - mouse_position = event.position; - cx.stop_propagation(); - } else { - editor.scroll_manager.set_is_dragging_scrollbar(false, cx); - if interactive_track_bounds.visibly_contains(&event.position, cx) { - editor.scroll_manager.show_scrollbar(cx); - } - } - }) - } - }); + // mouse_position = event.position; + // cx.stop_propagation(); + // } else { + // editor.scroll_manager.set_is_dragging_scrollbar(false, cx); + // if interactive_track_bounds.visibly_contains(&event.position, cx) { + // editor.scroll_manager.show_scrollbar(cx); + // } + // } + // }) + // } + // }); - if self.editor.read(cx).scroll_manager.is_dragging_scrollbar() { - cx.on_mouse_event({ - let editor = self.editor.clone(); - move |_: &MouseUpEvent, phase, cx| { - if phase == DispatchPhase::Capture { - return; - } + // if self.editor.read(cx).scroll_manager.is_dragging_scrollbar() { + // cx.on_mouse_event({ + // let editor = self.editor.clone(); + // move |_: &MouseUpEvent, phase, cx| { + // if phase == DispatchPhase::Capture { + // return; + // } - editor.update(cx, |editor, cx| { - editor.scroll_manager.set_is_dragging_scrollbar(false, cx); - cx.stop_propagation(); - }); - } - }); - } else { - cx.on_mouse_event({ - let editor = self.editor.clone(); - move |event: &MouseDownEvent, phase, cx| { - if phase == DispatchPhase::Capture { - return; - } + // editor.update(cx, |editor, cx| { + // editor.scroll_manager.set_is_dragging_scrollbar(false, cx); + // cx.stop_propagation(); + // }); + // } + // }); + // } else { + // cx.on_mouse_event({ + // let editor = self.editor.clone(); + // move |event: &MouseDownEvent, phase, cx| { + // if phase == DispatchPhase::Capture { + // return; + // } - editor.update(cx, |editor, cx| { - if track_bounds.contains(&event.position) { - editor.scroll_manager.set_is_dragging_scrollbar(true, cx); + // editor.update(cx, |editor, cx| { + // if track_bounds.contains(&event.position) { + // editor.scroll_manager.set_is_dragging_scrollbar(true, cx); - let y = event.position.y; - if y < thumb_top || thumb_bottom < y { - let center_row = - ((y - top) * max_row as f32 / height).round() as u32; - let top_row = center_row - .saturating_sub((row_range.end - row_range.start) as u32 / 2); - let mut position = editor.scroll_position(cx); - position.y = top_row as f32; - editor.set_scroll_position(position, cx); - } else { - editor.scroll_manager.show_scrollbar(cx); - } + // let y = event.position.y; + // if y < thumb_top || thumb_bottom < y { + // let center_row = + // ((y - top) * max_row as f32 / height).round() as u32; + // let top_row = center_row + // .saturating_sub((row_range.end - row_range.start) as u32 / 2); + // let mut position = editor.scroll_position(cx); + // position.y = top_row as f32; + // editor.set_scroll_position(position, cx); + // } else { + // editor.scroll_manager.show_scrollbar(cx); + // } - cx.stop_propagation(); - } - }); - } - }); - } + // cx.stop_propagation(); + // } + // }); + // } + // }); + // } } #[allow(clippy::too_many_arguments)] @@ -1658,7 +1553,7 @@ impl EditorElement { color: Hsla, corner_radius: Pixels, line_end_overshoot: Pixels, - layout: &LayoutState, + layout: &AfterEditorLayout, content_origin: gpui::Point, bounds: Bounds, cx: &mut ElementContext, @@ -1712,23 +1607,24 @@ impl EditorElement { fn paint_blocks( &mut self, bounds: Bounds, - layout: &mut LayoutState, + layout: &mut AfterEditorLayout, cx: &mut ElementContext, ) { let scroll_position = layout.position_map.snapshot.scroll_position(); let scroll_left = scroll_position.x * layout.position_map.em_width; let scroll_top = scroll_position.y * layout.position_map.line_height; - for mut block in layout.blocks.drain(..) { - let mut origin = bounds.origin - + point( - Pixels::ZERO, - block.row as f32 * layout.position_map.line_height - scroll_top, - ); - if !matches!(block.style, BlockStyle::Sticky) { - origin += point(-scroll_left, Pixels::ZERO); - } - block.element.draw(origin, block.available_space, cx); + for block in layout.blocks.drain(..) { + todo!("commit origin"); + // let mut origin = bounds.origin + // + point( + // Pixels::ZERO, + // block.row as f32 * layout.position_map.line_height - scroll_top, + // ); + // if !matches!(block.style, BlockStyle::Sticky) { + // origin += point(-scroll_left, Pixels::ZERO); + // } + // block.element.draw(origin, block.available_space, cx); } } @@ -1971,470 +1867,6 @@ impl EditorElement { } } - fn compute_layout(&mut self, bounds: Bounds, cx: &mut ElementContext) -> LayoutState { - self.editor.update(cx, |editor, cx| { - let snapshot = editor.snapshot(cx); - let style = self.style.clone(); - - let font_id = cx.text_system().resolve_font(&style.text.font()); - let font_size = style.text.font_size.to_pixels(cx.rem_size()); - let line_height = style.text.line_height_in_pixels(cx.rem_size()); - let em_width = cx - .text_system() - .typographic_bounds(font_id, font_size, 'm') - .unwrap() - .size - .width; - let em_advance = cx - .text_system() - .advance(font_id, font_size, 'm') - .unwrap() - .width; - - let gutter_dimensions = snapshot.gutter_dimensions( - font_id, - font_size, - em_width, - self.max_line_number_width(&snapshot, cx), - cx, - ); - - editor.gutter_width = gutter_dimensions.width; - - let text_width = bounds.size.width - gutter_dimensions.width; - let overscroll = size(em_width, px(0.)); - let _snapshot = { - editor.set_visible_line_count((bounds.size.height / line_height).into(), cx); - - let editor_width = text_width - gutter_dimensions.margin - overscroll.width - em_width; - let wrap_width = match editor.soft_wrap_mode(cx) { - SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance, - SoftWrap::EditorWidth => editor_width, - SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance), - }; - - if editor.set_wrap_width(Some(wrap_width), cx) { - editor.snapshot(cx) - } else { - snapshot - } - }; - - let wrap_guides = editor - .wrap_guides(cx) - .iter() - .map(|(guide, active)| (self.column_pixels(*guide, cx), *active)) - .collect::>(); - - let gutter_size = size(gutter_dimensions.width, bounds.size.height); - let text_size = size(text_width, bounds.size.height); - - let autoscroll_horizontally = - editor.autoscroll_vertically(bounds.size.height, line_height, cx); - let mut snapshot = editor.snapshot(cx); - - let scroll_position = snapshot.scroll_position(); - // The scroll position is a fractional point, the whole number of which represents - // the top of the window in terms of display rows. - let start_row = scroll_position.y as u32; - let height_in_lines = f32::from(bounds.size.height / line_height); - let max_row = snapshot.max_point().row(); - - // Add 1 to ensure selections bleed off screen - let end_row = 1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row); - - let start_anchor = if start_row == 0 { - Anchor::min() - } else { - snapshot - .buffer_snapshot - .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left)) - }; - let end_anchor = if end_row > max_row { - Anchor::max() - } else { - snapshot - .buffer_snapshot - .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right)) - }; - - let mut selections: Vec<(PlayerColor, Vec)> = Vec::new(); - let mut active_rows = BTreeMap::new(); - let is_singleton = editor.is_singleton(cx); - - let highlighted_rows = editor.highlighted_rows(); - let highlighted_ranges = editor.background_highlights_in_range( - start_anchor..end_anchor, - &snapshot.display_snapshot, - cx.theme().colors(), - ); - - let redacted_ranges = editor.redacted_ranges(start_anchor..end_anchor, &snapshot.display_snapshot, cx); - - let mut newest_selection_head = None; - - if editor.show_local_selections { - let mut local_selections: Vec> = editor - .selections - .disjoint_in_range(start_anchor..end_anchor, cx); - local_selections.extend(editor.selections.pending(cx)); - let mut layouts = Vec::new(); - let newest = editor.selections.newest(cx); - for selection in local_selections.drain(..) { - let is_empty = selection.start == selection.end; - let is_newest = selection == newest; - - let layout = SelectionLayout::new( - selection, - editor.selections.line_mode, - editor.cursor_shape, - &snapshot.display_snapshot, - is_newest, - true, - None, - ); - if is_newest { - newest_selection_head = Some(layout.head); - } - - for row in cmp::max(layout.active_rows.start, start_row) - ..=cmp::min(layout.active_rows.end, end_row) - { - let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty); - *contains_non_empty_selection |= !is_empty; - } - layouts.push(layout); - } - - let player = if editor.read_only(cx) { - cx.theme().players().read_only() - } else { - style.local_player - }; - - selections.push((player, layouts)); - } - - if let Some(collaboration_hub) = &editor.collaboration_hub { - // When following someone, render the local selections in their color. - if let Some(leader_id) = editor.leader_peer_id { - if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) { - if let Some(participant_index) = collaboration_hub - .user_participant_indices(cx) - .get(&collaborator.user_id) - { - if let Some((local_selection_style, _)) = selections.first_mut() { - *local_selection_style = cx - .theme() - .players() - .color_for_participant(participant_index.0); - } - } - } - } - - let mut remote_selections = HashMap::default(); - for selection in snapshot.remote_selections_in_range( - &(start_anchor..end_anchor), - collaboration_hub.as_ref(), - cx, - ) { - let selection_style = if let Some(participant_index) = selection.participant_index { - cx.theme() - .players() - .color_for_participant(participant_index.0) - } else { - cx.theme().players().absent() - }; - - // Don't re-render the leader's selections, since the local selections - // match theirs. - if Some(selection.peer_id) == editor.leader_peer_id { - continue; - } - let key = HoveredCursor{replica_id: selection.replica_id, selection_id: selection.selection.id}; - - let is_shown = editor.show_cursor_names || editor.hovered_cursors.contains_key(&key); - - remote_selections - .entry(selection.replica_id) - .or_insert((selection_style, Vec::new())) - .1 - .push(SelectionLayout::new( - selection.selection, - selection.line_mode, - selection.cursor_shape, - &snapshot.display_snapshot, - false, - false, - if is_shown { - selection.user_name - } else { - None - }, - )); - } - - selections.extend(remote_selections.into_values()); - } - - let scrollbar_settings = EditorSettings::get_global(cx).scrollbar; - let show_scrollbars = match scrollbar_settings.show { - ShowScrollbar::Auto => { - // Git - (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs()) - || - // Selections - (is_singleton && scrollbar_settings.selections && editor.has_background_highlights::()) - || - // Symbols Selections - (is_singleton && scrollbar_settings.symbols_selections && (editor.has_background_highlights::() || editor.has_background_highlights::())) - || - // Diagnostics - (is_singleton && scrollbar_settings.diagnostics && snapshot.buffer_snapshot.has_diagnostics()) - || - // Scrollmanager - editor.scroll_manager.scrollbars_visible() - } - ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(), - ShowScrollbar::Always => true, - ShowScrollbar::Never => false, - }; - - let head_for_relative = newest_selection_head.unwrap_or_else(|| { - let newest = editor.selections.newest::(cx); - SelectionLayout::new( - newest, - editor.selections.line_mode, - editor.cursor_shape, - &snapshot.display_snapshot, - true, - true, - None, - ) - .head - }); - - let (line_numbers, fold_statuses) = self.shape_line_numbers( - start_row..end_row, - &active_rows, - head_for_relative, - is_singleton, - &snapshot, - cx, - ); - - let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot); - - let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines); - - let mut max_visible_line_width = Pixels::ZERO; - let line_layouts = self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx); - for line_with_invisibles in &line_layouts { - if line_with_invisibles.line.width > max_visible_line_width { - max_visible_line_width = line_with_invisibles.line.width; - } - } - - let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx) - .unwrap() - .width; - let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width; - - let (scroll_width, blocks) = cx.with_element_context(|cx| { - cx.with_element_id(Some("editor_blocks"), |cx| { - self.layout_blocks( - start_row..end_row, - &snapshot, - bounds.size.width, - scroll_width, - text_width, - &gutter_dimensions, - em_width, - gutter_dimensions.width + gutter_dimensions.margin, - line_height, - &style, - &line_layouts, - editor, - cx, - ) - }) - }); - - let scroll_max = point( - f32::from((scroll_width - text_size.width) / em_width).max(0.0), - max_row as f32, - ); - - let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x); - - let autoscrolled = if autoscroll_horizontally { - editor.autoscroll_horizontally( - start_row, - text_size.width, - scroll_width, - em_width, - &line_layouts, - cx, - ) - } else { - false - }; - - if clamped || autoscrolled { - snapshot = editor.snapshot(cx); - } - - let gutter_settings = EditorSettings::get_global(cx).gutter; - - let mut context_menu = None; - let mut code_actions_indicator = None; - if let Some(newest_selection_head) = newest_selection_head { - if (start_row..end_row).contains(&newest_selection_head.row()) { - if editor.context_menu_visible() { - let max_height = cmp::min( - 12. * line_height, - cmp::max( - 3. * line_height, - (bounds.size.height - line_height) / 2., - ) - ); - context_menu = - editor.render_context_menu(newest_selection_head, &self.style, max_height, cx); - } - - let active = matches!( - editor.context_menu.read().as_ref(), - Some(crate::ContextMenu::CodeActions(_)) - ); - - if gutter_settings.code_actions { - code_actions_indicator = editor - .render_code_actions_indicator(&style, active, cx) - .map(|element| CodeActionsIndicator { - row: newest_selection_head.row(), - button: element, - }); - } - } - } - - let visible_rows = start_row..start_row + line_layouts.len() as u32; - let max_size = size( - (120. * em_width) // Default size - .min(bounds.size.width / 2.) // Shrink to half of the editor width - .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters - (16. * line_height) // Default size - .min(bounds.size.height / 2.) // Shrink to half of the editor height - .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines - ); - - let hover = if context_menu.is_some() { - None - } else { - editor.hover_state.render( - &snapshot, - &style, - visible_rows, - max_size, - editor.workspace.as_ref().map(|(w, _)| w.clone()), - cx, - ) - }; - - let editor_view = cx.view().clone(); - let fold_indicators = if gutter_settings.folds { - cx.with_element_context(|cx| { - cx.with_element_id(Some("gutter_fold_indicators"), |_cx| { - editor.render_fold_indicators( - fold_statuses, - &style, - editor.gutter_hovered, - line_height, - gutter_dimensions.margin, - editor_view, - ) - }) - }) - } else { - Vec::new() - }; - - let invisible_symbol_font_size = font_size / 2.; - let tab_invisible = cx - .text_system() - .shape_line( - "→".into(), - invisible_symbol_font_size, - &[TextRun { - len: "→".len(), - font: self.style.text.font(), - color: cx.theme().colors().editor_invisible, - background_color: None, - underline: None, - strikethrough: None, - }], - ) - .unwrap(); - let space_invisible = cx - .text_system() - .shape_line( - "•".into(), - invisible_symbol_font_size, - &[TextRun { - len: "•".len(), - font: self.style.text.font(), - color: cx.theme().colors().editor_invisible, - background_color: None, - underline: None, - strikethrough: None, - }], - ) - .unwrap(); - - LayoutState { - mode: snapshot.mode, - position_map: Arc::new(PositionMap { - size: bounds.size, - scroll_position: point( - scroll_position.x * em_width, - scroll_position.y * line_height, - ), - scroll_max, - line_layouts, - line_height, - em_width, - em_advance, - snapshot, - }), - visible_anchor_range: start_anchor..end_anchor, - visible_display_row_range: start_row..end_row, - wrap_guides, - gutter_size, - gutter_dimensions, - text_size, - scrollbar_row_range, - show_scrollbars, - is_singleton, - max_row, - active_rows, - highlighted_rows, - highlighted_ranges, - redacted_ranges, - line_numbers, - display_hunks, - blocks, - selections, - context_menu, - code_actions_indicator, - fold_indicators, - tab_invisible, - space_invisible, - hover_popovers: hover, - } - }) - } - #[allow(clippy::too_many_arguments)] fn layout_blocks( &self, @@ -2681,60 +2113,60 @@ impl EditorElement { fn paint_scroll_wheel_listener( &mut self, - interactive_bounds: &InteractiveBounds, - layout: &LayoutState, + // interactive_bounds: &InteractiveBounds, + layout: &AfterEditorLayout, cx: &mut ElementContext, ) { - cx.on_mouse_event({ - let position_map = layout.position_map.clone(); - let editor = self.editor.clone(); - let interactive_bounds = interactive_bounds.clone(); - let mut delta = ScrollDelta::default(); + // cx.on_mouse_event({ + // let position_map = layout.position_map.clone(); + // let editor = self.editor.clone(); + // let interactive_bounds = interactive_bounds.clone(); + // let mut delta = ScrollDelta::default(); - move |event: &ScrollWheelEvent, phase, cx| { - if phase == DispatchPhase::Bubble - && interactive_bounds.visibly_contains(&event.position, cx) - { - delta = delta.coalesce(event.delta); - editor.update(cx, |editor, cx| { - let position = event.position; - let position_map: &PositionMap = &position_map; - let bounds = &interactive_bounds; - if !bounds.visibly_contains(&position, cx) { - return; - } + // move |event: &ScrollWheelEvent, phase, cx| { + // if phase == DispatchPhase::Bubble + // && interactive_bounds.visibly_contains(&event.position, cx) + // { + // delta = delta.coalesce(event.delta); + // editor.update(cx, |editor, cx| { + // let position = event.position; + // let position_map: &PositionMap = &position_map; + // let bounds = &interactive_bounds; + // if !bounds.visibly_contains(&position, cx) { + // return; + // } - let line_height = position_map.line_height; - let max_glyph_width = position_map.em_width; - let (delta, axis) = match delta { - gpui::ScrollDelta::Pixels(mut pixels) => { - //Trackpad - let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels); - (pixels, axis) - } + // let line_height = position_map.line_height; + // let max_glyph_width = position_map.em_width; + // let (delta, axis) = match delta { + // gpui::ScrollDelta::Pixels(mut pixels) => { + // //Trackpad + // let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels); + // (pixels, axis) + // } - gpui::ScrollDelta::Lines(lines) => { - //Not trackpad - let pixels = - point(lines.x * max_glyph_width, lines.y * line_height); - (pixels, None) - } - }; + // gpui::ScrollDelta::Lines(lines) => { + // //Not trackpad + // let pixels = + // point(lines.x * max_glyph_width, lines.y * line_height); + // (pixels, None) + // } + // }; - let scroll_position = position_map.snapshot.scroll_position(); - let x = f32::from( - (scroll_position.x * max_glyph_width - delta.x) / max_glyph_width, - ); - let y = - f32::from((scroll_position.y * line_height - delta.y) / line_height); - let scroll_position = - point(x, y).clamp(&point(0., 0.), &position_map.scroll_max); - editor.scroll(scroll_position, axis, cx); - cx.stop_propagation(); - }); - } - } - }); + // let scroll_position = position_map.snapshot.scroll_position(); + // let x = f32::from( + // (scroll_position.x * max_glyph_width - delta.x) / max_glyph_width, + // ); + // let y = + // f32::from((scroll_position.y * line_height - delta.y) / line_height); + // let scroll_position = + // point(x, y).clamp(&point(0., 0.), &position_map.scroll_max); + // editor.scroll(scroll_position, axis, cx); + // cx.stop_propagation(); + // }); + // } + // } + // }); } fn paint_mouse_listeners( @@ -2742,106 +2174,106 @@ impl EditorElement { bounds: Bounds, gutter_bounds: Bounds, text_bounds: Bounds, - layout: &LayoutState, + layout: &AfterEditorLayout, cx: &mut ElementContext, ) { - let interactive_bounds = InteractiveBounds { - bounds: bounds.intersect(&cx.content_mask().bounds), - stacking_order: cx.stacking_order().clone(), - }; + // let interactive_bounds = InteractiveBounds { + // bounds: bounds.intersect(&cx.content_mask().bounds), + // stacking_order: cx.stacking_order().clone(), + // }; - self.paint_scroll_wheel_listener(&interactive_bounds, layout, cx); + // self.paint_scroll_wheel_listener(&interactive_bounds, layout, cx); - cx.on_mouse_event({ - let position_map = layout.position_map.clone(); - let editor = self.editor.clone(); - let stacking_order = cx.stacking_order().clone(); - let interactive_bounds = interactive_bounds.clone(); + // cx.on_mouse_event({ + // let position_map = layout.position_map.clone(); + // let editor = self.editor.clone(); + // let stacking_order = cx.stacking_order().clone(); + // let interactive_bounds = interactive_bounds.clone(); - move |event: &MouseDownEvent, phase, cx| { - if phase == DispatchPhase::Bubble - && interactive_bounds.visibly_contains(&event.position, cx) - { - match event.button { - MouseButton::Left => editor.update(cx, |editor, cx| { - Self::mouse_left_down( - editor, - event, - &position_map, - text_bounds, - gutter_bounds, - &stacking_order, - cx, - ); - }), - MouseButton::Right => editor.update(cx, |editor, cx| { - Self::mouse_right_down(editor, event, &position_map, text_bounds, cx); - }), - _ => {} - }; - } - } - }); + // move |event: &MouseDownEvent, phase, cx| { + // if phase == DispatchPhase::Bubble + // && interactive_bounds.visibly_contains(&event.position, cx) + // { + // match event.button { + // MouseButton::Left => editor.update(cx, |editor, cx| { + // Self::mouse_left_down( + // editor, + // event, + // &position_map, + // text_bounds, + // gutter_bounds, + // &stacking_order, + // cx, + // ); + // }), + // MouseButton::Right => editor.update(cx, |editor, cx| { + // Self::mouse_right_down(editor, event, &position_map, text_bounds, cx); + // }), + // _ => {} + // }; + // } + // } + // }); - cx.on_mouse_event({ - let position_map = layout.position_map.clone(); - let editor = self.editor.clone(); - let stacking_order = cx.stacking_order().clone(); - let interactive_bounds = interactive_bounds.clone(); + // cx.on_mouse_event({ + // let position_map = layout.position_map.clone(); + // let editor = self.editor.clone(); + // let stacking_order = cx.stacking_order().clone(); + // let interactive_bounds = interactive_bounds.clone(); - move |event: &MouseUpEvent, phase, cx| { - if phase == DispatchPhase::Bubble { - editor.update(cx, |editor, cx| { - Self::mouse_up( - editor, - event, - &position_map, - text_bounds, - &interactive_bounds, - &stacking_order, - cx, - ) - }); - } - } - }); - cx.on_mouse_event({ - let position_map = layout.position_map.clone(); - let editor = self.editor.clone(); - let stacking_order = cx.stacking_order().clone(); + // move |event: &MouseUpEvent, phase, cx| { + // if phase == DispatchPhase::Bubble { + // editor.update(cx, |editor, cx| { + // Self::mouse_up( + // editor, + // event, + // &position_map, + // text_bounds, + // &interactive_bounds, + // &stacking_order, + // cx, + // ) + // }); + // } + // } + // }); + // cx.on_mouse_event({ + // let position_map = layout.position_map.clone(); + // let editor = self.editor.clone(); + // let stacking_order = cx.stacking_order().clone(); - move |event: &MouseMoveEvent, phase, cx| { - // if editor.has_pending_selection() && event.pressed_button == Some(MouseButton::Left) { + // move |event: &MouseMoveEvent, phase, cx| { + // // if editor.has_pending_selection() && event.pressed_button == Some(MouseButton::Left) { - if phase == DispatchPhase::Bubble { - editor.update(cx, |editor, cx| { - if event.pressed_button == Some(MouseButton::Left) { - Self::mouse_dragged( - editor, - event, - &position_map, - text_bounds, - gutter_bounds, - &stacking_order, - cx, - ) - } + // if phase == DispatchPhase::Bubble { + // editor.update(cx, |editor, cx| { + // if event.pressed_button == Some(MouseButton::Left) { + // Self::mouse_dragged( + // editor, + // event, + // &position_map, + // text_bounds, + // gutter_bounds, + // &stacking_order, + // cx, + // ) + // } - if interactive_bounds.visibly_contains(&event.position, cx) { - Self::mouse_moved( - editor, - event, - &position_map, - text_bounds, - gutter_bounds, - &stacking_order, - cx, - ) - } - }); - } - } - }); + // if interactive_bounds.visibly_contains(&event.position, cx) { + // Self::mouse_moved( + // editor, + // event, + // &position_map, + // text_bounds, + // gutter_bounds, + // &stacking_order, + // cx, + // ) + // } + // }); + // } + // } + // }); } } @@ -2962,7 +2394,7 @@ impl LineWithInvisibles { fn draw( &self, - layout: &LayoutState, + layout: &AfterEditorLayout, row: u32, content_origin: gpui::Point, whitespace_setting: ShowWhitespaceSetting, @@ -2995,7 +2427,7 @@ impl LineWithInvisibles { fn draw_invisibles( &self, selection_ranges: &[Range], - layout: &LayoutState, + layout: &AfterEditorLayout, content_origin: gpui::Point, line_y: Pixels, row: u32, @@ -3045,142 +2477,725 @@ enum Invisible { } impl Element for EditorElement { - type State = (); + type BeforeLayout = (); + type AfterLayout = AfterEditorLayout; - fn request_layout( + fn before_layout( &mut self, - _element_state: Option, cx: &mut gpui::ElementContext, - ) -> (gpui::LayoutId, Self::State) { - cx.with_view_id(self.editor.entity_id(), |cx| { - self.editor.update(cx, |editor, cx| { - editor.set_style(self.style.clone(), cx); + ) -> (gpui::LayoutId, Self::BeforeLayout) { + self.editor.update(cx, |editor, cx| { + editor.set_style(self.style.clone(), cx); - let layout_id = match editor.mode { - EditorMode::SingleLine => { - let rem_size = cx.rem_size(); - let mut style = Style::default(); - style.size.width = relative(1.).into(); - style.size.height = self.style.text.line_height_in_pixels(rem_size).into(); - cx.with_element_context(|cx| cx.request_layout(&style, None)) + let layout_id = match editor.mode { + EditorMode::SingleLine => { + let rem_size = cx.rem_size(); + let mut style = Style::default(); + style.size.width = relative(1.).into(); + style.size.height = self.style.text.line_height_in_pixels(rem_size).into(); + cx.with_element_context(|cx| cx.request_layout(&style, None)) + } + EditorMode::AutoHeight { max_lines } => { + let editor_handle = cx.view().clone(); + let max_line_number_width = + self.max_line_number_width(&editor.snapshot(cx), cx); + cx.with_element_context(|cx| { + cx.request_measured_layout( + Style::default(), + move |known_dimensions, _, cx| { + editor_handle + .update(cx, |editor, cx| { + compute_auto_height_layout( + editor, + max_lines, + max_line_number_width, + known_dimensions, + cx, + ) + }) + .unwrap_or_default() + }, + ) + }) + } + EditorMode::Full => { + let mut style = Style::default(); + style.size.width = relative(1.).into(); + style.size.height = relative(1.).into(); + cx.with_element_context(|cx| cx.request_layout(&style, None)) + } + }; + + (layout_id, ()) + }) + } + + fn after_layout( + &mut self, + bounds: Bounds, + _: &mut Self::BeforeLayout, + cx: &mut ElementContext, + ) -> AfterEditorLayout { + cx.set_view_id(self.editor.entity_id()); + self.editor.update(cx, |editor, cx| { + let snapshot = editor.snapshot(cx); + let style = self.style.clone(); + + let font_id = cx.text_system().resolve_font(&style.text.font()); + let font_size = style.text.font_size.to_pixels(cx.rem_size()); + let line_height = style.text.line_height_in_pixels(cx.rem_size()); + let em_width = cx + .text_system() + .typographic_bounds(font_id, font_size, 'm') + .unwrap() + .size + .width; + let em_advance = cx + .text_system() + .advance(font_id, font_size, 'm') + .unwrap() + .width; + + let gutter_dimensions = snapshot.gutter_dimensions( + font_id, + font_size, + em_width, + self.max_line_number_width(&snapshot, cx), + cx, + ); + + editor.gutter_width = gutter_dimensions.width; + + let text_width = bounds.size.width - gutter_dimensions.width; + let overscroll = size(em_width, px(0.)); + editor.set_visible_line_count((bounds.size.height / line_height).into(), cx); + + let editor_width = text_width - gutter_dimensions.margin - overscroll.width - em_width; + let wrap_width = match editor.soft_wrap_mode(cx) { + SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance, + SoftWrap::EditorWidth => editor_width, + SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance), + }; + + editor.set_wrap_width(Some(wrap_width), cx); + + let wrap_guides = editor + .wrap_guides(cx) + .iter() + .map(|(guide, active)| (self.column_pixels(*guide, cx), *active)) + .collect::>(); + + let gutter_size = size(gutter_dimensions.width, bounds.size.height); + let text_size = size(text_width, bounds.size.height); + let gutter_bounds = Bounds { + origin: bounds.origin, + size: gutter_size, + }; + let text_bounds = Bounds { + origin: gutter_bounds.upper_right(), + size: text_size, + }; + + let autoscroll_horizontally = + editor.autoscroll_vertically(bounds.size.height, line_height, cx); + let mut snapshot = editor.snapshot(cx); + + let scroll_position = snapshot.scroll_position(); + // The scroll position is a fractional point, the whole number of which represents + // the top of the window in terms of display rows. + let start_row = scroll_position.y as u32; + let height_in_lines = f32::from(bounds.size.height / line_height); + let max_row = snapshot.max_point().row(); + + // Add 1 to ensure selections bleed off screen + let end_row = 1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row); + let visible_display_row_range = start_row..end_row; + + let start_anchor = if start_row == 0 { + Anchor::min() + } else { + snapshot + .buffer_snapshot + .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left)) + }; + let end_anchor = if end_row > max_row { + Anchor::max() + } else { + snapshot + .buffer_snapshot + .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right)) + }; + + let mut selections: Vec<(PlayerColor, Vec)> = Vec::new(); + let mut active_rows = BTreeMap::new(); + let is_singleton = editor.is_singleton(cx); + + let highlighted_rows = editor.highlighted_rows(); + let highlighted_ranges = editor.background_highlights_in_range( + start_anchor..end_anchor, + &snapshot.display_snapshot, + cx.theme().colors(), + ); + + let redacted_ranges = editor.redacted_ranges(start_anchor..end_anchor, &snapshot.display_snapshot, cx); + + let mut newest_selection_head = None; + + if editor.show_local_selections { + let mut local_selections: Vec> = editor + .selections + .disjoint_in_range(start_anchor..end_anchor, cx); + local_selections.extend(editor.selections.pending(cx)); + let mut layouts = Vec::new(); + let newest = editor.selections.newest(cx); + for selection in local_selections.drain(..) { + let is_empty = selection.start == selection.end; + let is_newest = selection == newest; + + let layout = SelectionLayout::new( + selection, + editor.selections.line_mode, + editor.cursor_shape, + &snapshot.display_snapshot, + is_newest, + true, + None, + ); + if is_newest { + newest_selection_head = Some(layout.head); } - EditorMode::AutoHeight { max_lines } => { - let editor_handle = cx.view().clone(); - let max_line_number_width = - self.max_line_number_width(&editor.snapshot(cx), cx); - cx.with_element_context(|cx| { - cx.request_measured_layout( - Style::default(), - move |known_dimensions, _, cx| { - editor_handle - .update(cx, |editor, cx| { - compute_auto_height_layout( - editor, - max_lines, - max_line_number_width, - known_dimensions, - cx, - ) - }) - .unwrap_or_default() - }, - ) - }) - } - EditorMode::Full => { - let mut style = Style::default(); - style.size.width = relative(1.).into(); - style.size.height = relative(1.).into(); - cx.with_element_context(|cx| cx.request_layout(&style, None)) + + for row in cmp::max(layout.active_rows.start, start_row) + ..=cmp::min(layout.active_rows.end, end_row) + { + let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty); + *contains_non_empty_selection |= !is_empty; } + layouts.push(layout); + } + + let player = if editor.read_only(cx) { + cx.theme().players().read_only() + } else { + style.local_player }; - (layout_id, ()) - }) + selections.push((player, layouts)); + } + + if let Some(collaboration_hub) = &editor.collaboration_hub { + // When following someone, render the local selections in their color. + if let Some(leader_id) = editor.leader_peer_id { + if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) { + if let Some(participant_index) = collaboration_hub + .user_participant_indices(cx) + .get(&collaborator.user_id) + { + if let Some((local_selection_style, _)) = selections.first_mut() { + *local_selection_style = cx + .theme() + .players() + .color_for_participant(participant_index.0); + } + } + } + } + + let mut remote_selections = HashMap::default(); + for selection in snapshot.remote_selections_in_range( + &(start_anchor..end_anchor), + collaboration_hub.as_ref(), + cx, + ) { + let selection_style = if let Some(participant_index) = selection.participant_index { + cx.theme() + .players() + .color_for_participant(participant_index.0) + } else { + cx.theme().players().absent() + }; + + // Don't re-render the leader's selections, since the local selections + // match theirs. + if Some(selection.peer_id) == editor.leader_peer_id { + continue; + } + let key = HoveredCursor{replica_id: selection.replica_id, selection_id: selection.selection.id}; + + let is_shown = editor.show_cursor_names || editor.hovered_cursors.contains_key(&key); + + remote_selections + .entry(selection.replica_id) + .or_insert((selection_style, Vec::new())) + .1 + .push(SelectionLayout::new( + selection.selection, + selection.line_mode, + selection.cursor_shape, + &snapshot.display_snapshot, + false, + false, + if is_shown { + selection.user_name + } else { + None + }, + )); + } + + selections.extend(remote_selections.into_values()); + } + + let scrollbar_settings = EditorSettings::get_global(cx).scrollbar; + let show_scrollbars = match scrollbar_settings.show { + ShowScrollbar::Auto => { + // Git + (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs()) + || + // Selections + (is_singleton && scrollbar_settings.selections && editor.has_background_highlights::()) + || + // Symbols Selections + (is_singleton && scrollbar_settings.symbols_selections && (editor.has_background_highlights::() || editor.has_background_highlights::())) + || + // Diagnostics + (is_singleton && scrollbar_settings.diagnostics && snapshot.buffer_snapshot.has_diagnostics()) + || + // Scrollmanager + editor.scroll_manager.scrollbars_visible() + } + ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(), + ShowScrollbar::Always => true, + ShowScrollbar::Never => false, + }; + + let head_for_relative = newest_selection_head.unwrap_or_else(|| { + let newest = editor.selections.newest::(cx); + SelectionLayout::new( + newest, + editor.selections.line_mode, + editor.cursor_shape, + &snapshot.display_snapshot, + true, + true, + None, + ) + .head + }); + + let (line_numbers, fold_statuses) = self.shape_line_numbers( + start_row..end_row, + &active_rows, + head_for_relative, + is_singleton, + &snapshot, + cx, + ); + + let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot); + + let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines); + + let mut max_visible_line_width = Pixels::ZERO; + let line_layouts = self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx); + for line_with_invisibles in &line_layouts { + if line_with_invisibles.line.width > max_visible_line_width { + max_visible_line_width = line_with_invisibles.line.width; + } + } + + let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx) + .unwrap() + .width; + let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width; + let scroll_position = point( + scroll_position.x * em_width, + scroll_position.y * line_height, + ); + + let mut cursors = Vec::::new(); + let cursor_corner_radius = 0.15 * line_height; + let mut invisible_display_ranges = SmallVec::<[Range; 32]>::new(); + + for (participant_ix, (player_color, selections)) in + selections.iter().enumerate() + { + for selection in selections.into_iter() { + if selection.is_local && !selection.range.is_empty() { + invisible_display_ranges.push(selection.range.clone()); + } + + if !selection.is_local || self.editor.read(cx).show_local_cursors(cx) { + let cursor_position = selection.head; + if + visible_display_row_range + .contains(&cursor_position.row()) + { + let cursor_row_layout = &line_layouts + [(cursor_position.row() - start_row) as usize] + .line; + let cursor_column = cursor_position.column() as usize; + + let cursor_character_x = + cursor_row_layout.x_for_index(cursor_column); + let mut block_width = cursor_row_layout + .x_for_index(cursor_column + 1) + - cursor_character_x; + if block_width == Pixels::ZERO { + block_width = em_width; + } + let block_text = if let CursorShape::Block = selection.cursor_shape + { + snapshot + .chars_at(cursor_position) + .next() + .and_then(|(character, _)| { + let text = if character == '\n' { + SharedString::from(" ") + } else { + SharedString::from(character.to_string()) + }; + let len = text.len(); + cx.text_system() + .shape_line( + text, + cursor_row_layout.font_size, + &[TextRun { + len, + font: self.style.text.font(), + color: self.style.background, + background_color: None, + strikethrough: None, + underline: None, + }], + ) + .log_err() + }) + } else { + None + }; + + let x = cursor_character_x - scroll_position.x; + let y = cursor_position.row() as f32 + * line_height + - scroll_position.y; + if selection.is_newest { + self.editor.update(cx, |editor, _| { + editor.pixel_position_of_newest_cursor = Some(point( + text_bounds.origin.x + x + block_width / 2., + text_bounds.origin.y + + y + + line_height / 2., + )) + }); + } + + cursors.push(CursorLayout { + color: player_color.cursor, + block_width, + origin: point(x, y), + line_height, + shape: selection.cursor_shape, + block_text, + cursor_name: selection.user_name.clone().map(|name| { + let text_size = line_height / 1.5; + + // todo!("name_origin should use the actual cursor bounds") + let name_origin = if cursor_position.row() == 0 { + point(bounds.right() - px(1.), bounds.top()) + } else { + point( + bounds.left(), + bounds.top() - text_size / 2. - px(1.), + ) + }; + let mut element = div() + .bg(player_color.cursor) + .text_size(text_size) + .px_0p5() + .line_height(text_size + px(2.)) + .text_color(self.style.background) + .child(name) + .into_any(); + cx.with_element_context(|cx| element.layout( + name_origin, + AvailableSpace::min_size(), + cx, + )); + element + }), + }); + } + } + } + } + + let (scroll_width, blocks) = cx.with_element_context(|cx| { + cx.with_element_id(Some("editor_blocks"), |cx| { + self.layout_blocks( + start_row..end_row, + &snapshot, + bounds.size.width, + scroll_width, + text_width, + &gutter_dimensions, + em_width, + gutter_dimensions.width + gutter_dimensions.margin, + line_height, + &style, + &line_layouts, + editor, + cx, + ) + }) + }); + + let scroll_max = point( + f32::from((scroll_width - text_size.width) / em_width).max(0.0), + max_row as f32, + ); + + let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x); + + let autoscrolled = if autoscroll_horizontally { + editor.autoscroll_horizontally( + start_row, + text_size.width, + scroll_width, + em_width, + &line_layouts, + cx, + ) + } else { + false + }; + + if clamped || autoscrolled { + snapshot = editor.snapshot(cx); + } + + let gutter_settings = EditorSettings::get_global(cx).gutter; + + let mut context_menu = None; + let mut code_actions_indicator = None; + if let Some(newest_selection_head) = newest_selection_head { + if (start_row..end_row).contains(&newest_selection_head.row()) { + if editor.context_menu_visible() { + let max_height = cmp::min( + 12. * line_height, + cmp::max( + 3. * line_height, + (bounds.size.height - line_height) / 2., + ) + ); + context_menu = + editor.render_context_menu(newest_selection_head, &self.style, max_height, cx); + } + + let active = matches!( + editor.context_menu.read().as_ref(), + Some(crate::ContextMenu::CodeActions(_)) + ); + + if gutter_settings.code_actions { + code_actions_indicator = editor + .render_code_actions_indicator(&style, active, cx) + .map(|element| { + let button = element.into_any_element(); + // todo!("commit root") + CodeActionsIndicator { + row: newest_selection_head.row(), + button, + } + }); + } + } + } + + let visible_rows = start_row..start_row + line_layouts.len() as u32; + let max_size = size( + (120. * em_width) // Default size + .min(bounds.size.width / 2.) // Shrink to half of the editor width + .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters + (16. * line_height) // Default size + .min(bounds.size.height / 2.) // Shrink to half of the editor height + .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines + ); + + let hover = if context_menu.is_some() { + None + } else { + editor.hover_state.render( + &snapshot, + &style, + visible_rows, + max_size, + editor.workspace.as_ref().map(|(w, _)| w.clone()), + cx, + ) + }; + + let editor_view = cx.view().clone(); + let fold_indicators = if gutter_settings.folds { + cx.with_element_context(|cx| { + cx.with_element_id(Some("gutter_fold_indicators"), |_cx| { + editor.render_fold_indicators( + fold_statuses, + &style, + editor.gutter_hovered, + line_height, + gutter_dimensions.margin, + editor_view, + ) + }) + }) + } else { + Vec::new() + }; + + let invisible_symbol_font_size = font_size / 2.; + let tab_invisible = cx + .text_system() + .shape_line( + "→".into(), + invisible_symbol_font_size, + &[TextRun { + len: "→".len(), + font: self.style.text.font(), + color: cx.theme().colors().editor_invisible, + background_color: None, + underline: None, + strikethrough: None, + }], + ) + .unwrap(); + let space_invisible = cx + .text_system() + .shape_line( + "•".into(), + invisible_symbol_font_size, + &[TextRun { + len: "•".len(), + font: self.style.text.font(), + color: cx.theme().colors().editor_invisible, + background_color: None, + underline: None, + strikethrough: None, + }], + ) + .unwrap(); + + let mouse_context_menu = editor.mouse_context_menu.as_ref().map(|context_menu| { + let mut element = overlay() + .position(context_menu.position) + .child(context_menu.context_menu.clone()) + .anchor(AnchorCorner::TopLeft) + .snap_to_window().into_any(); + cx.with_element_context(|cx| element.layout(gpui::Point::default(), AvailableSpace::min_size(), cx)); + element + }); + + AfterEditorLayout { + mode: snapshot.mode, + position_map: Arc::new(PositionMap { + size: bounds.size, + scroll_position, + scroll_max, + line_layouts, + line_height, + em_width, + em_advance, + snapshot, + }), + visible_anchor_range: start_anchor..end_anchor, + visible_display_row_range: start_row..end_row, + wrap_guides, + gutter_size, + gutter_dimensions, + text_size, + scrollbar_row_range, + show_scrollbars, + is_singleton, + max_row, + active_rows, + highlighted_rows, + highlighted_ranges, + redacted_ranges, + line_numbers, + display_hunks, + blocks, + selections, + cursors, + context_menu, + code_actions_indicator, + fold_indicators, + tab_invisible, + space_invisible, + hover_popovers: hover, + mouse_context_menu + } }) } fn paint( &mut self, bounds: Bounds, - _element_state: &mut Self::State, + _: &mut Self::BeforeLayout, + layout: &mut Self::AfterLayout, cx: &mut gpui::ElementContext, ) { let editor = self.editor.clone(); + cx.with_text_style( + Some(gpui::TextStyleRefinement { + font_size: Some(self.style.text.font_size), + line_height: Some(self.style.text.line_height), + ..Default::default() + }), + |cx| { + let gutter_bounds = Bounds { + origin: bounds.origin, + size: layout.gutter_size, + }; + let text_bounds = Bounds { + origin: gutter_bounds.upper_right(), + size: layout.text_size, + }; - cx.paint_view(self.editor.entity_id(), |cx| { - cx.with_text_style( - Some(gpui::TextStyleRefinement { - font_size: Some(self.style.text.font_size), - line_height: Some(self.style.text.line_height), - ..Default::default() - }), - |cx| { - let mut layout = self.compute_layout(bounds, cx); - let gutter_bounds = Bounds { - origin: bounds.origin, - size: layout.gutter_size, - }; - let text_bounds = Bounds { - origin: gutter_bounds.upper_right(), - size: layout.text_size, - }; + let focus_handle = editor.focus_handle(cx); + let key_context = self.editor.read(cx).key_context(cx); + cx.set_key_context(key_context); + cx.set_focus_handle(&focus_handle); + self.register_actions(cx); - let focus_handle = editor.focus_handle(cx); - let key_context = self.editor.read(cx).key_context(cx); - cx.with_key_dispatch(Some(key_context), Some(focus_handle.clone()), |_, cx| { - self.register_actions(cx); + cx.with_content_mask(Some(ContentMask { bounds }), |cx| { + self.register_key_listeners(cx, text_bounds, layout); + cx.handle_input( + &focus_handle, + ElementInputHandler::new(bounds, self.editor.clone()), + ); - cx.with_content_mask(Some(ContentMask { bounds }), |cx| { - self.register_key_listeners(cx, text_bounds, &layout); - cx.handle_input( - &focus_handle, - ElementInputHandler::new(bounds, self.editor.clone()), - ); - - self.paint_background(gutter_bounds, text_bounds, &layout, cx); - if layout.gutter_size.width > Pixels::ZERO { - self.paint_gutter(gutter_bounds, &mut layout, cx); - } - self.paint_text(text_bounds, &mut layout, cx); - - cx.with_z_index(0, |cx| { - self.paint_mouse_listeners( - bounds, - gutter_bounds, - text_bounds, - &layout, - cx, - ); - }); - if !layout.blocks.is_empty() { - cx.with_z_index(0, |cx| { - cx.with_element_id(Some("editor_blocks"), |cx| { - self.paint_blocks(bounds, &mut layout, cx); - }); - }) - } - - cx.with_z_index(1, |cx| { - self.paint_overlays(text_bounds, &mut layout, cx); - }); - - cx.with_z_index(2, |cx| self.paint_scrollbar(bounds, &mut layout, cx)); + self.paint_background(gutter_bounds, text_bounds, layout, cx); + if layout.gutter_size.width > Pixels::ZERO { + self.paint_gutter(gutter_bounds, layout, cx); + } + self.paint_text(text_bounds, layout, cx); + self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx); + if !layout.blocks.is_empty() { + cx.with_element_id(Some("editor_blocks"), |cx| { + self.paint_blocks(bounds, layout, cx); }); - }) - }, - ) - }) + } + self.paint_overlays(text_bounds, layout, cx); + self.paint_scrollbar(bounds, layout, cx); + }); + }, + ) } } impl IntoElement for EditorElement { type Element = Self; - fn element_id(&self) -> Option { - self.editor.element_id() - } - fn into_element(self) -> Self::Element { self } @@ -3188,7 +3203,7 @@ impl IntoElement for EditorElement { type BufferRow = u32; -pub struct LayoutState { +pub struct AfterEditorLayout { position_map: Arc, gutter_size: Size, gutter_dimensions: GutterDimensions, @@ -3205,6 +3220,7 @@ pub struct LayoutState { highlighted_ranges: Vec<(Range, Hsla)>, redacted_ranges: Vec>, selections: Vec<(PlayerColor, Vec)>, + cursors: Vec, scrollbar_row_range: Range, show_scrollbars: bool, is_singleton: bool, @@ -3212,12 +3228,13 @@ pub struct LayoutState { context_menu: Option<(DisplayPoint, AnyElement)>, code_actions_indicator: Option, hover_popovers: Option<(DisplayPoint, Vec)>, - fold_indicators: Vec>, + fold_indicators: Vec>, tab_invisible: ShapedLine, space_invisible: ShapedLine, + mouse_context_menu: Option, } -impl LayoutState { +impl AfterEditorLayout { fn line_end_overshoot(&self) -> Pixels { 0.15 * self.position_map.line_height } @@ -3225,7 +3242,7 @@ impl LayoutState { struct CodeActionsIndicator { row: u32, - button: IconButton, + button: AnyElement, } struct PositionMap { @@ -3336,26 +3353,17 @@ fn layout_line( ) } -#[derive(Debug)] -pub struct Cursor { +pub struct CursorLayout { origin: gpui::Point, block_width: Pixels, line_height: Pixels, color: Hsla, shape: CursorShape, block_text: Option, - cursor_name: Option, + cursor_name: Option, } -#[derive(Debug)] -pub struct CursorName { - string: SharedString, - color: Hsla, - is_top_row: bool, - z_index: u16, -} - -impl Cursor { +impl CursorLayout { pub fn new( origin: gpui::Point, block_width: Pixels, @@ -3363,9 +3371,9 @@ impl Cursor { color: Hsla, shape: CursorShape, block_text: Option, - cursor_name: Option, - ) -> Cursor { - Cursor { + cursor_name: Option, + ) -> CursorLayout { + CursorLayout { origin, block_width, line_height, @@ -3383,7 +3391,7 @@ impl Cursor { } } - pub fn paint(&self, origin: gpui::Point, cx: &mut ElementContext) { + pub fn paint(&mut self, origin: gpui::Point, cx: &mut ElementContext) { let bounds = match self.shape { CursorShape::Bar => Bounds { origin: self.origin + origin, @@ -3408,29 +3416,8 @@ impl Cursor { fill(bounds, self.color) }; - if let Some(name) = &self.cursor_name { - let text_size = self.line_height / 1.5; - - let name_origin = if name.is_top_row { - point(bounds.right() - px(1.), bounds.top()) - } else { - point(bounds.left(), bounds.top() - text_size / 2. - px(1.)) - }; - cx.with_z_index(name.z_index, |cx| { - div() - .bg(self.color) - .text_size(text_size) - .px_0p5() - .line_height(text_size + px(2.)) - .text_color(name.color) - .child(name.string.clone()) - .into_any_element() - .draw( - name_origin, - size(AvailableSpace::MinContent, AvailableSpace::MinContent), - cx, - ) - }) + if let Some(name) = &mut self.cursor_name { + name.paint(cx); } cx.paint_quad(cursor); @@ -3701,15 +3688,14 @@ mod tests { let state = cx .update_window(window.into(), |view, cx| { cx.with_element_context(|cx| { - cx.with_view_id(view.entity_id(), |cx| { - element.compute_layout( - Bounds { - origin: point(px(500.), px(500.)), - size: size(px(500.), px(500.)), - }, - cx, - ) - }) + element.after_layout( + Bounds { + origin: point(px(500.), px(500.)), + size: size(px(500.), px(500.)), + }, + &mut (), + cx, + ) }) }) .unwrap(); @@ -3797,15 +3783,14 @@ mod tests { let state = cx .update_window(window.into(), |view, cx| { cx.with_element_context(|cx| { - cx.with_view_id(view.entity_id(), |cx| { - element.compute_layout( - Bounds { - origin: point(px(500.), px(500.)), - size: size(px(500.), px(500.)), - }, - cx, - ) - }) + element.after_layout( + Bounds { + origin: point(px(500.), px(500.)), + size: size(px(500.), px(500.)), + }, + &mut (), + cx, + ) }) }) .unwrap(); @@ -3860,26 +3845,25 @@ mod tests { .unwrap(); let mut element = EditorElement::new(&editor, style); - let state = cx + let mut after_layout = cx .update_window(window.into(), |view, cx| { cx.with_element_context(|cx| { - cx.with_view_id(view.entity_id(), |cx| { - element.compute_layout( - Bounds { - origin: point(px(500.), px(500.)), - size: size(px(500.), px(500.)), - }, - cx, - ) - }) + element.after_layout( + Bounds { + origin: point(px(500.), px(500.)), + size: size(px(500.), px(500.)), + }, + &mut (), + cx, + ) }) }) .unwrap(); - let size = state.position_map.size; + let size = after_layout.position_map.size; - assert_eq!(state.position_map.line_layouts.len(), 4); + assert_eq!(after_layout.position_map.line_layouts.len(), 4); assert_eq!( - state + after_layout .line_numbers .iter() .map(Option::is_some) @@ -3890,7 +3874,7 @@ mod tests { // Don't panic. let bounds = Bounds::::new(Default::default(), size); cx.update_window(window.into(), |_, cx| { - cx.with_element_context(|cx| element.paint(bounds, &mut (), cx)) + cx.with_element_context(|cx| element.paint(bounds, &mut (), &mut after_layout, cx)) }) .unwrap() } @@ -4068,11 +4052,12 @@ mod tests { let layout_state = cx .update_window(window.into(), |_, cx| { cx.with_element_context(|cx| { - element.compute_layout( + element.after_layout( Bounds { origin: point(px(500.), px(500.)), size: size(px(500.), px(500.)), }, + &mut (), cx, ) }) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index a94b38df7b..9d0b70f5a1 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -117,7 +117,7 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie // Highlight the selected symbol using a background highlight this.highlight_inlay_background::( vec![inlay_hover.range], - |theme| theme.element_hover, // todo!("use a proper background here") + |theme| theme.element_hover, // todo("use a proper background here") cx, ); this.hover_state.info_popover = Some(hover_popover); @@ -332,7 +332,7 @@ fn show_hover( // Highlight the selected symbol using a background highlight this.highlight_background::( vec![symbol_range], - |theme| theme.element_hover, // todo! update theme + |theme| theme.element_hover, // todo update theme cx, ); } else { diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 65a6854ba9..a39600856f 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -3,11 +3,11 @@ use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint}; use crate::{char_kind, scroll::ScrollAnchor, CharKind, EditorStyle, ToOffset, ToPoint}; -use gpui::{px, Pixels, WindowTextSystem}; +use gpui::{px, Pixels}; use language::Point; use multi_buffer::MultiBufferSnapshot; -use std::{ops::Range, sync::Arc}; +use std::ops::Range; /// Defines search strategy for items in `movement` module. /// `FindRange::SingeLine` only looks for a match on a single line at a time, whereas @@ -21,7 +21,6 @@ pub enum FindRange { /// TextLayoutDetails encompasses everything we need to move vertically /// taking into account variable width characters. pub struct TextLayoutDetails { - pub(crate) text_system: Arc, pub(crate) editor_style: EditorStyle, pub(crate) rem_size: Pixels, pub scroll_anchor: ScrollAnchor, diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 3e663798a1..ca763bd2c0 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -537,13 +537,13 @@ impl Render for ExtensionsPage { return this.py_4().child(self.render_empty_state(cx)); } + let view = cx.view().clone(); + let scroll_handle = self.list.clone(); + let item_count = entries.len(); this.child( - canvas({ - let view = cx.view().clone(); - let scroll_handle = self.list.clone(); - let item_count = entries.len(); + canvas( move |bounds, cx| { - uniform_list::<_, Div, _>( + let mut list = uniform_list::<_, Div, _>( view, "entries", item_count, @@ -552,14 +552,12 @@ impl Render for ExtensionsPage { .size_full() .pb_4() .track_scroll(scroll_handle) - .into_any_element() - .draw( - bounds.origin, - bounds.size.map(AvailableSpace::Definite), - cx, - ) - } - }) + .into_any_element(); + list.layout(bounds.origin, bounds.size.into(), cx); + list + }, + |_bounds, mut list, cx| list.paint(cx), + ) .size_full(), ) })) diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 632247f57e..2041ebb468 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -1343,7 +1343,7 @@ pub fn copy_recursive<'a>( .boxed() } -// todo!(windows) +// todo(windows) // can we get file id not open the file twice? // https://github.com/rust-lang/rust/issues/63010 #[cfg(target_os = "windows")] diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 8431dd5599..9c9e22d9fc 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -96,7 +96,7 @@ objc = "0.2" flume = "0.11" open = "5.0.1" ashpd = "0.7.0" -# todo!(linux) - Technically do not use `randr`, but it doesn't compile otherwise +# todo(linux) - Technically do not use `randr`, but it doesn't compile otherwise xcb = { version = "1.3", features = ["as-raw-xcb-connection", "present", "randr", "xkb"] } wayland-client= { version = "0.31.2" } wayland-protocols = { version = "0.31.2", features = ["client", "staging", "unstable"] } diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 3a99bec94d..6de3a25686 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -674,17 +674,10 @@ impl<'a> VisualTestContext { f: impl FnOnce(&mut WindowContext) -> AnyElement, ) { self.update(|cx| { - let entity_id = cx - .window - .root_view - .as_ref() - .expect("Can't draw to this window without a root view") - .entity_id(); - cx.with_element_context(|cx| { - cx.with_view_id(entity_id, |cx| { - f(cx).draw(origin, space, cx); - }) + let mut element = f(cx); + element.layout(origin, space, cx); + element.paint(cx); }); cx.refresh(); diff --git a/crates/gpui/src/bounds_tree.rs b/crates/gpui/src/bounds_tree.rs new file mode 100644 index 0000000000..26a346a46c --- /dev/null +++ b/crates/gpui/src/bounds_tree.rs @@ -0,0 +1,365 @@ +#![allow(unused)] + +use crate::{Bounds, Half, Point}; +use std::{ + cmp, + fmt::Debug, + ops::{Add, Sub}, +}; + +#[derive(Debug)] +pub(crate) struct BoundsTree +where + U: Default + Clone + Debug, + T: Clone + Debug, +{ + root: Option, + nodes: Vec>, + stack: Vec, +} + +impl BoundsTree +where + U: Clone + Debug + PartialOrd + Add + Sub + Half + Default, + T: Clone + Debug, +{ + pub fn clear(&mut self) { + self.root = None; + self.nodes.clear(); + self.stack.clear(); + } + + pub fn insert(&mut self, new_bounds: Bounds, payload: T) -> u32 { + // If the tree is empty, make the root the new leaf. + if self.root.is_none() { + let new_node = self.push_leaf(new_bounds, payload, 1); + self.root = Some(new_node); + return 1; + } + + // Search for the best place to add the new leaf based on heuristics. + let mut max_intersecting_ordering = 0; + let mut index = self.root.unwrap(); + while let Node::Internal { + left, + right, + bounds: node_bounds, + .. + } = &mut self.nodes[index] + { + let left = *left; + let right = *right; + *node_bounds = node_bounds.union(&new_bounds); + self.stack.push(index); + + // Descend to the best-fit child, based on which one would increase + // the surface area the least. This attempts to keep the tree balanced + // in terms of surface area. If there is an intersection with the other child, + // add its keys to the intersections vector. + let left_cost = new_bounds + .union(&self.nodes[left].bounds()) + .half_perimeter(); + let right_cost = new_bounds + .union(&self.nodes[right].bounds()) + .half_perimeter(); + if left_cost < right_cost { + max_intersecting_ordering = + self.find_max_ordering(right, &new_bounds, max_intersecting_ordering); + index = left; + } else { + max_intersecting_ordering = + self.find_max_ordering(left, &new_bounds, max_intersecting_ordering); + index = right; + } + } + + // We've found a leaf ('index' now refers to a leaf node). + // We'll insert a new parent node above the leaf and attach our new leaf to it. + let sibling = index; + + // Check for collision with the located leaf node + let Node::Leaf { + bounds: sibling_bounds, + order: sibling_ordering, + .. + } = &self.nodes[index] + else { + unreachable!(); + }; + if sibling_bounds.intersects(&new_bounds) { + max_intersecting_ordering = cmp::max(max_intersecting_ordering, *sibling_ordering); + } + + let ordering = max_intersecting_ordering + 1; + let new_node = self.push_leaf(new_bounds, payload, ordering); + let new_parent = self.push_internal(sibling, new_node); + + // If there was an old parent, we need to update its children indices. + if let Some(old_parent) = self.stack.last().copied() { + let Node::Internal { left, right, .. } = &mut self.nodes[old_parent] else { + unreachable!(); + }; + + if *left == sibling { + *left = new_parent; + } else { + *right = new_parent; + } + } else { + // If the old parent was the root, the new parent is the new root. + self.root = Some(new_parent); + } + + for node_index in self.stack.drain(..) { + let Node::Internal { + max_order: max_ordering, + .. + } = &mut self.nodes[node_index] + else { + unreachable!() + }; + *max_ordering = cmp::max(*max_ordering, ordering); + } + + ordering + } + + /// Finds all nodes whose bounds contain the given point and pushes their (bounds, payload) pairs onto the result vector. + pub(crate) fn find_containing( + &mut self, + point: &Point, + result: &mut Vec>, + ) { + if let Some(mut index) = self.root { + self.stack.clear(); + self.stack.push(index); + + while let Some(current_index) = self.stack.pop() { + match &self.nodes[current_index] { + Node::Leaf { + bounds, + order, + data, + } => { + if bounds.contains(point) { + result.push(BoundsSearchResult { + bounds: bounds.clone(), + order: *order, + data: data.clone(), + }); + } + } + Node::Internal { + left, + right, + bounds, + .. + } => { + if bounds.contains(point) { + self.stack.push(*left); + self.stack.push(*right); + } + } + } + } + } + } + + fn find_max_ordering(&self, index: usize, bounds: &Bounds, mut max_ordering: u32) -> u32 { + match { + let this = &self; + &this.nodes[index] + } { + Node::Leaf { + bounds: node_bounds, + order: ordering, + .. + } => { + if bounds.intersects(node_bounds) { + max_ordering = cmp::max(*ordering, max_ordering); + } + } + Node::Internal { + left, + right, + bounds: node_bounds, + max_order: node_max_ordering, + .. + } => { + if bounds.intersects(node_bounds) && max_ordering < *node_max_ordering { + let left_max_ordering = self.nodes[*left].max_ordering(); + let right_max_ordering = self.nodes[*right].max_ordering(); + if left_max_ordering > right_max_ordering { + max_ordering = self.find_max_ordering(*left, bounds, max_ordering); + max_ordering = self.find_max_ordering(*right, bounds, max_ordering); + } else { + max_ordering = self.find_max_ordering(*right, bounds, max_ordering); + max_ordering = self.find_max_ordering(*left, bounds, max_ordering); + } + } + } + } + max_ordering + } + + fn push_leaf(&mut self, bounds: Bounds, payload: T, order: u32) -> usize { + self.nodes.push(Node::Leaf { + bounds, + data: payload, + order, + }); + self.nodes.len() - 1 + } + + fn push_internal(&mut self, left: usize, right: usize) -> usize { + let left_node = &self.nodes[left]; + let right_node = &self.nodes[right]; + let new_bounds = left_node.bounds().union(right_node.bounds()); + let max_ordering = cmp::max(left_node.max_ordering(), right_node.max_ordering()); + self.nodes.push(Node::Internal { + bounds: new_bounds, + left, + right, + max_order: max_ordering, + }); + self.nodes.len() - 1 + } +} + +impl Default for BoundsTree +where + U: Default + Clone + Debug, + T: Clone + Debug, +{ + fn default() -> Self { + BoundsTree { + root: None, + nodes: Vec::new(), + stack: Vec::new(), + } + } +} + +#[derive(Debug, Clone)] +enum Node +where + U: Clone + Default + Debug, + T: Clone + Debug, +{ + Leaf { + bounds: Bounds, + order: u32, + data: T, + }, + Internal { + left: usize, + right: usize, + bounds: Bounds, + max_order: u32, + }, +} + +impl Node +where + U: Clone + Default + Debug, + T: Clone + Debug, +{ + fn bounds(&self) -> &Bounds { + match self { + Node::Leaf { bounds, .. } => bounds, + Node::Internal { bounds, .. } => bounds, + } + } + + fn max_ordering(&self) -> u32 { + match self { + Node::Leaf { + order: ordering, .. + } => *ordering, + Node::Internal { + max_order: max_ordering, + .. + } => *max_ordering, + } + } +} + +pub(crate) struct BoundsSearchResult { + pub bounds: Bounds, + pub order: u32, + pub data: T, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Bounds, Point, Size}; + + #[test] + fn test_insert_and_find_containing() { + let mut tree = BoundsTree::::default(); + let bounds1 = Bounds { + origin: Point { x: 0.0, y: 0.0 }, + size: Size { + width: 10.0, + height: 10.0, + }, + }; + let bounds2 = Bounds { + origin: Point { x: 5.0, y: 5.0 }, + size: Size { + width: 10.0, + height: 10.0, + }, + }; + let bounds3 = Bounds { + origin: Point { x: 10.0, y: 10.0 }, + size: Size { + width: 10.0, + height: 10.0, + }, + }; + + // Insert bounds into the tree + tree.insert(bounds1.clone(), "Payload 1".to_string()); + tree.insert(bounds2.clone(), "Payload 2".to_string()); + tree.insert(bounds3.clone(), "Payload 3".to_string()); + + // Points for testing + let point_inside_bounds1 = Point { x: 1.0, y: 1.0 }; + let point_inside_bounds1_and_2 = Point { x: 6.0, y: 6.0 }; + let point_inside_bounds2_and_3 = Point { x: 12.0, y: 12.0 }; + let point_outside_all_bounds = Point { x: 21.0, y: 21.0 }; + + assert!(!bounds1.contains(&point_inside_bounds2_and_3)); + assert!(!bounds1.contains(&point_outside_all_bounds)); + assert!(bounds2.contains(&point_inside_bounds1_and_2)); + assert!(bounds2.contains(&point_inside_bounds2_and_3)); + assert!(!bounds2.contains(&point_outside_all_bounds)); + assert!(!bounds3.contains(&point_inside_bounds1)); + assert!(bounds3.contains(&point_inside_bounds2_and_3)); + assert!(!bounds3.contains(&point_outside_all_bounds)); + + // Test find_containing for different points + let mut result = Vec::new(); + tree.find_containing(&point_inside_bounds1, &mut result); + assert_eq!(result.len(), 1); + assert_eq!(result[0].data, "Payload 1"); + + result.clear(); + tree.find_containing(&point_inside_bounds1_and_2, &mut result); + assert_eq!(result.len(), 2); + assert!(result.iter().any(|r| r.data == "Payload 1")); + assert!(result.iter().any(|r| r.data == "Payload 2")); + + result.clear(); + tree.find_containing(&point_inside_bounds2_and_3, &mut result); + assert_eq!(result.len(), 2); + assert!(result.iter().any(|r| r.data == "Payload 2")); + assert!(result.iter().any(|r| r.data == "Payload 3")); + + result.clear(); + tree.find_containing(&point_outside_all_bounds, &mut result); + assert_eq!(result.len(), 0); + } +} diff --git a/crates/gpui/src/element.rs b/crates/gpui/src/element.rs index dd343387ba..21cf8c47a5 100644 --- a/crates/gpui/src/element.rs +++ b/crates/gpui/src/element.rs @@ -15,9 +15,6 @@ //! //! But some state is too simple and voluminous to store in every view that needs it, e.g. //! whether a hover has been started or not. For this, GPUI provides the [`Element::State`], associated type. -//! If an element returns an [`ElementId`] from [`IntoElement::element_id()`], and that element id -//! appears in the same place relative to other views and ElementIds in the frame, then the previous -//! frame's state will be passed to the element's layout and paint methods. //! //! # Implementing your own elements //! @@ -35,33 +32,48 @@ //! your own custom layout algorithm or rendering a code editor. use crate::{ - util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, ElementContext, ElementId, LayoutId, - Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA, + util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, DispatchNodeId, ElementContext, + ElementId, LayoutId, Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA, }; use derive_more::{Deref, DerefMut}; pub(crate) use smallvec::SmallVec; -use std::{any::Any, fmt::Debug, ops::DerefMut}; +use std::{any::Any, fmt::Debug, mem, ops::DerefMut}; /// Implemented by types that participate in laying out and painting the contents of a window. /// Elements form a tree and are laid out according to web-based layout rules, as implemented by Taffy. /// You can create custom elements by implementing this trait, see the module-level documentation /// for more details. pub trait Element: 'static + IntoElement { - /// The type of state to store for this element between frames. See the module-level documentation - /// for details. - type State: 'static; + /// The type of state returned from [`Element::before_layout`]. A mutable reference to this state is subsequently + /// provided to [`Element::after_layout`] and [`Element::paint`]. + type BeforeLayout: 'static; + + /// The type of state returned from [`Element::after_layout`]. A mutable reference to this state is subsequently + /// provided to [`Element::paint`]. + type AfterLayout: 'static; /// Before an element can be painted, we need to know where it's going to be and how big it is. /// Use this method to request a layout from Taffy and initialize the element's state. - fn request_layout( + fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout); + + /// After laying out an element, we need to commit its bounds to the current frame for hitbox + /// purposes. The state argument is the same state that was returned from [`Element::before_layout()`]. + fn after_layout( &mut self, - state: Option, + bounds: Bounds, + before_layout: &mut Self::BeforeLayout, cx: &mut ElementContext, - ) -> (LayoutId, Self::State); + ) -> Self::AfterLayout; /// Once layout has been completed, this method will be called to paint the element to the screen. - /// The state argument is the same state that was returned from [`Element::request_layout()`]. - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut ElementContext); + /// The state argument is the same state that was returned from [`Element::before_layout()`]. + fn paint( + &mut self, + bounds: Bounds, + before_layout: &mut Self::BeforeLayout, + after_layout: &mut Self::AfterLayout, + cx: &mut ElementContext, + ); /// Convert this element into a dynamically-typed [`AnyElement`]. fn into_any(self) -> AnyElement { @@ -75,10 +87,6 @@ pub trait IntoElement: Sized { /// Useful for converting other types into elements automatically, like Strings type Element: Element; - /// The [`ElementId`] of self once converted into an [`Element`]. - /// If present, the resulting element's state will be carried across frames. - fn element_id(&self) -> Option; - /// Convert self into a type that implements [`Element`]. fn into_element(self) -> Self::Element; @@ -86,41 +94,6 @@ pub trait IntoElement: Sized { fn into_any_element(self) -> AnyElement { self.into_element().into_any() } - - /// Convert into an element, then draw in the current window at the given origin. - /// The available space argument is provided to the layout engine to determine the size of the - // root element. Once the element is drawn, its associated element state is yielded to the - // given callback. - fn draw_and_update_state( - self, - origin: Point, - available_space: Size, - cx: &mut ElementContext, - f: impl FnOnce(&mut ::State, &mut ElementContext) -> R, - ) -> R - where - T: Clone + Default + Debug + Into, - { - let element = self.into_element(); - let element_id = element.element_id(); - let element = DrawableElement { - element: Some(element), - phase: ElementDrawPhase::Start, - }; - - let frame_state = - DrawableElement::draw(element, origin, available_space.map(Into::into), cx); - - if let Some(mut frame_state) = frame_state { - f(&mut frame_state, cx) - } else { - cx.with_element_state(element_id.unwrap(), |element_state, cx| { - let mut element_state = element_state.unwrap(); - let result = f(&mut element_state, cx); - (result, element_state) - }) - } - } } impl FluentBuilder for T {} @@ -188,24 +161,36 @@ impl Component { } impl Element for Component { - type State = AnyElement; + type BeforeLayout = AnyElement; + type AfterLayout = (); - fn request_layout( - &mut self, - _: Option, - cx: &mut ElementContext, - ) -> (LayoutId, Self::State) { + fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) { let mut element = self .0 .take() .unwrap() .render(cx.deref_mut()) .into_any_element(); - let layout_id = element.request_layout(cx); + let layout_id = element.before_layout(cx); (layout_id, element) } - fn paint(&mut self, _: Bounds, element: &mut Self::State, cx: &mut ElementContext) { + fn after_layout( + &mut self, + _: Bounds, + element: &mut AnyElement, + cx: &mut ElementContext, + ) { + element.after_layout(cx); + } + + fn paint( + &mut self, + _: Bounds, + element: &mut Self::BeforeLayout, + _: &mut Self::AfterLayout, + cx: &mut ElementContext, + ) { element.paint(cx) } } @@ -213,10 +198,6 @@ impl Element for Component { impl IntoElement for Component { type Element = Self; - fn element_id(&self) -> Option { - None - } - fn into_element(self) -> Self::Element { self } @@ -227,9 +208,11 @@ impl IntoElement for Component { pub(crate) struct GlobalElementId(SmallVec<[ElementId; 32]>); trait ElementObject { - fn element_id(&self) -> Option; + fn inner_element(&mut self) -> &mut dyn Any; - fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId; + fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId; + + fn after_layout(&mut self, cx: &mut ElementContext); fn paint(&mut self, cx: &mut ElementContext); @@ -238,110 +221,103 @@ trait ElementObject { available_space: Size, cx: &mut ElementContext, ) -> Size; - - fn draw( - &mut self, - origin: Point, - available_space: Size, - cx: &mut ElementContext, - ); } /// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window. -pub(crate) struct DrawableElement { - element: Option, - phase: ElementDrawPhase, +pub struct Drawable { + /// The drawn element. + pub element: E, + phase: ElementDrawPhase, } #[derive(Default)] -enum ElementDrawPhase { +enum ElementDrawPhase { #[default] Start, - LayoutRequested { + BeforeLayout { layout_id: LayoutId, - frame_state: Option, + before_layout: BeforeLayout, }, LayoutComputed { layout_id: LayoutId, available_space: Size, - frame_state: Option, + before_layout: BeforeLayout, }, + AfterLayout { + node_id: DispatchNodeId, + bounds: Bounds, + before_layout: BeforeLayout, + after_layout: AfterLayout, + }, + Painted, } /// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window. -impl DrawableElement { +impl Drawable { fn new(element: E) -> Self { - DrawableElement { - element: Some(element), + Drawable { + element, phase: ElementDrawPhase::Start, } } - fn element_id(&self) -> Option { - self.element.as_ref()?.element_id() + fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId { + match mem::take(&mut self.phase) { + ElementDrawPhase::Start => { + let (layout_id, before_layout) = self.element.before_layout(cx); + self.phase = ElementDrawPhase::BeforeLayout { + layout_id, + before_layout, + }; + layout_id + } + _ => panic!("must call before_layout only once"), + } } - fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId { - let (layout_id, frame_state) = if let Some(id) = self.element.as_ref().unwrap().element_id() - { - let layout_id = cx.with_element_state(id, |element_state, cx| { - self.element - .as_mut() - .unwrap() - .request_layout(element_state, cx) - }); - (layout_id, None) - } else { - let (layout_id, frame_state) = self.element.as_mut().unwrap().request_layout(None, cx); - (layout_id, Some(frame_state)) - }; - - self.phase = ElementDrawPhase::LayoutRequested { - layout_id, - frame_state, - }; - layout_id - } - - fn paint(mut self, cx: &mut ElementContext) -> Option { - match self.phase { - ElementDrawPhase::LayoutRequested { + fn after_layout(&mut self, cx: &mut ElementContext) { + match mem::take(&mut self.phase) { + ElementDrawPhase::BeforeLayout { layout_id, - frame_state, + mut before_layout, } | ElementDrawPhase::LayoutComputed { layout_id, - frame_state, + mut before_layout, .. } => { let bounds = cx.layout_bounds(layout_id); - - if let Some(mut frame_state) = frame_state { - self.element - .take() - .unwrap() - .paint(bounds, &mut frame_state, cx); - Some(frame_state) - } else { - let element_id = self - .element - .as_ref() - .unwrap() - .element_id() - .expect("if we don't have frame state, we should have element state"); - cx.with_element_state(element_id, |element_state, cx| { - let mut element_state = element_state.unwrap(); - self.element - .take() - .unwrap() - .paint(bounds, &mut element_state, cx); - ((), element_state) - }); - None - } + cx.layout_element(|node_id, cx| { + let after_layout = self.element.after_layout(bounds, &mut before_layout, cx); + self.phase = ElementDrawPhase::AfterLayout { + node_id, + bounds, + before_layout, + after_layout, + }; + }); } + _ => panic!("must call before_layout before after_layout"), + } + } - _ => panic!("must call layout before paint"), + fn paint(&mut self, cx: &mut ElementContext) -> E::BeforeLayout { + match mem::take(&mut self.phase) { + ElementDrawPhase::AfterLayout { + node_id, + bounds, + mut before_layout, + mut after_layout, + .. + } => { + cx.paint_element(node_id, |cx| { + self.element + .paint(bounds, &mut before_layout, &mut after_layout, cx); + }); + self.phase = ElementDrawPhase::Painted; + before_layout + } + _ => panic!("must call after_layout before paint"), } } @@ -351,66 +327,63 @@ impl DrawableElement { cx: &mut ElementContext, ) -> Size { if matches!(&self.phase, ElementDrawPhase::Start) { - self.request_layout(cx); + self.before_layout(cx); } - let layout_id = match &mut self.phase { - ElementDrawPhase::LayoutRequested { + let layout_id = match mem::take(&mut self.phase) { + ElementDrawPhase::BeforeLayout { layout_id, - frame_state, + before_layout, } => { - cx.compute_layout(*layout_id, available_space); - let layout_id = *layout_id; + cx.compute_layout(layout_id, available_space); self.phase = ElementDrawPhase::LayoutComputed { layout_id, available_space, - frame_state: frame_state.take(), + before_layout, }; layout_id } ElementDrawPhase::LayoutComputed { layout_id, available_space: prev_available_space, - .. + before_layout, } => { - if available_space != *prev_available_space { - cx.compute_layout(*layout_id, available_space); - *prev_available_space = available_space; + if available_space != prev_available_space { + cx.compute_layout(layout_id, available_space); } - *layout_id + self.phase = ElementDrawPhase::LayoutComputed { + layout_id, + available_space, + before_layout, + }; + layout_id } _ => panic!("cannot measure after painting"), }; cx.layout_bounds(layout_id).size } - - fn draw( - mut self, - origin: Point, - available_space: Size, - cx: &mut ElementContext, - ) -> Option { - self.measure(available_space, cx); - cx.with_absolute_element_offset(origin, |cx| self.paint(cx)) - } } -impl ElementObject for Option> +impl ElementObject for Drawable where E: Element, - E::State: 'static, + E::BeforeLayout: 'static, { - fn element_id(&self) -> Option { - self.as_ref().unwrap().element_id() + fn inner_element(&mut self) -> &mut dyn Any { + &mut self.element } - fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId { - DrawableElement::request_layout(self.as_mut().unwrap(), cx) + fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId { + Drawable::before_layout(self, cx) + } + + fn after_layout(&mut self, cx: &mut ElementContext) { + Drawable::after_layout(self, cx); } fn paint(&mut self, cx: &mut ElementContext) { - DrawableElement::paint(self.take().unwrap(), cx); + Drawable::paint(self, cx); } fn measure( @@ -418,16 +391,7 @@ where available_space: Size, cx: &mut ElementContext, ) -> Size { - DrawableElement::measure(self.as_mut().unwrap(), available_space, cx) - } - - fn draw( - &mut self, - origin: Point, - available_space: Size, - cx: &mut ElementContext, - ) { - DrawableElement::draw(self.take().unwrap(), origin, available_space, cx); + Drawable::measure(self, available_space, cx) } } @@ -438,18 +402,28 @@ impl AnyElement { pub(crate) fn new(element: E) -> Self where E: 'static + Element, - E::State: Any, + E::BeforeLayout: Any, { let element = ELEMENT_ARENA - .with_borrow_mut(|arena| arena.alloc(|| Some(DrawableElement::new(element)))) + .with_borrow_mut(|arena| arena.alloc(|| Drawable::new(element))) .map(|element| element as &mut dyn ElementObject); AnyElement(element) } + /// Attempt to downcast a reference to the boxed element to a specific type. + pub fn downcast_mut(&mut self) -> Option<&mut T> { + self.0.inner_element().downcast_mut() + } + /// Request the layout ID of the element stored in this `AnyElement`. /// Used for laying out child elements in a parent element. - pub fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId { - self.0.request_layout(cx) + pub fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId { + self.0.before_layout(cx) + } + + /// Commits the element bounds of this [AnyElement] for hitbox purposes. + pub fn after_layout(&mut self, cx: &mut ElementContext) { + self.0.after_layout(cx) } /// Paints the element stored in this `AnyElement`. @@ -466,35 +440,44 @@ impl AnyElement { self.0.measure(available_space, cx) } - /// Initializes this element and performs layout in the available space, then paints it at the given origin. - pub fn draw( + /// Initializes this element, performs layout if needed and commits its bounds. + pub fn layout( &mut self, - origin: Point, + absolute_offset: Point, available_space: Size, cx: &mut ElementContext, - ) { - self.0.draw(origin, available_space, cx) - } - - /// Returns the element ID of the element stored in this `AnyElement`, if any. - pub fn inner_id(&self) -> Option { - self.0.element_id() + ) -> Size { + let size = self.measure(available_space, cx); + cx.with_absolute_element_offset(absolute_offset, |cx| self.after_layout(cx)); + size } } impl Element for AnyElement { - type State = (); + type BeforeLayout = (); + type AfterLayout = (); - fn request_layout( - &mut self, - _: Option, - cx: &mut ElementContext, - ) -> (LayoutId, Self::State) { - let layout_id = self.request_layout(cx); + fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) { + let layout_id = self.before_layout(cx); (layout_id, ()) } - fn paint(&mut self, _: Bounds, _: &mut Self::State, cx: &mut ElementContext) { + fn after_layout( + &mut self, + _: Bounds, + _: &mut Self::BeforeLayout, + cx: &mut ElementContext, + ) { + self.after_layout(cx) + } + + fn paint( + &mut self, + _: Bounds, + _: &mut Self::BeforeLayout, + _: &mut Self::AfterLayout, + cx: &mut ElementContext, + ) { self.paint(cx) } } @@ -502,10 +485,6 @@ impl Element for AnyElement { impl IntoElement for AnyElement { type Element = Self; - fn element_id(&self) -> Option { - None - } - fn into_element(self) -> Self::Element { self } @@ -521,30 +500,32 @@ pub struct Empty; impl IntoElement for Empty { type Element = Self; - fn element_id(&self) -> Option { - None - } - fn into_element(self) -> Self::Element { self } } impl Element for Empty { - type State = (); + type BeforeLayout = (); + type AfterLayout = (); - fn request_layout( - &mut self, - _state: Option, - cx: &mut ElementContext, - ) -> (LayoutId, Self::State) { + fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) { (cx.request_layout(&crate::Style::default(), None), ()) } + fn after_layout( + &mut self, + _bounds: Bounds, + _state: &mut Self::BeforeLayout, + _cx: &mut ElementContext, + ) { + } + fn paint( &mut self, _bounds: Bounds, - _state: &mut Self::State, + _before_layout: &mut Self::BeforeLayout, + _after_layout: &mut Self::AfterLayout, _cx: &mut ElementContext, ) { } diff --git a/crates/gpui/src/elements/canvas.rs b/crates/gpui/src/elements/canvas.rs index 8011f51e0c..623dfa2280 100644 --- a/crates/gpui/src/elements/canvas.rs +++ b/crates/gpui/src/elements/canvas.rs @@ -4,54 +4,68 @@ use crate::{Bounds, Element, ElementContext, IntoElement, Pixels, Style, StyleRe /// Construct a canvas element with the given paint callback. /// Useful for adding short term custom drawing to a view. -pub fn canvas(callback: impl 'static + FnOnce(&Bounds, &mut ElementContext)) -> Canvas { +pub fn canvas( + after_layout: impl 'static + FnOnce(Bounds, &mut ElementContext) -> T, + paint: impl 'static + FnOnce(Bounds, T, &mut ElementContext), +) -> Canvas { Canvas { - paint_callback: Some(Box::new(callback)), + after_layout: Some(Box::new(after_layout)), + paint: Some(Box::new(paint)), style: StyleRefinement::default(), } } /// A canvas element, meant for accessing the low level paint API without defining a whole /// custom element -pub struct Canvas { - paint_callback: Option, &mut ElementContext)>>, +pub struct Canvas { + after_layout: Option, &mut ElementContext) -> T>>, + paint: Option, T, &mut ElementContext)>>, style: StyleRefinement, } -impl IntoElement for Canvas { +impl IntoElement for Canvas { type Element = Self; - fn element_id(&self) -> Option { - None - } - fn into_element(self) -> Self::Element { self } } -impl Element for Canvas { - type State = Style; +impl Element for Canvas { + type BeforeLayout = Style; + type AfterLayout = Option; - fn request_layout( - &mut self, - _: Option, - cx: &mut ElementContext, - ) -> (crate::LayoutId, Self::State) { + fn before_layout(&mut self, cx: &mut ElementContext) -> (crate::LayoutId, Self::BeforeLayout) { let mut style = Style::default(); style.refine(&self.style); let layout_id = cx.request_layout(&style, []); (layout_id, style) } - fn paint(&mut self, bounds: Bounds, style: &mut Style, cx: &mut ElementContext) { + fn after_layout( + &mut self, + bounds: Bounds, + _before_layout: &mut Style, + cx: &mut ElementContext, + ) -> Option { + Some(self.after_layout.take().unwrap()(bounds, cx)) + } + + fn paint( + &mut self, + bounds: Bounds, + style: &mut Style, + after_layout: &mut Self::AfterLayout, + cx: &mut ElementContext, + ) { + let after_layout = after_layout.take().unwrap(); style.paint(bounds, cx, |cx| { - (self.paint_callback.take().unwrap())(&bounds, cx) + (self.paint.take().unwrap())(bounds, after_layout, cx) }); } } -impl Styled for Canvas { +impl Styled for Canvas { fn style(&mut self) -> &mut crate::StyleRefinement { &mut self.style } diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index a234961127..618034b848 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -17,13 +17,12 @@ use crate::{ point, px, size, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, Bounds, - ClickEvent, DispatchPhase, Element, ElementContext, ElementId, FocusHandle, Global, - IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, + ClickEvent, DispatchPhase, Element, ElementContext, ElementId, FocusHandle, Global, Hitbox, + HitboxId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, - ScrollWheelEvent, SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task, - View, Visibility, WindowContext, + ScrollWheelEvent, SharedString, Size, Style, StyleRefinement, Styled, Task, View, Visibility, + WindowContext, }; - use collections::HashMap; use refineable::Refineable; use smallvec::SmallVec; @@ -85,10 +84,8 @@ impl Interactivity { listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, ) { self.mouse_down_listeners - .push(Box::new(move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble - && event.button == button - && bounds.visibly_contains(&event.position, cx) + .push(Box::new(move |event, phase, hitbox, cx| { + if phase == DispatchPhase::Bubble && event.button == button && hitbox.is_hovered(cx) { (listener)(event, cx) } @@ -104,8 +101,8 @@ impl Interactivity { listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, ) { self.mouse_down_listeners - .push(Box::new(move |event, bounds, phase, cx| { - if phase == DispatchPhase::Capture && bounds.visibly_contains(&event.position, cx) { + .push(Box::new(move |event, phase, hitbox, cx| { + if phase == DispatchPhase::Capture && hitbox.is_hovered(cx) { (listener)(event, cx) } })); @@ -120,8 +117,8 @@ impl Interactivity { listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, ) { self.mouse_down_listeners - .push(Box::new(move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { + .push(Box::new(move |event, phase, hitbox, cx| { + if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) { (listener)(event, cx) } })); @@ -137,10 +134,8 @@ impl Interactivity { listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, ) { self.mouse_up_listeners - .push(Box::new(move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble - && event.button == button - && bounds.visibly_contains(&event.position, cx) + .push(Box::new(move |event, phase, hitbox, cx| { + if phase == DispatchPhase::Bubble && event.button == button && hitbox.is_hovered(cx) { (listener)(event, cx) } @@ -156,8 +151,8 @@ impl Interactivity { listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, ) { self.mouse_up_listeners - .push(Box::new(move |event, bounds, phase, cx| { - if phase == DispatchPhase::Capture && bounds.visibly_contains(&event.position, cx) { + .push(Box::new(move |event, phase, hitbox, cx| { + if phase == DispatchPhase::Capture && hitbox.is_hovered(cx) { (listener)(event, cx) } })); @@ -172,8 +167,8 @@ impl Interactivity { listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, ) { self.mouse_up_listeners - .push(Box::new(move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { + .push(Box::new(move |event, phase, hitbox, cx| { + if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) { (listener)(event, cx) } })); @@ -189,9 +184,8 @@ impl Interactivity { listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, ) { self.mouse_down_listeners - .push(Box::new(move |event, bounds, phase, cx| { - if phase == DispatchPhase::Capture && !bounds.visibly_contains(&event.position, cx) - { + .push(Box::new(move |event, phase, hitbox, cx| { + if phase == DispatchPhase::Capture && !hitbox.is_hovered(cx) { (listener)(event, cx) } })); @@ -208,10 +202,10 @@ impl Interactivity { listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, ) { self.mouse_up_listeners - .push(Box::new(move |event, bounds, phase, cx| { + .push(Box::new(move |event, phase, hitbox, cx| { if phase == DispatchPhase::Capture && event.button == button - && !bounds.visibly_contains(&event.position, cx) + && !hitbox.is_hovered(cx) { (listener)(event, cx); } @@ -227,8 +221,8 @@ impl Interactivity { listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static, ) { self.mouse_move_listeners - .push(Box::new(move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { + .push(Box::new(move |event, phase, hitbox, cx| { + if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) { (listener)(event, cx); } })); @@ -248,7 +242,7 @@ impl Interactivity { T: 'static, { self.mouse_move_listeners - .push(Box::new(move |event, bounds, phase, cx| { + .push(Box::new(move |event, phase, hitbox, cx| { if phase == DispatchPhase::Capture && cx .active_drag @@ -258,7 +252,7 @@ impl Interactivity { (listener)( &DragMoveEvent { event: event.clone(), - bounds: bounds.bounds, + bounds: hitbox.bounds.clone(), drag: PhantomData, }, cx, @@ -276,8 +270,8 @@ impl Interactivity { listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static, ) { self.scroll_wheel_listeners - .push(Box::new(move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { + .push(Box::new(move |event, phase, hitbox, cx| { + if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) { (listener)(event, cx); } })); @@ -483,7 +477,7 @@ impl Interactivity { /// Block the mouse from interacting with this element or any of it's children /// The imperative API equivalent to [`InteractiveElement::block_mouse`] pub fn block_mouse(&mut self) { - self.block_mouse = true; + self.occlude_mouse = true; } } @@ -872,7 +866,7 @@ pub trait StatefulInteractiveElement: InteractiveElement { /// Track the scroll state of this element with the given handle. fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self { - self.interactivity().scroll_handle = Some(scroll_handle.clone()); + self.interactivity().tracked_scroll_handle = Some(scroll_handle.clone()); self } @@ -979,15 +973,15 @@ pub trait FocusableElement: InteractiveElement { } pub(crate) type MouseDownListener = - Box; + Box; pub(crate) type MouseUpListener = - Box; + Box; pub(crate) type MouseMoveListener = - Box; + Box; pub(crate) type ScrollWheelListener = - Box; + Box; pub(crate) type ClickListener = Box; @@ -1031,6 +1025,16 @@ pub struct Div { children: SmallVec<[AnyElement; 2]>, } +/// A frame state for a `Div` element, which contains layout IDs for its children. +/// +/// This struct is used internally by the `Div` element to manage the layout state of its children +/// during the UI update cycle. It holds a small vector of `LayoutId` values, each corresponding to +/// a child element of the `Div`. These IDs are used to query the layout engine for the computed +/// bounds of the children after the layout phase is complete. +pub struct DivFrameState { + child_layout_ids: SmallVec<[LayoutId; 2]>, +} + impl Styled for Div { fn style(&mut self) -> &mut StyleRefinement { &mut self.interactivity.base_style @@ -1050,54 +1054,41 @@ impl ParentElement for Div { } impl Element for Div { - type State = DivState; + type BeforeLayout = DivFrameState; + type AfterLayout = Option; - fn request_layout( - &mut self, - element_state: Option, - cx: &mut ElementContext, - ) -> (LayoutId, Self::State) { + fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) { let mut child_layout_ids = SmallVec::new(); - let (layout_id, interactive_state) = self.interactivity.layout( - element_state.map(|s| s.interactive_state), - cx, - |style, cx| { - cx.with_text_style(style.text_style().cloned(), |cx| { - child_layout_ids = self - .children - .iter_mut() - .map(|child| child.request_layout(cx)) - .collect::>(); - cx.request_layout(&style, child_layout_ids.iter().copied()) - }) - }, - ); - ( - layout_id, - DivState { - interactive_state, - child_layout_ids, - }, - ) + let layout_id = self.interactivity.before_layout(cx, |style, cx| { + cx.with_text_style(style.text_style().cloned(), |cx| { + child_layout_ids = self + .children + .iter_mut() + .map(|child| child.before_layout(cx)) + .collect::>(); + cx.request_layout(&style, child_layout_ids.iter().copied()) + }) + }); + (layout_id, DivFrameState { child_layout_ids }) } - fn paint( + fn after_layout( &mut self, bounds: Bounds, - element_state: &mut Self::State, + before_layout: &mut Self::BeforeLayout, cx: &mut ElementContext, - ) { + ) -> Option { let mut child_min = point(Pixels::MAX, Pixels::MAX); let mut child_max = Point::default(); - let content_size = if element_state.child_layout_ids.is_empty() { + let content_size = if before_layout.child_layout_ids.is_empty() { bounds.size - } else if let Some(scroll_handle) = self.interactivity.scroll_handle.as_ref() { + } else if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() { let mut state = scroll_handle.0.borrow_mut(); - state.child_bounds = Vec::with_capacity(element_state.child_layout_ids.len()); + state.child_bounds = Vec::with_capacity(before_layout.child_layout_ids.len()); state.bounds = bounds; let requested = state.requested_scroll_top.take(); - for (ix, child_layout_id) in element_state.child_layout_ids.iter().enumerate() { + for (ix, child_layout_id) in before_layout.child_layout_ids.iter().enumerate() { let child_bounds = cx.layout_bounds(*child_layout_id); child_min = child_min.min(&child_bounds.origin); child_max = child_max.max(&child_bounds.lower_right()); @@ -1112,7 +1103,7 @@ impl Element for Div { } (child_max - child_min).into() } else { - for child_layout_id in &element_state.child_layout_ids { + for child_layout_id in &before_layout.child_layout_ids { let child_bounds = cx.layout_bounds(*child_layout_id); child_min = child_min.min(&child_bounds.origin); child_max = child_max.max(&child_bounds.lower_right()); @@ -1120,60 +1111,57 @@ impl Element for Div { (child_max - child_min).into() }; - self.interactivity.paint( + self.interactivity.after_layout( bounds, content_size, - &mut element_state.interactive_state, cx, - |_style, scroll_offset, cx| { + |_style, scroll_offset, hitbox, cx| { cx.with_element_offset(scroll_offset, |cx| { for child in &mut self.children { - child.paint(cx); + child.after_layout(cx); } - }) + }); + hitbox }, - ); + ) + } + + fn paint( + &mut self, + bounds: Bounds, + _before_layout: &mut Self::BeforeLayout, + hitbox: &mut Option, + cx: &mut ElementContext, + ) { + self.interactivity + .paint(bounds, hitbox.as_ref(), cx, |_style, cx| { + for child in &mut self.children { + child.paint(cx); + } + }); } } impl IntoElement for Div { type Element = Self; - fn element_id(&self) -> Option { - self.interactivity.element_id.clone() - } - fn into_element(self) -> Self::Element { self } } -/// The state a div needs to keep track of between frames. -pub struct DivState { - child_layout_ids: SmallVec<[LayoutId; 2]>, - interactive_state: InteractiveElementState, -} - -impl DivState { - /// Is the div currently being clicked on? - pub fn is_active(&self) -> bool { - self.interactive_state - .pending_mouse_down - .as_ref() - .map_or(false, |pending| pending.borrow().is_some()) - } -} - /// The interactivity struct. Powers all of the general-purpose /// interactivity in the `Div` element. #[derive(Default)] pub struct Interactivity { /// The element ID of the element pub element_id: Option, + pub(crate) content_size: Size, pub(crate) key_context: Option, pub(crate) focusable: bool, pub(crate) tracked_focus_handle: Option, - pub(crate) scroll_handle: Option, + pub(crate) tracked_scroll_handle: Option, + pub(crate) scroll_offset: Option>>>, pub(crate) group: Option, /// The base style of the element, before any modifications are applied /// by focus, active, etc. @@ -1202,7 +1190,7 @@ pub struct Interactivity { pub(crate) drag_listener: Option<(Box, DragListener)>, pub(crate) hover_listener: Option>, pub(crate) tooltip_builder: Option, - pub(crate) block_mouse: bool, + pub(crate) occlude_mouse: bool, #[cfg(debug_assertions)] pub(crate) location: Option>, @@ -1211,68 +1199,146 @@ pub struct Interactivity { pub(crate) debug_selector: Option, } -/// The bounds and depth of an element in the computed element tree. -#[derive(Clone, Debug)] -pub struct InteractiveBounds { - /// The 2D bounds of the element - pub bounds: Bounds, - /// The 'stacking order', or depth, for this element - pub stacking_order: StackingOrder, -} - -impl InteractiveBounds { - /// Checks whether this point was inside these bounds in the rendered frame, and that these bounds where the topmost layer - /// Never call this during paint to perform hover calculations. It will reference the previous frame and could cause flicker. - pub fn visibly_contains(&self, point: &Point, cx: &WindowContext) -> bool { - self.bounds.contains(point) && cx.was_top_layer(point, &self.stacking_order) - } - - /// Checks whether this point was inside these bounds, and that these bounds where the topmost layer - /// under an active drag - pub fn drag_target_contains(&self, point: &Point, cx: &WindowContext) -> bool { - self.bounds.contains(point) - && cx.was_top_layer_under_active_drag(point, &self.stacking_order) - } -} - impl Interactivity { /// Layout this element according to this interactivity state's configured styles - pub fn layout( + pub fn before_layout( &mut self, - element_state: Option, cx: &mut ElementContext, f: impl FnOnce(Style, &mut ElementContext) -> LayoutId, - ) -> (LayoutId, InteractiveElementState) { - let mut element_state = element_state.unwrap_or_default(); + ) -> LayoutId { + cx.with_element_state::( + self.element_id.clone(), + |element_state, cx| { + let mut element_state = + element_state.map(|element_state| element_state.unwrap_or_default()); - if cx.has_active_drag() { - if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref() { - *pending_mouse_down.borrow_mut() = None; + if let Some(element_state) = element_state.as_ref() { + if cx.has_active_drag() { + if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref() + { + *pending_mouse_down.borrow_mut() = None; + } + if let Some(clicked_state) = element_state.clicked_state.as_ref() { + *clicked_state.borrow_mut() = ElementClickedState::default(); + } + } + } + + // Ensure we store a focus handle in our element state if we're focusable. + // If there's an explicit focus handle we're tracking, use that. Otherwise + // create a new handle and store it in the element state, which lives for as + // as frames contain an element with this id. + if self.focusable { + if self.tracked_focus_handle.is_none() { + if let Some(element_state) = element_state.as_mut() { + self.tracked_focus_handle = Some( + element_state + .focus_handle + .get_or_insert_with(|| cx.focus_handle()) + .clone(), + ); + } + } + } + + if let Some(scroll_handle) = self.tracked_scroll_handle.as_ref() { + self.scroll_offset = Some(scroll_handle.0.borrow().offset.clone()); + } else if self.base_style.overflow.x == Some(Overflow::Scroll) + || self.base_style.overflow.y == Some(Overflow::Scroll) + { + if let Some(element_state) = element_state.as_mut() { + self.scroll_offset = Some( + element_state + .scroll_offset + .get_or_insert_with(|| Rc::default()) + .clone(), + ); + } + } + + let style = self.compute_style_internal(None, element_state.as_mut(), cx); + let layout_id = f(style, cx); + (layout_id, element_state) + }, + ) + } + + /// Commit the bounds of this element according to this interactivity state's configured styles. + pub fn after_layout( + &mut self, + bounds: Bounds, + content_size: Size, + cx: &mut ElementContext, + f: impl FnOnce(&Style, Point, Option, &mut ElementContext) -> R, + ) -> R { + self.content_size = content_size; + cx.with_element_state::( + self.element_id.clone(), + |element_state, cx| { + let mut element_state = + element_state.map(|element_state| element_state.unwrap_or_default()); + let style = self.compute_style_internal(None, element_state.as_mut(), cx); + + cx.with_text_style(style.text_style().cloned(), |cx| { + cx.with_content_mask(style.overflow_mask(bounds, cx.rem_size()), |cx| { + let hitbox = if self.occlude_mouse + || style.mouse_cursor.is_some() + || self.hover_style.is_some() + || self.group_hover_style.is_some() + { + Some(cx.insert_hitbox(bounds, self.occlude_mouse)) + } else { + None + }; + let scroll_offset = self.clamp_scroll_position(bounds, &style, cx); + let result = f(&style, scroll_offset, hitbox, cx); + (result, element_state) + }) + }) + }, + ) + } + + fn clamp_scroll_position( + &mut self, + bounds: Bounds, + style: &Style, + cx: &mut ElementContext, + ) -> Point { + if let Some(scroll_offset) = self.scroll_offset.as_ref() { + if let Some(scroll_handle) = &self.tracked_scroll_handle { + scroll_handle.0.borrow_mut().overflow = style.overflow; } - if let Some(clicked_state) = element_state.clicked_state.as_ref() { - *clicked_state.borrow_mut() = ElementClickedState::default(); - } - } - // Ensure we store a focus handle in our element state if we're focusable. - // If there's an explicit focus handle we're tracking, use that. Otherwise - // create a new handle and store it in the element state, which lives for as - // as frames contain an element with this id. - if self.focusable { - element_state.focus_handle.get_or_insert_with(|| { - self.tracked_focus_handle - .clone() - .unwrap_or_else(|| cx.focus_handle()) - }); + let rem_size = cx.rem_size(); + let padding_size = size( + style + .padding + .left + .to_pixels(bounds.size.width.into(), rem_size) + + style + .padding + .right + .to_pixels(bounds.size.width.into(), rem_size), + style + .padding + .top + .to_pixels(bounds.size.height.into(), rem_size) + + style + .padding + .bottom + .to_pixels(bounds.size.height.into(), rem_size), + ); + let scroll_max = (self.content_size + padding_size - bounds.size).max(&Size::default()); + // Clamp scroll offset in case scroll max is smaller now (e.g., if children + // were removed or the bounds became larger). + let mut scroll_offset = scroll_offset.borrow_mut(); + scroll_offset.x = scroll_offset.x.clamp(-scroll_max.width, px(0.)); + scroll_offset.y = scroll_offset.y.clamp(-scroll_max.height, px(0.)); + *scroll_offset + } else { + Point::default() } - - if let Some(scroll_handle) = self.scroll_handle.as_ref() { - element_state.scroll_offset = Some(scroll_handle.0.borrow().offset.clone()); - } - - let style = self.compute_style(None, &mut element_state, cx); - let layout_id = f(style, cx); - (layout_id, element_state) } /// Paint this element according to this interactivity state's configured styles @@ -1286,731 +1352,672 @@ impl Interactivity { pub fn paint( &mut self, bounds: Bounds, - content_size: Size, - element_state: &mut InteractiveElementState, + hitbox: Option<&Hitbox>, cx: &mut ElementContext, - f: impl FnOnce(&Style, Point, &mut ElementContext), + f: impl FnOnce(&Style, &mut ElementContext), ) { - let style = self.compute_style(Some(bounds), element_state, cx); - let z_index = style.z_index.unwrap_or(0); + cx.with_element_state::( + self.element_id.clone(), + |element_state, cx| { + let mut element_state = + element_state.map(|element_state| element_state.unwrap_or_default()); - #[cfg(any(feature = "test-support", test))] - if let Some(debug_selector) = &self.debug_selector { - cx.window - .next_frame - .debug_bounds - .insert(debug_selector.clone(), bounds); - } + let style = self.compute_style_internal(hitbox, element_state.as_mut(), cx); - let paint_hover_group_handler = |cx: &mut ElementContext| { - let hover_group_bounds = self - .group_hover_style - .as_ref() - .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx)); + #[cfg(any(feature = "test-support", test))] + if let Some(debug_selector) = &self.debug_selector { + cx.window + .next_frame + .debug_bounds + .insert(debug_selector.clone(), bounds); + } - if let Some(group_bounds) = hover_group_bounds { - let hovered = group_bounds.contains(&cx.mouse_position()); - cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { - if phase == DispatchPhase::Capture - && group_bounds.contains(&event.position) != hovered - { - cx.refresh(); - } - }); - } - }; + if style.visibility == Visibility::Hidden { + self.paint_hover_group_handler(cx); + return ((), element_state); + } - if style.visibility == Visibility::Hidden { - cx.with_z_index(z_index, |cx| paint_hover_group_handler(cx)); - return; - } + style.paint(bounds, cx, |cx: &mut ElementContext| { + cx.with_text_style(style.text_style().cloned(), |cx| { + cx.with_content_mask(style.overflow_mask(bounds, cx.rem_size()), |cx| { + self.paint_hover_group_handler(cx); - cx.with_z_index(z_index, |cx| { - style.paint(bounds, cx, |cx: &mut ElementContext| { - cx.with_text_style(style.text_style().cloned(), |cx| { - cx.with_content_mask(style.overflow_mask(bounds, cx.rem_size()), |cx| { - #[cfg(debug_assertions)] - if self.element_id.is_some() - && (style.debug - || style.debug_below - || cx.has_global::()) - && bounds.contains(&cx.mouse_position()) - { - const FONT_SIZE: crate::Pixels = crate::Pixels(10.); - let element_id = format!("{:?}", self.element_id.as_ref().unwrap()); - let str_len = element_id.len(); + if let Some(hitbox) = hitbox { + self.paint_debug_info(hitbox, &style, cx); - let render_debug_text = |cx: &mut ElementContext| { - if let Some(text) = cx - .text_system() - .shape_text( - element_id.into(), - FONT_SIZE, - &[cx.text_style().to_run(str_len)], - None, - ) - .ok() - .and_then(|mut text| text.pop()) - { - text.paint(bounds.origin, FONT_SIZE, cx).ok(); - - let text_bounds = crate::Bounds { - origin: bounds.origin, - size: text.size(FONT_SIZE), - }; - if self.location.is_some() - && text_bounds.contains(&cx.mouse_position()) - && cx.modifiers().command - { - let command_held = cx.modifiers().command; - cx.on_key_event({ - move |e: &crate::ModifiersChangedEvent, _phase, cx| { - if e.modifiers.command != command_held - && text_bounds.contains(&cx.mouse_position()) - { - cx.refresh(); - } - } - }); - - let hovered = bounds.contains(&cx.mouse_position()); - cx.on_mouse_event( - move |event: &MouseMoveEvent, phase, cx| { - if phase == DispatchPhase::Capture - && bounds.contains(&event.position) != hovered - { - cx.refresh(); - } - }, - ); - - cx.on_mouse_event({ - let location = self.location.unwrap(); - move |e: &crate::MouseDownEvent, phase, cx| { - if text_bounds.contains(&e.position) - && phase.capture() - { - cx.stop_propagation(); - let Ok(dir) = std::env::current_dir() else { - return; - }; - - eprintln!( - "This element was created at:\n{}:{}:{}", - dir.join(location.file()).to_string_lossy(), - location.line(), - location.column() - ); - } - } - }); - cx.paint_quad(crate::outline( - crate::Bounds { - origin: bounds.origin - + crate::point( - crate::px(0.), - FONT_SIZE - px(2.), - ), - size: crate::Size { - width: text_bounds.size.width, - height: crate::px(1.), - }, - }, - crate::red(), - )) + if !cx.has_active_drag() { + if let Some(mouse_cursor) = style.mouse_cursor { + cx.set_cursor_style(mouse_cursor, hitbox); } } - }; - cx.with_z_index(1, |cx| { - cx.with_text_style( - Some(crate::TextStyleRefinement { - color: Some(crate::red()), - line_height: Some(FONT_SIZE.into()), - background_color: Some(crate::white()), - ..Default::default() - }), - render_debug_text, - ) - }); - } + if let Some(group) = self.group.clone() { + GroupHitboxes::push(group, hitbox.id, cx); + } - let interactive_bounds = InteractiveBounds { - bounds: bounds.intersect(&cx.content_mask().bounds), - stacking_order: cx.stacking_order().clone(), - }; + self.paint_mouse_listeners(hitbox, element_state.as_mut(), cx); + self.paint_scroll_listener(hitbox, &style, cx); + } - if self.block_mouse - || style.background.as_ref().is_some_and(|fill| { - fill.color().is_some_and(|color| !color.is_transparent()) - }) - { - cx.add_opaque_layer(interactive_bounds.bounds); - } + self.paint_keyboard_listeners(cx); + f(&style, cx); - if !cx.has_active_drag() { - if let Some(mouse_cursor) = style.mouse_cursor { - let hovered = bounds.contains(&cx.mouse_position()); - if hovered { - cx.set_cursor_style( - mouse_cursor, - interactive_bounds.stacking_order.clone(), + if hitbox.is_some() { + if let Some(group) = self.group.as_ref() { + GroupHitboxes::pop(group, cx); + } + } + }); + }); + }); + + ((), element_state) + }, + ); + } + + fn paint_debug_info(&mut self, hitbox: &Hitbox, style: &Style, cx: &mut ElementContext) { + // todo!("ensure that an hitbox is present when painting debug info"). + + #[cfg(debug_assertions)] + if self.element_id.is_some() + && (style.debug || style.debug_below || cx.has_global::()) + && hitbox.is_hovered(cx) + { + const FONT_SIZE: crate::Pixels = crate::Pixels(10.); + let element_id = format!("{:?}", self.element_id.as_ref().unwrap()); + let str_len = element_id.len(); + + let render_debug_text = |cx: &mut ElementContext| { + if let Some(text) = cx + .text_system() + .shape_text( + element_id.into(), + FONT_SIZE, + &[cx.text_style().to_run(str_len)], + None, + ) + .ok() + .and_then(|mut text| text.pop()) + { + text.paint(hitbox.bounds.origin, FONT_SIZE, cx).ok(); + + let text_bounds = crate::Bounds { + origin: hitbox.bounds.origin, + size: text.size(FONT_SIZE), + }; + if self.location.is_some() + && text_bounds.contains(&cx.mouse_position()) + && cx.modifiers().command + { + let command_held = cx.modifiers().command; + cx.on_key_event({ + move |e: &crate::ModifiersChangedEvent, _phase, cx| { + if e.modifiers.command != command_held + && text_bounds.contains(&cx.mouse_position()) + { + cx.refresh(); + } + } + }); + + let was_hovered = hitbox.is_hovered(cx); + cx.on_mouse_event({ + let hitbox = hitbox.clone(); + move |_: &MouseMoveEvent, phase, cx| { + if phase == DispatchPhase::Capture { + let hovered = hitbox.is_hovered(cx); + if hovered != was_hovered { + cx.refresh(); + } + } + } + }); + + cx.on_mouse_event({ + let hitbox = hitbox.clone(); + let location = self.location.unwrap(); + move |e: &crate::MouseDownEvent, phase, cx| { + if text_bounds.contains(&e.position) + && phase.capture() + && hitbox.is_hovered(cx) + { + cx.stop_propagation(); + let Ok(dir) = std::env::current_dir() else { + return; + }; + + eprintln!( + "This element was created at:\n{}:{}:{}", + dir.join(location.file()).to_string_lossy(), + location.line(), + location.column() ); } } - } + }); + cx.paint_quad(crate::outline( + crate::Bounds { + origin: hitbox.bounds.origin + + crate::point(crate::px(0.), FONT_SIZE - px(2.)), + size: crate::Size { + width: text_bounds.size.width, + height: crate::px(1.), + }, + }, + crate::red(), + )) + } + } + }; - // If this element can be focused, register a mouse down listener - // that will automatically transfer focus when hitting the element. - // This behavior can be suppressed by using `cx.prevent_default()`. - if let Some(focus_handle) = element_state.focus_handle.clone() { - cx.on_mouse_event({ - let interactive_bounds = interactive_bounds.clone(); - move |event: &MouseDownEvent, phase, cx| { - if phase == DispatchPhase::Bubble - && !cx.default_prevented() - && interactive_bounds.visibly_contains(&event.position, cx) - { - cx.focus(&focus_handle); - // If there is a parent that is also focusable, prevent it - // from transferring focus because we already did so. - cx.prevent_default(); - } - } - }); - } + cx.with_text_style( + Some(crate::TextStyleRefinement { + color: Some(crate::red()), + line_height: Some(FONT_SIZE.into()), + background_color: Some(crate::white()), + ..Default::default() + }), + render_debug_text, + ) + } + } - for listener in self.mouse_down_listeners.drain(..) { - let interactive_bounds = interactive_bounds.clone(); - cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { - listener(event, &interactive_bounds, phase, cx); - }) - } + fn paint_mouse_listeners( + &mut self, + hitbox: &Hitbox, + element_state: Option<&mut InteractiveElementState>, + cx: &mut ElementContext, + ) { + // If this element can be focused, register a mouse down listener + // that will automatically transfer focus when hitting the element. + // This behavior can be suppressed by using `cx.prevent_default()`. + if let Some(focus_handle) = self.tracked_focus_handle.clone() { + let hitbox = hitbox.clone(); + cx.on_mouse_event(move |_: &MouseDownEvent, phase, cx| { + if phase == DispatchPhase::Bubble + && hitbox.is_hovered(cx) + && !cx.default_prevented() + { + cx.focus(&focus_handle); + // If there is a parent that is also focusable, prevent it + // from transferring focus because we already did so. + cx.prevent_default(); + } + }); + } - for listener in self.mouse_up_listeners.drain(..) { - let interactive_bounds = interactive_bounds.clone(); - cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { - listener(event, &interactive_bounds, phase, cx); - }) - } + for listener in self.mouse_down_listeners.drain(..) { + let hitbox = hitbox.clone(); + cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { + listener(event, phase, &hitbox, cx); + }) + } - for listener in self.mouse_move_listeners.drain(..) { - let interactive_bounds = interactive_bounds.clone(); - cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { - listener(event, &interactive_bounds, phase, cx); - }) - } + for listener in self.mouse_up_listeners.drain(..) { + let hitbox = hitbox.clone(); + cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { + listener(event, phase, &hitbox, cx); + }) + } - for listener in self.scroll_wheel_listeners.drain(..) { - let interactive_bounds = interactive_bounds.clone(); - cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { - listener(event, &interactive_bounds, phase, cx); - }) - } + for listener in self.mouse_move_listeners.drain(..) { + let hitbox = hitbox.clone(); + cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { + listener(event, phase, &hitbox, cx); + }) + } - paint_hover_group_handler(cx); + for listener in self.scroll_wheel_listeners.drain(..) { + let hitbox = hitbox.clone(); + cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { + listener(event, phase, &hitbox, cx); + }) + } - if self.hover_style.is_some() - || self.base_style.mouse_cursor.is_some() - || cx.active_drag.is_some() && !self.drag_over_styles.is_empty() - { - let bounds = bounds.intersect(&cx.content_mask().bounds); - let hovered = bounds.contains(&cx.mouse_position()); - cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { - if phase == DispatchPhase::Capture - && bounds.contains(&event.position) != hovered - { - cx.refresh(); - } - }); - } + if self.hover_style.is_some() + || self.base_style.mouse_cursor.is_some() + || cx.active_drag.is_some() && !self.drag_over_styles.is_empty() + { + let hitbox = hitbox.clone(); + let was_hovered = hitbox.is_hovered(cx); + cx.on_mouse_event(move |_: &MouseMoveEvent, phase, cx| { + let hovered = hitbox.is_hovered(cx); + if phase == DispatchPhase::Capture && hovered != was_hovered { + cx.refresh(); + } + }); + } - let mut drag_listener = mem::take(&mut self.drag_listener); - let drop_listeners = mem::take(&mut self.drop_listeners); - let click_listeners = mem::take(&mut self.click_listeners); - let can_drop_predicate = mem::take(&mut self.can_drop_predicate); + let mut drag_listener = mem::take(&mut self.drag_listener); + let drop_listeners = mem::take(&mut self.drop_listeners); + let click_listeners = mem::take(&mut self.click_listeners); + let can_drop_predicate = mem::take(&mut self.can_drop_predicate); - if !drop_listeners.is_empty() { - cx.on_mouse_event({ - let interactive_bounds = interactive_bounds.clone(); - move |event: &MouseUpEvent, phase, cx| { - if let Some(drag) = &cx.active_drag { - if phase == DispatchPhase::Bubble - && interactive_bounds - .drag_target_contains(&event.position, cx) - { - let drag_state_type = drag.value.as_ref().type_id(); - for (drop_state_type, listener) in &drop_listeners { - if *drop_state_type == drag_state_type { - let drag = cx.active_drag.take().expect( - "checked for type drag state type above", - ); + if !drop_listeners.is_empty() { + let hitbox = hitbox.clone(); + cx.on_mouse_event({ + move |_: &MouseUpEvent, phase, cx| { + if let Some(drag) = &cx.active_drag { + if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) { + let drag_state_type = drag.value.as_ref().type_id(); + for (drop_state_type, listener) in &drop_listeners { + if *drop_state_type == drag_state_type { + let drag = cx + .active_drag + .take() + .expect("checked for type drag state type above"); - let mut can_drop = true; - if let Some(predicate) = &can_drop_predicate { - can_drop = predicate( - drag.value.as_ref(), - cx.deref_mut(), - ); - } - - if can_drop { - listener( - drag.value.as_ref(), - cx.deref_mut(), - ); - cx.refresh(); - cx.stop_propagation(); - } - } - } - } - } - } - }); - } - - if !click_listeners.is_empty() || drag_listener.is_some() { - let pending_mouse_down = element_state - .pending_mouse_down - .get_or_insert_with(Default::default) - .clone(); - - let clicked_state = element_state - .clicked_state - .get_or_insert_with(Default::default) - .clone(); - - cx.on_mouse_event({ - let interactive_bounds = interactive_bounds.clone(); - let pending_mouse_down = pending_mouse_down.clone(); - move |event: &MouseDownEvent, phase, cx| { - if phase == DispatchPhase::Bubble - && event.button == MouseButton::Left - && interactive_bounds.visibly_contains(&event.position, cx) - { - *pending_mouse_down.borrow_mut() = Some(event.clone()); - cx.refresh(); - } - } - }); - - cx.on_mouse_event({ - let pending_mouse_down = pending_mouse_down.clone(); - move |event: &MouseMoveEvent, phase, cx| { - if phase == DispatchPhase::Capture { - return; + let mut can_drop = true; + if let Some(predicate) = &can_drop_predicate { + can_drop = predicate(drag.value.as_ref(), cx.deref_mut()); } - let mut pending_mouse_down = pending_mouse_down.borrow_mut(); - if let Some(mouse_down) = pending_mouse_down.clone() { - if !cx.has_active_drag() - && (event.position - mouse_down.position).magnitude() - > DRAG_THRESHOLD - { - if let Some((drag_value, drag_listener)) = - drag_listener.take() - { - *clicked_state.borrow_mut() = - ElementClickedState::default(); - let cursor_offset = event.position - bounds.origin; - let drag = (drag_listener)(drag_value.as_ref(), cx); - cx.active_drag = Some(AnyDrag { - view: drag, - value: drag_value, - cursor_offset, - }); - pending_mouse_down.take(); - cx.refresh(); - cx.stop_propagation(); - } - } - } - } - }); - - cx.on_mouse_event({ - let interactive_bounds = interactive_bounds.clone(); - let mut captured_mouse_down = None; - move |event: &MouseUpEvent, phase, cx| match phase { - // Clear the pending mouse down during the capture phase, - // so that it happens even if another event handler stops - // propagation. - DispatchPhase::Capture => { - let mut pending_mouse_down = - pending_mouse_down.borrow_mut(); - if pending_mouse_down.is_some() { - captured_mouse_down = pending_mouse_down.take(); - cx.refresh(); - } - } - // Fire click handlers during the bubble phase. - DispatchPhase::Bubble => { - if let Some(mouse_down) = captured_mouse_down.take() { - if interactive_bounds - .visibly_contains(&event.position, cx) - { - let mouse_click = ClickEvent { - down: mouse_down, - up: event.clone(), - }; - for listener in &click_listeners { - listener(&mouse_click, cx); - } - } - } - } - } - }); - } - - if let Some(hover_listener) = self.hover_listener.take() { - let was_hovered = element_state - .hover_state - .get_or_insert_with(Default::default) - .clone(); - let has_mouse_down = element_state - .pending_mouse_down - .get_or_insert_with(Default::default) - .clone(); - let interactive_bounds = interactive_bounds.clone(); - - cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { - if phase != DispatchPhase::Bubble { - return; - } - let is_hovered = interactive_bounds - .visibly_contains(&event.position, cx) - && has_mouse_down.borrow().is_none() - && !cx.has_active_drag(); - let mut was_hovered = was_hovered.borrow_mut(); - - if is_hovered != *was_hovered { - *was_hovered = is_hovered; - drop(was_hovered); - - hover_listener(&is_hovered, cx.deref_mut()); - } - }); - } - - if let Some(tooltip_builder) = self.tooltip_builder.take() { - let active_tooltip = element_state - .active_tooltip - .get_or_insert_with(Default::default) - .clone(); - let pending_mouse_down = element_state - .pending_mouse_down - .get_or_insert_with(Default::default) - .clone(); - let interactive_bounds = interactive_bounds.clone(); - - cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { - let is_hovered = interactive_bounds - .visibly_contains(&event.position, cx) - && pending_mouse_down.borrow().is_none(); - if !is_hovered { - active_tooltip.borrow_mut().take(); - return; - } - - if phase != DispatchPhase::Bubble { - return; - } - - if active_tooltip.borrow().is_none() { - let task = cx.spawn({ - let active_tooltip = active_tooltip.clone(); - let tooltip_builder = tooltip_builder.clone(); - - move |mut cx| async move { - cx.background_executor().timer(TOOLTIP_DELAY).await; - cx.update(|cx| { - active_tooltip.borrow_mut().replace( - ActiveTooltip { - tooltip: Some(AnyTooltip { - view: tooltip_builder(cx), - cursor_offset: cx.mouse_position(), - }), - _task: None, - }, - ); - cx.refresh(); - }) - .ok(); - } - }); - active_tooltip.borrow_mut().replace(ActiveTooltip { - tooltip: None, - _task: Some(task), - }); - } - }); - - let active_tooltip = element_state - .active_tooltip - .get_or_insert_with(Default::default) - .clone(); - cx.on_mouse_event(move |_: &MouseDownEvent, _, _| { - active_tooltip.borrow_mut().take(); - }); - - if let Some(active_tooltip) = element_state - .active_tooltip - .get_or_insert_with(Default::default) - .borrow() - .as_ref() - { - if let Some(tooltip) = active_tooltip.tooltip.clone() { - cx.set_tooltip(tooltip); - } - } - } - - let active_state = element_state - .clicked_state - .get_or_insert_with(Default::default) - .clone(); - if active_state.borrow().is_clicked() { - cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| { - if phase == DispatchPhase::Capture { - *active_state.borrow_mut() = ElementClickedState::default(); - cx.refresh(); - } - }); - } else { - let active_group_bounds = self - .group_active_style - .as_ref() - .and_then(|group_active| GroupBounds::get(&group_active.group, cx)); - let interactive_bounds = interactive_bounds.clone(); - cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| { - if phase == DispatchPhase::Bubble && !cx.default_prevented() { - let group = active_group_bounds - .map_or(false, |bounds| bounds.contains(&down.position)); - let element = - interactive_bounds.visibly_contains(&down.position, cx); - if group || element { - *active_state.borrow_mut() = - ElementClickedState { group, element }; - cx.refresh(); - } - } - }); - } - - let overflow = style.overflow; - if overflow.x == Overflow::Scroll || overflow.y == Overflow::Scroll { - if let Some(scroll_handle) = &self.scroll_handle { - scroll_handle.0.borrow_mut().overflow = overflow; - } - - let scroll_offset = element_state - .scroll_offset - .get_or_insert_with(Rc::default) - .clone(); - let line_height = cx.line_height(); - let rem_size = cx.rem_size(); - let padding_size = size( - style - .padding - .left - .to_pixels(bounds.size.width.into(), rem_size) - + style - .padding - .right - .to_pixels(bounds.size.width.into(), rem_size), - style - .padding - .top - .to_pixels(bounds.size.height.into(), rem_size) - + style - .padding - .bottom - .to_pixels(bounds.size.height.into(), rem_size), - ); - let scroll_max = - (content_size + padding_size - bounds.size).max(&Size::default()); - // Clamp scroll offset in case scroll max is smaller now (e.g., if children - // were removed or the bounds became larger). - { - let mut scroll_offset = scroll_offset.borrow_mut(); - scroll_offset.x = scroll_offset.x.clamp(-scroll_max.width, px(0.)); - scroll_offset.y = scroll_offset.y.clamp(-scroll_max.height, px(0.)); - } - - let interactive_bounds = interactive_bounds.clone(); - cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { - if phase == DispatchPhase::Bubble - && interactive_bounds.visibly_contains(&event.position, cx) - { - let mut scroll_offset = scroll_offset.borrow_mut(); - let old_scroll_offset = *scroll_offset; - let delta = event.delta.pixel_delta(line_height); - - if overflow.x == Overflow::Scroll { - let mut delta_x = Pixels::ZERO; - if !delta.x.is_zero() { - delta_x = delta.x; - } else if overflow.y != Overflow::Scroll { - delta_x = delta.y; - } - - scroll_offset.x = (scroll_offset.x + delta_x) - .clamp(-scroll_max.width, px(0.)); - } - - if overflow.y == Overflow::Scroll { - let mut delta_y = Pixels::ZERO; - if !delta.y.is_zero() { - delta_y = delta.y; - } else if overflow.x != Overflow::Scroll { - delta_y = delta.x; - } - - scroll_offset.y = (scroll_offset.y + delta_y) - .clamp(-scroll_max.height, px(0.)); - } - - if *scroll_offset != old_scroll_offset { + if can_drop { + listener(drag.value.as_ref(), cx.deref_mut()); cx.refresh(); cx.stop_propagation(); } } - }); + } } - - if let Some(group) = self.group.clone() { - GroupBounds::push(group, bounds, cx); - } - - let scroll_offset = element_state - .scroll_offset - .as_ref() - .map(|scroll_offset| *scroll_offset.borrow()); - - let key_down_listeners = mem::take(&mut self.key_down_listeners); - let key_up_listeners = mem::take(&mut self.key_up_listeners); - let action_listeners = mem::take(&mut self.action_listeners); - cx.with_key_dispatch( - self.key_context.clone(), - element_state.focus_handle.clone(), - |_, cx| { - for listener in key_down_listeners { - cx.on_key_event(move |event: &KeyDownEvent, phase, cx| { - listener(event, phase, cx); - }) - } - - for listener in key_up_listeners { - cx.on_key_event(move |event: &KeyUpEvent, phase, cx| { - listener(event, phase, cx); - }) - } - - for (action_type, listener) in action_listeners { - cx.on_action(action_type, listener) - } - - f(&style, scroll_offset.unwrap_or_default(), cx) - }, - ); - - if let Some(group) = self.group.as_ref() { - GroupBounds::pop(group, cx); - } - }); - }); + } + } }); - }); + } + + if let Some(element_state) = element_state { + if !click_listeners.is_empty() || drag_listener.is_some() { + let pending_mouse_down = element_state + .pending_mouse_down + .get_or_insert_with(Default::default) + .clone(); + + let clicked_state = element_state + .clicked_state + .get_or_insert_with(Default::default) + .clone(); + + cx.on_mouse_event({ + let pending_mouse_down = pending_mouse_down.clone(); + let hitbox = hitbox.clone(); + move |event: &MouseDownEvent, phase, cx| { + if phase == DispatchPhase::Bubble + && event.button == MouseButton::Left + && hitbox.is_hovered(cx) + { + *pending_mouse_down.borrow_mut() = Some(event.clone()); + cx.refresh(); + } + } + }); + + cx.on_mouse_event({ + let pending_mouse_down = pending_mouse_down.clone(); + let hitbox = hitbox.clone(); + move |event: &MouseMoveEvent, phase, cx| { + if phase == DispatchPhase::Capture { + return; + } + + let mut pending_mouse_down = pending_mouse_down.borrow_mut(); + if let Some(mouse_down) = pending_mouse_down.clone() { + if !cx.has_active_drag() + && (event.position - mouse_down.position).magnitude() + > DRAG_THRESHOLD + { + if let Some((drag_value, drag_listener)) = drag_listener.take() { + *clicked_state.borrow_mut() = ElementClickedState::default(); + let cursor_offset = event.position - hitbox.bounds.origin; + let drag = (drag_listener)(drag_value.as_ref(), cx); + cx.active_drag = Some(AnyDrag { + view: drag, + value: drag_value, + cursor_offset, + }); + pending_mouse_down.take(); + cx.refresh(); + cx.stop_propagation(); + } + } + } + } + }); + + cx.on_mouse_event({ + let mut captured_mouse_down = None; + let hitbox = hitbox.clone(); + move |event: &MouseUpEvent, phase, cx| match phase { + // Clear the pending mouse down during the capture phase, + // so that it happens even if another event handler stops + // propagation. + DispatchPhase::Capture => { + let mut pending_mouse_down = pending_mouse_down.borrow_mut(); + if pending_mouse_down.is_some() && hitbox.is_hovered(cx) { + captured_mouse_down = pending_mouse_down.take(); + cx.refresh(); + } + } + // Fire click handlers during the bubble phase. + DispatchPhase::Bubble => { + if let Some(mouse_down) = captured_mouse_down.take() { + let mouse_click = ClickEvent { + down: mouse_down, + up: event.clone(), + }; + for listener in &click_listeners { + listener(&mouse_click, cx); + } + } + } + } + }); + } + + if let Some(hover_listener) = self.hover_listener.take() { + let hitbox = hitbox.clone(); + let was_hovered = element_state + .hover_state + .get_or_insert_with(Default::default) + .clone(); + let has_mouse_down = element_state + .pending_mouse_down + .get_or_insert_with(Default::default) + .clone(); + + cx.on_mouse_event(move |_: &MouseMoveEvent, phase, cx| { + if phase != DispatchPhase::Bubble { + return; + } + let is_hovered = has_mouse_down.borrow().is_none() + && !cx.has_active_drag() + && hitbox.is_hovered(cx); + let mut was_hovered = was_hovered.borrow_mut(); + + if is_hovered != *was_hovered { + *was_hovered = is_hovered; + drop(was_hovered); + + hover_listener(&is_hovered, cx.deref_mut()); + } + }); + } + + if let Some(tooltip_builder) = self.tooltip_builder.take() { + let active_tooltip = element_state + .active_tooltip + .get_or_insert_with(Default::default) + .clone(); + let pending_mouse_down = element_state + .pending_mouse_down + .get_or_insert_with(Default::default) + .clone(); + let hitbox = hitbox.clone(); + + cx.on_mouse_event(move |_: &MouseMoveEvent, phase, cx| { + let is_hovered = pending_mouse_down.borrow().is_none() && hitbox.is_hovered(cx); + if !is_hovered { + active_tooltip.borrow_mut().take(); + return; + } + + if phase != DispatchPhase::Bubble { + return; + } + + if active_tooltip.borrow().is_none() { + let task = cx.spawn({ + let active_tooltip = active_tooltip.clone(); + let tooltip_builder = tooltip_builder.clone(); + + move |mut cx| async move { + cx.background_executor().timer(TOOLTIP_DELAY).await; + cx.update(|cx| { + active_tooltip.borrow_mut().replace(ActiveTooltip { + tooltip: Some(AnyTooltip { + view: tooltip_builder(cx), + cursor_offset: cx.mouse_position(), + }), + _task: None, + }); + cx.refresh(); + }) + .ok(); + } + }); + active_tooltip.borrow_mut().replace(ActiveTooltip { + tooltip: None, + _task: Some(task), + }); + } + }); + + let active_tooltip = element_state + .active_tooltip + .get_or_insert_with(Default::default) + .clone(); + cx.on_mouse_event(move |_: &MouseDownEvent, _, _| { + active_tooltip.borrow_mut().take(); + }); + + if let Some(active_tooltip) = element_state + .active_tooltip + .get_or_insert_with(Default::default) + .borrow() + .as_ref() + { + if let Some(tooltip) = active_tooltip.tooltip.clone() { + cx.set_tooltip(tooltip); + } + } + } + + let active_state = element_state + .clicked_state + .get_or_insert_with(Default::default) + .clone(); + if active_state.borrow().is_clicked() { + cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| { + if phase == DispatchPhase::Capture { + *active_state.borrow_mut() = ElementClickedState::default(); + cx.refresh(); + } + }); + } else { + let active_group_hitbox = self + .group_active_style + .as_ref() + .and_then(|group_active| GroupHitboxes::get(&group_active.group, cx)); + let hitbox = hitbox.clone(); + cx.on_mouse_event(move |_: &MouseDownEvent, phase, cx| { + if phase == DispatchPhase::Bubble && !cx.default_prevented() { + let group_hovered = active_group_hitbox + .map_or(false, |group_hitbox_id| group_hitbox_id.is_hovered(cx)); + let element_hovered = hitbox.is_hovered(cx); + if group_hovered || element_hovered { + *active_state.borrow_mut() = ElementClickedState { + group: group_hovered, + element: element_hovered, + }; + cx.refresh(); + } + } + }); + } + } + } + + fn paint_keyboard_listeners(&mut self, cx: &mut ElementContext) { + let key_down_listeners = mem::take(&mut self.key_down_listeners); + let key_up_listeners = mem::take(&mut self.key_up_listeners); + let action_listeners = mem::take(&mut self.action_listeners); + if let Some(context) = self.key_context.clone() { + cx.set_key_context(context); + } + if let Some(focus_handle) = self.tracked_focus_handle.as_ref() { + cx.set_focus_handle(focus_handle); + } + + for listener in key_down_listeners { + cx.on_key_event(move |event: &KeyDownEvent, phase, cx| { + listener(event, phase, cx); + }) + } + + for listener in key_up_listeners { + cx.on_key_event(move |event: &KeyUpEvent, phase, cx| { + listener(event, phase, cx); + }) + } + + for (action_type, listener) in action_listeners { + cx.on_action(action_type, listener) + } + } + + fn paint_hover_group_handler(&self, cx: &mut ElementContext) { + let group_hitbox = self + .group_hover_style + .as_ref() + .and_then(|group_hover| GroupHitboxes::get(&group_hover.group, cx)); + + if let Some(group_hitbox) = group_hitbox { + let was_hovered = group_hitbox.is_hovered(cx); + cx.on_mouse_event(move |_: &MouseMoveEvent, phase, cx| { + let hovered = group_hitbox.is_hovered(cx); + if phase == DispatchPhase::Capture && hovered != was_hovered { + cx.refresh(); + } + }); + } + } + + fn paint_scroll_listener(&self, hitbox: &Hitbox, style: &Style, cx: &mut ElementContext) { + if let Some(scroll_offset) = self.scroll_offset.clone() { + let overflow = style.overflow; + let line_height = cx.line_height(); + let hitbox = hitbox.clone(); + cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { + if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) { + let mut scroll_offset = scroll_offset.borrow_mut(); + let old_scroll_offset = *scroll_offset; + let delta = event.delta.pixel_delta(line_height); + + if overflow.x == Overflow::Scroll { + let mut delta_x = Pixels::ZERO; + if !delta.x.is_zero() { + delta_x = delta.x; + } else if overflow.y != Overflow::Scroll { + delta_x = delta.y; + } + + scroll_offset.x += delta_x; + } + + if overflow.y == Overflow::Scroll { + let mut delta_y = Pixels::ZERO; + if !delta.y.is_zero() { + delta_y = delta.y; + } else if overflow.x != Overflow::Scroll { + delta_y = delta.x; + } + + scroll_offset.y += delta_y; + } + + if *scroll_offset != old_scroll_offset { + cx.refresh(); + cx.stop_propagation(); + } + } + }); + } } /// Compute the visual style for this element, based on the current bounds and the element's state. - pub fn compute_style( + pub fn compute_style(&self, hitbox: Option<&Hitbox>, cx: &mut ElementContext) -> Style { + cx.with_element_state(self.element_id.clone(), |element_state, cx| { + let mut element_state = + element_state.map(|element_state| element_state.unwrap_or_default()); + let style = self.compute_style_internal(hitbox, element_state.as_mut(), cx); + (style, element_state) + }) + } + + /// Called from internal methods that have already called with_element_state. + fn compute_style_internal( &self, - bounds: Option>, - element_state: &mut InteractiveElementState, + hitbox: Option<&Hitbox>, + element_state: Option<&mut InteractiveElementState>, cx: &mut ElementContext, ) -> Style { let mut style = Style::default(); style.refine(&self.base_style); - cx.with_z_index(style.z_index.unwrap_or(0), |cx| { - if let Some(focus_handle) = self.tracked_focus_handle.as_ref() { - if let Some(in_focus_style) = self.in_focus_style.as_ref() { - if focus_handle.within_focused(cx) { - style.refine(in_focus_style); + if let Some(focus_handle) = self.tracked_focus_handle.as_ref() { + if let Some(in_focus_style) = self.in_focus_style.as_ref() { + if focus_handle.within_focused(cx) { + style.refine(in_focus_style); + } + } + + if let Some(focus_style) = self.focus_style.as_ref() { + if focus_handle.is_focused(cx) { + style.refine(focus_style); + } + } + } + + if let Some(hitbox) = hitbox { + if !cx.has_active_drag() { + if let Some(group_hover) = self.group_hover_style.as_ref() { + if let Some(group_hitbox_id) = + GroupHitboxes::get(&group_hover.group, cx.deref_mut()) + { + if group_hitbox_id.is_hovered(cx) { + style.refine(&group_hover.style); + } } } - if let Some(focus_style) = self.focus_style.as_ref() { - if focus_handle.is_focused(cx) { - style.refine(focus_style); + if let Some(hover_style) = self.hover_style.as_ref() { + if hitbox.is_hovered(cx) { + style.refine(hover_style); } } } - if let Some(bounds) = bounds { - let mouse_position = cx.mouse_position(); - if !cx.has_active_drag() { - if let Some(group_hover) = self.group_hover_style.as_ref() { - if let Some(group_bounds) = - GroupBounds::get(&group_hover.group, cx.deref_mut()) - { - if group_bounds.contains(&mouse_position) { - style.refine(&group_hover.style); - } - } - } - - if let Some(hover_style) = self.hover_style.as_ref() { - if bounds - .intersect(&cx.content_mask().bounds) - .contains(&mouse_position) - { - style.refine(hover_style); - } - } + if let Some(drag) = cx.active_drag.take() { + let mut can_drop = true; + if let Some(can_drop_predicate) = &self.can_drop_predicate { + can_drop = can_drop_predicate(drag.value.as_ref(), cx.deref_mut()); } - if let Some(drag) = cx.active_drag.take() { - let mut can_drop = true; - if let Some(can_drop_predicate) = &self.can_drop_predicate { - can_drop = can_drop_predicate(drag.value.as_ref(), cx.deref_mut()); - } - - if can_drop { - for (state_type, group_drag_style) in &self.group_drag_over_styles { - if let Some(group_bounds) = - GroupBounds::get(&group_drag_style.group, cx.deref_mut()) - { - if *state_type == drag.value.as_ref().type_id() - && group_bounds.contains(&mouse_position) - { - style.refine(&group_drag_style.style); - } - } - } - - for (state_type, build_drag_over_style) in &self.drag_over_styles { + if can_drop { + for (state_type, group_drag_style) in &self.group_drag_over_styles { + if let Some(group_hitbox_id) = + GroupHitboxes::get(&group_drag_style.group, cx.deref_mut()) + { if *state_type == drag.value.as_ref().type_id() - && bounds - .intersect(&cx.content_mask().bounds) - .contains(&mouse_position) - && cx.was_top_layer_under_active_drag( - &mouse_position, - cx.stacking_order(), - ) + && group_hitbox_id.is_hovered(cx) { - style.refine(&build_drag_over_style(drag.value.as_ref(), cx)); + style.refine(&group_drag_style.style); } } } - cx.active_drag = Some(drag); + for (state_type, build_drag_over_style) in &self.drag_over_styles { + if *state_type == drag.value.as_ref().type_id() && hitbox.is_hovered(cx) { + style.refine(&build_drag_over_style(drag.value.as_ref(), cx)); + } + } } - } + cx.active_drag = Some(drag); + } + } + + if let Some(element_state) = element_state { let clicked_state = element_state .clicked_state .get_or_insert_with(Default::default) @@ -2026,7 +2033,7 @@ impl Interactivity { style.refine(active_style) } } - }); + } style } @@ -2067,12 +2074,12 @@ impl ElementClickedState { } #[derive(Default)] -pub(crate) struct GroupBounds(HashMap; 1]>>); +pub(crate) struct GroupHitboxes(HashMap>); -impl Global for GroupBounds {} +impl Global for GroupHitboxes {} -impl GroupBounds { - pub fn get(name: &SharedString, cx: &mut AppContext) -> Option> { +impl GroupHitboxes { + pub fn get(name: &SharedString, cx: &mut AppContext) -> Option { cx.default_global::() .0 .get(name) @@ -2080,12 +2087,12 @@ impl GroupBounds { .cloned() } - pub fn push(name: SharedString, bounds: Bounds, cx: &mut AppContext) { + pub fn push(name: SharedString, hitbox_id: HitboxId, cx: &mut AppContext) { cx.default_global::() .0 .entry(name) .or_default() - .push(bounds); + .push(hitbox_id); } pub fn pop(name: &SharedString, cx: &mut AppContext) { @@ -2125,18 +2132,30 @@ impl Element for Focusable where E: Element, { - type State = E::State; + type BeforeLayout = E::BeforeLayout; + type AfterLayout = E::AfterLayout; - fn request_layout( - &mut self, - state: Option, - cx: &mut ElementContext, - ) -> (LayoutId, Self::State) { - self.element.request_layout(state, cx) + fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) { + self.element.before_layout(cx) } - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut ElementContext) { - self.element.paint(bounds, state, cx) + fn after_layout( + &mut self, + bounds: Bounds, + state: &mut Self::BeforeLayout, + cx: &mut ElementContext, + ) -> E::AfterLayout { + self.element.after_layout(bounds, state, cx) + } + + fn paint( + &mut self, + bounds: Bounds, + before_layout: &mut Self::BeforeLayout, + after_layout: &mut Self::AfterLayout, + cx: &mut ElementContext, + ) { + self.element.paint(bounds, before_layout, after_layout, cx) } } @@ -2146,10 +2165,6 @@ where { type Element = E::Element; - fn element_id(&self) -> Option { - self.element.element_id() - } - fn into_element(self) -> Self::Element { self.element.into_element() } @@ -2200,18 +2215,30 @@ impl Element for Stateful where E: Element, { - type State = E::State; + type BeforeLayout = E::BeforeLayout; + type AfterLayout = E::AfterLayout; - fn request_layout( - &mut self, - state: Option, - cx: &mut ElementContext, - ) -> (LayoutId, Self::State) { - self.element.request_layout(state, cx) + fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) { + self.element.before_layout(cx) } - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut ElementContext) { - self.element.paint(bounds, state, cx) + fn after_layout( + &mut self, + bounds: Bounds, + state: &mut Self::BeforeLayout, + cx: &mut ElementContext, + ) -> E::AfterLayout { + self.element.after_layout(bounds, state, cx) + } + + fn paint( + &mut self, + bounds: Bounds, + before_layout: &mut Self::BeforeLayout, + after_layout: &mut Self::AfterLayout, + cx: &mut ElementContext, + ) { + self.element.paint(bounds, before_layout, after_layout, cx); } } @@ -2221,10 +2248,6 @@ where { type Element = Self; - fn element_id(&self) -> Option { - self.element.element_id() - } - fn into_element(self) -> Self::Element { self } diff --git a/crates/gpui/src/elements/img.rs b/crates/gpui/src/elements/img.rs index 32009e04db..a5c781a9a4 100644 --- a/crates/gpui/src/elements/img.rs +++ b/crates/gpui/src/elements/img.rs @@ -2,8 +2,8 @@ use std::path::PathBuf; use std::sync::Arc; use crate::{ - point, size, Bounds, DevicePixels, Element, ElementContext, ImageData, InteractiveElement, - InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedUri, Size, + point, size, Bounds, DevicePixels, Element, ElementContext, Hitbox, ImageData, + InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, SharedUri, Size, StyleRefinement, Styled, UriOrPath, }; use futures::FutureExt; @@ -88,86 +88,85 @@ impl Img { } impl Element for Img { - type State = InteractiveElementState; + type BeforeLayout = (); + type AfterLayout = Option; - fn request_layout( + fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) { + let layout_id = self + .interactivity + .before_layout(cx, |style, cx| cx.request_layout(&style, [])); + (layout_id, ()) + } + + fn after_layout( &mut self, - element_state: Option, + bounds: Bounds, + _before_layout: &mut Self::BeforeLayout, cx: &mut ElementContext, - ) -> (LayoutId, Self::State) { + ) -> Option { self.interactivity - .layout(element_state, cx, |style, cx| cx.request_layout(&style, [])) + .after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox) } fn paint( &mut self, bounds: Bounds, - element_state: &mut Self::State, + _: &mut Self::BeforeLayout, + hitbox: &mut Self::AfterLayout, cx: &mut ElementContext, ) { let source = self.source.clone(); - self.interactivity.paint( - bounds, - bounds.size, - element_state, - cx, - |style, _scroll_offset, cx| { + self.interactivity + .paint(bounds, hitbox.as_ref(), cx, |style, cx| { let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size()); - cx.with_z_index(1, |cx| { - match source { - ImageSource::Uri(_) | ImageSource::File(_) => { - let uri_or_path: UriOrPath = match source { - ImageSource::Uri(uri) => uri.into(), - ImageSource::File(path) => path.into(), - _ => unreachable!(), - }; + match source { + ImageSource::Uri(_) | ImageSource::File(_) => { + let uri_or_path: UriOrPath = match source { + ImageSource::Uri(uri) => uri.into(), + ImageSource::File(path) => path.into(), + _ => unreachable!(), + }; - let image_future = cx.image_cache.get(uri_or_path.clone(), cx); - if let Some(data) = image_future - .clone() - .now_or_never() - .and_then(|result| result.ok()) - { - let new_bounds = preserve_aspect_ratio(bounds, data.size()); - cx.paint_image(new_bounds, corner_radii, data, self.grayscale) - .log_err(); - } else { - cx.spawn(|mut cx| async move { - if image_future.await.ok().is_some() { - cx.on_next_frame(|cx| cx.refresh()); - } - }) - .detach(); - } - } - - ImageSource::Data(data) => { + let image_future = cx.image_cache.get(uri_or_path.clone(), cx); + if let Some(data) = image_future + .clone() + .now_or_never() + .and_then(|result| result.ok()) + { let new_bounds = preserve_aspect_ratio(bounds, data.size()); cx.paint_image(new_bounds, corner_radii, data, self.grayscale) .log_err(); + } else { + cx.spawn(|mut cx| async move { + if image_future.await.ok().is_some() { + cx.on_next_frame(|cx| cx.refresh()); + } + }) + .detach(); } + } - #[cfg(target_os = "macos")] - ImageSource::Surface(surface) => { - let size = size(surface.width().into(), surface.height().into()); - let new_bounds = preserve_aspect_ratio(bounds, size); - // TODO: Add support for corner_radii and grayscale. - cx.paint_surface(new_bounds, surface); - } - }; - }); - }, - ) + ImageSource::Data(data) => { + let new_bounds = preserve_aspect_ratio(bounds, data.size()); + cx.paint_image(new_bounds, corner_radii, data, self.grayscale) + .log_err(); + } + + #[cfg(target_os = "macos")] + ImageSource::Surface(surface) => { + let size = size(surface.width().into(), surface.height().into()); + let new_bounds = preserve_aspect_ratio(bounds, size); + // TODO: Add support for corner_radii and grayscale. + cx.paint_surface(new_bounds, surface); + } + } + }) } } impl IntoElement for Img { type Element = Self; - fn element_id(&self) -> Option { - self.interactivity.element_id.clone() - } - fn into_element(self) -> Self::Element { self } diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index f449862668..b4cbae9aed 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -8,11 +8,12 @@ use crate::{ point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, DispatchPhase, Edges, - Element, ElementContext, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, + Element, ElementContext, HitboxId, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled, WindowContext, }; use collections::VecDeque; use refineable::Refineable as _; +use smallvec::SmallVec; use std::{cell::RefCell, ops::Range, rc::Rc}; use sum_tree::{Bias, SumTree}; use taffy::style::Overflow; @@ -96,6 +97,13 @@ struct LayoutItemsResponse { item_elements: VecDeque, } +/// Frame state used by the [List] element. +#[derive(Default)] +pub struct ListFrameState { + scroll_top: ListOffset, + items: SmallVec<[AnyElement; 32]>, +} + #[derive(Clone)] enum ListItem { Unrendered, @@ -302,7 +310,6 @@ impl StateInner { height: Pixels, delta: Point, cx: &mut WindowContext, - padding: Edges, ) { // Drop scroll events after a reset, since we can't calculate // the new logical scroll top without the item heights @@ -310,6 +317,7 @@ impl StateInner { return; } + let padding = self.last_padding.unwrap_or_default(); let scroll_max = (self.items.summary().height + padding.top + padding.bottom - height).max(px(0.)); let new_scroll_top = (self.scroll_top(scroll_top) - delta.y) @@ -516,13 +524,13 @@ pub struct ListOffset { } impl Element for List { - type State = (); + type BeforeLayout = ListFrameState; + type AfterLayout = HitboxId; - fn request_layout( + fn before_layout( &mut self, - _state: Option, cx: &mut crate::ElementContext, - ) -> (crate::LayoutId, Self::State) { + ) -> (crate::LayoutId, Self::BeforeLayout) { let layout_id = match self.sizing_behavior { ListSizingBehavior::Infer => { let mut style = Style::default(); @@ -580,15 +588,15 @@ impl Element for List { }) } }; - (layout_id, ()) + (layout_id, ListFrameState::default()) } - fn paint( + fn after_layout( &mut self, - bounds: Bounds, - _state: &mut Self::State, - cx: &mut crate::ElementContext, - ) { + bounds: Bounds, + before_layout: &mut Self::BeforeLayout, + cx: &mut ElementContext, + ) -> HitboxId { let state = &mut *self.state.0.borrow_mut(); state.reset = false; @@ -615,12 +623,11 @@ impl Element for List { cx.with_content_mask(Some(ContentMask { bounds }), |cx| { let mut item_origin = bounds.origin + Point::new(px(0.), padding.top); item_origin.y -= layout_response.scroll_top.offset_in_item; - for item_element in &mut layout_response.item_elements { - let item_height = item_element - .measure(layout_response.available_item_space, cx) - .height; - item_element.draw(item_origin, layout_response.available_item_space, cx); - item_origin.y += item_height; + for mut item_element in layout_response.item_elements { + let item_size = + item_element.layout(item_origin, layout_response.available_item_space, cx); + before_layout.items.push(item_element); + item_origin.y += item_size.height; } }); } @@ -628,20 +635,33 @@ impl Element for List { state.last_layout_bounds = Some(bounds); state.last_padding = Some(padding); + cx.insert_hitbox(bounds, false).id + } + + fn paint( + &mut self, + bounds: Bounds, + before_layout: &mut Self::BeforeLayout, + hitbox_id: &mut HitboxId, + cx: &mut crate::ElementContext, + ) { + cx.with_content_mask(Some(ContentMask { bounds }), |cx| { + for item in &mut before_layout.items { + item.paint(cx); + } + }); + let list_state = self.state.clone(); let height = bounds.size.height; - + let scroll_top = before_layout.scroll_top; + let hitbox_id = *hitbox_id; cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { - if phase == DispatchPhase::Bubble - && bounds.contains(&event.position) - && cx.was_top_layer(&event.position, cx.stacking_order()) - { + if phase == DispatchPhase::Bubble && hitbox_id.is_hovered(cx) { list_state.0.borrow_mut().scroll( - &layout_response.scroll_top, + &scroll_top, height, event.delta.pixel_delta(px(20.)), cx, - padding, ) } }); @@ -651,10 +671,6 @@ impl Element for List { impl IntoElement for List { type Element = Self; - fn element_id(&self) -> Option { - None - } - fn into_element(self) -> Self::Element { self } @@ -761,7 +777,7 @@ mod test { cx.draw( point(px(0.), px(0.)), size(px(100.), px(20.)).into(), - |_| list(state.clone()).w_full().h_full().z_index(10).into_any(), + |_| list(state.clone()).w_full().h_full().into_any(), ); // Reset diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index ed23205ae7..cd60e8de6b 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -9,6 +9,7 @@ use crate::{ /// The state that the overlay element uses to track its children. pub struct OverlayState { child_layout_ids: SmallVec<[LayoutId; 4]>, + offset: Point, } /// An overlay element that can be used to display UI that @@ -69,17 +70,14 @@ impl ParentElement for Overlay { } impl Element for Overlay { - type State = OverlayState; + type BeforeLayout = OverlayState; + type AfterLayout = (); - fn request_layout( - &mut self, - _: Option, - cx: &mut ElementContext, - ) -> (crate::LayoutId, Self::State) { + fn before_layout(&mut self, cx: &mut ElementContext) -> (crate::LayoutId, Self::BeforeLayout) { let child_layout_ids = self .children .iter_mut() - .map(|child| child.request_layout(cx)) + .map(|child| child.before_layout(cx)) .collect::>(); let overlay_style = Style { @@ -90,22 +88,28 @@ impl Element for Overlay { let layout_id = cx.request_layout(&overlay_style, child_layout_ids.iter().copied()); - (layout_id, OverlayState { child_layout_ids }) + ( + layout_id, + OverlayState { + child_layout_ids, + offset: Point::default(), + }, + ) } - fn paint( + fn after_layout( &mut self, - bounds: crate::Bounds, - element_state: &mut Self::State, + bounds: Bounds, + before_layout: &mut Self::BeforeLayout, cx: &mut ElementContext, ) { - if element_state.child_layout_ids.is_empty() { + if before_layout.child_layout_ids.is_empty() { return; } let mut child_min = point(Pixels::MAX, Pixels::MAX); let mut child_max = Point::default(); - for child_layout_id in &element_state.child_layout_ids { + for child_layout_id in &before_layout.child_layout_ids { let child_bounds = cx.layout_bounds(*child_layout_id); child_min = child_min.min(&child_bounds.origin); child_max = child_max.max(&child_bounds.lower_right()); @@ -165,25 +169,30 @@ impl Element for Overlay { desired.origin.y = limits.origin.y; } - let mut offset = cx.element_offset() + desired.origin - bounds.origin; - offset = point(offset.x.round(), offset.y.round()); - cx.with_absolute_element_offset(offset, |cx| { - cx.break_content_mask(|cx| { - for child in &mut self.children { - child.paint(cx); - } - }) - }) + before_layout.offset = cx.element_offset() + desired.origin - bounds.origin; + before_layout.offset = point( + before_layout.offset.x.round(), + before_layout.offset.y.round(), + ); + + for child in self.children.drain(..) { + cx.defer_draw(child, before_layout.offset, 1); + } + } + + fn paint( + &mut self, + _bounds: crate::Bounds, + _before_layout: &mut Self::BeforeLayout, + _after_layout: &mut Self::AfterLayout, + _cx: &mut ElementContext, + ) { } } impl IntoElement for Overlay { type Element = Self; - fn element_id(&self) -> Option { - None - } - fn into_element(self) -> Self::Element { self } diff --git a/crates/gpui/src/elements/svg.rs b/crates/gpui/src/elements/svg.rs index 2ef0888563..cd215ebac1 100644 --- a/crates/gpui/src/elements/svg.rs +++ b/crates/gpui/src/elements/svg.rs @@ -1,6 +1,6 @@ use crate::{ - Bounds, Element, ElementContext, ElementId, InteractiveElement, InteractiveElementState, - Interactivity, IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, + Bounds, Element, ElementContext, Hitbox, InteractiveElement, Interactivity, IntoElement, + LayoutId, Pixels, SharedString, StyleRefinement, Styled, }; use util::ResultExt; @@ -27,28 +27,37 @@ impl Svg { } impl Element for Svg { - type State = InteractiveElementState; + type BeforeLayout = (); + type AfterLayout = Option; - fn request_layout( + fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) { + let layout_id = self + .interactivity + .before_layout(cx, |style, cx| cx.request_layout(&style, None)); + (layout_id, ()) + } + + fn after_layout( &mut self, - element_state: Option, + bounds: Bounds, + _before_layout: &mut Self::BeforeLayout, cx: &mut ElementContext, - ) -> (LayoutId, Self::State) { - self.interactivity.layout(element_state, cx, |style, cx| { - cx.request_layout(&style, None) - }) + ) -> Option { + self.interactivity + .after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox) } fn paint( &mut self, bounds: Bounds, - element_state: &mut Self::State, + _before_layout: &mut Self::BeforeLayout, + hitbox: &mut Option, cx: &mut ElementContext, ) where Self: Sized, { self.interactivity - .paint(bounds, bounds.size, element_state, cx, |style, _, cx| { + .paint(bounds, hitbox.as_ref(), cx, |style, cx| { if let Some((path, color)) = self.path.as_ref().zip(style.text.color) { cx.paint_svg(bounds, path.clone(), color).log_err(); } @@ -59,10 +68,6 @@ impl Element for Svg { impl IntoElement for Svg { type Element = Self; - fn element_id(&self) -> Option { - self.interactivity.element_id.clone() - } - fn into_element(self) -> Self::Element { self } diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index c07f581910..32da3a9490 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -1,7 +1,7 @@ use crate::{ ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementContext, ElementId, - HighlightStyle, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, - Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine, + HighlightStyle, Hitbox, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, + Pixels, Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine, TOOLTIP_DELAY, }; use anyhow::anyhow; @@ -17,30 +17,37 @@ use std::{ use util::ResultExt; impl Element for &'static str { - type State = TextState; + type BeforeLayout = TextState; + type AfterLayout = (); - fn request_layout( - &mut self, - _: Option, - cx: &mut ElementContext, - ) -> (LayoutId, Self::State) { + fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) { let mut state = TextState::default(); let layout_id = state.layout(SharedString::from(*self), None, cx); (layout_id, state) } - fn paint(&mut self, bounds: Bounds, state: &mut TextState, cx: &mut ElementContext) { - state.paint(bounds, self, cx) + fn after_layout( + &mut self, + _bounds: Bounds, + _text_state: &mut Self::BeforeLayout, + _cx: &mut ElementContext, + ) { + } + + fn paint( + &mut self, + bounds: Bounds, + text_state: &mut TextState, + _: &mut (), + cx: &mut ElementContext, + ) { + text_state.paint(bounds, self, cx) } } impl IntoElement for &'static str { type Element = Self; - fn element_id(&self) -> Option { - None - } - fn into_element(self) -> Self::Element { self } @@ -49,41 +56,44 @@ impl IntoElement for &'static str { impl IntoElement for String { type Element = SharedString; - fn element_id(&self) -> Option { - None - } - fn into_element(self) -> Self::Element { self.into() } } impl Element for SharedString { - type State = TextState; + type BeforeLayout = TextState; + type AfterLayout = (); - fn request_layout( - &mut self, - _: Option, - cx: &mut ElementContext, - ) -> (LayoutId, Self::State) { + fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) { let mut state = TextState::default(); let layout_id = state.layout(self.clone(), None, cx); (layout_id, state) } - fn paint(&mut self, bounds: Bounds, state: &mut TextState, cx: &mut ElementContext) { + fn after_layout( + &mut self, + _bounds: Bounds, + _text_state: &mut Self::BeforeLayout, + _cx: &mut ElementContext, + ) { + } + + fn paint( + &mut self, + bounds: Bounds, + text_state: &mut Self::BeforeLayout, + _: &mut Self::AfterLayout, + cx: &mut ElementContext, + ) { let text_str: &str = self.as_ref(); - state.paint(bounds, text_str, cx) + text_state.paint(bounds, text_str, cx) } } impl IntoElement for SharedString { type Element = Self; - fn element_id(&self) -> Option { - None - } - fn into_element(self) -> Self::Element { self } @@ -138,30 +148,37 @@ impl StyledText { } impl Element for StyledText { - type State = TextState; + type BeforeLayout = TextState; + type AfterLayout = (); - fn request_layout( - &mut self, - _: Option, - cx: &mut ElementContext, - ) -> (LayoutId, Self::State) { + fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) { let mut state = TextState::default(); let layout_id = state.layout(self.text.clone(), self.runs.take(), cx); (layout_id, state) } - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut ElementContext) { - state.paint(bounds, &self.text, cx) + fn after_layout( + &mut self, + _bounds: Bounds, + _state: &mut Self::BeforeLayout, + _cx: &mut ElementContext, + ) { + } + + fn paint( + &mut self, + bounds: Bounds, + text_state: &mut Self::BeforeLayout, + _: &mut Self::AfterLayout, + cx: &mut ElementContext, + ) { + text_state.paint(bounds, &self.text, cx) } } impl IntoElement for StyledText { type Element = Self; - fn element_id(&self) -> Option { - None - } - fn into_element(self) -> Self::Element { self } @@ -324,8 +341,8 @@ struct InteractiveTextClickEvent { } #[doc(hidden)] +#[derive(Default)] pub struct InteractiveTextState { - text_state: TextState, mouse_down_index: Rc>>, hovered_index: Rc>>, active_tooltip: Rc>>, @@ -385,179 +402,181 @@ impl InteractiveText { } impl Element for InteractiveText { - type State = InteractiveTextState; + type BeforeLayout = TextState; + type AfterLayout = Hitbox; - fn request_layout( - &mut self, - state: Option, - cx: &mut ElementContext, - ) -> (LayoutId, Self::State) { - if let Some(InteractiveTextState { - mouse_down_index, - hovered_index, - active_tooltip, - .. - }) = state - { - let (layout_id, text_state) = self.text.request_layout(None, cx); - let element_state = InteractiveTextState { - text_state, - mouse_down_index, - hovered_index, - active_tooltip, - }; - (layout_id, element_state) - } else { - let (layout_id, text_state) = self.text.request_layout(None, cx); - let element_state = InteractiveTextState { - text_state, - mouse_down_index: Rc::default(), - hovered_index: Rc::default(), - active_tooltip: Rc::default(), - }; - (layout_id, element_state) - } + fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) { + self.text.before_layout(cx) } - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut ElementContext) { - if let Some(click_listener) = self.click_listener.take() { - let mouse_position = cx.mouse_position(); - if let Some(ix) = state.text_state.index_for_position(bounds, mouse_position) { - if self - .clickable_ranges - .iter() - .any(|range| range.contains(&ix)) - { - let stacking_order = cx.stacking_order().clone(); - cx.set_cursor_style(crate::CursorStyle::PointingHand, stacking_order); - } - } + fn after_layout( + &mut self, + bounds: Bounds, + state: &mut Self::BeforeLayout, + cx: &mut ElementContext, + ) -> Hitbox { + self.text.after_layout(bounds, state, cx); + cx.insert_hitbox(bounds, false) + } - let text_state = state.text_state.clone(); - let mouse_down = state.mouse_down_index.clone(); - if let Some(mouse_down_index) = mouse_down.get() { - let clickable_ranges = mem::take(&mut self.clickable_ranges); - cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { - if phase == DispatchPhase::Bubble { - if let Some(mouse_up_index) = - text_state.index_for_position(bounds, event.position) + fn paint( + &mut self, + bounds: Bounds, + text_state: &mut Self::BeforeLayout, + hitbox: &mut Hitbox, + cx: &mut ElementContext, + ) { + cx.with_element_state::( + Some(self.element_id.clone()), + |interactive_state, cx| { + let mut interactive_state = interactive_state.unwrap().unwrap_or_default(); + if let Some(click_listener) = self.click_listener.take() { + let mouse_position = cx.mouse_position(); + if let Some(ix) = text_state.index_for_position(bounds, mouse_position) { + if self + .clickable_ranges + .iter() + .any(|range| range.contains(&ix)) { - click_listener( - &clickable_ranges, - InteractiveTextClickEvent { - mouse_down_index, - mouse_up_index, - }, - cx, - ) - } - - mouse_down.take(); - cx.refresh(); - } - }); - } else { - cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { - if phase == DispatchPhase::Bubble { - if let Some(mouse_down_index) = - text_state.index_for_position(bounds, event.position) - { - mouse_down.set(Some(mouse_down_index)); - cx.refresh(); + cx.set_cursor_style(crate::CursorStyle::PointingHand, hitbox) } } - }); - } - } - if let Some(hover_listener) = self.hover_listener.take() { - let text_state = state.text_state.clone(); - let hovered_index = state.hovered_index.clone(); - cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { - if phase == DispatchPhase::Bubble { - let current = hovered_index.get(); - let updated = text_state.index_for_position(bounds, event.position); - if current != updated { - hovered_index.set(updated); - hover_listener(updated, event.clone(), cx); - cx.refresh(); - } - } - }); - } - if let Some(tooltip_builder) = self.tooltip_builder.clone() { - let active_tooltip = state.active_tooltip.clone(); - let pending_mouse_down = state.mouse_down_index.clone(); - let text_state = state.text_state.clone(); - cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { - let position = text_state.index_for_position(bounds, event.position); - let is_hovered = position.is_some() && pending_mouse_down.get().is_none(); - if !is_hovered { - active_tooltip.take(); - return; - } - let position = position.unwrap(); + let text_state = text_state.clone(); + let mouse_down = interactive_state.mouse_down_index.clone(); + if let Some(mouse_down_index) = mouse_down.get() { + let hitbox = hitbox.clone(); + let clickable_ranges = mem::take(&mut self.clickable_ranges); + cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { + if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) { + if let Some(mouse_up_index) = + text_state.index_for_position(bounds, event.position) + { + click_listener( + &clickable_ranges, + InteractiveTextClickEvent { + mouse_down_index, + mouse_up_index, + }, + cx, + ) + } - if phase != DispatchPhase::Bubble { - return; - } - - if active_tooltip.borrow().is_none() { - let task = cx.spawn({ - let active_tooltip = active_tooltip.clone(); - let tooltip_builder = tooltip_builder.clone(); - - move |mut cx| async move { - cx.background_executor().timer(TOOLTIP_DELAY).await; - cx.update(|cx| { - let new_tooltip = - tooltip_builder(position, cx).map(|tooltip| ActiveTooltip { - tooltip: Some(AnyTooltip { - view: tooltip, - cursor_offset: cx.mouse_position(), - }), - _task: None, - }); - *active_tooltip.borrow_mut() = new_tooltip; + mouse_down.take(); cx.refresh(); - }) - .ok(); + } + }); + } else { + let hitbox = hitbox.clone(); + cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { + if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) { + if let Some(mouse_down_index) = + text_state.index_for_position(bounds, event.position) + { + mouse_down.set(Some(mouse_down_index)); + cx.refresh(); + } + } + }); + } + } + + if let Some(hover_listener) = self.hover_listener.take() { + let hitbox = hitbox.clone(); + let text_state = text_state.clone(); + let hovered_index = interactive_state.hovered_index.clone(); + cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { + if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) { + let current = hovered_index.get(); + let updated = text_state.index_for_position(bounds, event.position); + if current != updated { + hovered_index.set(updated); + hover_listener(updated, event.clone(), cx); + cx.refresh(); + } } }); - *active_tooltip.borrow_mut() = Some(ActiveTooltip { - tooltip: None, - _task: Some(task), - }); } - }); - let active_tooltip = state.active_tooltip.clone(); - cx.on_mouse_event(move |_: &MouseDownEvent, _, _| { - active_tooltip.take(); - }); + if let Some(tooltip_builder) = self.tooltip_builder.clone() { + let hitbox = hitbox.clone(); + let active_tooltip = interactive_state.active_tooltip.clone(); + let pending_mouse_down = interactive_state.mouse_down_index.clone(); + let text_state = text_state.clone(); - if let Some(tooltip) = state - .active_tooltip - .clone() - .borrow() - .as_ref() - .and_then(|at| at.tooltip.clone()) - { - cx.set_tooltip(tooltip); - } - } + cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { + let position = text_state.index_for_position(bounds, event.position); + let is_hovered = position.is_some() + && hitbox.is_hovered(cx) + && pending_mouse_down.get().is_none(); + if !is_hovered { + active_tooltip.take(); + return; + } + let position = position.unwrap(); - self.text.paint(bounds, &mut state.text_state, cx) + if phase != DispatchPhase::Bubble { + return; + } + + if active_tooltip.borrow().is_none() { + let task = cx.spawn({ + let active_tooltip = active_tooltip.clone(); + let tooltip_builder = tooltip_builder.clone(); + + move |mut cx| async move { + cx.background_executor().timer(TOOLTIP_DELAY).await; + cx.update(|cx| { + let new_tooltip = + tooltip_builder(position, cx).map(|tooltip| { + ActiveTooltip { + tooltip: Some(AnyTooltip { + view: tooltip, + cursor_offset: cx.mouse_position(), + }), + _task: None, + } + }); + *active_tooltip.borrow_mut() = new_tooltip; + cx.refresh(); + }) + .ok(); + } + }); + *active_tooltip.borrow_mut() = Some(ActiveTooltip { + tooltip: None, + _task: Some(task), + }); + } + }); + + let active_tooltip = interactive_state.active_tooltip.clone(); + cx.on_mouse_event(move |_: &MouseDownEvent, _, _| { + active_tooltip.take(); + }); + + if let Some(tooltip) = interactive_state + .active_tooltip + .clone() + .borrow() + .as_ref() + .and_then(|at| at.tooltip.clone()) + { + cx.set_tooltip(tooltip); + } + } + + self.text.paint(bounds, text_state, &mut (), cx); + + ((), Some(interactive_state)) + }, + ); } } impl IntoElement for InteractiveText { type Element = Self; - fn element_id(&self) -> Option { - Some(self.element_id.clone()) - } - fn into_element(self) -> Self::Element { self } diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 8a6651524b..de8ac008ee 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -6,8 +6,8 @@ use crate::{ point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementContext, - ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId, - Pixels, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext, + ElementId, Hitbox, InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Render, + Size, StyleRefinement, Styled, View, ViewContext, WindowContext, }; use smallvec::SmallVec; use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; @@ -42,13 +42,13 @@ where }; UniformList { - id: id.clone(), item_count, item_to_measure_index: 0, render_items: Box::new(render_range), interactivity: Interactivity { element_id: Some(id), base_style: Box::new(base_style), + occlude_mouse: true, #[cfg(debug_assertions)] location: Some(*core::panic::Location::caller()), @@ -61,7 +61,6 @@ where /// A list element for efficiently laying out and displaying a list of uniform-height elements. pub struct UniformList { - id: ElementId, item_count: usize, item_to_measure_index: usize, render_items: @@ -70,6 +69,12 @@ pub struct UniformList { scroll_handle: Option, } +/// Frame state used by the [UniformList]. +pub struct UniformListFrameState { + item_size: Size, + items: SmallVec<[AnyElement; 32]>, +} + /// A handle for controlling the scroll position of a uniform list. /// This should be stored in your view and passed to the uniform_list on each frame. #[derive(Clone, Default)] @@ -97,72 +102,47 @@ impl Styled for UniformList { } } -#[doc(hidden)] -#[derive(Default)] -pub struct UniformListState { - interactive: InteractiveElementState, - item_size: Size, -} - impl Element for UniformList { - type State = UniformListState; + type BeforeLayout = UniformListFrameState; + type AfterLayout = Option; - fn request_layout( - &mut self, - state: Option, - cx: &mut ElementContext, - ) -> (LayoutId, Self::State) { + fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) { let max_items = self.item_count; - let item_size = state - .as_ref() - .map(|s| s.item_size) - .unwrap_or_else(|| self.measure_item(None, cx)); + let item_size = self.measure_item(None, cx); + let layout_id = self.interactivity.before_layout(cx, |style, cx| { + cx.request_measured_layout(style, move |known_dimensions, available_space, _cx| { + let desired_height = item_size.height * max_items; + let width = known_dimensions + .width + .unwrap_or(match available_space.width { + AvailableSpace::Definite(x) => x, + AvailableSpace::MinContent | AvailableSpace::MaxContent => item_size.width, + }); - let (layout_id, interactive) = - self.interactivity - .layout(state.map(|s| s.interactive), cx, |style, cx| { - cx.request_measured_layout( - style, - move |known_dimensions, available_space, _cx| { - let desired_height = item_size.height * max_items; - let width = - known_dimensions - .width - .unwrap_or(match available_space.width { - AvailableSpace::Definite(x) => x, - AvailableSpace::MinContent | AvailableSpace::MaxContent => { - item_size.width - } - }); + let height = match available_space.height { + AvailableSpace::Definite(height) => desired_height.min(height), + AvailableSpace::MinContent | AvailableSpace::MaxContent => desired_height, + }; + size(width, height) + }) + }); - let height = match available_space.height { - AvailableSpace::Definite(height) => desired_height.min(height), - AvailableSpace::MinContent | AvailableSpace::MaxContent => { - desired_height - } - }; - size(width, height) - }, - ) - }); - - let element_state = UniformListState { - interactive, - item_size, - }; - - (layout_id, element_state) + ( + layout_id, + UniformListFrameState { + item_size, + items: SmallVec::new(), + }, + ) } - fn paint( + fn after_layout( &mut self, - bounds: Bounds, - element_state: &mut Self::State, + bounds: Bounds, + before_layout: &mut Self::BeforeLayout, cx: &mut ElementContext, - ) { - let style = - self.interactivity - .compute_style(Some(bounds), &mut element_state.interactive, cx); + ) -> Option { + let style = self.interactivity.compute_style(None, cx); let border = style.border_widths.to_pixels(cx.rem_size()); let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size()); @@ -172,17 +152,12 @@ impl Element for UniformList { - point(border.right + padding.right, border.bottom + padding.bottom), ); - let item_size = element_state.item_size; let content_size = Size { width: padded_bounds.size.width, - height: item_size.height * self.item_count + padding.top + padding.bottom, + height: before_layout.item_size.height * self.item_count + padding.top + padding.bottom, }; - let shared_scroll_offset = element_state - .interactive - .scroll_offset - .get_or_insert_with(Rc::default) - .clone(); + let shared_scroll_offset = self.interactivity.scroll_offset.clone().unwrap(); let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height; let shared_scroll_to_item = self @@ -190,12 +165,11 @@ impl Element for UniformList { .as_mut() .and_then(|handle| handle.deferred_scroll_to_item.take()); - self.interactivity.paint( + self.interactivity.after_layout( bounds, content_size, - &mut element_state.interactive, cx, - |style, mut scroll_offset, cx| { + |style, mut scroll_offset, hitbox, cx| { let border = style.border_widths.to_pixels(cx.rem_size()); let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size()); @@ -238,36 +212,45 @@ impl Element for UniformList { ..cmp::min(last_visible_element_ix, self.item_count); let mut items = (self.render_items)(visible_range.clone(), cx); - cx.with_z_index(1, |cx| { - let content_mask = ContentMask { bounds }; - cx.with_content_mask(Some(content_mask), |cx| { - for (item, ix) in items.iter_mut().zip(visible_range) { - let item_origin = padded_bounds.origin - + point( - px(0.), - item_height * ix + scroll_offset.y + padding.top, - ); - let available_space = size( - AvailableSpace::Definite(padded_bounds.size.width), - AvailableSpace::Definite(item_height), - ); - item.draw(item_origin, available_space, cx); - } - }); + let content_mask = ContentMask { bounds }; + cx.with_content_mask(Some(content_mask), |cx| { + for (mut item, ix) in items.into_iter().zip(visible_range) { + let item_origin = padded_bounds.origin + + point(px(0.), item_height * ix + scroll_offset.y + padding.top); + let available_space = size( + AvailableSpace::Definite(padded_bounds.size.width), + AvailableSpace::Definite(item_height), + ); + item.layout(item_origin, available_space, cx); + before_layout.items.push(item); + } }); } + + hitbox }, ) } + + fn paint( + &mut self, + bounds: Bounds, + before_layout: &mut Self::BeforeLayout, + hitbox: &mut Option, + cx: &mut ElementContext, + ) { + self.interactivity + .paint(bounds, hitbox.as_ref(), cx, |_, cx| { + for item in &mut before_layout.items { + item.paint(cx); + } + }) + } } impl IntoElement for UniformList { type Element = Self; - fn element_id(&self) -> Option { - Some(self.id.clone()) - } - fn into_element(self) -> Self::Element { self } diff --git a/crates/gpui/src/geometry.rs b/crates/gpui/src/geometry.rs index dd826b68f5..68486043d4 100644 --- a/crates/gpui/src/geometry.rs +++ b/crates/gpui/src/geometry.rs @@ -828,6 +828,28 @@ where y: self.origin.y.clone() + self.size.height.clone().half(), } } + + /// Calculates the half perimeter of a rectangle defined by the bounds. + /// + /// The half perimeter is calculated as the sum of the width and the height of the rectangle. + /// This method is generic over the type `T` which must implement the `Sub` trait to allow + /// calculation of the width and height from the bounds' origin and size, as well as the `Add` trait + /// to sum the width and height for the half perimeter. + /// + /// # Examples + /// + /// ``` + /// # use zed::{Bounds, Point, Size}; + /// let bounds = Bounds { + /// origin: Point { x: 0, y: 0 }, + /// size: Size { width: 10, height: 20 }, + /// }; + /// let half_perimeter = bounds.half_perimeter(); + /// assert_eq!(half_perimeter, 30); + /// ``` + pub fn half_perimeter(&self) -> T { + self.size.width.clone() + self.size.height.clone() + } } impl + Sub> Bounds { @@ -2617,6 +2639,12 @@ pub trait Half { fn half(&self) -> Self; } +impl Half for i32 { + fn half(&self) -> Self { + self / 2 + } +} + impl Half for f32 { fn half(&self) -> Self { self / 2. diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index ff1daa59ea..342a86cf62 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -70,6 +70,7 @@ mod app; mod arena; mod assets; +mod bounds_tree; mod color; mod element; mod elements; diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index af56f4344f..0e5daa916c 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -54,11 +54,12 @@ use crate::{ KeyContext, Keymap, KeymatchResult, Keystroke, KeystrokeMatcher, WindowContext, }; use collections::FxHashMap; -use smallvec::{smallvec, SmallVec}; +use smallvec::SmallVec; use std::{ any::{Any, TypeId}, cell::RefCell, mem, + ops::Range, rc::Rc, }; @@ -68,6 +69,7 @@ pub(crate) struct DispatchNodeId(usize); pub(crate) struct DispatchTree { node_stack: Vec, pub(crate) context_stack: Vec, + view_stack: Vec, nodes: Vec, focusable_node_ids: FxHashMap, view_node_ids: FxHashMap, @@ -81,7 +83,7 @@ pub(crate) struct DispatchNode { pub key_listeners: Vec, pub action_listeners: Vec, pub context: Option, - focus_id: Option, + pub focus_id: Option, view_id: Option, parent: Option, } @@ -99,6 +101,7 @@ impl DispatchTree { Self { node_stack: Vec::new(), context_stack: Vec::new(), + view_stack: Vec::new(), nodes: Vec::new(), focusable_node_ids: FxHashMap::default(), view_node_ids: FxHashMap::default(), @@ -111,72 +114,113 @@ impl DispatchTree { pub fn clear(&mut self) { self.node_stack.clear(); self.context_stack.clear(); + self.view_stack.clear(); self.nodes.clear(); self.focusable_node_ids.clear(); self.view_node_ids.clear(); self.keystroke_matchers.clear(); } - pub fn push_node( - &mut self, - context: Option, - focus_id: Option, - view_id: Option, - ) { + pub fn len(&self) -> usize { + self.nodes.len() + } + + pub fn push_node(&mut self) -> DispatchNodeId { let parent = self.node_stack.last().copied(); let node_id = DispatchNodeId(self.nodes.len()); + self.nodes.push(DispatchNode { parent, - focus_id, - view_id, ..Default::default() }); self.node_stack.push(node_id); + node_id + } - if let Some(context) = context { - self.active_node().context = Some(context.clone()); + pub fn move_to_next_node(&mut self) -> DispatchNodeId { + let next_node_id = DispatchNodeId(self.active_node_id().0 + 1); + let next_node_parent = self.nodes[next_node_id.0].parent; + while self.node_stack.last().copied() != next_node_parent { + self.pop_node(); + } + + self.node_stack.push(next_node_id); + let active_node = &self.nodes[next_node_id.0]; + if let Some(view_id) = active_node.view_id { + self.view_stack.push(view_id) + } + if let Some(context) = active_node.context.clone() { self.context_stack.push(context); } - if let Some(focus_id) = focus_id { - self.focusable_node_ids.insert(focus_id, node_id); - } + next_node_id + } - if let Some(view_id) = view_id { + pub fn set_key_context(&mut self, context: KeyContext) { + self.active_node().context = Some(context.clone()); + self.context_stack.push(context); + } + + pub fn set_focus_id(&mut self, focus_id: FocusId) { + let node_id = *self.node_stack.last().unwrap(); + self.nodes[node_id.0].focus_id = Some(focus_id); + self.focusable_node_ids.insert(focus_id, node_id); + } + + pub fn set_view_id(&mut self, view_id: EntityId) { + if self.parent_view_id() != Some(view_id) { + let node_id = *self.node_stack.last().unwrap(); + self.nodes[node_id.0].view_id = Some(view_id); self.view_node_ids.insert(view_id, node_id); + self.view_stack.push(view_id); } } + pub fn parent_view_id(&self) -> Option { + self.view_stack.last().copied() + } + pub fn pop_node(&mut self) { let node = &self.nodes[self.active_node_id().0]; if node.context.is_some() { self.context_stack.pop(); } + if node.view_id.is_some() { + self.view_stack.pop(); + } self.node_stack.pop(); } fn move_node(&mut self, source: &mut DispatchNode) { - self.push_node(source.context.take(), source.focus_id, source.view_id); + self.push_node(); + if let Some(context) = source.context.take() { + self.set_key_context(context); + } + if let Some(focus_id) = source.focus_id.take() { + self.set_focus_id(focus_id); + } + if let Some(view_id) = source.view_id.take() { + self.set_view_id(view_id); + } + let target = self.active_node(); target.key_listeners = mem::take(&mut source.key_listeners); target.action_listeners = mem::take(&mut source.action_listeners); } - pub fn reuse_view(&mut self, view_id: EntityId, source: &mut Self) -> SmallVec<[EntityId; 8]> { - let view_source_node_id = source - .view_node_ids - .get(&view_id) - .expect("view should exist in previous dispatch tree"); - let view_source_node = &mut source.nodes[view_source_node_id.0]; - self.move_node(view_source_node); - - let mut grafted_view_ids = smallvec![view_id]; - let mut source_stack = vec![*view_source_node_id]; + pub fn reuse_subtree( + &mut self, + range: Range, + source: &mut Self, + ) -> SmallVec<[EntityId; 8]> { + let mut grafted_view_ids = SmallVec::new(); + let mut source_stack = vec![]; for (source_node_id, source_node) in source .nodes .iter_mut() .enumerate() - .skip(view_source_node_id.0 + 1) + .skip(range.start) + .take(range.len()) { let source_node_id = DispatchNodeId(source_node_id); while let Some(source_ancestor) = source_stack.last() { @@ -188,14 +232,10 @@ impl DispatchTree { } } - if source_stack.is_empty() { - break; - } else { - source_stack.push(source_node_id); - self.move_node(source_node); - if let Some(view_id) = source_node.view_id { - grafted_view_ids.push(view_id); - } + source_stack.push(source_node_id); + self.move_node(source_node); + if let Some(view_id) = source_node.view_id { + grafted_view_ids.push(view_id); } } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index b02d521a8c..346687bbf2 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -1,6 +1,6 @@ -// todo!(linux): remove +// todo(linux): remove #![cfg_attr(target_os = "linux", allow(dead_code))] -// todo!("windows"): remove +// todo("windows"): remove #![cfg_attr(windows, allow(dead_code))] mod app_menu; @@ -63,7 +63,7 @@ pub(crate) fn current_platform() -> Rc { pub(crate) fn current_platform() -> Rc { Rc::new(LinuxPlatform::new()) } -// todo!("windows") +// todo("windows") #[cfg(target_os = "windows")] pub(crate) fn current_platform() -> Rc { unimplemented!() diff --git a/crates/gpui/src/platform/blade/blade_renderer.rs b/crates/gpui/src/platform/blade/blade_renderer.rs index f73f668260..554479ef1c 100644 --- a/crates/gpui/src/platform/blade/blade_renderer.rs +++ b/crates/gpui/src/platform/blade/blade_renderer.rs @@ -564,7 +564,7 @@ impl BladeRenderer { } PrimitiveBatch::Paths(paths) => { let mut encoder = pass.with(&self.pipelines.paths); - //todo!(linux): group by texture ID + // todo(linux): group by texture ID for path in paths { let tile = &self.path_tiles[&path.id]; let tex_info = self.atlas.get_texture_info(tile.texture_id); diff --git a/crates/gpui/src/platform/linux/dispatcher.rs b/crates/gpui/src/platform/linux/dispatcher.rs index 4f2b27dce3..9dc0442035 100644 --- a/crates/gpui/src/platform/linux/dispatcher.rs +++ b/crates/gpui/src/platform/linux/dispatcher.rs @@ -1,7 +1,7 @@ #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] -//todo!(linux): remove +// todo(linux): remove #![allow(unused_variables)] use crate::{PlatformDispatcher, TaskLabel}; diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 1195859ef3..f6e80d1c94 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -141,19 +141,19 @@ impl Platform for LinuxPlatform { self.inner.loop_signal.stop(); } - //todo!(linux) + // todo(linux) fn restart(&self) {} - //todo!(linux) + // todo(linux) fn activate(&self, ignoring_other_apps: bool) {} - //todo!(linux) + // todo(linux) fn hide(&self) {} - //todo!(linux) + // todo(linux) fn hide_other_apps(&self) {} - //todo!(linux) + // todo(linux) fn unhide_other_apps(&self) {} fn displays(&self) -> Vec> { @@ -164,7 +164,7 @@ impl Platform for LinuxPlatform { self.client.display(id) } - //todo!(linux) + // todo(linux) fn active_window(&self) -> Option { None } @@ -328,7 +328,7 @@ impl Platform for LinuxPlatform { unimplemented!() } - //todo!(linux) + // todo(linux) fn set_menus(&self, menus: Vec, keymap: &Keymap) {} fn local_timezone(&self) -> UtcOffset { @@ -339,18 +339,18 @@ impl Platform for LinuxPlatform { unimplemented!() } - //todo!(linux) + // todo(linux) fn set_cursor_style(&self, style: CursorStyle) {} - //todo!(linux) + // todo(linux) fn should_auto_hide_scrollbars(&self) -> bool { false } - //todo!(linux) + // todo(linux) fn write_to_clipboard(&self, item: ClipboardItem) {} - //todo!(linux) + // todo(linux) fn read_from_clipboard(&self) -> Option { None } diff --git a/crates/gpui/src/platform/linux/text_system.rs b/crates/gpui/src/platform/linux/text_system.rs index 497d615588..8fd1be323b 100644 --- a/crates/gpui/src/platform/linux/text_system.rs +++ b/crates/gpui/src/platform/linux/text_system.rs @@ -32,7 +32,7 @@ impl LinuxTextSystem { pub(crate) fn new() -> Self { let mut font_system = FontSystem::new(); - // todo!(linux) make font loading non-blocking + // todo(linux) make font loading non-blocking font_system.db_mut().load_system_fonts(); Self(RwLock::new(LinuxTextSystemState { @@ -59,7 +59,7 @@ impl PlatformTextSystem for LinuxTextSystem { self.0.write().add_fonts(fonts) } - // todo!(linux) ensure that this integrates with platform font loading + // todo(linux) ensure that this integrates with platform font loading // do we need to do more than call load_system_fonts()? fn all_font_names(&self) -> Vec { self.0 @@ -71,13 +71,13 @@ impl PlatformTextSystem for LinuxTextSystem { .collect() } - // todo!(linux) + // todo(linux) fn all_font_families(&self) -> Vec { Vec::new() } fn font_id(&self, font: &Font) -> Result { - // todo!(linux): Do we need to use CosmicText's Font APIs? Can we consolidate this to use font_kit? + // todo(linux): Do we need to use CosmicText's Font APIs? Can we consolidate this to use font_kit? let lock = self.0.upgradable_read(); if let Some(font_id) = lock.font_selections.get(font) { Ok(*font_id) @@ -127,13 +127,13 @@ impl PlatformTextSystem for LinuxTextSystem { FontMetrics { units_per_em: metrics.units_per_em as u32, ascent: metrics.ascent, - descent: -metrics.descent, // todo!(linux) confirm this is correct + descent: -metrics.descent, // todo(linux) confirm this is correct line_gap: metrics.leading, underline_position: metrics.underline_offset, underline_thickness: metrics.stroke_size, cap_height: metrics.cap_height, x_height: metrics.x_height, - // todo!(linux): Compute this correctly + // todo(linux): Compute this correctly bounding_box: Bounds { origin: point(0.0, 0.0), size: size(metrics.max_width, metrics.ascent + metrics.descent), @@ -146,7 +146,7 @@ impl PlatformTextSystem for LinuxTextSystem { let metrics = lock.fonts[font_id.0].as_swash().metrics(&[]); let glyph_metrics = lock.fonts[font_id.0].as_swash().glyph_metrics(&[]); let glyph_id = glyph_id.0 as u16; - // todo!(linux): Compute this correctly + // todo(linux): Compute this correctly // see https://github.com/servo/font-kit/blob/master/src/loaders/freetype.rs#L614-L620 Ok(Bounds { origin: point(0.0, 0.0), @@ -181,7 +181,7 @@ impl PlatformTextSystem for LinuxTextSystem { self.0.write().layout_line(text, font_size, runs) } - // todo!(linux) Confirm that this has been superseded by the LineWrapper + // todo(linux) Confirm that this has been superseded by the LineWrapper fn wrap_line( &self, text: &str, @@ -256,7 +256,7 @@ impl LinuxTextSystemState { } fn is_emoji(&self, font_id: FontId) -> bool { - // todo!(linux): implement this correctly + // todo(linux): implement this correctly self.postscript_names_by_font_id .get(&font_id) .map_or(false, |postscript_name| { @@ -264,7 +264,7 @@ impl LinuxTextSystemState { }) } - // todo!(linux) both raster functions have problems because I am not sure this is the correct mapping from cosmic text to gpui system + // todo(linux) both raster functions have problems because I am not sure this is the correct mapping from cosmic text to gpui system fn raster_bounds(&mut self, params: &RenderGlyphParams) -> Result> { let font = &self.fonts[params.font_id.0]; let font_system = &mut self.font_system; @@ -297,7 +297,7 @@ impl LinuxTextSystemState { if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 { Err(anyhow!("glyph bounds are empty")) } else { - // todo!(linux) handle subpixel variants + // todo(linux) handle subpixel variants let bitmap_size = glyph_bounds.size; let font = &self.fonts[params.font_id.0]; let font_system = &mut self.font_system; @@ -320,13 +320,13 @@ impl LinuxTextSystemState { } } - // todo!(linux) This is all a quick first pass, maybe we should be using cosmic_text::Buffer + // todo(linux) This is all a quick first pass, maybe we should be using cosmic_text::Buffer #[profiling::function] fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout { let mut attrs_list = AttrsList::new(Attrs::new()); let mut offs = 0; for run in font_runs { - // todo!(linux) We need to check we are doing utf properly + // todo(linux) We need to check we are doing utf properly let font = &self.fonts[run.font_id.0]; let font = self.font_system.db().face(font.id()).unwrap(); attrs_list.add_span( @@ -343,11 +343,11 @@ impl LinuxTextSystemState { let layout = line.layout( &mut self.font_system, font_size.0, - f32::MAX, // todo!(linux) we don't have a width cause this should technically not be wrapped I believe + f32::MAX, // todo(linux) we don't have a width cause this should technically not be wrapped I believe cosmic_text::Wrap::None, ); let mut runs = Vec::new(); - // todo!(linux) what I think can happen is layout returns possibly multiple lines which means we should be probably working with it higher up in the text rendering + // todo(linux) what I think can happen is layout returns possibly multiple lines which means we should be probably working with it higher up in the text rendering let layout = layout.first().unwrap(); for glyph in &layout.glyphs { let font_id = glyph.font_id; @@ -358,7 +358,7 @@ impl LinuxTextSystemState { .unwrap(), ); let mut glyphs = SmallVec::new(); - // todo!(linux) this is definitely wrong, each glyph in glyphs from cosmic-text is a cluster with one glyph, ShapedRun takes a run of glyphs with the same font and direction + // todo(linux) this is definitely wrong, each glyph in glyphs from cosmic-text is a cluster with one glyph, ShapedRun takes a run of glyphs with the same font and direction glyphs.push(ShapedGlyph { id: GlyphId(glyph.glyph_id as u32), position: point((glyph.x).into(), glyph.y.into()), diff --git a/crates/gpui/src/platform/linux/wayland.rs b/crates/gpui/src/platform/linux/wayland.rs index 63e106cc6d..79f3aa223c 100644 --- a/crates/gpui/src/platform/linux/wayland.rs +++ b/crates/gpui/src/platform/linux/wayland.rs @@ -1,4 +1,4 @@ -//todo!(linux): remove this once the relevant functionality has been implemented +// todo(linux): remove this once the relevant functionality has been implemented #![allow(unused_variables)] pub(crate) use client::*; diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 55e5670110..957515c8ff 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -183,7 +183,7 @@ impl Client for WaylandClient { let decoration = decoration_manager.get_toplevel_decoration(&toplevel, &self.qh, xdg_surface.id()); - // todo!(linux) - options.titlebar is lacking information required for wayland. + // todo(linux) - options.titlebar is lacking information required for wayland. // Especially, whether a titlebar is wanted in itself. // // Removing the titlebar also removes the entire window frame (ie. the ability to @@ -482,7 +482,7 @@ impl Dispatch for WaylandClientState { wl_keyboard::KeyState::Pressed => { let input = PlatformInput::KeyDown(KeyDownEvent { keystroke: Keystroke::from_xkb(keymap_state, state.modifiers, keycode), - is_held: false, // todo!(linux) + is_held: false, // todo(linux) }); focused_window.handle_input(input.clone()); diff --git a/crates/gpui/src/platform/linux/wayland/display.rs b/crates/gpui/src/platform/linux/wayland/display.rs index 0d8b6dbd3f..0b76a4b7dc 100644 --- a/crates/gpui/src/platform/linux/wayland/display.rs +++ b/crates/gpui/src/platform/linux/wayland/display.rs @@ -8,17 +8,17 @@ use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Size}; pub(crate) struct WaylandDisplay {} impl PlatformDisplay for WaylandDisplay { - // todo!(linux) + // todo(linux) fn id(&self) -> DisplayId { DisplayId(123) // return some fake data so it doesn't panic } - // todo!(linux) + // todo(linux) fn uuid(&self) -> anyhow::Result { Ok(Uuid::from_bytes([0; 16])) // return some fake data so it doesn't panic } - // todo!(linux) + // todo(linux) fn bounds(&self) -> Bounds { Bounds { origin: Default::default(), diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index 8ad1b81cd6..11e1743b03 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/crates/gpui/src/platform/linux/wayland/window.rs @@ -132,7 +132,7 @@ impl WaylandWindowState { size: Size { width: 500, height: 500, - }, //todo!(implement) + }, // todo(implement) }, WindowBounds::Fixed(bounds) => bounds.map(|p| p.0 as i32), }; @@ -200,7 +200,7 @@ impl WaylandWindowState { pub fn set_decoration_state(&self, state: WaylandDecorationState) { self.inner.borrow_mut().decoration_state = state; log::trace!("Window decorations are now handled by {:?}", state); - // todo!(linux) - Handle this properly + // todo(linux) - Handle this properly } pub fn close(&self) { @@ -250,7 +250,7 @@ impl HasDisplayHandle for WaylandWindow { } impl PlatformWindow for WaylandWindow { - //todo!(linux) + // todo(linux) fn bounds(&self) -> WindowBounds { WindowBounds::Maximized } @@ -267,32 +267,32 @@ impl PlatformWindow for WaylandWindow { self.0.inner.borrow_mut().scale } - //todo!(linux) + // todo(linux) fn titlebar_height(&self) -> Pixels { unimplemented!() } - // todo!(linux) + // todo(linux) fn appearance(&self) -> WindowAppearance { WindowAppearance::Light } - // todo!(linux) + // todo(linux) fn display(&self) -> Rc { Rc::new(WaylandDisplay {}) } - // todo!(linux) + // todo(linux) fn mouse_position(&self) -> Point { Point::default() } - //todo!(linux) + // todo(linux) fn modifiers(&self) -> Modifiers { crate::Modifiers::default() } - //todo!(linux) + // todo(linux) fn as_any_mut(&mut self) -> &mut dyn Any { unimplemented!() } @@ -305,7 +305,7 @@ impl PlatformWindow for WaylandWindow { self.0.inner.borrow_mut().input_handler.take() } - //todo!(linux) + // todo(linux) fn prompt( &self, level: PromptLevel, @@ -317,7 +317,7 @@ impl PlatformWindow for WaylandWindow { } fn activate(&self) { - //todo!(linux) + // todo(linux) } fn set_title(&mut self, title: &str) { @@ -325,23 +325,23 @@ impl PlatformWindow for WaylandWindow { } fn set_edited(&mut self, edited: bool) { - //todo!(linux) + // todo(linux) } fn show_character_palette(&self) { - //todo!(linux) + // todo(linux) } fn minimize(&self) { - //todo!(linux) + // todo(linux) } fn zoom(&self) { - //todo!(linux) + // todo(linux) } fn toggle_full_screen(&self) { - //todo!(linux) + // todo(linux) } fn on_request_frame(&self, callback: Box) { @@ -361,7 +361,7 @@ impl PlatformWindow for WaylandWindow { } fn on_fullscreen(&self, callback: Box) { - //todo!(linux) + // todo(linux) } fn on_moved(&self, callback: Box) { @@ -377,10 +377,10 @@ impl PlatformWindow for WaylandWindow { } fn on_appearance_changed(&self, callback: Box) { - //todo!(linux) + // todo(linux) } - // todo!(linux) + // todo(linux) fn is_topmost_for_position(&self, position: Point) -> bool { false } diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index cfacca9d2b..171c9ca3e1 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/crates/gpui/src/platform/linux/x11/window.rs @@ -1,4 +1,4 @@ -//todo!(linux): remove +// todo(linux): remove #![allow(unused)] use crate::{ @@ -99,7 +99,7 @@ pub(crate) struct X11WindowState { #[derive(Clone)] pub(crate) struct X11Window(pub(crate) Rc); -//todo!(linux): Remove other RawWindowHandle implementation +// todo(linux): Remove other RawWindowHandle implementation unsafe impl blade_rwh::HasRawWindowHandle for RawWindow { fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle { let mut wh = blade_rwh::XcbWindowHandle::empty(); @@ -301,7 +301,7 @@ impl X11WindowState { let mut inner = self.inner.borrow_mut(); let old_bounds = mem::replace(&mut inner.bounds, bounds); do_move = old_bounds.origin != bounds.origin; - //todo!(linux): use normal GPUI types here, refactor out the double + // todo(linux): use normal GPUI types here, refactor out the double // viewport check and extra casts ( ) let gpu_size = query_render_extent(&self.xcb_connection, self.x_window); if inner.renderer.viewport_size() != gpu_size { @@ -377,12 +377,12 @@ impl PlatformWindow for X11Window { self.0.inner.borrow_mut().scale_factor } - //todo!(linux) + // todo(linux) fn titlebar_height(&self) -> Pixels { unimplemented!() } - //todo!(linux) + // todo(linux) fn appearance(&self) -> WindowAppearance { WindowAppearance::Light } @@ -402,7 +402,7 @@ impl PlatformWindow for X11Window { ) } - //todo!(linux) + // todo(linux) fn modifiers(&self) -> Modifiers { Modifiers::default() } @@ -419,7 +419,7 @@ impl PlatformWindow for X11Window { self.0.inner.borrow_mut().input_handler.take() } - //todo!(linux) + // todo(linux) fn prompt( &self, _level: PromptLevel, @@ -447,10 +447,10 @@ impl PlatformWindow for X11Window { }); } - //todo!(linux) + // todo(linux) fn set_edited(&mut self, edited: bool) {} - //todo!(linux), this corresponds to `orderFrontCharacterPalette` on macOS, + // todo(linux), this corresponds to `orderFrontCharacterPalette` on macOS, // but it looks like the equivalent for Linux is GTK specific: // // https://docs.gtk.org/gtk3/signal.Entry.insert-emoji.html @@ -460,17 +460,17 @@ impl PlatformWindow for X11Window { unimplemented!() } - //todo!(linux) + // todo(linux) fn minimize(&self) { unimplemented!() } - //todo!(linux) + // todo(linux) fn zoom(&self) { unimplemented!() } - //todo!(linux) + // todo(linux) fn toggle_full_screen(&self) { unimplemented!() } @@ -511,7 +511,7 @@ impl PlatformWindow for X11Window { self.0.callbacks.borrow_mut().appearance_changed = Some(callback); } - //todo!(linux) + // todo(linux) fn is_topmost_for_position(&self, _position: Point) -> bool { unimplemented!() } diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index d7a027e5cb..cea25d9e0b 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -293,6 +293,7 @@ impl MetalRenderer { znear: 0.0, zfar: 1.0, }); + for batch in scene.batches() { let ok = match batch { PrimitiveBatch::Shadows(shadows) => self.draw_shadows( diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 0c86e0627e..d97a4fc5ab 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -126,7 +126,7 @@ impl Platform for TestPlatform { #[cfg(target_os = "macos")] return Arc::new(crate::platform::mac::MacTextSystem::new()); - // todo!("windows") + // todo("windows") #[cfg(target_os = "windows")] unimplemented!() } diff --git a/crates/gpui/src/platform/test/text_system.rs b/crates/gpui/src/platform/test/text_system.rs index 0e877aabbd..ca3a9e3a33 100644 --- a/crates/gpui/src/platform/test/text_system.rs +++ b/crates/gpui/src/platform/test/text_system.rs @@ -7,7 +7,7 @@ use std::borrow::Cow; pub(crate) struct TestTextSystem {} -//todo!(linux) +// todo(linux) #[allow(unused)] impl PlatformTextSystem for TestTextSystem { fn add_fonts(&self, fonts: Vec>) -> Result<()> { diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 9597be71b5..814d51814a 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -1,48 +1,21 @@ -// todo!("windows"): remove +// todo("windows"): remove #![cfg_attr(windows, allow(dead_code))] use crate::{ - point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, EntityId, Hsla, Pixels, - Point, ScaledPixels, StackingOrder, + bounds_tree::BoundsTree, point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, + Hsla, Pixels, Point, ScaledPixels, }; -use collections::{BTreeMap, FxHashSet}; use std::{fmt::Debug, iter::Peekable, slice}; #[allow(non_camel_case_types, unused)] pub(crate) type PathVertex_ScaledPixels = PathVertex; -pub(crate) type LayerId = u32; pub(crate) type DrawOrder = u32; -#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)] -#[repr(C)] -pub(crate) struct ViewId { - low_bits: u32, - high_bits: u32, -} - -impl From for ViewId { - fn from(value: EntityId) -> Self { - let value = value.as_u64(); - Self { - low_bits: value as u32, - high_bits: (value >> 32) as u32, - } - } -} - -impl From for EntityId { - fn from(value: ViewId) -> Self { - let value = (value.low_bits as u64) | ((value.high_bits as u64) << 32); - value.into() - } -} - #[derive(Default)] pub(crate) struct Scene { - last_layer: Option<(StackingOrder, LayerId)>, - layers_by_order: BTreeMap, - orders_by_layer: BTreeMap, + pub(crate) primitives: Vec, + primitive_bounds: BoundsTree, pub(crate) shadows: Vec, pub(crate) quads: Vec, pub(crate) paths: Vec>, @@ -54,12 +27,11 @@ pub(crate) struct Scene { impl Scene { pub fn clear(&mut self) { - self.last_layer = None; - self.layers_by_order.clear(); - self.orders_by_layer.clear(); + self.primitives.clear(); + self.primitive_bounds.clear(); + self.paths.clear(); self.shadows.clear(); self.quads.clear(); - self.paths.clear(); self.underlines.clear(); self.monochrome_sprites.clear(); self.polychrome_sprites.clear(); @@ -70,6 +42,66 @@ impl Scene { &self.paths } + pub fn len(&self) -> usize { + self.primitives.len() + } + + pub(crate) fn push(&mut self, primitive: impl Into) { + let mut primitive = primitive.into(); + let clipped_bounds = primitive + .bounds() + .intersect(&primitive.content_mask().bounds); + if clipped_bounds.size.width <= ScaledPixels(0.) + || clipped_bounds.size.height <= ScaledPixels(0.) + { + return; + } + + let order = self.primitive_bounds.insert(clipped_bounds, ()); + match &mut primitive { + Primitive::Shadow(shadow) => { + shadow.order = order; + self.shadows.push(shadow.clone()); + } + Primitive::Quad(quad) => { + quad.order = order; + self.quads.push(quad.clone()); + } + Primitive::Path(path) => { + path.order = order; + path.id = PathId(self.paths.len()); + self.paths.push(path.clone()); + } + Primitive::Underline(underline) => { + underline.order = order; + self.underlines.push(underline.clone()); + } + Primitive::MonochromeSprite(sprite) => { + sprite.order = order; + self.monochrome_sprites.push(sprite.clone()); + } + Primitive::PolychromeSprite(sprite) => { + sprite.order = order; + self.polychrome_sprites.push(sprite.clone()); + } + Primitive::Surface(surface) => { + surface.order = order; + self.surfaces.push(surface.clone()); + } + } + self.primitives.push(primitive); + } + + pub fn finish(&mut self) { + self.shadows.sort_unstable(); + self.quads.sort_unstable(); + self.paths.sort_unstable(); + self.underlines.sort_unstable(); + self.monochrome_sprites.sort_unstable(); + self.polychrome_sprites.sort_unstable(); + self.surfaces.sort_unstable(); + } + pub(crate) fn batches(&self) -> impl Iterator { BatchIterator { shadows: &self.shadows, @@ -95,162 +127,54 @@ impl Scene { surfaces_iter: self.surfaces.iter().peekable(), } } +} - pub(crate) fn insert(&mut self, order: &StackingOrder, primitive: impl Into) { - let primitive = primitive.into(); - let clipped_bounds = primitive - .bounds() - .intersect(&primitive.content_mask().bounds); - if clipped_bounds.size.width <= ScaledPixels(0.) - || clipped_bounds.size.height <= ScaledPixels(0.) - { - return; - } +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)] +pub(crate) enum PrimitiveKind { + Shadow, + #[default] + Quad, + Path, + Underline, + MonochromeSprite, + PolychromeSprite, + Surface, +} - let layer_id = self.layer_id_for_order(order); - match primitive { - Primitive::Shadow(mut shadow) => { - shadow.layer_id = layer_id; - self.shadows.push(shadow); - } - Primitive::Quad(mut quad) => { - quad.layer_id = layer_id; - self.quads.push(quad); - } - Primitive::Path(mut path) => { - path.layer_id = layer_id; - path.id = PathId(self.paths.len()); - self.paths.push(path); - } - Primitive::Underline(mut underline) => { - underline.layer_id = layer_id; - self.underlines.push(underline); - } - Primitive::MonochromeSprite(mut sprite) => { - sprite.layer_id = layer_id; - self.monochrome_sprites.push(sprite); - } - Primitive::PolychromeSprite(mut sprite) => { - sprite.layer_id = layer_id; - self.polychrome_sprites.push(sprite); - } - Primitive::Surface(mut surface) => { - surface.layer_id = layer_id; - self.surfaces.push(surface); - } +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)] +pub(crate) enum Primitive { + Shadow(Shadow), + Quad(Quad), + Path(Path), + Underline(Underline), + MonochromeSprite(MonochromeSprite), + PolychromeSprite(PolychromeSprite), + Surface(Surface), +} + +impl Primitive { + pub fn bounds(&self) -> &Bounds { + match self { + Primitive::Shadow(shadow) => &shadow.bounds, + Primitive::Quad(quad) => &quad.bounds, + Primitive::Path(path) => &path.bounds, + Primitive::Underline(underline) => &underline.bounds, + Primitive::MonochromeSprite(sprite) => &sprite.bounds, + Primitive::PolychromeSprite(sprite) => &sprite.bounds, + Primitive::Surface(surface) => &surface.bounds, } } - fn layer_id_for_order(&mut self, order: &StackingOrder) -> LayerId { - if let Some((last_order, last_layer_id)) = self.last_layer.as_ref() { - if order == last_order { - return *last_layer_id; - } + pub fn content_mask(&self) -> &ContentMask { + match self { + Primitive::Shadow(shadow) => &shadow.content_mask, + Primitive::Quad(quad) => &quad.content_mask, + Primitive::Path(path) => &path.content_mask, + Primitive::Underline(underline) => &underline.content_mask, + Primitive::MonochromeSprite(sprite) => &sprite.content_mask, + Primitive::PolychromeSprite(sprite) => &sprite.content_mask, + Primitive::Surface(surface) => &surface.content_mask, } - - let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) { - *layer_id - } else { - let next_id = self.layers_by_order.len() as LayerId; - self.layers_by_order.insert(order.clone(), next_id); - self.orders_by_layer.insert(next_id, order.clone()); - next_id - }; - self.last_layer = Some((order.clone(), layer_id)); - layer_id - } - - pub fn reuse_views(&mut self, views: &FxHashSet, prev_scene: &mut Self) { - for shadow in prev_scene.shadows.drain(..) { - if views.contains(&shadow.view_id.into()) { - let order = &prev_scene.orders_by_layer[&shadow.layer_id]; - self.insert(order, shadow); - } - } - - for quad in prev_scene.quads.drain(..) { - if views.contains(&quad.view_id.into()) { - let order = &prev_scene.orders_by_layer[&quad.layer_id]; - self.insert(order, quad); - } - } - - for path in prev_scene.paths.drain(..) { - if views.contains(&path.view_id.into()) { - let order = &prev_scene.orders_by_layer[&path.layer_id]; - self.insert(order, path); - } - } - - for underline in prev_scene.underlines.drain(..) { - if views.contains(&underline.view_id.into()) { - let order = &prev_scene.orders_by_layer[&underline.layer_id]; - self.insert(order, underline); - } - } - - for sprite in prev_scene.monochrome_sprites.drain(..) { - if views.contains(&sprite.view_id.into()) { - let order = &prev_scene.orders_by_layer[&sprite.layer_id]; - self.insert(order, sprite); - } - } - - for sprite in prev_scene.polychrome_sprites.drain(..) { - if views.contains(&sprite.view_id.into()) { - let order = &prev_scene.orders_by_layer[&sprite.layer_id]; - self.insert(order, sprite); - } - } - - for surface in prev_scene.surfaces.drain(..) { - if views.contains(&surface.view_id.into()) { - let order = &prev_scene.orders_by_layer[&surface.layer_id]; - self.insert(order, surface); - } - } - } - - pub fn finish(&mut self) { - let mut orders = vec![0; self.layers_by_order.len()]; - for (ix, layer_id) in self.layers_by_order.values().enumerate() { - orders[*layer_id as usize] = ix as u32; - } - - for shadow in &mut self.shadows { - shadow.order = orders[shadow.layer_id as usize]; - } - self.shadows.sort_by_key(|shadow| shadow.order); - - for quad in &mut self.quads { - quad.order = orders[quad.layer_id as usize]; - } - self.quads.sort_by_key(|quad| quad.order); - - for path in &mut self.paths { - path.order = orders[path.layer_id as usize]; - } - self.paths.sort_by_key(|path| path.order); - - for underline in &mut self.underlines { - underline.order = orders[underline.layer_id as usize]; - } - self.underlines.sort_by_key(|underline| underline.order); - - for monochrome_sprite in &mut self.monochrome_sprites { - monochrome_sprite.order = orders[monochrome_sprite.layer_id as usize]; - } - self.monochrome_sprites.sort_by_key(|sprite| sprite.order); - - for polychrome_sprite in &mut self.polychrome_sprites { - polychrome_sprite.order = orders[polychrome_sprite.layer_id as usize]; - } - self.polychrome_sprites.sort_by_key(|sprite| sprite.order); - - for surface in &mut self.surfaces { - surface.order = orders[surface.layer_id as usize]; - } - self.surfaces.sort_by_key(|surface| surface.order); } } @@ -439,54 +363,6 @@ impl<'a> Iterator for BatchIterator<'a> { } } -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)] -pub(crate) enum PrimitiveKind { - Shadow, - #[default] - Quad, - Path, - Underline, - MonochromeSprite, - PolychromeSprite, - Surface, -} - -pub(crate) enum Primitive { - Shadow(Shadow), - Quad(Quad), - Path(Path), - Underline(Underline), - MonochromeSprite(MonochromeSprite), - PolychromeSprite(PolychromeSprite), - Surface(Surface), -} - -impl Primitive { - pub fn bounds(&self) -> &Bounds { - match self { - Primitive::Shadow(shadow) => &shadow.bounds, - Primitive::Quad(quad) => &quad.bounds, - Primitive::Path(path) => &path.bounds, - Primitive::Underline(underline) => &underline.bounds, - Primitive::MonochromeSprite(sprite) => &sprite.bounds, - Primitive::PolychromeSprite(sprite) => &sprite.bounds, - Primitive::Surface(surface) => &surface.bounds, - } - } - - pub fn content_mask(&self) -> &ContentMask { - match self { - Primitive::Shadow(shadow) => &shadow.content_mask, - Primitive::Quad(quad) => &quad.content_mask, - Primitive::Path(path) => &path.content_mask, - Primitive::Underline(underline) => &underline.content_mask, - Primitive::MonochromeSprite(sprite) => &sprite.content_mask, - Primitive::PolychromeSprite(sprite) => &sprite.content_mask, - Primitive::Surface(surface) => &surface.content_mask, - } - } -} - #[derive(Debug)] pub(crate) enum PrimitiveBatch<'a> { Shadows(&'a [Shadow]), @@ -507,8 +383,6 @@ pub(crate) enum PrimitiveBatch<'a> { #[derive(Default, Debug, Clone, Eq, PartialEq)] #[repr(C)] pub(crate) struct Quad { - pub view_id: ViewId, - pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, @@ -539,8 +413,6 @@ impl From for Primitive { #[derive(Debug, Clone, Eq, PartialEq)] #[repr(C)] pub(crate) struct Underline { - pub view_id: ViewId, - pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, @@ -570,8 +442,6 @@ impl From for Primitive { #[derive(Debug, Clone, Eq, PartialEq)] #[repr(C)] pub(crate) struct Shadow { - pub view_id: ViewId, - pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, pub corner_radii: Corners, @@ -602,8 +472,6 @@ impl From for Primitive { #[derive(Clone, Debug, Eq, PartialEq)] #[repr(C)] pub(crate) struct MonochromeSprite { - pub view_id: ViewId, - pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, @@ -635,8 +503,6 @@ impl From for Primitive { #[derive(Clone, Debug, Eq, PartialEq)] #[repr(C)] pub(crate) struct PolychromeSprite { - pub view_id: ViewId, - pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, @@ -669,8 +535,6 @@ impl From for Primitive { #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct Surface { - pub view_id: ViewId, - pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, @@ -700,11 +564,9 @@ impl From for Primitive { pub(crate) struct PathId(pub(crate) usize); /// A line made up of a series of vertices and control points. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Path { pub(crate) id: PathId, - pub(crate) view_id: ViewId, - layer_id: LayerId, order: DrawOrder, pub(crate) bounds: Bounds

, pub(crate) content_mask: ContentMask

, @@ -720,8 +582,6 @@ impl Path { pub fn new(start: Point) -> Self { Self { id: PathId(0), - view_id: ViewId::default(), - layer_id: LayerId::default(), order: DrawOrder::default(), vertices: Vec::new(), start, @@ -740,8 +600,6 @@ impl Path { pub fn scale(&self, factor: f32) -> Path { Path { id: self.id, - view_id: self.view_id, - layer_id: self.layer_id, order: self.order, bounds: self.bounds.scale(factor), content_mask: self.content_mask.scale(factor), diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index 71dccaf170..69d2146d69 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -115,9 +115,6 @@ pub struct Style { /// The mouse cursor style shown when the mouse pointer is over an element. pub mouse_cursor: Option, - /// The z-index to set for this element - pub z_index: Option, - /// Whether to draw a red debugging outline around this element #[cfg(debug_assertions)] pub debug: bool, @@ -208,7 +205,7 @@ impl Default for TextStyle { fn default() -> Self { TextStyle { color: black(), - // todo!(linux) make this configurable or choose better default + // todo(linux) make this configurable or choose better default font_family: if cfg!(target_os = "linux") { "FreeMono".into() } else { @@ -323,6 +320,13 @@ pub struct HighlightStyle { impl Eq for HighlightStyle {} impl Style { + /// Returns true if the style is visible and the background is opaque. + pub fn has_opaque_background(&self) -> bool { + self.background + .as_ref() + .is_some_and(|fill| fill.color().is_some_and(|color| !color.is_transparent())) + } + /// Get the text style in this element style. pub fn text_style(&self) -> Option<&TextStyleRefinement> { if self.text.is_some() { @@ -402,97 +406,87 @@ impl Style { let rem_size = cx.rem_size(); - cx.with_z_index(0, |cx| { - cx.paint_shadows( - bounds, - self.corner_radii.to_pixels(bounds.size, rem_size), - &self.box_shadow, - ); - }); + cx.paint_shadows( + bounds, + self.corner_radii.to_pixels(bounds.size, rem_size), + &self.box_shadow, + ); let background_color = self.background.as_ref().and_then(Fill::color); if background_color.map_or(false, |color| !color.is_transparent()) { - cx.with_z_index(1, |cx| { - let mut border_color = background_color.unwrap_or_default(); - border_color.a = 0.; - cx.paint_quad(quad( - bounds, - self.corner_radii.to_pixels(bounds.size, rem_size), - background_color.unwrap_or_default(), - Edges::default(), - border_color, - )); - }); + let mut border_color = background_color.unwrap_or_default(); + border_color.a = 0.; + cx.paint_quad(quad( + bounds, + self.corner_radii.to_pixels(bounds.size, rem_size), + background_color.unwrap_or_default(), + Edges::default(), + border_color, + )); } - cx.with_z_index(2, |cx| { - continuation(cx); - }); + continuation(cx); if self.is_border_visible() { - cx.with_z_index(3, |cx| { - let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size); - let border_widths = self.border_widths.to_pixels(rem_size); - let max_border_width = border_widths.max(); - let max_corner_radius = corner_radii.max(); + let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size); + let border_widths = self.border_widths.to_pixels(rem_size); + let max_border_width = border_widths.max(); + let max_corner_radius = corner_radii.max(); - let top_bounds = Bounds::from_corners( - bounds.origin, - bounds.upper_right() - + point(Pixels::ZERO, max_border_width.max(max_corner_radius)), - ); - let bottom_bounds = Bounds::from_corners( - bounds.lower_left() - - point(Pixels::ZERO, max_border_width.max(max_corner_radius)), - bounds.lower_right(), - ); - let left_bounds = Bounds::from_corners( - top_bounds.lower_left(), - bottom_bounds.origin + point(max_border_width, Pixels::ZERO), - ); - let right_bounds = Bounds::from_corners( - top_bounds.lower_right() - point(max_border_width, Pixels::ZERO), - bottom_bounds.upper_right(), - ); + let top_bounds = Bounds::from_corners( + bounds.origin, + bounds.upper_right() + point(Pixels::ZERO, max_border_width.max(max_corner_radius)), + ); + let bottom_bounds = Bounds::from_corners( + bounds.lower_left() - point(Pixels::ZERO, max_border_width.max(max_corner_radius)), + bounds.lower_right(), + ); + let left_bounds = Bounds::from_corners( + top_bounds.lower_left(), + bottom_bounds.origin + point(max_border_width, Pixels::ZERO), + ); + let right_bounds = Bounds::from_corners( + top_bounds.lower_right() - point(max_border_width, Pixels::ZERO), + bottom_bounds.upper_right(), + ); - let mut background = self.border_color.unwrap_or_default(); - background.a = 0.; - let quad = quad( - bounds, - corner_radii, - background, - border_widths, - self.border_color.unwrap_or_default(), - ); + let mut background = self.border_color.unwrap_or_default(); + background.a = 0.; + let quad = quad( + bounds, + corner_radii, + background, + border_widths, + self.border_color.unwrap_or_default(), + ); - cx.with_content_mask(Some(ContentMask { bounds: top_bounds }), |cx| { - cx.paint_quad(quad.clone()); - }); - cx.with_content_mask( - Some(ContentMask { - bounds: right_bounds, - }), - |cx| { - cx.paint_quad(quad.clone()); - }, - ); - cx.with_content_mask( - Some(ContentMask { - bounds: bottom_bounds, - }), - |cx| { - cx.paint_quad(quad.clone()); - }, - ); - cx.with_content_mask( - Some(ContentMask { - bounds: left_bounds, - }), - |cx| { - cx.paint_quad(quad); - }, - ); + cx.with_content_mask(Some(ContentMask { bounds: top_bounds }), |cx| { + cx.paint_quad(quad.clone()); }); + cx.with_content_mask( + Some(ContentMask { + bounds: right_bounds, + }), + |cx| { + cx.paint_quad(quad.clone()); + }, + ); + cx.with_content_mask( + Some(ContentMask { + bounds: bottom_bounds, + }), + |cx| { + cx.paint_quad(quad.clone()); + }, + ); + cx.with_content_mask( + Some(ContentMask { + bounds: left_bounds, + }), + |cx| { + cx.paint_quad(quad); + }, + ); } #[cfg(debug_assertions)] @@ -545,7 +539,6 @@ impl Default for Style { box_shadow: Default::default(), text: TextStyleRefinement::default(), mouse_cursor: None, - z_index: None, #[cfg(debug_assertions)] debug: false, diff --git a/crates/gpui/src/styled.rs b/crates/gpui/src/styled.rs index d4014ffc2f..db4c49c177 100644 --- a/crates/gpui/src/styled.rs +++ b/crates/gpui/src/styled.rs @@ -15,12 +15,6 @@ pub trait Styled: Sized { gpui_macros::style_helpers!(); - /// Set the z-index of this element. - fn z_index(mut self, z_index: u16) -> Self { - self.style().z_index = Some(z_index); - self - } - /// Sets the position of the element to `relative`. /// [Docs](https://tailwindcss.com/docs/position) fn relative(mut self) -> Self { diff --git a/crates/gpui/src/taffy.rs b/crates/gpui/src/taffy.rs index 0797c8f3b4..ce5ca171f3 100644 --- a/crates/gpui/src/taffy.rs +++ b/crates/gpui/src/taffy.rs @@ -51,7 +51,7 @@ impl TaffyLayoutEngine { self.styles.get(&layout_id) } - pub fn request_layout( + pub fn before_layout( &mut self, style: &Style, rem_size: Pixels, @@ -447,6 +447,27 @@ pub enum AvailableSpace { MaxContent, } +impl AvailableSpace { + /// Returns a `Size` with both width and height set to `AvailableSpace::MinContent`. + /// + /// This function is useful when you want to create a `Size` with the minimum content constraints + /// for both dimensions. + /// + /// # Examples + /// + /// ``` + /// let min_content_size = AvailableSpace::min_size(); + /// assert_eq!(min_content_size.width, AvailableSpace::MinContent); + /// assert_eq!(min_content_size.height, AvailableSpace::MinContent); + /// ``` + pub const fn min_size() -> Size { + Size { + width: Self::MinContent, + height: Self::MinContent, + } + } +} + impl From for TaffyAvailableSpace { fn from(space: AvailableSpace) -> TaffyAvailableSpace { match space { diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 7e2a76a9dd..905d308e26 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -291,8 +291,8 @@ impl WindowTextSystem { } } - pub(crate) fn with_view(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R { - self.line_layout_cache.with_view(view_id, f) + pub(crate) fn set_parent_view_id(&self, view_id: Option) { + self.line_layout_cache.set_parent_view_id(view_id); } /// Shape the given line, at the given font_size, for painting to the screen. diff --git a/crates/gpui/src/text_system/line.rs b/crates/gpui/src/text_system/line.rs index fbf34d39b2..3395e8b78a 100644 --- a/crates/gpui/src/text_system/line.rs +++ b/crates/gpui/src/text_system/line.rs @@ -118,11 +118,13 @@ fn paint_line( let mut current_underline: Option<(Point, UnderlineStyle)> = None; let mut current_strikethrough: Option<(Point, StrikethroughStyle)> = None; let mut current_background: Option<(Point, Hsla)> = None; - let text_system = cx.text_system().clone(); let mut glyph_origin = origin; let mut prev_glyph_position = Point::default(); for (run_ix, run) in layout.runs.iter().enumerate() { - let max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size; + let max_glyph_size = cx + .text_system() + .bounding_box(run.font_id, layout.font_size) + .size; for (glyph_ix, glyph) in run.glyphs.iter().enumerate() { glyph_origin.x += glyph.position.x - prev_glyph_position.x; diff --git a/crates/gpui/src/text_system/line_layout.rs b/crates/gpui/src/text_system/line_layout.rs index 014c2bb1ec..529f34f335 100644 --- a/crates/gpui/src/text_system/line_layout.rs +++ b/crates/gpui/src/text_system/line_layout.rs @@ -4,6 +4,7 @@ use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use smallvec::SmallVec; use std::{ borrow::Borrow, + cell::Cell, hash::{Hash, Hasher}, sync::Arc, }; @@ -276,7 +277,7 @@ impl WrappedLineLayout { } pub(crate) struct LineLayoutCache { - view_stack: Mutex>, + parent_view_id: Cell>, previous_frame: Mutex>>, current_frame: RwLock>>, previous_frame_wrapped: Mutex>>, @@ -287,7 +288,7 @@ pub(crate) struct LineLayoutCache { impl LineLayoutCache { pub fn new(platform_text_system: Arc) -> Self { Self { - view_stack: Mutex::default(), + parent_view_id: Cell::default(), previous_frame: Mutex::default(), current_frame: RwLock::default(), previous_frame_wrapped: Mutex::default(), @@ -297,8 +298,6 @@ impl LineLayoutCache { } pub fn finish_frame(&self, reused_views: &FxHashSet) { - debug_assert_eq!(self.view_stack.lock().len(), 0); - let mut prev_frame = self.previous_frame.lock(); let mut curr_frame = self.current_frame.write(); for (key, layout) in prev_frame.drain() { @@ -324,15 +323,8 @@ impl LineLayoutCache { std::mem::swap(&mut *prev_frame_wrapped, &mut *curr_frame_wrapped); } - pub fn with_view(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R { - self.view_stack.lock().push(view_id); - let result = f(); - self.view_stack.lock().pop(); - result - } - - fn parent_view_id(&self) -> Option { - self.view_stack.lock().last().copied() + pub fn set_parent_view_id(&self, view_id: Option) { + self.parent_view_id.replace(view_id); } pub fn layout_wrapped_line( @@ -347,7 +339,7 @@ impl LineLayoutCache { font_size, runs, wrap_width, - parent_view_id: self.parent_view_id(), + parent_view_id: self.parent_view_id.get(), } as &dyn AsCacheKeyRef; let current_frame = self.current_frame_wrapped.upgradable_read(); @@ -376,7 +368,7 @@ impl LineLayoutCache { font_size, runs: SmallVec::from(runs), wrap_width, - parent_view_id: self.parent_view_id(), + parent_view_id: self.parent_view_id.get(), }; current_frame.insert(key, layout.clone()); layout @@ -389,7 +381,7 @@ impl LineLayoutCache { font_size, runs, wrap_width: None, - parent_view_id: self.parent_view_id(), + parent_view_id: self.parent_view_id.get(), } as &dyn AsCacheKeyRef; let current_frame = self.current_frame.upgradable_read(); @@ -408,7 +400,7 @@ impl LineLayoutCache { font_size, runs: SmallVec::from(runs), wrap_width: None, - parent_view_id: self.parent_view_id(), + parent_view_id: self.parent_view_id.get(), }; current_frame.insert(key, layout.clone()); layout diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 207eac6381..1728450c59 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -1,14 +1,15 @@ use crate::{ - seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, Bounds, + seal::Sealed, AfterLayoutIndex, AnyElement, AnyModel, AnyWeakModel, AppContext, Bounds, ContentMask, Element, ElementContext, ElementId, Entity, EntityId, Flatten, FocusHandle, - FocusableView, IntoElement, LayoutId, Model, Pixels, Point, Render, Size, StackingOrder, Style, - TextStyle, ViewContext, VisualContext, WeakModel, + FocusableView, IntoElement, LayoutId, Model, PaintIndex, Pixels, Render, Style, TextStyle, + ViewContext, VisualContext, WeakModel, }; use anyhow::{Context, Result}; use std::{ any::{type_name, TypeId}, fmt, hash::{Hash, Hasher}, + ops::Range, }; /// A view is a piece of state that can be presented on screen by implementing the [Render] trait. @@ -20,17 +21,16 @@ pub struct View { impl Sealed for View {} -#[doc(hidden)] -pub struct AnyViewState { +struct AnyViewState { root_style: Style, - next_stacking_order_id: u16, - cache_key: Option, - element: Option, + after_layout_range: Range, + paint_range: Range, + cache_key: ViewCacheKey, } +#[derive(Default)] struct ViewCacheKey { bounds: Bounds, - stacking_order: StackingOrder, content_mask: ContentMask, text_style: TextStyle, } @@ -90,22 +90,39 @@ impl View { } impl Element for View { - type State = Option; + type BeforeLayout = AnyElement; + type AfterLayout = (); - fn request_layout( - &mut self, - _state: Option, - cx: &mut ElementContext, - ) -> (LayoutId, Self::State) { - cx.with_view_id(self.entity_id(), |cx| { + fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) { + cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| { let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element()); - let layout_id = element.request_layout(cx); - (layout_id, Some(element)) + let layout_id = element.before_layout(cx); + (layout_id, element) }) } - fn paint(&mut self, _: Bounds, element: &mut Self::State, cx: &mut ElementContext) { - cx.paint_view(self.entity_id(), |cx| element.take().unwrap().paint(cx)); + fn after_layout( + &mut self, + _: Bounds, + element: &mut Self::BeforeLayout, + cx: &mut ElementContext, + ) { + cx.set_view_id(self.entity_id()); + cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| { + element.after_layout(cx) + }) + } + + fn paint( + &mut self, + _: Bounds, + element: &mut Self::BeforeLayout, + _: &mut Self::AfterLayout, + cx: &mut ElementContext, + ) { + cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| { + element.paint(cx) + }) } } @@ -203,7 +220,7 @@ impl Eq for WeakView {} #[derive(Clone, Debug)] pub struct AnyView { model: AnyModel, - request_layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement), + render: fn(&AnyView, &mut ElementContext) -> AnyElement, cache: bool, } @@ -220,7 +237,7 @@ impl AnyView { pub fn downgrade(&self) -> AnyWeakView { AnyWeakView { model: self.model.downgrade(), - layout: self.request_layout, + render: self.render, } } @@ -231,7 +248,7 @@ impl AnyView { Ok(model) => Ok(View { model }), Err(model) => Err(Self { model, - request_layout: self.request_layout, + render: self.render, cache: self.cache, }), } @@ -246,113 +263,154 @@ impl AnyView { pub fn entity_id(&self) -> EntityId { self.model.entity_id() } - - pub(crate) fn draw( - &self, - origin: Point, - available_space: Size, - cx: &mut ElementContext, - ) { - cx.paint_view(self.entity_id(), |cx| { - cx.with_absolute_element_offset(origin, |cx| { - let (layout_id, mut rendered_element) = (self.request_layout)(self, cx); - cx.compute_layout(layout_id, available_space); - rendered_element.paint(cx) - }); - }) - } } impl From> for AnyView { fn from(value: View) -> Self { AnyView { model: value.model.into_any(), - request_layout: any_view::request_layout::, + render: any_view::render::, cache: false, } } } impl Element for AnyView { - type State = AnyViewState; + type BeforeLayout = Option; + type AfterLayout = Option; - fn request_layout( - &mut self, - state: Option, - cx: &mut ElementContext, - ) -> (LayoutId, Self::State) { - cx.with_view_id(self.entity_id(), |cx| { - if self.cache - && !cx.window.dirty_views.contains(&self.entity_id()) - && !cx.window.refreshing - { - if let Some(state) = state { - let layout_id = cx.request_layout(&state.root_style, None); - return (layout_id, state); - } - } + fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) { + if self.cache { + cx.with_element_state::( + Some(ElementId::View(self.entity_id())), + |element_state, cx| { + let mut element_state = element_state.unwrap(); - let (layout_id, element) = (self.request_layout)(self, cx); - let root_style = cx.layout_style(layout_id).unwrap().clone(); - let state = AnyViewState { - root_style, - next_stacking_order_id: 0, - cache_key: None, - element: Some(element), - }; - (layout_id, state) - }) + if !cx.window.dirty_views.contains(&self.entity_id()) && !cx.window.refreshing { + if let Some(root_style) = element_state + .as_ref() + .map(|element_state| &element_state.root_style) + { + let layout_id = cx.request_layout(root_style, None); + return ((layout_id, None), element_state); + } + } + + let mut element = (self.render)(self, cx); + let layout_id = element.before_layout(cx); + let element_state = Some(AnyViewState { + root_style: cx.layout_style(layout_id).unwrap().clone(), + cache_key: ViewCacheKey::default(), + after_layout_range: AfterLayoutIndex::default() + ..AfterLayoutIndex::default(), + paint_range: PaintIndex::default()..PaintIndex::default(), + }); + ((layout_id, Some(element)), element_state) + }, + ) + } else { + cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| { + let mut element = (self.render)(self, cx); + let layout_id = element.before_layout(cx); + (layout_id, Some(element)) + }) + } } - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut ElementContext) { - cx.paint_view(self.entity_id(), |cx| { - if !self.cache { - state.element.take().unwrap().paint(cx); - return; - } + fn after_layout( + &mut self, + bounds: Bounds, + element: &mut Self::BeforeLayout, + cx: &mut ElementContext, + ) -> Option { + cx.set_view_id(self.entity_id()); + if self.cache { + cx.with_element_state::( + Some(ElementId::View(self.entity_id())), + |element_state, cx| { + let mut element_state = element_state.unwrap().unwrap(); - if let Some(cache_key) = state.cache_key.as_mut() { - if cache_key.bounds == bounds - && cache_key.content_mask == cx.content_mask() - && cache_key.stacking_order == *cx.stacking_order() - && cache_key.text_style == cx.text_style() - { - cx.reuse_view(state.next_stacking_order_id); - return; - } - } + let after_layout_start = cx.window.next_frame.after_layout_index(); + let content_mask = cx.content_mask(); + let text_style = cx.text_style(); - if let Some(mut element) = state.element.take() { - element.paint(cx); - } else { - let mut element = (self.request_layout)(self, cx).1; - element.draw(bounds.origin, bounds.size.into(), cx); - } + let element = if let Some(mut element) = element.take() { + element.after_layout(cx); + Some(element) + } else if element_state.cache_key.bounds == bounds + && element_state.cache_key.content_mask == content_mask + && element_state.cache_key.text_style == text_style + { + cx.reuse_after_layout(element_state.after_layout_range.clone()); + None + } else { + let mut element = (self.render)(self, cx); + let layout_id = element.before_layout(cx); + cx.compute_layout(layout_id, bounds.size.into()); + element_state.root_style = cx.layout_style(layout_id).unwrap().clone(); + cx.with_absolute_element_offset(bounds.origin, |cx| { + element.after_layout(cx) + }); - state.next_stacking_order_id = cx - .window - .next_frame - .next_stacking_order_ids - .last() - .copied() - .unwrap(); - state.cache_key = Some(ViewCacheKey { - bounds, - stacking_order: cx.stacking_order().clone(), - content_mask: cx.content_mask(), - text_style: cx.text_style(), - }); - }) + Some(element) + }; + + let after_layout_end = cx.window.next_frame.after_layout_index(); + element_state.after_layout_range = after_layout_start..after_layout_end; + element_state.cache_key.bounds = bounds; + element_state.cache_key.content_mask = content_mask; + element_state.cache_key.text_style = text_style; + + (element, Some(element_state)) + }, + ) + } else { + cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| { + let mut element = element.take().unwrap(); + element.after_layout(cx); + Some(element) + }) + } + } + + fn paint( + &mut self, + _bounds: Bounds, + _: &mut Self::BeforeLayout, + element: &mut Self::AfterLayout, + cx: &mut ElementContext, + ) { + if self.cache { + cx.with_element_state::( + Some(ElementId::View(self.entity_id())), + |element_state, cx| { + let mut element_state = element_state.unwrap().unwrap(); + + let paint_start = cx.window.next_frame.paint_index(); + + if let Some(element) = element { + element.paint(cx); + } else { + cx.reuse_paint(element_state.paint_range.clone()); + } + + let paint_end = cx.window.next_frame.paint_index(); + element_state.paint_range = paint_start..paint_end; + + ((), Some(element_state)) + }, + ) + } else { + cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| { + element.as_mut().unwrap().paint(cx); + }) + } } } impl IntoElement for View { type Element = View; - fn element_id(&self) -> Option { - Some(ElementId::from_entity_id(self.model.entity_id)) - } - fn into_element(self) -> Self::Element { self } @@ -361,10 +419,6 @@ impl IntoElement for View { impl IntoElement for AnyView { type Element = Self; - fn element_id(&self) -> Option { - Some(ElementId::from_entity_id(self.model.entity_id)) - } - fn into_element(self) -> Self::Element { self } @@ -373,7 +427,7 @@ impl IntoElement for AnyView { /// A weak, dynamically-typed view handle that does not prevent the view from being released. pub struct AnyWeakView { model: AnyWeakModel, - layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement), + render: fn(&AnyView, &mut ElementContext) -> AnyElement, } impl AnyWeakView { @@ -382,7 +436,7 @@ impl AnyWeakView { let model = self.model.upgrade()?; Some(AnyView { model, - request_layout: self.layout, + render: self.render, cache: false, }) } @@ -392,7 +446,7 @@ impl From> for AnyWeakView { fn from(view: WeakView) -> Self { Self { model: view.model.into(), - layout: any_view::request_layout::, + render: any_view::render::, } } } @@ -412,15 +466,13 @@ impl std::fmt::Debug for AnyWeakView { } mod any_view { - use crate::{AnyElement, AnyView, ElementContext, IntoElement, LayoutId, Render}; + use crate::{AnyElement, AnyView, ElementContext, IntoElement, Render}; - pub(crate) fn request_layout( + pub(crate) fn render( view: &AnyView, cx: &mut ElementContext, - ) -> (LayoutId, AnyElement) { + ) -> AnyElement { let view = view.clone().downcast::().unwrap(); - let mut element = view.update(cx, |view, cx| view.render(cx).into_any_element()); - let layout_id = element.request_layout(cx); - (layout_id, element) + view.update(cx, |view, cx| view.render(cx).into_any_element()) } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index f615b192ee..0bbdc8519b 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1,13 +1,12 @@ use crate::{ - px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext, - AvailableSpace, Bounds, Context, Corners, CursorStyle, DispatchActionListener, DispatchNodeId, - DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, - Global, GlobalElementId, Hsla, KeyBinding, KeyContext, KeyDownEvent, KeyMatch, KeymatchResult, - Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers, MouseButton, MouseMoveEvent, - MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, - PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, Subscription, - TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowAppearance, WindowBounds, - WindowOptions, WindowTextSystem, + px, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext, Bounds, + Context, Corners, CursorStyle, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, + Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, Global, GlobalElementId, + Hsla, KeyBinding, KeyDownEvent, KeyMatch, KeymatchResult, Keystroke, KeystrokeEvent, Model, + ModelContext, Modifiers, MouseButton, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, + PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Render, ScaledPixels, + SharedString, Size, SubscriberSet, Subscription, TaffyLayoutEngine, Task, View, VisualContext, + WeakView, WindowAppearance, WindowBounds, WindowOptions, WindowTextSystem, }; use anyhow::{anyhow, Context as _, Result}; use collections::FxHashSet; @@ -37,8 +36,6 @@ use util::{measure, ResultExt}; mod element_cx; pub use element_cx::*; -const ACTIVE_DRAG_Z_INDEX: u16 = 1; - /// A global stacking order, which is created by stacking successive z-index values. /// Each z-index will always be interpreted in the context of its parent z-index. #[derive(Debug, Deref, DerefMut, Clone, Ord, PartialOrd, PartialEq, Eq, Default)] @@ -257,6 +254,7 @@ pub struct Window { pub(crate) element_id_stack: GlobalElementId, pub(crate) rendered_frame: Frame, pub(crate) next_frame: Frame, + pub(crate) next_hitbox_id: HitboxId, next_frame_callbacks: Rc>>, pub(crate) dirty_views: FxHashSet, pub(crate) focus_handles: Arc>>, @@ -264,6 +262,7 @@ pub struct Window { focus_lost_listeners: SubscriberSet<(), AnyObserver>, default_prevented: bool, mouse_position: Point, + mouse_hit_test: HitTest, modifiers: Modifiers, scale_factor: f32, bounds: WindowBounds, @@ -451,12 +450,14 @@ impl Window { rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), next_frame_callbacks, + next_hitbox_id: HitboxId::default(), dirty_views: FxHashSet::default(), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), focus_listeners: SubscriberSet::new(), focus_lost_listeners: SubscriberSet::new(), default_prevented: true, mouse_position, + mouse_hit_test: HitTest::default(), modifiers, scale_factor, bounds, @@ -583,7 +584,7 @@ impl<'a> WindowContext<'a> { } /// Accessor for the text system. - pub fn text_system(&self) -> &Arc { + pub fn text_system(&self) -> &WindowTextSystem { &self.window.text_system } @@ -857,95 +858,6 @@ impl<'a> WindowContext<'a> { self.window.modifiers } - /// Returns true if there is no opaque layer containing the given point - /// on top of the given level. Layers who are extensions of the queried layer - /// are not considered to be on top of queried layer. - pub fn was_top_layer(&self, point: &Point, layer: &StackingOrder) -> bool { - // Precondition: the depth map is ordered from topmost to bottomost. - - for (opaque_layer, _, bounds) in self.window.rendered_frame.depth_map.iter() { - if layer >= opaque_layer { - // The queried layer is either above or is the same as the this opaque layer. - // Anything after this point is guaranteed to be below the queried layer. - return true; - } - - if !bounds.contains(point) { - // This opaque layer is above the queried layer but it doesn't contain - // the given position, so we can ignore it even if it's above. - continue; - } - - // At this point, we've established that this opaque layer is on top of the queried layer - // and contains the position: - // If neither the opaque layer or the queried layer is an extension of the other then - // we know they are on different stacking orders, and return false. - let is_on_same_layer = opaque_layer - .iter() - .zip(layer.iter()) - .all(|(a, b)| a.z_index == b.z_index); - - if !is_on_same_layer { - return false; - } - } - - true - } - - pub(crate) fn was_top_layer_under_active_drag( - &self, - point: &Point, - layer: &StackingOrder, - ) -> bool { - // Precondition: the depth map is ordered from topmost to bottomost. - - for (opaque_layer, _, bounds) in self.window.rendered_frame.depth_map.iter() { - if layer >= opaque_layer { - // The queried layer is either above or is the same as the this opaque layer. - // Anything after this point is guaranteed to be below the queried layer. - return true; - } - - if !bounds.contains(point) { - // This opaque layer is above the queried layer but it doesn't contain - // the given position, so we can ignore it even if it's above. - continue; - } - - // All normal content is rendered with a base z-index of 0, we know that if the root of this opaque layer - // equals `ACTIVE_DRAG_Z_INDEX` then it must be the drag layer and we can ignore it as we are - // looking to see if the queried layer was the topmost underneath the drag layer. - if opaque_layer - .first() - .map(|c| c.z_index == ACTIVE_DRAG_Z_INDEX) - .unwrap_or(false) - { - continue; - } - - // At this point, we've established that this opaque layer is on top of the queried layer - // and contains the position: - // If neither the opaque layer or the queried layer is an extension of the other then - // we know they are on different stacking orders, and return false. - let is_on_same_layer = opaque_layer - .iter() - .zip(layer.iter()) - .all(|(a, b)| a.z_index == b.z_index); - - if !is_on_same_layer { - return false; - } - } - - true - } - - /// Called during painting to get the current stacking order. - pub fn stacking_order(&self) -> &StackingOrder { - &self.window.next_frame.z_index_stack - } - /// Produces a new frame and assigns it to `rendered_frame`. To actually show /// the contents of the new [Scene], use [present]. #[profiling::function] @@ -959,54 +871,7 @@ impl<'a> WindowContext<'a> { requested_handler.handler = input_handler; } - let root_view = self.window.root_view.take().unwrap(); - self.with_element_context(|cx| { - cx.with_z_index(0, |cx| { - cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| { - // We need to use cx.cx here so we can utilize borrow splitting - for (action_type, action_listeners) in &cx.cx.app.global_action_listeners { - for action_listener in action_listeners.iter().cloned() { - cx.cx.window.next_frame.dispatch_tree.on_action( - *action_type, - Rc::new( - move |action: &dyn Any, phase, cx: &mut WindowContext<'_>| { - action_listener(action, phase, cx) - }, - ), - ) - } - } - - let available_space = cx.window.viewport_size.map(Into::into); - root_view.draw(Point::default(), available_space, cx); - }) - }) - }); - - if let Some(active_drag) = self.app.active_drag.take() { - self.with_element_context(|cx| { - cx.with_z_index(ACTIVE_DRAG_Z_INDEX, |cx| { - let offset = cx.mouse_position() - active_drag.cursor_offset; - let available_space = - size(AvailableSpace::MinContent, AvailableSpace::MinContent); - active_drag.view.draw(offset, available_space, cx); - }) - }); - self.active_drag = Some(active_drag); - } else if let Some(tooltip_request) = self.window.next_frame.tooltip_request.take() { - self.with_element_context(|cx| { - cx.with_z_index(1, |cx| { - let available_space = - size(AvailableSpace::MinContent, AvailableSpace::MinContent); - tooltip_request.tooltip.view.draw( - tooltip_request.tooltip.cursor_offset, - available_space, - cx, - ); - }) - }); - self.window.next_frame.tooltip_request = Some(tooltip_request); - } + self.with_element_context(|cx| cx.draw_roots()); self.window.dirty_views.clear(); self.window @@ -1018,13 +883,10 @@ impl<'a> WindowContext<'a> { ); self.window.next_frame.focus = self.window.focus; self.window.next_frame.window_active = self.window.active.get(); - self.window.root_view = Some(root_view); // Set the cursor only if we're the active window. - let cursor_style_request = self.window.next_frame.requested_cursor_style.take(); if self.is_window_active() { - let cursor_style = - cursor_style_request.map_or(CursorStyle::Arrow, |request| request.style); + let cursor_style = self.compute_cursor_style().unwrap_or(CursorStyle::Arrow); self.platform.set_cursor_style(cursor_style); } @@ -1097,6 +959,18 @@ impl<'a> WindowContext<'a> { profiling::finish_frame!(); } + fn compute_cursor_style(&mut self) -> Option { + // TODO: maybe we should have a HashMap keyed by HitboxId. + let request = self + .window + .next_frame + .cursor_styles + .iter() + .rev() + .find(|request| request.hitbox_id.is_hovered(self))?; + Some(request.style) + } + /// Dispatch a given keystroke as though the user had typed it. /// You can create a keystroke with Keystroke::parse(""). pub fn dispatch_keystroke(&mut self, keystroke: Keystroke) -> bool { @@ -1231,43 +1105,32 @@ impl<'a> WindowContext<'a> { } fn dispatch_mouse_event(&mut self, event: &dyn Any) { - if let Some(mut handlers) = self - .window - .rendered_frame - .mouse_listeners - .remove(&event.type_id()) - { - // Because handlers may add other handlers, we sort every time. - handlers.sort_by(|(a, _, _), (b, _, _)| a.cmp(b)); + self.window.mouse_hit_test = self.window.rendered_frame.hit_test(self.mouse_position()); + let mut mouse_listeners = mem::take(&mut self.window.rendered_frame.mouse_listeners); + self.with_element_context(|cx| { // Capture phase, events bubble from back to front. Handlers for this phase are used for // special purposes, such as detecting events outside of a given Bounds. - for (_, _, handler) in &mut handlers { - self.with_element_context(|cx| { - handler(event, DispatchPhase::Capture, cx); - }); - if !self.app.propagate_event { + for listener in &mut mouse_listeners { + let listener = listener.as_mut().unwrap(); + listener(event, DispatchPhase::Capture, cx); + if !cx.app.propagate_event { break; } } // Bubble phase, where most normal handlers do their work. - if self.app.propagate_event { - for (_, _, handler) in handlers.iter_mut().rev() { - self.with_element_context(|cx| { - handler(event, DispatchPhase::Bubble, cx); - }); - if !self.app.propagate_event { + if cx.app.propagate_event { + for listener in mouse_listeners.iter_mut().rev() { + let listener = listener.as_mut().unwrap(); + listener(event, DispatchPhase::Bubble, cx); + if !cx.app.propagate_event { break; } } } - - self.window - .rendered_frame - .mouse_listeners - .insert(event.type_id(), handlers); - } + }); + self.window.rendered_frame.mouse_listeners = mouse_listeners; if self.app.propagate_event && self.has_active_drag() { if event.is::() { @@ -1337,6 +1200,7 @@ impl<'a> WindowContext<'a> { }) .log_err(); })); + self.window.pending_input = Some(currently_pending); self.propagate_event = false; @@ -1645,12 +1509,11 @@ impl<'a> WindowContext<'a> { } pub(crate) fn parent_view_id(&self) -> EntityId { - *self - .window + self.window .next_frame - .view_stack - .last() - .expect("a view should always be on the stack while drawing") + .dispatch_tree + .parent_view_id() + .unwrap() } /// Register an action listener on the window for the next frame. The type of action @@ -2681,12 +2544,6 @@ impl Display for ElementId { } } -impl ElementId { - pub(crate) fn from_entity_id(entity_id: EntityId) -> Self { - ElementId::View(entity_id) - } -} - impl TryInto for ElementId { type Error = anyhow::Error; diff --git a/crates/gpui/src/window/element_cx.rs b/crates/gpui/src/window/element_cx.rs index 46b5a21cf3..220e27cf88 100644 --- a/crates/gpui/src/window/element_cx.rs +++ b/crates/gpui/src/window/element_cx.rs @@ -16,6 +16,7 @@ use std::{ any::{Any, TypeId}, borrow::{Borrow, BorrowMut, Cow}, mem, + ops::Range, rc::Rc, sync::Arc, }; @@ -26,80 +27,124 @@ use derive_more::{Deref, DerefMut}; #[cfg(target_os = "macos")] use media::core_video::CVImageBuffer; use smallvec::SmallVec; -use util::post_inc; use crate::{ - prelude::*, size, AnyTooltip, AppContext, AvailableSpace, Bounds, BoxShadow, ContentMask, - Corners, CursorStyle, DevicePixels, DispatchPhase, DispatchTree, ElementId, ElementStateBox, - EntityId, FocusHandle, FocusId, FontId, GlobalElementId, GlyphId, Hsla, ImageData, - InputHandler, IsZero, KeyContext, KeyEvent, LayoutId, MonochromeSprite, MouseEvent, PaintQuad, - Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad, RenderGlyphParams, - RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size, StackingContext, - StackingOrder, StrikethroughStyle, Style, TextStyleRefinement, Underline, UnderlineStyle, - Window, WindowContext, SUBPIXEL_VARIANTS, + prelude::*, size, AnyElement, AnyTooltip, AppContext, AvailableSpace, Bounds, BoxShadow, + ContentMask, Corners, CursorStyle, DevicePixels, DispatchNodeId, DispatchPhase, DispatchTree, + ElementId, ElementStateBox, EntityId, FocusHandle, FocusId, FontId, GlobalElementId, GlyphId, + Hsla, ImageData, InputHandler, IsZero, KeyContext, KeyEvent, LayoutId, MonochromeSprite, + MouseEvent, PaintQuad, Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad, + RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size, + StrikethroughStyle, Style, TextStyleRefinement, Underline, UnderlineStyle, Window, + WindowContext, SUBPIXEL_VARIANTS, }; -type AnyMouseListener = Box; +pub(crate) type AnyMouseListener = + Box; pub(crate) struct RequestedInputHandler { pub(crate) view_id: EntityId, pub(crate) handler: Option, } -pub(crate) struct TooltipRequest { - pub(crate) view_id: EntityId, - pub(crate) tooltip: AnyTooltip, -} - #[derive(Clone)] pub(crate) struct CursorStyleRequest { + pub(crate) hitbox_id: HitboxId, pub(crate) style: CursorStyle, - stacking_order: StackingOrder, +} + +/// An identifier for a [Hitbox]. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub struct HitboxId(usize); + +impl HitboxId { + /// Checks if the hitbox with this id is currently hovered. + pub fn is_hovered(&self, cx: &WindowContext) -> bool { + cx.window.mouse_hit_test.0.contains(self) + } +} + +/// A rectangular region that potentially blocks hitboxes inserted prior. +/// See [ElementContext::insert_hitbox] for more details. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Hitbox { + /// A unique identifier for the hitbox + pub id: HitboxId, + /// The bounds of the hitbox + pub bounds: Bounds, + /// Whether the hitbox occludes other hitboxes inserted prior. + pub opaque: bool, +} + +impl Hitbox { + /// Checks if the hitbox is currently hovered. + pub fn is_hovered(&self, cx: &WindowContext) -> bool { + self.id.is_hovered(cx) + } +} + +#[derive(Default)] +pub(crate) struct HitTest(SmallVec<[HitboxId; 8]>); + +pub(crate) struct DeferredDraw { + priority: usize, + element: Option, + absolute_offset: Point, + layout_range: Range, + paint_range: Range, } pub(crate) struct Frame { pub(crate) focus: Option, pub(crate) window_active: bool, - pub(crate) element_states: FxHashMap, - pub(crate) mouse_listeners: FxHashMap>, + pub(crate) element_states: FxHashMap<(GlobalElementId, TypeId), ElementStateBox>, + pub(crate) mouse_listeners: Vec>, pub(crate) dispatch_tree: DispatchTree, pub(crate) scene: Scene, - pub(crate) depth_map: Vec<(StackingOrder, EntityId, Bounds)>, - pub(crate) z_index_stack: StackingOrder, - pub(crate) next_stacking_order_ids: Vec, - pub(crate) next_root_z_index: u16, + pub(crate) hitboxes: Vec, + pub(crate) deferred_draws: Vec, pub(crate) content_mask_stack: Vec>, pub(crate) element_offset_stack: Vec>, pub(crate) requested_input_handler: Option, - pub(crate) tooltip_request: Option, - pub(crate) cursor_styles: FxHashMap, - pub(crate) requested_cursor_style: Option, + pub(crate) tooltip_requests: Vec>, + pub(crate) cursor_styles: Vec, pub(crate) view_stack: Vec, pub(crate) reused_views: FxHashSet, - #[cfg(any(test, feature = "test-support"))] pub(crate) debug_bounds: FxHashMap>, } +#[derive(Clone, Default)] +pub(crate) struct AfterLayoutIndex { + hitboxes_index: usize, + tooltips_index: usize, + deferred_draws_index: usize, +} + +#[derive(Clone, Default, Debug)] +pub(crate) struct PaintIndex { + scene_index: usize, + mouse_listeners_index: usize, + cursor_styles_index: usize, + dispatch_tree_index: usize, +} + impl Frame { pub(crate) fn new(dispatch_tree: DispatchTree) -> Self { Frame { focus: None, window_active: false, element_states: FxHashMap::default(), - mouse_listeners: FxHashMap::default(), + mouse_listeners: Vec::new(), dispatch_tree, scene: Scene::default(), - depth_map: Vec::new(), - z_index_stack: StackingOrder::default(), - next_stacking_order_ids: vec![0], - next_root_z_index: 0, + hitboxes: Vec::new(), + deferred_draws: Vec::new(), content_mask_stack: Vec::new(), element_offset_stack: Vec::new(), requested_input_handler: None, - tooltip_request: None, - cursor_styles: FxHashMap::default(), - requested_cursor_style: None, + tooltip_requests: Vec::new(), + cursor_styles: Vec::new(), view_stack: Vec::new(), reused_views: FxHashSet::default(), @@ -110,20 +155,48 @@ impl Frame { pub(crate) fn clear(&mut self) { self.element_states.clear(); - self.mouse_listeners.values_mut().for_each(Vec::clear); + self.mouse_listeners.clear(); self.dispatch_tree.clear(); - self.depth_map.clear(); - self.next_stacking_order_ids = vec![0]; - self.next_root_z_index = 0; self.reused_views.clear(); self.scene.clear(); self.requested_input_handler.take(); - self.tooltip_request.take(); + self.tooltip_requests.clear(); self.cursor_styles.clear(); - self.requested_cursor_style.take(); + self.hitboxes.clear(); + self.deferred_draws.clear(); debug_assert_eq!(self.view_stack.len(), 0); } + pub(crate) fn after_layout_index(&self) -> AfterLayoutIndex { + AfterLayoutIndex { + hitboxes_index: self.hitboxes.len(), + tooltips_index: self.tooltip_requests.len(), + deferred_draws_index: self.deferred_draws.len(), + } + } + + pub(crate) fn paint_index(&self) -> PaintIndex { + PaintIndex { + scene_index: self.scene.len(), + mouse_listeners_index: self.mouse_listeners.len(), + cursor_styles_index: self.cursor_styles.len(), + dispatch_tree_index: self.dispatch_tree.len(), + } + } + + pub(crate) fn hit_test(&self, position: Point) -> HitTest { + let mut hit_test = HitTest::default(); + for hitbox in self.hitboxes.iter().rev() { + if hitbox.bounds.contains(&position) { + hit_test.0.push(hitbox.id); + if hitbox.opaque { + break; + } + } + } + hit_test + } + pub(crate) fn focus_path(&self) -> SmallVec<[FocusId; 8]> { self.focus .map(|focus_id| self.dispatch_tree.focus_path(focus_id)) @@ -131,28 +204,6 @@ impl Frame { } pub(crate) fn finish(&mut self, prev_frame: &mut Self) { - // Reuse mouse listeners that didn't change since the last frame. - for (type_id, listeners) in &mut prev_frame.mouse_listeners { - let next_listeners = self.mouse_listeners.entry(*type_id).or_default(); - for (order, view_id, listener) in listeners.drain(..) { - if self.reused_views.contains(&view_id) { - next_listeners.push((order, view_id, listener)); - } - } - } - - // Reuse entries in the depth map that didn't change since the last frame. - for (order, view_id, bounds) in prev_frame.depth_map.drain(..) { - if self.reused_views.contains(&view_id) { - match self - .depth_map - .binary_search_by(|(level, _, _)| order.cmp(level)) - { - Ok(i) | Err(i) => self.depth_map.insert(i, (order, view_id, bounds)), - } - } - } - // Retain element states for views that didn't change since the last frame. for (element_id, state) in prev_frame.element_states.drain() { if self.reused_views.contains(&state.parent_view_id) { @@ -160,9 +211,6 @@ impl Frame { } } - // Reuse geometry that didn't change since the last frame. - self.scene - .reuse_views(&self.reused_views, &mut prev_frame.scene); self.scene.finish(); } } @@ -316,15 +364,136 @@ impl<'a> VisualContext for ElementContext<'a> { } impl<'a> ElementContext<'a> { - pub(crate) fn reuse_view(&mut self, next_stacking_order_id: u16) { - let view_id = self.parent_view_id(); - let grafted_view_ids = self - .cx - .window - .next_frame - .dispatch_tree - .reuse_view(view_id, &mut self.cx.window.rendered_frame.dispatch_tree); - for view_id in grafted_view_ids { + pub(crate) fn draw_roots(&mut self) { + // Measure and layout all root elements. + let mut root_element = self.window.root_view.as_ref().unwrap().clone().into_any(); + let available_space = self.window.viewport_size.map(Into::into); + root_element.layout(Point::default(), available_space, self); + + let mut active_drag_element = None; + let mut tooltip_element = None; + if let Some(active_drag) = self.app.active_drag.take() { + let mut element = active_drag.view.clone().into_any(); + let offset = self.mouse_position() - active_drag.cursor_offset; + element.layout(offset, AvailableSpace::min_size(), self); + active_drag_element = Some(element); + self.app.active_drag = Some(active_drag); + } else if let Some(tooltip_request) = + self.window.next_frame.tooltip_requests.last().cloned() + { + let tooltip_request = tooltip_request.unwrap(); + let mut element = tooltip_request.view.clone().into_any(); + let offset = tooltip_request.cursor_offset; + element.layout(offset, AvailableSpace::min_size(), self); + tooltip_element = Some(element); + } + + let mut sorted_deferred_draws = + (0..self.window.next_frame.deferred_draws.len()).collect::>(); + sorted_deferred_draws + .sort_unstable_by_key(|ix| self.window.next_frame.deferred_draws[*ix].priority); + self.layout_deferred_draws(&sorted_deferred_draws); + + self.window.mouse_hit_test = self.window.next_frame.hit_test(self.window.mouse_position); + + // Now actually paint the elements. + root_element.paint(self); + + if let Some(mut drag_element) = active_drag_element { + drag_element.paint(self); + } else if let Some(mut tooltip_element) = tooltip_element { + tooltip_element.paint(self); + } + + self.paint_deferred_draws(&sorted_deferred_draws); + } + + fn layout_deferred_draws(&mut self, deferred_draw_indices: &[usize]) { + let mut deferred_draws = mem::take(&mut self.window.next_frame.deferred_draws); + for deferred_draw_ix in deferred_draw_indices { + let deferred_draw = &mut deferred_draws[*deferred_draw_ix]; + if let Some(element) = deferred_draw.element.as_mut() { + deferred_draw.layout_range.start = self.window.next_frame.after_layout_index(); + self.with_absolute_element_offset(deferred_draw.absolute_offset, |cx| { + element.after_layout(cx) + }); + deferred_draw.layout_range.end = self.window.next_frame.after_layout_index(); + } else { + self.reuse_after_layout(deferred_draw.layout_range.clone()); + } + } + self.window.next_frame.deferred_draws = deferred_draws; + } + + fn paint_deferred_draws(&mut self, deferred_draw_indices: &[usize]) { + let mut deferred_draws = mem::take(&mut self.window.next_frame.deferred_draws); + for deferred_draw_ix in deferred_draw_indices { + let mut deferred_draw = &mut deferred_draws[*deferred_draw_ix]; + if let Some(element) = deferred_draw.element.as_mut() { + deferred_draw.paint_range.start = self.window.next_frame.paint_index(); + element.paint(self); + deferred_draw.paint_range.end = self.window.next_frame.paint_index(); + } else { + self.reuse_paint(deferred_draw.paint_range.clone()); + } + } + self.window.next_frame.deferred_draws = deferred_draws; + } + + pub(crate) fn reuse_after_layout(&mut self, range: Range) { + let window = &mut self.window; + window.next_frame.hitboxes.extend( + window.rendered_frame.hitboxes[range.start.hitboxes_index..range.end.hitboxes_index] + .iter() + .cloned(), + ); + window.next_frame.tooltip_requests.extend( + window.rendered_frame.tooltip_requests + [range.start.tooltips_index..range.end.tooltips_index] + .iter_mut() + .map(|request| request.take()), + ); + window.next_frame.deferred_draws.extend( + window.rendered_frame.deferred_draws + [range.start.deferred_draws_index..range.end.deferred_draws_index] + .iter() + .map(|deferred_draw| DeferredDraw { + priority: deferred_draw.priority, + element: None, + absolute_offset: deferred_draw.absolute_offset, + layout_range: deferred_draw.layout_range.clone(), + paint_range: deferred_draw.paint_range.clone(), + }), + ); + } + + pub(crate) fn reuse_paint(&mut self, range: Range) { + let parent_view_id = self.parent_view_id(); + let window = &mut self.cx.window; + + window.next_frame.cursor_styles.extend( + window.rendered_frame.cursor_styles + [range.start.cursor_styles_index..range.end.cursor_styles_index] + .iter() + .cloned(), + ); + window.next_frame.mouse_listeners.extend( + window.rendered_frame.mouse_listeners + [range.start.mouse_listeners_index..range.end.mouse_listeners_index] + .iter_mut() + .map(|listener| listener.take()), + ); + for primitive in + &window.rendered_frame.scene.primitives[range.start.scene_index..range.end.scene_index] + { + window.next_frame.scene.push(primitive.clone()); + } + + let grafted_view_ids = window.next_frame.dispatch_tree.reuse_subtree( + range.start.dispatch_tree_index..range.end.dispatch_tree_index, + &mut window.rendered_frame.dispatch_tree, + ); + for view_id in [parent_view_id].into_iter().chain(grafted_view_ids) { assert!(self.window.next_frame.reused_views.insert(view_id)); // Reuse the previous input handler requested during painting of the reused view. @@ -338,46 +507,7 @@ impl<'a> ElementContext<'a> { self.window.next_frame.requested_input_handler = self.window.rendered_frame.requested_input_handler.take(); } - - // Reuse the tooltip previously requested during painting of the reused view. - if self - .window - .rendered_frame - .tooltip_request - .as_ref() - .map_or(false, |requested| requested.view_id == view_id) - { - self.window.next_frame.tooltip_request = - self.window.rendered_frame.tooltip_request.take(); - } - - // Reuse the cursor styles previously requested during painting of the reused view. - if let Some(cursor_style_request) = - self.window.rendered_frame.cursor_styles.remove(&view_id) - { - self.set_cursor_style( - cursor_style_request.style, - cursor_style_request.stacking_order, - ); - } } - - debug_assert!( - next_stacking_order_id - >= self - .window - .next_frame - .next_stacking_order_ids - .last() - .copied() - .unwrap() - ); - *self - .window - .next_frame - .next_stacking_order_ids - .last_mut() - .unwrap() = next_stacking_order_id; } /// Push a text style onto the stack, and call a function with that style active. @@ -397,33 +527,19 @@ impl<'a> ElementContext<'a> { } /// Updates the cursor style at the platform level. - pub fn set_cursor_style(&mut self, style: CursorStyle, stacking_order: StackingOrder) { - let view_id = self.parent_view_id(); - let style_request = CursorStyleRequest { - style, - stacking_order, - }; - if self - .window - .next_frame - .requested_cursor_style - .as_ref() - .map_or(true, |prev_style_request| { - style_request.stacking_order >= prev_style_request.stacking_order - }) - { - self.window.next_frame.requested_cursor_style = Some(style_request.clone()); - } + pub fn set_cursor_style(&mut self, style: CursorStyle, hitbox: &Hitbox) { self.window .next_frame .cursor_styles - .insert(view_id, style_request); + .push(CursorStyleRequest { + hitbox_id: hitbox.id, + style, + }); } /// Sets a tooltip to be rendered for the upcoming frame pub fn set_tooltip(&mut self, tooltip: AnyTooltip) { - let view_id = self.parent_view_id(); - self.window.next_frame.tooltip_request = Some(TooltipRequest { view_id, tooltip }); + self.window.next_frame.tooltip_requests.push(Some(tooltip)); } /// Pushes the given element id onto the global stack and invokes the given closure @@ -465,65 +581,6 @@ impl<'a> ElementContext<'a> { } } - /// Invoke the given function with the content mask reset to that - /// of the window. - pub fn break_content_mask(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { - let mask = ContentMask { - bounds: Bounds { - origin: Point::default(), - size: self.window().viewport_size, - }, - }; - - let new_root_z_index = post_inc(&mut self.window_mut().next_frame.next_root_z_index); - let new_stacking_order_id = post_inc( - self.window_mut() - .next_frame - .next_stacking_order_ids - .last_mut() - .unwrap(), - ); - let new_context = StackingContext { - z_index: new_root_z_index, - id: new_stacking_order_id, - }; - - let old_stacking_order = mem::take(&mut self.window_mut().next_frame.z_index_stack); - - self.window_mut().next_frame.z_index_stack.push(new_context); - self.window_mut().next_frame.content_mask_stack.push(mask); - let result = f(self); - self.window_mut().next_frame.content_mask_stack.pop(); - self.window_mut().next_frame.z_index_stack = old_stacking_order; - - result - } - - /// Called during painting to invoke the given closure in a new stacking context. The given - /// z-index is interpreted relative to the previous call to `stack`. - pub fn with_z_index(&mut self, z_index: u16, f: impl FnOnce(&mut Self) -> R) -> R { - let new_stacking_order_id = post_inc( - self.window_mut() - .next_frame - .next_stacking_order_ids - .last_mut() - .unwrap(), - ); - self.window_mut().next_frame.next_stacking_order_ids.push(0); - let new_context = StackingContext { - z_index, - id: new_stacking_order_id, - }; - - self.window_mut().next_frame.z_index_stack.push(new_context); - let result = f(self); - self.window_mut().next_frame.z_index_stack.pop(); - - self.window_mut().next_frame.next_stacking_order_ids.pop(); - - result - } - /// Updates the global element offset relative to the current offset. This is used to implement /// scrolling. pub fn with_element_offset( @@ -592,25 +649,32 @@ impl<'a> ElementContext<'a> { /// when drawing the next frame. pub fn with_element_state( &mut self, - id: ElementId, - f: impl FnOnce(Option, &mut Self) -> (R, S), + element_id: Option, + f: impl FnOnce(Option>, &mut Self) -> (R, Option), ) -> R where S: 'static, { - self.with_element_id(Some(id), |cx| { + let id_is_none = element_id.is_none(); + self.with_element_id(element_id, |cx| { + if id_is_none { + let (result, state) = f(None, cx); + debug_assert!(state.is_none(), "you must not return an element state when passing None for the element id"); + result + } else { let global_id = cx.window().element_id_stack.clone(); + let key = (global_id, TypeId::of::()); if let Some(any) = cx .window_mut() .next_frame .element_states - .remove(&global_id) + .remove(&key) .or_else(|| { cx.window_mut() .rendered_frame .element_states - .remove(&global_id) + .remove(&key) }) { let ElementStateBox { @@ -646,13 +710,13 @@ impl<'a> ElementContext<'a> { // Requested: () <- AnyElement let state = state_box .take() - .expect("element state is already on the stack"); - let (result, state) = f(Some(state), cx); - state_box.replace(state); + .expect("reentrant call to with_element_state for the same state type and element id"); + let (result, state) = f(Some(Some(state)), cx); + state_box.replace(state.expect("you must return ")); cx.window_mut() .next_frame .element_states - .insert(global_id, ElementStateBox { + .insert(key, ElementStateBox { inner: state_box, parent_view_id, #[cfg(debug_assertions)] @@ -660,14 +724,14 @@ impl<'a> ElementContext<'a> { }); result } else { - let (result, state) = f(None, cx); + let (result, state) = f(Some(None), cx); let parent_view_id = cx.parent_view_id(); cx.window_mut() .next_frame .element_states - .insert(global_id, + .insert(key, ElementStateBox { - inner: Box::new(Some(state)), + inner: Box::new(Some(state.expect("you must return Some when you pass some element id"))), parent_view_id, #[cfg(debug_assertions)] type_name: std::any::type_name::() @@ -676,8 +740,28 @@ impl<'a> ElementContext<'a> { ); result } - }) + } + }) } + + /// Defers the drawing of the given element, scheduling it to be painted on top of the currently-drawn tree + /// at a later time. The `priority` parameter determines the drawing order relative to other deferred elements, + /// with higher values being drawn on top. + pub fn defer_draw( + &mut self, + element: AnyElement, + absolute_offset: Point, + priority: usize, + ) { + self.window.next_frame.deferred_draws.push(DeferredDraw { + priority, + element: Some(element), + absolute_offset, + layout_range: AfterLayoutIndex::default()..AfterLayoutIndex::default(), + paint_range: PaintIndex::default()..PaintIndex::default(), + }); + } + /// Paint one or more drop shadows into the scene for the next frame at the current z-index. pub fn paint_shadows( &mut self, @@ -687,26 +771,19 @@ impl<'a> ElementContext<'a> { ) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); - let view_id = self.parent_view_id(); - let window = &mut *self.window; for shadow in shadows { let mut shadow_bounds = bounds; shadow_bounds.origin += shadow.offset; shadow_bounds.dilate(shadow.spread_radius); - window.next_frame.scene.insert( - &window.next_frame.z_index_stack, - Shadow { - view_id: view_id.into(), - layer_id: 0, - order: 0, - bounds: shadow_bounds.scale(scale_factor), - content_mask: content_mask.scale(scale_factor), - corner_radii: corner_radii.scale(scale_factor), - color: shadow.color, - blur_radius: shadow.blur_radius.scale(scale_factor), - pad: 0, - }, - ); + self.window.next_frame.scene.push(Shadow { + order: 0, + bounds: shadow_bounds.scale(scale_factor), + content_mask: content_mask.scale(scale_factor), + corner_radii: corner_radii.scale(scale_factor), + color: shadow.color, + blur_radius: shadow.blur_radius.scale(scale_factor), + pad: 0, + }); } } @@ -716,39 +793,24 @@ impl<'a> ElementContext<'a> { pub fn paint_quad(&mut self, quad: PaintQuad) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); - let view_id = self.parent_view_id(); - - let window = &mut *self.window; - window.next_frame.scene.insert( - &window.next_frame.z_index_stack, - Quad { - view_id: view_id.into(), - layer_id: 0, - order: 0, - bounds: quad.bounds.scale(scale_factor), - content_mask: content_mask.scale(scale_factor), - background: quad.background, - border_color: quad.border_color, - corner_radii: quad.corner_radii.scale(scale_factor), - border_widths: quad.border_widths.scale(scale_factor), - }, - ); + self.window.next_frame.scene.push(Quad { + order: 0, + bounds: quad.bounds.scale(scale_factor), + content_mask: content_mask.scale(scale_factor), + background: quad.background, + border_color: quad.border_color, + corner_radii: quad.corner_radii.scale(scale_factor), + border_widths: quad.border_widths.scale(scale_factor), + }); } /// Paint the given `Path` into the scene for the next frame at the current z-index. pub fn paint_path(&mut self, mut path: Path, color: impl Into) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); - let view_id = self.parent_view_id(); - path.content_mask = content_mask; path.color = color.into(); - path.view_id = view_id.into(); - let window = &mut *self.window; - window - .next_frame - .scene - .insert(&window.next_frame.z_index_stack, path.scale(scale_factor)); + self.window.next_frame.scene.push(path.scale(scale_factor)); } /// Paint an underline into the scene for the next frame at the current z-index. @@ -769,22 +831,15 @@ impl<'a> ElementContext<'a> { size: size(width, height), }; let content_mask = self.content_mask(); - let view_id = self.parent_view_id(); - let window = &mut *self.window; - window.next_frame.scene.insert( - &window.next_frame.z_index_stack, - Underline { - view_id: view_id.into(), - layer_id: 0, - order: 0, - bounds: bounds.scale(scale_factor), - content_mask: content_mask.scale(scale_factor), - color: style.color.unwrap_or_default(), - thickness: style.thickness.scale(scale_factor), - wavy: style.wavy, - }, - ); + self.window.next_frame.scene.push(Underline { + order: 0, + bounds: bounds.scale(scale_factor), + content_mask: content_mask.scale(scale_factor), + color: style.color.unwrap_or_default(), + thickness: style.thickness.scale(scale_factor), + wavy: style.wavy, + }); } /// Paint a strikethrough into the scene for the next frame at the current z-index. @@ -801,22 +856,15 @@ impl<'a> ElementContext<'a> { size: size(width, height), }; let content_mask = self.content_mask(); - let view_id = self.parent_view_id(); - let window = &mut *self.window; - window.next_frame.scene.insert( - &window.next_frame.z_index_stack, - Underline { - view_id: view_id.into(), - layer_id: 0, - order: 0, - bounds: bounds.scale(scale_factor), - content_mask: content_mask.scale(scale_factor), - thickness: style.thickness.scale(scale_factor), - color: style.color.unwrap_or_default(), - wavy: false, - }, - ); + self.window.next_frame.scene.push(Underline { + order: 0, + bounds: bounds.scale(scale_factor), + content_mask: content_mask.scale(scale_factor), + thickness: style.thickness.scale(scale_factor), + color: style.color.unwrap_or_default(), + wavy: false, + }); } /// Paints a monochrome (non-emoji) glyph into the scene for the next frame at the current z-index. @@ -862,20 +910,13 @@ impl<'a> ElementContext<'a> { size: tile.bounds.size.map(Into::into), }; let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.parent_view_id(); - let window = &mut *self.window; - window.next_frame.scene.insert( - &window.next_frame.z_index_stack, - MonochromeSprite { - view_id: view_id.into(), - layer_id: 0, - order: 0, - bounds, - content_mask, - color, - tile, - }, - ); + self.window.next_frame.scene.push(MonochromeSprite { + order: 0, + bounds, + content_mask, + color, + tile, + }); } Ok(()) } @@ -919,23 +960,16 @@ impl<'a> ElementContext<'a> { size: tile.bounds.size.map(Into::into), }; let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.parent_view_id(); - let window = &mut *self.window; - window.next_frame.scene.insert( - &window.next_frame.z_index_stack, - PolychromeSprite { - view_id: view_id.into(), - layer_id: 0, - order: 0, - bounds, - corner_radii: Default::default(), - content_mask, - tile, - grayscale: false, - pad: 0, - }, - ); + self.window.next_frame.scene.push(PolychromeSprite { + order: 0, + bounds, + corner_radii: Default::default(), + content_mask, + tile, + grayscale: false, + pad: 0, + }); } Ok(()) } @@ -965,21 +999,14 @@ impl<'a> ElementContext<'a> { Ok((params.size, Cow::Owned(bytes))) })?; let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.parent_view_id(); - let window = &mut *self.window; - window.next_frame.scene.insert( - &window.next_frame.z_index_stack, - MonochromeSprite { - view_id: view_id.into(), - layer_id: 0, - order: 0, - bounds, - content_mask, - color, - tile, - }, - ); + self.window.next_frame.scene.push(MonochromeSprite { + order: 0, + bounds, + content_mask, + color, + tile, + }); Ok(()) } @@ -1004,23 +1031,16 @@ impl<'a> ElementContext<'a> { })?; let content_mask = self.content_mask().scale(scale_factor); let corner_radii = corner_radii.scale(scale_factor); - let view_id = self.parent_view_id(); - let window = &mut *self.window; - window.next_frame.scene.insert( - &window.next_frame.z_index_stack, - PolychromeSprite { - view_id: view_id.into(), - layer_id: 0, - order: 0, - bounds, - content_mask, - corner_radii, - tile, - grayscale, - pad: 0, - }, - ); + self.window.next_frame.scene.push(PolychromeSprite { + order: 0, + bounds, + content_mask, + corner_radii, + tile, + grayscale, + pad: 0, + }); Ok(()) } @@ -1030,19 +1050,12 @@ impl<'a> ElementContext<'a> { let scale_factor = self.scale_factor(); let bounds = bounds.scale(scale_factor); let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.parent_view_id(); - let window = &mut *self.window; - window.next_frame.scene.insert( - &window.next_frame.z_index_stack, - crate::Surface { - view_id: view_id.into(), - layer_id: 0, - order: 0, - bounds, - content_mask, - image_buffer, - }, - ); + self.window.next_frame.scene.push(crate::Surface { + order: 0, + bounds, + content_mask, + image_buffer, + }); } #[must_use] @@ -1063,7 +1076,7 @@ impl<'a> ElementContext<'a> { .layout_engine .as_mut() .unwrap() - .request_layout(style, rem_size, &self.cx.app.layout_id_buffer) + .before_layout(style, rem_size, &self.cx.app.layout_id_buffer) } /// Add a node to the layout tree for the current frame. Instead of taking a `Style` and children, @@ -1119,73 +1132,69 @@ impl<'a> ElementContext<'a> { .requested_style(layout_id) } - /// Called during painting to track which z-index is on top at each pixel position - pub fn add_opaque_layer(&mut self, bounds: Bounds) { - let stacking_order = self.window.next_frame.z_index_stack.clone(); - let view_id = self.parent_view_id(); - let depth_map = &mut self.window.next_frame.depth_map; - match depth_map.binary_search_by(|(level, _, _)| stacking_order.cmp(level)) { - Ok(i) | Err(i) => depth_map.insert(i, (stacking_order, view_id, bounds)), - } + /// This method should be called during `after_layout`. You can use + /// the returned [Hitbox] during `paint` or in an event handler + /// to determine whether the inserted hitbox was the topmost. + pub fn insert_hitbox(&mut self, bounds: Bounds, opaque: bool) -> Hitbox { + let content_mask = self.content_mask(); + let window = &mut self.window; + let id = window.next_hitbox_id; + window.next_hitbox_id.0 += 1; + let hitbox = Hitbox { + id, + bounds: bounds.intersect(&content_mask.bounds), + opaque, + }; + window.next_frame.hitboxes.push(hitbox.clone()); + hitbox } - /// Invoke the given function with the given focus handle present on the key dispatch stack. - /// If you want an element to participate in key dispatch, use this method to push its key context and focus handle into the stack during paint. - pub fn with_key_dispatch( - &mut self, - context: Option, - focus_handle: Option, - f: impl FnOnce(Option, &mut Self) -> R, - ) -> R { - let window = &mut self.window; - let focus_id = focus_handle.as_ref().map(|handle| handle.id); - window + /// Sets the key context for the current element. This context will be used to translate + /// keybindings into actions. + pub fn set_key_context(&mut self, context: KeyContext) { + self.window .next_frame .dispatch_tree - .push_node(context.clone(), focus_id, None); + .set_key_context(context); + } - let result = f(focus_handle, self); + /// Sets the focus handle for the current element. This handle will be used to manage focus state + /// and keyboard event dispatch for the element. + pub fn set_focus_handle(&mut self, focus_handle: &FocusHandle) { + self.window + .next_frame + .dispatch_tree + .set_focus_id(focus_handle.id); + } + /// Sets the view id for the current element, which will be used to manage view caching. + pub fn set_view_id(&mut self, view_id: EntityId) { + self.window.next_frame.dispatch_tree.set_view_id(view_id); + self.window.text_system.set_parent_view_id(Some(view_id)); + } + + pub(crate) fn layout_element( + &mut self, + f: impl FnOnce(DispatchNodeId, &mut Self) -> R, + ) -> R { + let node_id = self.window.next_frame.dispatch_tree.push_node(); + let result = f(node_id, self); self.window.next_frame.dispatch_tree.pop_node(); - + let parent_view_id = self.window.next_frame.dispatch_tree.parent_view_id(); + self.window.text_system.set_parent_view_id(parent_view_id); result } - /// Invoke the given function with the given view id present on the view stack. - /// This is a fairly low-level method used to layout views. - pub fn with_view_id(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { - let text_system = self.text_system().clone(); - text_system.with_view(view_id, || { - if self.window.next_frame.view_stack.last() == Some(&view_id) { - f(self) - } else { - self.window.next_frame.view_stack.push(view_id); - let result = f(self); - self.window.next_frame.view_stack.pop(); - result - } - }) - } - - /// Invoke the given function with the given view id present on the view stack. - /// This is a fairly low-level method used to paint views. - pub fn paint_view(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { - let text_system = self.text_system().clone(); - text_system.with_view(view_id, || { - if self.window.next_frame.view_stack.last() == Some(&view_id) { - f(self) - } else { - self.window.next_frame.view_stack.push(view_id); - self.window - .next_frame - .dispatch_tree - .push_node(None, None, Some(view_id)); - let result = f(self); - self.window.next_frame.dispatch_tree.pop_node(); - self.window.next_frame.view_stack.pop(); - result - } - }) + pub(crate) fn paint_element( + &mut self, + node_to_paint: DispatchNodeId, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + let current_node_id = self.window.next_frame.dispatch_tree.move_to_next_node(); + assert_eq!(current_node_id, node_to_paint); + let parent_view_id = self.window.next_frame.dispatch_tree.parent_view_id(); + self.window.text_system.set_parent_view_id(parent_view_id); + f(self) } /// Sets an input handler, such as [`ElementInputHandler`][element_input_handler], which interfaces with the @@ -1214,22 +1223,13 @@ impl<'a> ElementContext<'a> { &mut self, mut handler: impl FnMut(&Event, DispatchPhase, &mut ElementContext) + 'static, ) { - let view_id = self.parent_view_id(); - let order = self.window.next_frame.z_index_stack.clone(); - self.window - .next_frame - .mouse_listeners - .entry(TypeId::of::()) - .or_default() - .push(( - order, - view_id, - Box::new( - move |event: &dyn Any, phase: DispatchPhase, cx: &mut ElementContext<'_>| { - handler(event.downcast_ref().unwrap(), phase, cx) - }, - ), - )) + self.window.next_frame.mouse_listeners.push(Some(Box::new( + move |event: &dyn Any, phase: DispatchPhase, cx: &mut ElementContext<'_>| { + if let Some(event) = event.downcast_ref() { + handler(event, phase, cx) + } + }, + ))); } /// Register a key event listener on the window for the next frame. The type of event diff --git a/crates/gpui_macros/src/derive_into_element.rs b/crates/gpui_macros/src/derive_into_element.rs index c3015d8c09..e430c1dfaf 100644 --- a/crates/gpui_macros/src/derive_into_element.rs +++ b/crates/gpui_macros/src/derive_into_element.rs @@ -13,10 +13,6 @@ pub fn derive_into_element(input: TokenStream) -> TokenStream { { type Element = gpui::Component; - fn element_id(&self) -> Option { - None - } - fn into_element(self) -> Self::Element { gpui::Component::new(self) } diff --git a/crates/install_cli/src/install_cli.rs b/crates/install_cli/src/install_cli.rs index 52a6656b90..61c5aa2fb9 100644 --- a/crates/install_cli/src/install_cli.rs +++ b/crates/install_cli/src/install_cli.rs @@ -18,7 +18,7 @@ pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> { // If the symlink is not there or is outdated, first try replacing it // without escalating. smol::fs::remove_file(link_path).await.log_err(); - // todo!("windows") + // todo("windows") #[cfg(not(windows))] { if smol::fs::unix::symlink(&cli_path, link_path) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 8723183789..7a8a55e95e 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -807,7 +807,7 @@ impl Render for LspLogToolbarItemView { .justify_between() .child(Label::new(RPC_MESSAGES)) .child( - div().z_index(120).child( + div().child( Checkbox::new( ix, if row.rpc_trace_enabled { diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index 082e77fc36..ac0da4610c 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -1,9 +1,9 @@ use editor::{scroll::Autoscroll, Anchor, Editor, ExcerptId}; use gpui::{ - actions, canvas, div, rems, uniform_list, AnyElement, AppContext, AvailableSpace, Div, - EventEmitter, FocusHandle, FocusableView, Hsla, InteractiveElement, IntoElement, Model, - MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Render, Styled, - UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext, + actions, canvas, div, rems, uniform_list, AnyElement, AppContext, Div, EventEmitter, + FocusHandle, FocusableView, Hsla, InteractiveElement, IntoElement, Model, MouseButton, + MouseDownEvent, MouseMoveEvent, ParentElement, Render, Styled, UniformListScrollHandle, View, + ViewContext, VisualContext, WeakView, WindowContext, }; use language::{Buffer, OwnedSyntaxLayer}; use std::{mem, ops::Range}; @@ -277,7 +277,7 @@ impl Render for SyntaxTreeView { .and_then(|buffer| buffer.active_layer.as_ref()) { let layer = layer.clone(); - let list = uniform_list( + let mut list = uniform_list( cx.view().clone(), "SyntaxTreeView", layer.node().descendant_count(), @@ -356,16 +356,16 @@ impl Render for SyntaxTreeView { ) .size_full() .track_scroll(self.list_scroll_handle.clone()) - .text_bg(cx.theme().colors().background); + .text_bg(cx.theme().colors().background).into_any_element(); rendered = rendered.child( - canvas(move |bounds, cx| { - list.into_any_element().draw( - bounds.origin, - bounds.size.map(AvailableSpace::Definite), - cx, - ) - }) + canvas( + move |bounds, cx| { + list.layout(bounds.origin, bounds.size.into(), cx); + list + }, + |_, mut list, cx| list.paint(cx), + ) .size_full(), ); } diff --git a/crates/languages/src/csharp.rs b/crates/languages/src/csharp.rs index fabd0a1952..149aa754c7 100644 --- a/crates/languages/src/csharp.rs +++ b/crates/languages/src/csharp.rs @@ -81,7 +81,7 @@ impl super::LspAdapter for OmniSharpAdapter { archive.unpack(container_dir).await?; } - // todo!("windows") + // todo("windows") #[cfg(not(windows))] { fs::set_permissions( diff --git a/crates/languages/src/elixir.rs b/crates/languages/src/elixir.rs index 3ebf05caad..a70543f9d3 100644 --- a/crates/languages/src/elixir.rs +++ b/crates/languages/src/elixir.rs @@ -358,7 +358,7 @@ impl LspAdapter for NextLspAdapter { } futures::io::copy(response.body_mut(), &mut file).await?; - // todo!("windows") + // todo("windows") #[cfg(not(windows))] { fs::set_permissions( diff --git a/crates/languages/src/lua.rs b/crates/languages/src/lua.rs index eeb9b7639d..9a3447e678 100644 --- a/crates/languages/src/lua.rs +++ b/crates/languages/src/lua.rs @@ -83,7 +83,7 @@ impl super::LspAdapter for LuaLspAdapter { archive.unpack(container_dir).await?; } - // todo!("windows") + // todo("windows") #[cfg(not(windows))] { fs::set_permissions( diff --git a/crates/languages/src/purescript.rs b/crates/languages/src/purescript.rs index 0aea798041..931809a1ed 100644 --- a/crates/languages/src/purescript.rs +++ b/crates/languages/src/purescript.rs @@ -26,7 +26,7 @@ pub struct PurescriptLspAdapter { } impl PurescriptLspAdapter { - // todo!(linux): remove + // todo(linux): remove #[cfg_attr(target_os = "linux", allow(dead_code))] pub fn new(node: Arc) -> Self { Self { node } diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index e375e5e831..51914ee3fa 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -74,7 +74,7 @@ impl LspAdapter for RustLspAdapter { let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); let mut file = File::create(&destination_path).await?; futures::io::copy(decompressed_bytes, &mut file).await?; - // todo!("windows") + // todo("windows") #[cfg(not(windows))] { fs::set_permissions( diff --git a/crates/languages/src/toml.rs b/crates/languages/src/toml.rs index f008cbf197..c8eda04800 100644 --- a/crates/languages/src/toml.rs +++ b/crates/languages/src/toml.rs @@ -72,7 +72,7 @@ impl LspAdapter for TaploLspAdapter { futures::io::copy(decompressed_bytes, &mut file).await?; - // todo!("windows") + // todo("windows") #[cfg(not(windows))] { fs::set_permissions( diff --git a/crates/languages/src/zig.rs b/crates/languages/src/zig.rs index 3c830fba4c..d60d829a6f 100644 --- a/crates/languages/src/zig.rs +++ b/crates/languages/src/zig.rs @@ -86,7 +86,7 @@ impl LspAdapter for ZlsAdapter { archive.unpack(container_dir).await?; } - // todo!("windows") + // todo("windows") #[cfg(not(windows))] { fs::set_permissions( diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 45f5ac1d1d..bfe36cece1 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -73,7 +73,7 @@ impl TestServer { } pub async fn create_room(&self, room: String) -> Result<()> { - //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; let mut server_rooms = self.rooms.lock(); @@ -87,7 +87,7 @@ impl TestServer { async fn delete_room(&self, room: String) -> Result<()> { // TODO: clear state associated with all `Room`s. - //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; let mut server_rooms = self.rooms.lock(); @@ -98,7 +98,7 @@ impl TestServer { } async fn join_room(&self, token: String, client_room: Arc) -> Result<()> { - //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; @@ -147,7 +147,7 @@ impl TestServer { } async fn leave_room(&self, token: String) -> Result<()> { - //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; let claims = live_kit_server::token::validate(&token, &self.secret_key)?; @@ -169,7 +169,7 @@ impl TestServer { async fn remove_participant(&self, room_name: String, identity: String) -> Result<()> { // TODO: clear state associated with the `Room`. - //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; @@ -193,7 +193,7 @@ impl TestServer { identity: String, permission: proto::ParticipantPermission, ) -> Result<()> { - //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; let mut server_rooms = self.rooms.lock(); @@ -205,7 +205,7 @@ impl TestServer { } pub async fn disconnect_client(&self, client_identity: String) { - //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; let mut server_rooms = self.rooms.lock(); @@ -221,7 +221,7 @@ impl TestServer { token: String, local_track: LocalVideoTrack, ) -> Result { - //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; let claims = live_kit_server::token::validate(&token, &self.secret_key)?; @@ -276,7 +276,7 @@ impl TestServer { token: String, _local_track: &LocalAudioTrack, ) -> Result { - //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; @@ -559,7 +559,7 @@ impl Room { pub fn display_sources(self: &Arc) -> impl Future>> { let this = self.clone(); async move { - //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged #[cfg(any(test, feature = "test-support"))] { let server = this.test_server(); diff --git a/crates/storybook/src/stories.rs b/crates/storybook/src/stories.rs index 8a49c4372a..7777af2aa3 100644 --- a/crates/storybook/src/stories.rs +++ b/crates/storybook/src/stories.rs @@ -7,7 +7,6 @@ mod picker; mod scroll; mod text; mod viewport_units; -mod z_index; pub use auto_height_editor::*; pub use cursor::*; @@ -18,4 +17,3 @@ pub use picker::*; pub use scroll::*; pub use text::*; pub use viewport_units::*; -pub use z_index::*; diff --git a/crates/storybook/src/stories/z_index.rs b/crates/storybook/src/stories/z_index.rs deleted file mode 100644 index e32e39a2d1..0000000000 --- a/crates/storybook/src/stories/z_index.rs +++ /dev/null @@ -1,172 +0,0 @@ -use gpui::{px, rgb, Div, IntoElement, Render, RenderOnce}; -use story::Story; -use ui::prelude::*; - -/// A reimplementation of the MDN `z-index` example, found here: -/// [https://developer.mozilla.org/en-US/docs/Web/CSS/z-index](https://developer.mozilla.org/en-US/docs/Web/CSS/z-index). -pub struct ZIndexStory; - -impl Render for ZIndexStory { - fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - Story::container().child(Story::title("z-index")).child( - div() - .flex() - .child( - div() - .w(px(250.)) - .child(Story::label("z-index: auto")) - .child(ZIndexExample::new(0)), - ) - .child( - div() - .w(px(250.)) - .child(Story::label("z-index: 1")) - .child(ZIndexExample::new(1)), - ) - .child( - div() - .w(px(250.)) - .child(Story::label("z-index: 3")) - .child(ZIndexExample::new(3)), - ) - .child( - div() - .w(px(250.)) - .child(Story::label("z-index: 5")) - .child(ZIndexExample::new(5)), - ) - .child( - div() - .w(px(250.)) - .child(Story::label("z-index: 7")) - .child(ZIndexExample::new(7)), - ), - ) - } -} - -trait Styles: Styled + Sized { - // Trailing `_` is so we don't collide with `block` style `StyleHelpers`. - fn block_(self) -> Self { - self.absolute() - .w(px(150.)) - .h(px(50.)) - .text_color(rgb(0x000000)) - } - - fn blue(self) -> Self { - self.bg(rgb(0xe5e8fc)) - .border_5() - .border_color(rgb(0x112382)) - .line_height(px(55.)) - // HACK: Simulate `text-align: center`. - .pl(px(24.)) - } - - fn red(self) -> Self { - self.bg(rgb(0xfce5e7)) - .border_5() - .border_color(rgb(0xe3a1a7)) - // HACK: Simulate `text-align: center`. - .pl(px(8.)) - } -} - -impl Styles for Div {} - -#[derive(IntoElement)] -struct ZIndexExample { - z_index: u16, -} - -impl RenderOnce for ZIndexExample { - fn render(self, _cx: &mut WindowContext) -> impl IntoElement { - div() - .relative() - .size_full() - // Example element. - .child( - div() - .absolute() - .top(px(15.)) - .left(px(15.)) - .w(px(180.)) - .h(px(230.)) - .bg(rgb(0xfcfbe5)) - .text_color(rgb(0x000000)) - .border_5() - .border_color(rgb(0xe3e0a1)) - .line_height(px(215.)) - // HACK: Simulate `text-align: center`. - .pl(px(24.)) - .z_index(self.z_index) - .child(SharedString::from(format!( - "z-index: {}", - if self.z_index == 0 { - "auto".to_string() - } else { - self.z_index.to_string() - } - ))), - ) - // Blue blocks. - .child( - div() - .blue() - .block_() - .top(px(0.)) - .left(px(0.)) - .z_index(6) - .child("z-index: 6"), - ) - .child( - div() - .blue() - .block_() - .top(px(30.)) - .left(px(30.)) - .z_index(4) - .child("z-index: 4"), - ) - .child( - div() - .blue() - .block_() - .top(px(60.)) - .left(px(60.)) - .z_index(2) - .child("z-index: 2"), - ) - // Red blocks. - .child( - div() - .red() - .block_() - .top(px(150.)) - .left(px(0.)) - .child("z-index: auto"), - ) - .child( - div() - .red() - .block_() - .top(px(180.)) - .left(px(30.)) - .child("z-index: auto"), - ) - .child( - div() - .red() - .block_() - .top(px(210.)) - .left(px(60.)) - .child("z-index: auto"), - ) - } -} - -impl ZIndexExample { - pub fn new(z_index: u16) -> Self { - Self { z_index } - } -} diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs index 120e60d34a..c8cda13018 100644 --- a/crates/storybook/src/story_selector.rs +++ b/crates/storybook/src/story_selector.rs @@ -35,7 +35,6 @@ pub enum ComponentStory { ToggleButton, Text, ViewportUnits, - ZIndex, Picker, } @@ -67,7 +66,6 @@ impl ComponentStory { Self::TabBar => cx.new_view(|_| ui::TabBarStory).into(), Self::ToggleButton => cx.new_view(|_| ui::ToggleButtonStory).into(), Self::ViewportUnits => cx.new_view(|_| crate::stories::ViewportUnitsStory).into(), - Self::ZIndex => cx.new_view(|_| ZIndexStory).into(), Self::Picker => PickerStory::new(cx).into(), } } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index c396bf6d1f..8dbf32a026 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -399,7 +399,7 @@ impl TerminalBuilder { #[cfg(unix)] let (fd, shell_pid) = (pty.file().as_raw_fd(), pty.child().id()); - // todo!("windows") + // todo("windows") #[cfg(windows)] let (fd, shell_pid) = (-1, 0); @@ -667,7 +667,7 @@ impl Terminal { fn update_process_info(&mut self) -> bool { #[cfg(unix)] let mut pid = unsafe { libc::tcgetpgrp(self.shell_fd as i32) }; - // todo!("windows") + // todo("windows") #[cfg(windows)] let mut pid = -1; if pid < 0 { diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index cd439fca1f..762ce3b1f5 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -1,11 +1,11 @@ -use editor::{Cursor, HighlightedRange, HighlightedRangeLine}; +use editor::{CursorLayout, HighlightedRange, HighlightedRangeLine}; use gpui::{ - div, fill, point, px, relative, AnyElement, AvailableSpace, Bounds, DispatchPhase, Element, - ElementContext, ElementId, FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, Hsla, - InputHandler, InteractiveBounds, InteractiveElement, InteractiveElementState, Interactivity, - IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, MouseMoveEvent, - Pixels, Point, ShapedLine, StatefulInteractiveElement, StrikethroughStyle, Styled, TextRun, - TextStyle, UnderlineStyle, WeakView, WhiteSpace, WindowContext, WindowTextSystem, + div, fill, point, px, relative, AnyElement, Bounds, DispatchPhase, Element, ElementContext, + FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, Hitbox, Hsla, InputHandler, + InteractiveElement, Interactivity, IntoElement, LayoutId, Model, ModelContext, + ModifiersChangedEvent, Pixels, Point, ShapedLine, StatefulInteractiveElement, + StrikethroughStyle, Styled, TextRun, TextStyle, UnderlineStyle, WeakView, WhiteSpace, + WindowContext, WindowTextSystem, }; use itertools::Itertools; use language::CursorShape; @@ -29,10 +29,11 @@ use std::{fmt::Debug, ops::RangeInclusive}; /// The information generated during layout that is necessary for painting. pub struct LayoutState { + hitbox: Hitbox, cells: Vec, rects: Vec, relative_highlighted_ranges: Vec<(RangeInclusive, Hsla)>, - cursor: Option, + cursor: Option, background_color: Hsla, dimensions: TerminalSize, mode: TermMode, @@ -392,216 +393,217 @@ impl TerminalElement { result } - fn compute_layout(&self, bounds: Bounds, cx: &mut ElementContext) -> LayoutState { - let settings = ThemeSettings::get_global(cx).clone(); + // todo!("I accidentally pulled this method in when merging. We should see if there are relevant changes.") + // fn compute_layout(&self, bounds: Bounds, cx: &mut ElementContext) -> LayoutState { + // let settings = ThemeSettings::get_global(cx).clone(); - let buffer_font_size = settings.buffer_font_size(cx); + // let buffer_font_size = settings.buffer_font_size(cx); - let terminal_settings = TerminalSettings::get_global(cx); - let font_family = terminal_settings - .font_family - .as_ref() - .map(|string| string.clone().into()) - .unwrap_or(settings.buffer_font.family); + // let terminal_settings = TerminalSettings::get_global(cx); + // let font_family = terminal_settings + // .font_family + // .as_ref() + // .map(|string| string.clone().into()) + // .unwrap_or(settings.buffer_font.family); - let font_features = terminal_settings - .font_features - .clone() - .unwrap_or(settings.buffer_font.features.clone()); + // let font_features = terminal_settings + // .font_features + // .clone() + // .unwrap_or(settings.buffer_font.features.clone()); - let line_height = terminal_settings.line_height.value(); - let font_size = terminal_settings.font_size.clone(); + // let line_height = terminal_settings.line_height.value(); + // let font_size = terminal_settings.font_size.clone(); - let font_size = - font_size.map_or(buffer_font_size, |size| theme::adjusted_font_size(size, cx)); + // let font_size = + // font_size.map_or(buffer_font_size, |size| theme::adjusted_font_size(size, cx)); - let theme = cx.theme().clone(); + // let theme = cx.theme().clone(); - let link_style = HighlightStyle { - color: Some(theme.colors().link_text_hover), - font_weight: None, - font_style: None, - background_color: None, - underline: Some(UnderlineStyle { - thickness: px(1.0), - color: Some(theme.colors().link_text_hover), - wavy: false, - }), - strikethrough: None, - fade_out: None, - }; + // let link_style = HighlightStyle { + // color: Some(theme.colors().link_text_hover), + // font_weight: None, + // font_style: None, + // background_color: None, + // underline: Some(UnderlineStyle { + // thickness: px(1.0), + // color: Some(theme.colors().link_text_hover), + // wavy: false, + // }), + // strikethrough: None, + // fade_out: None, + // }; - let text_style = TextStyle { - font_family, - font_features, - font_size: font_size.into(), - font_style: FontStyle::Normal, - line_height: line_height.into(), - background_color: None, - white_space: WhiteSpace::Normal, - // These are going to be overridden per-cell - underline: None, - strikethrough: None, - color: theme.colors().text, - font_weight: FontWeight::NORMAL, - }; + // let text_style = TextStyle { + // font_family, + // font_features, + // font_size: font_size.into(), + // font_style: FontStyle::Normal, + // line_height: line_height.into(), + // background_color: None, + // white_space: WhiteSpace::Normal, + // // These are going to be overridden per-cell + // underline: None, + // strikethrough: None, + // color: theme.colors().text, + // font_weight: FontWeight::NORMAL, + // }; - let text_system = cx.text_system(); - let player_color = theme.players().local(); - let match_color = theme.colors().search_match_background; - let gutter; - let dimensions = { - let rem_size = cx.rem_size(); - let font_pixels = text_style.font_size.to_pixels(rem_size); - let line_height = font_pixels * line_height.to_pixels(rem_size); - let font_id = cx.text_system().resolve_font(&text_style.font()); + // let text_system = cx.text_system(); + // let player_color = theme.players().local(); + // let match_color = theme.colors().search_match_background; + // let gutter; + // let dimensions = { + // let rem_size = cx.rem_size(); + // let font_pixels = text_style.font_size.to_pixels(rem_size); + // let line_height = font_pixels * line_height.to_pixels(rem_size); + // let font_id = cx.text_system().resolve_font(&text_style.font()); - let cell_width = text_system - .advance(font_id, font_pixels, 'm') - .unwrap() - .width; - gutter = cell_width; + // let cell_width = text_system + // .advance(font_id, font_pixels, 'm') + // .unwrap() + // .width; + // gutter = cell_width; - let mut size = bounds.size.clone(); - size.width -= gutter; + // let mut size = bounds.size.clone(); + // size.width -= gutter; - // https://github.com/zed-industries/zed/issues/2750 - // if the terminal is one column wide, rendering 🦀 - // causes alacritty to misbehave. - if size.width < cell_width * 2.0 { - size.width = cell_width * 2.0; - } + // // https://github.com/zed-industries/zed/issues/2750 + // // if the terminal is one column wide, rendering 🦀 + // // causes alacritty to misbehave. + // if size.width < cell_width * 2.0 { + // size.width = cell_width * 2.0; + // } - TerminalSize::new(line_height, cell_width, size) - }; + // TerminalSize::new(line_height, cell_width, size) + // }; - let search_matches = self.terminal.read(cx).matches.clone(); + // let search_matches = self.terminal.read(cx).matches.clone(); - let background_color = theme.colors().terminal_background; + // let background_color = theme.colors().terminal_background; - let last_hovered_word = self.terminal.update(cx, |terminal, cx| { - terminal.set_size(dimensions); - terminal.sync(cx); - if self.can_navigate_to_selected_word && terminal.can_navigate_to_selected_word() { - terminal.last_content.last_hovered_word.clone() - } else { - None - } - }); + // let last_hovered_word = self.terminal.update(cx, |terminal, cx| { + // terminal.set_size(dimensions); + // terminal.sync(cx); + // if self.can_navigate_to_selected_word && terminal.can_navigate_to_selected_word() { + // terminal.last_content.last_hovered_word.clone() + // } else { + // None + // } + // }); - if bounds.contains(&cx.mouse_position()) { - let stacking_order = cx.stacking_order().clone(); - if self.can_navigate_to_selected_word && last_hovered_word.is_some() { - cx.set_cursor_style(gpui::CursorStyle::PointingHand, stacking_order); - } else { - cx.set_cursor_style(gpui::CursorStyle::IBeam, stacking_order); - } - } + // if bounds.contains(&cx.mouse_position()) { + // let stacking_order = cx.stacking_order().clone(); + // if self.can_navigate_to_selected_word && last_hovered_word.is_some() { + // cx.set_cursor_style(gpui::CursorStyle::PointingHand, todo!()); + // } else { + // cx.set_cursor_style(gpui::CursorStyle::IBeam, todo!()); + // } + // } - let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| { - div() - .size_full() - .id("terminal-element") - .tooltip(move |cx| Tooltip::text(hovered_word.word.clone(), cx)) - .into_any_element() - }); + // let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| { + // div() + // .size_full() + // .id("terminal-element") + // .tooltip(move |cx| Tooltip::text(hovered_word.word.clone(), cx)) + // .into_any_element() + // }); - let TerminalContent { - cells, - mode, - display_offset, - cursor_char, - selection, - cursor, - .. - } = &self.terminal.read(cx).last_content; + // let TerminalContent { + // cells, + // mode, + // display_offset, + // cursor_char, + // selection, + // cursor, + // .. + // } = &self.terminal.read(cx).last_content; - // searches, highlights to a single range representations - let mut relative_highlighted_ranges = Vec::new(); - for search_match in search_matches { - relative_highlighted_ranges.push((search_match, match_color)) - } - if let Some(selection) = selection { - relative_highlighted_ranges - .push((selection.start..=selection.end, player_color.selection)); - } + // // searches, highlights to a single range representations + // let mut relative_highlighted_ranges = Vec::new(); + // for search_match in search_matches { + // relative_highlighted_ranges.push((search_match, match_color)) + // } + // if let Some(selection) = selection { + // relative_highlighted_ranges + // .push((selection.start..=selection.end, player_color.selection)); + // } - // then have that representation be converted to the appropriate highlight data structure + // // then have that representation be converted to the appropriate highlight data structure - let (cells, rects) = TerminalElement::layout_grid( - cells, - &text_style, - &cx.text_system(), - last_hovered_word - .as_ref() - .map(|last_hovered_word| (link_style, &last_hovered_word.word_match)), - cx, - ); + // let (cells, rects) = TerminalElement::layout_grid( + // cells, + // &text_style, + // &cx.text_system(), + // last_hovered_word + // .as_ref() + // .map(|last_hovered_word| (link_style, &last_hovered_word.word_match)), + // cx, + // ); - // Layout cursor. Rectangle is used for IME, so we should lay it out even - // if we don't end up showing it. - let cursor = if let AlacCursorShape::Hidden = cursor.shape { - None - } else { - let cursor_point = DisplayCursor::from(cursor.point, *display_offset); - let cursor_text = { - let str_trxt = cursor_char.to_string(); - let len = str_trxt.len(); - cx.text_system() - .shape_line( - str_trxt.into(), - text_style.font_size.to_pixels(cx.rem_size()), - &[TextRun { - len, - font: text_style.font(), - color: theme.colors().terminal_background, - background_color: None, - underline: Default::default(), - strikethrough: None, - }], - ) - .unwrap() - }; + // // Layout cursor. Rectangle is used for IME, so we should lay it out even + // // if we don't end up showing it. + // let cursor = if let AlacCursorShape::Hidden = cursor.shape { + // None + // } else { + // let cursor_point = DisplayCursor::from(cursor.point, *display_offset); + // let cursor_text = { + // let str_trxt = cursor_char.to_string(); + // let len = str_trxt.len(); + // cx.text_system() + // .shape_line( + // str_trxt.into(), + // text_style.font_size.to_pixels(cx.rem_size()), + // &[TextRun { + // len, + // font: text_style.font(), + // color: theme.colors().terminal_background, + // background_color: None, + // underline: Default::default(), + // strikethrough: None, + // }], + // ) + // .unwrap() + // }; - let focused = self.focused; - TerminalElement::shape_cursor(cursor_point, dimensions, &cursor_text).map( - move |(cursor_position, block_width)| { - let (shape, text) = match cursor.shape { - AlacCursorShape::Block if !focused => (CursorShape::Hollow, None), - AlacCursorShape::Block => (CursorShape::Block, Some(cursor_text)), - AlacCursorShape::Underline => (CursorShape::Underscore, None), - AlacCursorShape::Beam => (CursorShape::Bar, None), - AlacCursorShape::HollowBlock => (CursorShape::Hollow, None), - //This case is handled in the if wrapping the whole cursor layout - AlacCursorShape::Hidden => unreachable!(), - }; + // let focused = self.focused; + // TerminalElement::shape_cursor(cursor_point, dimensions, &cursor_text).map( + // move |(cursor_position, block_width)| { + // let (shape, text) = match cursor.shape { + // AlacCursorShape::Block if !focused => (CursorShape::Hollow, None), + // AlacCursorShape::Block => (CursorShape::Block, Some(cursor_text)), + // AlacCursorShape::Underline => (CursorShape::Underscore, None), + // AlacCursorShape::Beam => (CursorShape::Bar, None), + // AlacCursorShape::HollowBlock => (CursorShape::Hollow, None), + // //This case is handled in the if wrapping the whole cursor layout + // AlacCursorShape::Hidden => unreachable!(), + // }; - Cursor::new( - cursor_position, - block_width, - dimensions.line_height, - theme.players().local().cursor, - shape, - text, - None, - ) - }, - ) - }; + // CursorLayout::new( + // cursor_position, + // block_width, + // dimensions.line_height, + // theme.players().local().cursor, + // shape, + // text, + // None, + // ) + // }, + // ) + // }; - LayoutState { - cells, - cursor, - background_color, - dimensions, - rects, - relative_highlighted_ranges, - mode: *mode, - display_offset: *display_offset, - hyperlink_tooltip, - gutter, - } - } + // LayoutState { + // cells, + // cursor, + // background_color, + // dimensions, + // rects, + // relative_highlighted_ranges, + // mode: *mode, + // display_offset: *display_offset, + // hyperlink_tooltip, + // gutter, + // } + // } fn generic_button_handler( connection: Model, @@ -626,175 +628,398 @@ impl TerminalElement { bounds: Bounds, cx: &mut ElementContext, ) { - let focus = self.focus.clone(); - let terminal = self.terminal.clone(); - let interactive_bounds = InteractiveBounds { - bounds: bounds.intersect(&cx.content_mask().bounds), - stacking_order: cx.stacking_order().clone(), - }; + // let focus = self.focus.clone(); + // let terminal = self.terminal.clone(); + // let interactive_bounds = InteractiveBounds { + // bounds: bounds.intersect(&cx.content_mask().bounds), + // stacking_order: cx.stacking_order().clone(), + // }; - self.interactivity.on_mouse_down(MouseButton::Left, { - let terminal = terminal.clone(); - let focus = focus.clone(); - move |e, cx| { - cx.focus(&focus); - terminal.update(cx, |terminal, cx| { - terminal.mouse_down(&e, origin); - cx.notify(); - }) - } - }); + // self.interactivity.on_mouse_down(MouseButton::Left, { + // let terminal = terminal.clone(); + // let focus = focus.clone(); + // move |e, cx| { + // cx.focus(&focus); + // terminal.update(cx, |terminal, cx| { + // terminal.mouse_down(&e, origin); + // cx.notify(); + // }) + // } + // }); - cx.on_mouse_event({ - let bounds = bounds.clone(); - let focus = self.focus.clone(); - let terminal = self.terminal.clone(); - move |e: &MouseMoveEvent, phase, cx| { - if phase != DispatchPhase::Bubble || !focus.is_focused(cx) { - return; - } + // cx.on_mouse_event({ + // let bounds = bounds.clone(); + // let focus = self.focus.clone(); + // let terminal = self.terminal.clone(); + // move |e: &MouseMoveEvent, phase, cx| { + // if phase != DispatchPhase::Bubble || !focus.is_focused(cx) { + // return; + // } - if e.pressed_button.is_some() && !cx.has_active_drag() { - let visibly_contains = interactive_bounds.visibly_contains(&e.position, cx); - terminal.update(cx, |terminal, cx| { - if !terminal.selection_started() { - if visibly_contains { - terminal.mouse_drag(e, origin, bounds); - cx.notify(); - } - } else { - terminal.mouse_drag(e, origin, bounds); - cx.notify(); - } - }) - } + // if e.pressed_button.is_some() && !cx.has_active_drag() { + // let visibly_contains = interactive_bounds.visibly_contains(&e.position, cx); + // terminal.update(cx, |terminal, cx| { + // if !terminal.selection_started() { + // if visibly_contains { + // terminal.mouse_drag(e, origin, bounds); + // cx.notify(); + // } + // } else { + // terminal.mouse_drag(e, origin, bounds); + // cx.notify(); + // } + // }) + // } - if interactive_bounds.visibly_contains(&e.position, cx) { - terminal.update(cx, |terminal, cx| { - terminal.mouse_move(&e, origin); - cx.notify(); - }) - } - } - }); + // if interactive_bounds.visibly_contains(&e.position, cx) { + // terminal.update(cx, |terminal, cx| { + // terminal.mouse_move(&e, origin); + // cx.notify(); + // }) + // } + // } + // }); - self.interactivity.on_mouse_up( - MouseButton::Left, - TerminalElement::generic_button_handler( - terminal.clone(), - origin, - focus.clone(), - move |terminal, origin, e, cx| { - terminal.mouse_up(&e, origin, cx); - }, - ), - ); - self.interactivity.on_scroll_wheel({ - let terminal = terminal.clone(); - move |e, cx| { - terminal.update(cx, |terminal, cx| { - terminal.scroll_wheel(e, origin); - cx.notify(); - }) - } - }); + // self.interactivity.on_mouse_up( + // MouseButton::Left, + // TerminalElement::generic_button_handler( + // terminal.clone(), + // origin, + // focus.clone(), + // move |terminal, origin, e, cx| { + // terminal.mouse_up(&e, origin, cx); + // }, + // ), + // ); + // self.interactivity.on_scroll_wheel({ + // let terminal = terminal.clone(); + // move |e, cx| { + // terminal.update(cx, |terminal, cx| { + // terminal.scroll_wheel(e, origin); + // cx.notify(); + // }) + // } + // }); - // Mouse mode handlers: - // All mouse modes need the extra click handlers - if mode.intersects(TermMode::MOUSE_MODE) { - self.interactivity.on_mouse_down( - MouseButton::Right, - TerminalElement::generic_button_handler( - terminal.clone(), - origin, - focus.clone(), - move |terminal, origin, e, _cx| { - terminal.mouse_down(&e, origin); - }, - ), - ); - self.interactivity.on_mouse_down( - MouseButton::Middle, - TerminalElement::generic_button_handler( - terminal.clone(), - origin, - focus.clone(), - move |terminal, origin, e, _cx| { - terminal.mouse_down(&e, origin); - }, - ), - ); - self.interactivity.on_mouse_up( - MouseButton::Right, - TerminalElement::generic_button_handler( - terminal.clone(), - origin, - focus.clone(), - move |terminal, origin, e, cx| { - terminal.mouse_up(&e, origin, cx); - }, - ), - ); - self.interactivity.on_mouse_up( - MouseButton::Middle, - TerminalElement::generic_button_handler( - terminal, - origin, - focus, - move |terminal, origin, e, cx| { - terminal.mouse_up(&e, origin, cx); - }, - ), - ); - } + // // Mouse mode handlers: + // // All mouse modes need the extra click handlers + // if mode.intersects(TermMode::MOUSE_MODE) { + // self.interactivity.on_mouse_down( + // MouseButton::Right, + // TerminalElement::generic_button_handler( + // terminal.clone(), + // origin, + // focus.clone(), + // move |terminal, origin, e, _cx| { + // terminal.mouse_down(&e, origin); + // }, + // ), + // ); + // self.interactivity.on_mouse_down( + // MouseButton::Middle, + // TerminalElement::generic_button_handler( + // terminal.clone(), + // origin, + // focus.clone(), + // move |terminal, origin, e, _cx| { + // terminal.mouse_down(&e, origin); + // }, + // ), + // ); + // self.interactivity.on_mouse_up( + // MouseButton::Right, + // TerminalElement::generic_button_handler( + // terminal.clone(), + // origin, + // focus.clone(), + // move |terminal, origin, e, cx| { + // terminal.mouse_up(&e, origin, cx); + // }, + // ), + // ); + // self.interactivity.on_mouse_up( + // MouseButton::Middle, + // TerminalElement::generic_button_handler( + // terminal, + // origin, + // focus, + // move |terminal, origin, e, cx| { + // terminal.mouse_up(&e, origin, cx); + // }, + // ), + // ); + // } } } impl Element for TerminalElement { - type State = InteractiveElementState; + type BeforeLayout = (); + type AfterLayout = LayoutState; - fn request_layout( + fn before_layout(&mut self, cx: &mut ElementContext<'_>) -> (LayoutId, Self::BeforeLayout) { + let layout_id = self.interactivity.before_layout(cx, |mut style, cx| { + style.size.width = relative(1.).into(); + style.size.height = relative(1.).into(); + cx.request_layout(&style, None) + }); + + (layout_id, ()) + } + + fn after_layout( &mut self, - element_state: Option, - cx: &mut ElementContext<'_>, - ) -> (LayoutId, Self::State) { - let (layout_id, interactive_state) = - self.interactivity - .layout(element_state, cx, |mut style, cx| { - style.size.width = relative(1.).into(); - style.size.height = relative(1.).into(); - let layout_id = cx.request_layout(&style, None); + bounds: Bounds, + _before_layout: &mut Self::BeforeLayout, + cx: &mut ElementContext, + ) -> LayoutState { + self.interactivity.after_layout( + bounds, + bounds.size, + cx, + |_style, _scroll_offset, _hitbox, cx| { + let settings = ThemeSettings::get_global(cx).clone(); - layout_id + let buffer_font_size = settings.buffer_font_size(cx); + + let terminal_settings = TerminalSettings::get_global(cx); + let font_family = terminal_settings + .font_family + .as_ref() + .map(|string| string.clone().into()) + .unwrap_or(settings.buffer_font.family); + + let font_features = terminal_settings + .font_features + .clone() + .unwrap_or(settings.buffer_font.features.clone()); + + let line_height = terminal_settings.line_height.value(); + let font_size = terminal_settings.font_size.clone(); + + let font_size = + font_size.map_or(buffer_font_size, |size| theme::adjusted_font_size(size, cx)); + + let theme = cx.theme().clone(); + + let link_style = HighlightStyle { + color: Some(theme.colors().link_text_hover), + font_weight: None, + font_style: None, + background_color: None, + underline: Some(UnderlineStyle { + thickness: px(1.0), + color: Some(theme.colors().link_text_hover), + wavy: false, + }), + strikethrough: None, + fade_out: None, + }; + + let text_style = TextStyle { + font_family, + font_features, + font_size: font_size.into(), + font_style: FontStyle::Normal, + line_height: line_height.into(), + background_color: None, + white_space: WhiteSpace::Normal, + // These are going to be overridden per-cell + underline: None, + strikethrough: None, + color: theme.colors().text, + font_weight: FontWeight::NORMAL, + }; + + let text_system = cx.text_system(); + let player_color = theme.players().local(); + let match_color = theme.colors().search_match_background; + let gutter; + let dimensions = { + let rem_size = cx.rem_size(); + let font_pixels = text_style.font_size.to_pixels(rem_size); + let line_height = font_pixels * line_height.to_pixels(rem_size); + let font_id = cx.text_system().resolve_font(&text_style.font()); + + let cell_width = text_system + .advance(font_id, font_pixels, 'm') + .unwrap() + .width; + gutter = cell_width; + + let mut size = bounds.size.clone(); + size.width -= gutter; + + // https://github.com/zed-industries/zed/issues/2750 + // if the terminal is one column wide, rendering 🦀 + // causes alacritty to misbehave. + if size.width < cell_width * 2.0 { + size.width = cell_width * 2.0; + } + + TerminalSize::new(line_height, cell_width, size) + }; + + let search_matches = self.terminal.read(cx).matches.clone(); + + let background_color = theme.colors().terminal_background; + + let last_hovered_word = self.terminal.update(cx, |terminal, cx| { + terminal.set_size(dimensions); + terminal.sync(cx); + if self.can_navigate_to_selected_word + && terminal.can_navigate_to_selected_word() + { + terminal.last_content.last_hovered_word.clone() + } else { + None + } }); - (layout_id, interactive_state) + let hitbox = cx.insert_hitbox(bounds, false); + if self.can_navigate_to_selected_word && last_hovered_word.is_some() { + cx.set_cursor_style(gpui::CursorStyle::PointingHand, &hitbox); + } else { + cx.set_cursor_style(gpui::CursorStyle::IBeam, &hitbox) + } + + let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| { + let mut element = div() + .size_full() + .id("terminal-element") + .tooltip(move |cx| Tooltip::text(hovered_word.word.clone(), cx)) + .into_any_element(); + element.layout( + bounds.origin + point(gutter, px(0.)), + bounds.size.into(), + cx, + ); + element + }); + + let TerminalContent { + cells, + mode, + display_offset, + cursor_char, + selection, + cursor, + .. + } = &self.terminal.read(cx).last_content; + + // searches, highlights to a single range representations + let mut relative_highlighted_ranges = Vec::new(); + for search_match in search_matches { + relative_highlighted_ranges.push((search_match, match_color)) + } + if let Some(selection) = selection { + relative_highlighted_ranges + .push((selection.start..=selection.end, player_color.selection)); + } + + // then have that representation be converted to the appropriate highlight data structure + + let (cells, rects) = TerminalElement::layout_grid( + cells, + &text_style, + &cx.text_system(), + last_hovered_word + .as_ref() + .map(|last_hovered_word| (link_style, &last_hovered_word.word_match)), + cx, + ); + + // Layout cursor. Rectangle is used for IME, so we should lay it out even + // if we don't end up showing it. + let cursor = if !self.cursor_visible || cursor.shape == AlacCursorShape::Hidden { + None + } else { + let cursor_point = DisplayCursor::from(cursor.point, *display_offset); + let cursor_text = { + let str_trxt = cursor_char.to_string(); + let len = str_trxt.len(); + cx.text_system() + .shape_line( + str_trxt.into(), + text_style.font_size.to_pixels(cx.rem_size()), + &[TextRun { + len, + font: text_style.font(), + color: theme.colors().terminal_background, + background_color: None, + underline: Default::default(), + strikethrough: None, + }], + ) + .unwrap() + }; + + let focused = self.focused; + TerminalElement::shape_cursor(cursor_point, dimensions, &cursor_text).map( + move |(cursor_position, block_width)| { + let (shape, text) = match cursor.shape { + AlacCursorShape::Block if !focused => (CursorShape::Hollow, None), + AlacCursorShape::Block => (CursorShape::Block, Some(cursor_text)), + AlacCursorShape::Underline => (CursorShape::Underscore, None), + AlacCursorShape::Beam => (CursorShape::Bar, None), + AlacCursorShape::HollowBlock => (CursorShape::Hollow, None), + //This case is handled in the if wrapping the whole cursor layout + AlacCursorShape::Hidden => unreachable!(), + }; + + CursorLayout::new( + cursor_position, + block_width, + dimensions.line_height, + theme.players().local().cursor, + shape, + text, + None, + ) + }, + ) + }; + + LayoutState { + hitbox, + cells, + cursor, + background_color, + dimensions, + rects, + relative_highlighted_ranges, + mode: *mode, + display_offset: *display_offset, + hyperlink_tooltip, + gutter, + } + }, + ) } fn paint( &mut self, bounds: Bounds, - state: &mut Self::State, + _: &mut Self::BeforeLayout, + after_layout: &mut Self::AfterLayout, cx: &mut ElementContext<'_>, ) { - let mut layout = self.compute_layout(bounds, cx); - - cx.paint_quad(fill(bounds, layout.background_color)); - let origin = bounds.origin + Point::new(layout.gutter, px(0.)); + cx.paint_quad(fill(bounds, after_layout.background_color)); + let origin = bounds.origin + Point::new(after_layout.gutter, px(0.)); let terminal_input_handler = TerminalInputHandler { terminal: self.terminal.clone(), - cursor_bounds: layout + cursor_bounds: after_layout .cursor .as_ref() .map(|cursor| cursor.bounding_rect(origin)), workspace: self.workspace.clone(), }; - self.register_mouse_listeners(origin, layout.mode, bounds, cx); + self.register_mouse_listeners(origin, after_layout.mode, bounds, cx); + let hitbox = after_layout.hitbox.clone(); self.interactivity - .paint(bounds, bounds.size, state, cx, |_, _, cx| { + .paint(bounds, Some(&hitbox), cx, |_, cx| { cx.handle_input(&self.focus, terminal_input_handler); cx.on_key_event({ @@ -813,46 +1038,40 @@ impl Element for TerminalElement { } }); - for rect in &layout.rects { - rect.paint(origin, &layout, cx); + for rect in &after_layout.rects { + rect.paint(origin, &after_layout, cx); } - cx.with_z_index(1, |cx| { - for (relative_highlighted_range, color) in - layout.relative_highlighted_ranges.iter() - { - if let Some((start_y, highlighted_range_lines)) = - to_highlighted_range_lines(relative_highlighted_range, &layout, origin) - { - let hr = HighlightedRange { - start_y, //Need to change this - line_height: layout.dimensions.line_height, - lines: highlighted_range_lines, - color: color.clone(), - //Copied from editor. TODO: move to theme or something - corner_radius: 0.15 * layout.dimensions.line_height, - }; - hr.paint(bounds, cx); - } + for (relative_highlighted_range, color) in + after_layout.relative_highlighted_ranges.iter() + { + if let Some((start_y, highlighted_range_lines)) = to_highlighted_range_lines( + relative_highlighted_range, + &after_layout, + origin, + ) { + let hr = HighlightedRange { + start_y, //Need to change this + line_height: after_layout.dimensions.line_height, + lines: highlighted_range_lines, + color: color.clone(), + //Copied from editor. TODO: move to theme or something + corner_radius: 0.15 * after_layout.dimensions.line_height, + }; + hr.paint(bounds, cx); } - }); - - cx.with_z_index(2, |cx| { - for cell in &layout.cells { - cell.paint(origin, &layout, bounds, cx); - } - }); - - if self.cursor_visible { - cx.with_z_index(3, |cx| { - if let Some(cursor) = &layout.cursor { - cursor.paint(origin, cx); - } - }); } - if let Some(mut element) = layout.hyperlink_tooltip.take() { - element.draw(origin, bounds.size.map(AvailableSpace::Definite), cx) + for cell in &after_layout.cells { + cell.paint(origin, &after_layout, bounds, cx); + } + + if let Some(cursor) = &mut after_layout.cursor { + cursor.paint(origin, cx); + } + + if let Some(mut element) = after_layout.hyperlink_tooltip.take() { + element.paint(cx); } }); } @@ -861,10 +1080,6 @@ impl Element for TerminalElement { impl IntoElement for TerminalElement { type Element = Self; - fn element_id(&self) -> Option { - Some("terminal".into()) - } - fn into_element(self) -> Self::Element { self } diff --git a/crates/theme/src/one_themes.rs b/crates/theme/src/one_themes.rs index c461779bd1..73104b07c1 100644 --- a/crates/theme/src/one_themes.rs +++ b/crates/theme/src/one_themes.rs @@ -100,7 +100,7 @@ pub(crate) fn one_dark() -> Theme { editor_document_highlight_write_background: gpui::red(), terminal_background: bg, - // todo!("Use one colors for terminal") + // todo("Use one colors for terminal") terminal_foreground: crate::white().dark().step_12(), terminal_bright_foreground: crate::white().dark().step_11(), terminal_dim_foreground: crate::white().dark().step_10(), diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index 17910ef973..b4bdc8f342 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -130,7 +130,7 @@ pub struct ThemeColors { /// The border color of the scrollbar track. pub scrollbar_track_border: Hsla, // /// The opacity of the scrollbar status marks, like diagnostic states and git status. - // todo!() + // todo() // pub scrollbar_status_opacity: Hsla, // === diff --git a/crates/time_format/src/time_format.rs b/crates/time_format/src/time_format.rs index 0084802aae..1a6002b700 100644 --- a/crates/time_format/src/time_format.rs +++ b/crates/time_format/src/time_format.rs @@ -25,8 +25,8 @@ pub fn format_localized_timestamp( } #[cfg(not(target_os = "macos"))] { - //todo!(linux) respect user's date/time preferences - //todo!(windows) respect user's date/time preferences + // todo(linux) respect user's date/time preferences + // todo(windows) respect user's date/time preferences format_timestamp_fallback(reference, timestamp, timezone) } } diff --git a/crates/ui/src/components/avatar/avatar.rs b/crates/ui/src/components/avatar/avatar.rs index d93b280e4b..1f8913bbd2 100644 --- a/crates/ui/src/components/avatar/avatar.rs +++ b/crates/ui/src/components/avatar/avatar.rs @@ -122,9 +122,6 @@ impl RenderOnce for Avatar { .size(image_size) .bg(cx.theme().colors().ghost_element_background), ) - .children( - self.indicator - .map(|indicator| div().z_index(1).child(indicator)), - ) + .children(self.indicator.map(|indicator| div().child(indicator))) } } diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index 0bbbee2900..33393a75bf 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/crates/ui/src/components/popover_menu.rs @@ -2,9 +2,9 @@ use std::{cell::RefCell, rc::Rc}; use gpui::{ overlay, point, prelude::FluentBuilder, px, rems, AnchorCorner, AnyElement, Bounds, - DismissEvent, DispatchPhase, Element, ElementContext, ElementId, InteractiveBounds, - IntoElement, LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View, - VisualContext, WindowContext, + DismissEvent, DispatchPhase, Element, ElementContext, ElementId, HitboxId, IntoElement, + LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, + WindowContext, }; use crate::{Clickable, Selectable}; @@ -107,6 +107,21 @@ impl PopoverMenu { } }) } + + fn with_element_state( + &mut self, + cx: &mut ElementContext, + f: impl FnOnce(&mut Self, &mut PopoverMenuElementState, &mut ElementContext) -> R, + ) -> R { + cx.with_element_state::, _>( + Some(self.id.clone()), + |element_state, cx| { + let mut element_state = element_state.unwrap().unwrap_or_default(); + let result = f(self, &mut element_state, cx); + (result, Some(element_state)) + }, + ) + } } /// Creates a [`PopoverMenu`] @@ -121,114 +136,136 @@ pub fn popover_menu(id: impl Into) -> PopoverMenu } } -pub struct PopoverMenuState { +pub struct PopoverMenuElementState { + menu: Rc>>>, + child_bounds: Option>, +} + +impl Clone for PopoverMenuElementState { + fn clone(&self) -> Self { + Self { + menu: Rc::clone(&self.menu), + child_bounds: self.child_bounds.clone(), + } + } +} + +impl Default for PopoverMenuElementState { + fn default() -> Self { + Self { + menu: Rc::default(), + child_bounds: None, + } + } +} + +pub struct PopoverMenuFrameState { child_layout_id: Option, child_element: Option, - child_bounds: Option>, menu_element: Option, - menu: Rc>>>, } impl Element for PopoverMenu { - type State = PopoverMenuState; + type BeforeLayout = PopoverMenuFrameState; + type AfterLayout = Option; - fn request_layout( + fn before_layout(&mut self, cx: &mut ElementContext) -> (gpui::LayoutId, Self::BeforeLayout) { + self.with_element_state(cx, |this, element_state, cx| { + let mut menu_layout_id = None; + + let menu_element = element_state.menu.borrow_mut().as_mut().map(|menu| { + let mut overlay = overlay().snap_to_window().anchor(this.anchor); + + if let Some(child_bounds) = element_state.child_bounds { + overlay = overlay.position( + this.resolved_attach().corner(child_bounds) + this.resolved_offset(cx), + ); + } + + let mut element = overlay.child(menu.clone()).into_any(); + menu_layout_id = Some(element.before_layout(cx)); + element + }); + + let mut child_element = this.child_builder.take().map(|child_builder| { + (child_builder)(element_state.menu.clone(), this.menu_builder.clone()) + }); + + let child_layout_id = child_element + .as_mut() + .map(|child_element| child_element.before_layout(cx)); + + let layout_id = cx.request_layout( + &gpui::Style::default(), + menu_layout_id.into_iter().chain(child_layout_id), + ); + + ( + layout_id, + PopoverMenuFrameState { + child_element, + child_layout_id, + menu_element, + }, + ) + }) + } + + fn after_layout( &mut self, - element_state: Option, + _bounds: Bounds, + before_layout: &mut Self::BeforeLayout, cx: &mut ElementContext, - ) -> (gpui::LayoutId, Self::State) { - let mut menu_layout_id = None; - - let (menu, child_bounds) = if let Some(element_state) = element_state { - (element_state.menu, element_state.child_bounds) - } else { - (Rc::default(), None) - }; - - let menu_element = menu.borrow_mut().as_mut().map(|menu| { - let mut overlay = overlay().snap_to_window().anchor(self.anchor); - - if let Some(child_bounds) = child_bounds { - overlay = overlay.position( - self.resolved_attach().corner(child_bounds) + self.resolved_offset(cx), - ); + ) -> Option { + self.with_element_state(cx, |_this, element_state, cx| { + if let Some(child) = before_layout.child_element.as_mut() { + child.after_layout(cx); } - let mut element = overlay.child(menu.clone()).into_any(); - menu_layout_id = Some(element.request_layout(cx)); - element - }); + if let Some(menu) = before_layout.menu_element.as_mut() { + menu.after_layout(cx); + } - let mut child_element = self - .child_builder - .take() - .map(|child_builder| (child_builder)(menu.clone(), self.menu_builder.clone())); - - let child_layout_id = child_element - .as_mut() - .map(|child_element| child_element.request_layout(cx)); - - let layout_id = cx.request_layout( - &gpui::Style::default(), - menu_layout_id.into_iter().chain(child_layout_id), - ); - - ( - layout_id, - PopoverMenuState { - menu, - child_element, - child_layout_id, - menu_element, - child_bounds, - }, - ) + before_layout.child_layout_id.map(|layout_id| { + let bounds = cx.layout_bounds(layout_id); + element_state.child_bounds = Some(bounds); + cx.insert_hitbox(bounds, false).id + }) + }) } fn paint( &mut self, _: Bounds, - element_state: &mut Self::State, + before_layout: &mut Self::BeforeLayout, + child_hitbox: &mut Option, cx: &mut ElementContext, ) { - if let Some(mut child) = element_state.child_element.take() { - child.paint(cx); - } - - if let Some(child_layout_id) = element_state.child_layout_id.take() { - element_state.child_bounds = Some(cx.layout_bounds(child_layout_id)); - } - - if let Some(mut menu) = element_state.menu_element.take() { - menu.paint(cx); - - if let Some(child_bounds) = element_state.child_bounds { - let interactive_bounds = InteractiveBounds { - bounds: child_bounds, - stacking_order: cx.stacking_order().clone(), - }; - - // Mouse-downing outside the menu dismisses it, so we don't - // want a click on the toggle to re-open it. - cx.on_mouse_event(move |e: &MouseDownEvent, phase, cx| { - if phase == DispatchPhase::Bubble - && interactive_bounds.visibly_contains(&e.position, cx) - { - cx.stop_propagation() - } - }) + self.with_element_state(cx, |_this, _element_state, cx| { + if let Some(mut child) = before_layout.child_element.take() { + child.paint(cx); } - } + + if let Some(mut menu) = before_layout.menu_element.take() { + menu.paint(cx); + + if let Some(child_hitbox) = *child_hitbox { + // Mouse-downing outside the menu dismisses it, so we don't + // want a click on the toggle to re-open it. + cx.on_mouse_event(move |_: &MouseDownEvent, phase, cx| { + if phase == DispatchPhase::Bubble && child_hitbox.is_hovered(cx) { + cx.stop_propagation() + } + }) + } + } + }) } } impl IntoElement for PopoverMenu { type Element = Self; - fn element_id(&self) -> Option { - Some(self.id.clone()) - } - fn into_element(self) -> Self::Element { self } diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index b08f3911cb..3e000def19 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -2,7 +2,7 @@ use std::{cell::RefCell, rc::Rc}; use gpui::{ overlay, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase, Element, - ElementContext, ElementId, InteractiveBounds, IntoElement, LayoutId, ManagedView, MouseButton, + ElementContext, ElementId, Hitbox, IntoElement, LayoutId, ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, WindowContext, }; @@ -37,6 +37,21 @@ impl RightClickMenu { self.attach = Some(attach); self } + + fn with_element_state( + &mut self, + cx: &mut ElementContext, + f: impl FnOnce(&mut Self, &mut MenuHandleElementState, &mut ElementContext) -> R, + ) -> R { + cx.with_element_state::, _>( + Some(self.id.clone()), + |element_state, cx| { + let mut element_state = element_state.unwrap().unwrap_or_default(); + let result = f(self, &mut element_state, cx); + (result, Some(element_state)) + }, + ) + } } /// Creates a [`RightClickMenu`] @@ -50,139 +65,169 @@ pub fn right_click_menu(id: impl Into) -> RightClickM } } -pub struct MenuHandleState { +pub struct MenuHandleElementState { menu: Rc>>>, position: Rc>>, +} + +impl Clone for MenuHandleElementState { + fn clone(&self) -> Self { + Self { + menu: Rc::clone(&self.menu), + position: Rc::clone(&self.position), + } + } +} + +impl Default for MenuHandleElementState { + fn default() -> Self { + Self { + menu: Rc::default(), + position: Rc::default(), + } + } +} + +pub struct MenuHandleFrameState { child_layout_id: Option, child_element: Option, menu_element: Option, } impl Element for RightClickMenu { - type State = MenuHandleState; + type BeforeLayout = MenuHandleFrameState; + type AfterLayout = Hitbox; - fn request_layout( + fn before_layout(&mut self, cx: &mut ElementContext) -> (gpui::LayoutId, Self::BeforeLayout) { + self.with_element_state(cx, |this, element_state, cx| { + let mut menu_layout_id = None; + + let menu_element = element_state.menu.borrow_mut().as_mut().map(|menu| { + let mut overlay = overlay().snap_to_window(); + if let Some(anchor) = this.anchor { + overlay = overlay.anchor(anchor); + } + overlay = overlay.position(*element_state.position.borrow()); + + let mut element = overlay.child(menu.clone()).into_any(); + menu_layout_id = Some(element.before_layout(cx)); + element + }); + + let mut child_element = this + .child_builder + .take() + .map(|child_builder| (child_builder)(element_state.menu.borrow().is_some())); + + let child_layout_id = child_element + .as_mut() + .map(|child_element| child_element.before_layout(cx)); + + let layout_id = cx.request_layout( + &gpui::Style::default(), + menu_layout_id.into_iter().chain(child_layout_id), + ); + + ( + layout_id, + MenuHandleFrameState { + child_element, + child_layout_id, + menu_element, + }, + ) + }) + } + + fn after_layout( &mut self, - element_state: Option, + bounds: Bounds, + before_layout: &mut Self::BeforeLayout, cx: &mut ElementContext, - ) -> (gpui::LayoutId, Self::State) { - let (menu, position) = if let Some(element_state) = element_state { - (element_state.menu, element_state.position) - } else { - (Rc::default(), Rc::default()) - }; + ) -> Hitbox { + cx.with_element_id(Some(self.id.clone()), |cx| { + // todo!("occlude child bounds instead?") + let hitbox = cx.insert_hitbox(bounds, false); - let mut menu_layout_id = None; - - let menu_element = menu.borrow_mut().as_mut().map(|menu| { - let mut overlay = overlay().snap_to_window(); - if let Some(anchor) = self.anchor { - overlay = overlay.anchor(anchor); + if let Some(child) = before_layout.child_element.as_mut() { + child.after_layout(cx); } - overlay = overlay.position(*position.borrow()); - let mut element = overlay.child(menu.clone()).into_any(); - menu_layout_id = Some(element.request_layout(cx)); - element - }); + if let Some(menu) = before_layout.menu_element.as_mut() { + menu.after_layout(cx); + } - let mut child_element = self - .child_builder - .take() - .map(|child_builder| (child_builder)(menu.borrow().is_some())); - - let child_layout_id = child_element - .as_mut() - .map(|child_element| child_element.request_layout(cx)); - - let layout_id = cx.request_layout( - &gpui::Style::default(), - menu_layout_id.into_iter().chain(child_layout_id), - ); - - ( - layout_id, - MenuHandleState { - menu, - position, - child_element, - child_layout_id, - menu_element, - }, - ) + hitbox + }) } fn paint( &mut self, - bounds: Bounds, - element_state: &mut Self::State, + _bounds: Bounds, + before_layout: &mut Self::BeforeLayout, + hitbox: &mut Self::AfterLayout, cx: &mut ElementContext, ) { - if let Some(mut child) = element_state.child_element.take() { - child.paint(cx); - } - - if let Some(mut menu) = element_state.menu_element.take() { - menu.paint(cx); - return; - } - - let Some(builder) = self.menu_builder.take() else { - return; - }; - let menu = element_state.menu.clone(); - let position = element_state.position.clone(); - let attach = self.attach.clone(); - let child_layout_id = element_state.child_layout_id.clone(); - let child_bounds = cx.layout_bounds(child_layout_id.unwrap()); - - let interactive_bounds = InteractiveBounds { - bounds: bounds.intersect(&cx.content_mask().bounds), - stacking_order: cx.stacking_order().clone(), - }; - cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { - if phase == DispatchPhase::Bubble - && event.button == MouseButton::Right - && interactive_bounds.visibly_contains(&event.position, cx) - { - cx.stop_propagation(); - cx.prevent_default(); - - let new_menu = (builder)(cx); - let menu2 = menu.clone(); - let previous_focus_handle = cx.focused(); - - cx.subscribe(&new_menu, move |modal, _: &DismissEvent, cx| { - if modal.focus_handle(cx).contains_focused(cx) { - if previous_focus_handle.is_some() { - cx.focus(previous_focus_handle.as_ref().unwrap()) - } - } - *menu2.borrow_mut() = None; - cx.refresh(); - }) - .detach(); - cx.focus_view(&new_menu); - *menu.borrow_mut() = Some(new_menu); - - *position.borrow_mut() = if attach.is_some() && child_layout_id.is_some() { - attach.unwrap().corner(child_bounds) - } else { - cx.mouse_position() - }; - cx.refresh(); + self.with_element_state(cx, |this, element_state, cx| { + if let Some(mut child) = before_layout.child_element.take() { + child.paint(cx); } - }); + + if let Some(mut menu) = before_layout.menu_element.take() { + menu.paint(cx); + return; + } + + let Some(builder) = this.menu_builder.take() else { + return; + }; + + let attach = this.attach.clone(); + let menu = element_state.menu.clone(); + let position = element_state.position.clone(); + let child_layout_id = before_layout.child_layout_id.clone(); + let child_bounds = cx.layout_bounds(child_layout_id.unwrap()); + + let hitbox_id = hitbox.id; + cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { + if phase == DispatchPhase::Bubble + && event.button == MouseButton::Right + && hitbox_id.is_hovered(cx) + { + cx.stop_propagation(); + cx.prevent_default(); + + let new_menu = (builder)(cx); + let menu2 = menu.clone(); + let previous_focus_handle = cx.focused(); + + cx.subscribe(&new_menu, move |modal, _: &DismissEvent, cx| { + if modal.focus_handle(cx).contains_focused(cx) { + if previous_focus_handle.is_some() { + cx.focus(previous_focus_handle.as_ref().unwrap()) + } + } + *menu2.borrow_mut() = None; + cx.refresh(); + }) + .detach(); + cx.focus_view(&new_menu); + *menu.borrow_mut() = Some(new_menu); + *position.borrow_mut() = if attach.is_some() && child_layout_id.is_some() { + attach.unwrap().corner(child_bounds) + } else { + cx.mouse_position() + }; + cx.refresh(); + } + }); + }) } } impl IntoElement for RightClickMenu { type Element = Self; - fn element_id(&self) -> Option { - Some(self.id.clone()) - } - fn into_element(self) -> Self::Element { self } diff --git a/crates/ui/src/components/tab_bar.rs b/crates/ui/src/components/tab_bar.rs index abfd7284f2..0d3208d29c 100644 --- a/crates/ui/src/components/tab_bar.rs +++ b/crates/ui/src/components/tab_bar.rs @@ -123,7 +123,6 @@ impl RenderOnce for TabBar { .absolute() .top_0() .left_0() - .z_index(1) .size_full() .border_b() .border_color(cx.theme().colors().border), @@ -131,7 +130,6 @@ impl RenderOnce for TabBar { .child( h_flex() .id("tabs") - .z_index(2) .flex_grow() .overflow_x_scroll() .when_some(self.scroll_handle, |cx, scroll_handle| { diff --git a/crates/ui/src/styled_ext.rs b/crates/ui/src/styled_ext.rs index 2b4cc2b395..c8be8e5a00 100644 --- a/crates/ui/src/styled_ext.rs +++ b/crates/ui/src/styled_ext.rs @@ -7,7 +7,7 @@ use crate::{ElevationIndex, UiTextSize}; fn elevated(this: E, cx: &mut WindowContext, index: ElevationIndex) -> E { this.bg(cx.theme().colors().elevated_surface_background) - .z_index(index.z_index()) + // .z_index(index.z_index()) // TODO! we need a new solution here. .rounded(px(8.)) .border() .border_color(cx.theme().colors().border_variant) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 70059e47b6..6e53c12df8 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -563,7 +563,6 @@ impl Render for Dock { cx.stop_propagation(); } })) - .z_index(1) .block_mouse(); match self.position() { diff --git a/crates/workspace/src/modal_layer.rs b/crates/workspace/src/modal_layer.rs index f67b78f7d1..db67e1618a 100644 --- a/crates/workspace/src/modal_layer.rs +++ b/crates/workspace/src/modal_layer.rs @@ -139,21 +139,15 @@ impl Render for ModalLayer { return div(); }; - div() - .absolute() - .size_full() - .top_0() - .left_0() - .z_index(169) - .child( - v_flex() - .h(px(0.0)) - .top_20() - .flex() - .flex_col() - .items_center() - .track_focus(&active_modal.focus_handle) - .child(h_flex().child(active_modal.modal.view())), - ) + div().absolute().size_full().top_0().left_0().child( + v_flex() + .h(px(0.0)) + .top_20() + .flex() + .flex_col() + .items_center() + .track_focus(&active_modal.focus_handle) + .child(h_flex().child(active_modal.modal.view())), + ) } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 65315aec02..23b2770541 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1549,7 +1549,6 @@ impl Pane { fn render_menu_overlay(menu: &View) -> Div { div() .absolute() - .z_index(1) .bottom_0() .right_0() .size_0() @@ -1867,7 +1866,6 @@ impl Render for Pane { .child( // drag target div() - .z_index(1) .invisible() .absolute() .bg(theme::color_alpha( diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 6f55dc800e..96e2fd5df3 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -258,7 +258,6 @@ impl Member { .right_3() .elevation_2(cx) .p_1() - .z_index(1) .child(status_box) .when_some( leader_join_data, @@ -588,9 +587,9 @@ mod element { use std::{cell::RefCell, iter, rc::Rc, sync::Arc}; use gpui::{ - px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, IntoElement, - MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style, - WeakView, WindowContext, + px, relative, Along, AnyElement, Axis, Bounds, Element, IntoElement, MouseDownEvent, + MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style, WeakView, + WindowContext, }; use parking_lot::Mutex; use settings::Settings; @@ -753,58 +752,47 @@ mod element { size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)), }; - cx.with_z_index(3, |cx| { - if handle_bounds.contains(&cx.mouse_position()) { - let stacking_order = cx.stacking_order().clone(); - let cursor_style = match axis { - Axis::Vertical => CursorStyle::ResizeUpDown, - Axis::Horizontal => CursorStyle::ResizeLeftRight, - }; - cx.set_cursor_style(cursor_style, stacking_order); + let handle_hitbox = cx.insert_hitbox(handle_bounds, false); + cx.paint_quad(gpui::fill(divider_bounds, cx.theme().colors().border)); + + cx.on_mouse_event({ + let dragged_handle = dragged_handle.clone(); + let flexes = flexes.clone(); + let workspace = workspace.clone(); + move |e: &MouseDownEvent, phase, cx| { + if phase.bubble() && handle_hitbox.id.is_hovered(cx) { + dragged_handle.replace(Some(ix)); + if e.click_count >= 2 { + let mut borrow = flexes.lock(); + *borrow = vec![1.; borrow.len()]; + workspace + .update(cx, |this, cx| this.schedule_serialize(cx)) + .log_err(); + + cx.refresh(); + } + cx.stop_propagation(); + } } - - cx.add_opaque_layer(handle_bounds); - cx.paint_quad(gpui::fill(divider_bounds, cx.theme().colors().border)); - - cx.on_mouse_event({ - let dragged_handle = dragged_handle.clone(); - let flexes = flexes.clone(); - let workspace = workspace.clone(); - move |e: &MouseDownEvent, phase, cx| { - if phase.bubble() && handle_bounds.contains(&e.position) { - dragged_handle.replace(Some(ix)); - if e.click_count >= 2 { - let mut borrow = flexes.lock(); - *borrow = vec![1.; borrow.len()]; - workspace - .update(cx, |this, cx| this.schedule_serialize(cx)) - .log_err(); - - cx.refresh(); - } - cx.stop_propagation(); - } + }); + cx.on_mouse_event({ + let workspace = workspace.clone(); + move |e: &MouseMoveEvent, phase, cx| { + let dragged_handle = dragged_handle.borrow(); + if phase.bubble() && *dragged_handle == Some(ix) && handle_hitbox.is_hovered(cx) + { + Self::compute_resize( + &flexes, + e, + ix, + axis, + pane_bounds.origin, + axis_bounds.size, + workspace.clone(), + cx, + ) } - }); - cx.on_mouse_event({ - let workspace = workspace.clone(); - move |e: &MouseMoveEvent, phase, cx| { - let dragged_handle = dragged_handle.borrow(); - - if phase.bubble() && *dragged_handle == Some(ix) { - Self::compute_resize( - &flexes, - e, - ix, - axis, - pane_bounds.origin, - axis_bounds.size, - workspace.clone(), - cx, - ) - } - } - }); + } }); } } @@ -812,40 +800,54 @@ mod element { impl IntoElement for PaneAxisElement { type Element = Self; - fn element_id(&self) -> Option { - Some(self.basis.into()) - } - fn into_element(self) -> Self::Element { self } } impl Element for PaneAxisElement { - type State = Rc>>; + type BeforeLayout = (); + type AfterLayout = (); - fn request_layout( + fn before_layout( &mut self, - state: Option, cx: &mut ui::prelude::ElementContext, - ) -> (gpui::LayoutId, Self::State) { + ) -> (gpui::LayoutId, Self::BeforeLayout) { let mut style = Style::default(); style.flex_grow = 1.; style.flex_shrink = 1.; style.flex_basis = relative(0.).into(); style.size.width = relative(1.).into(); style.size.height = relative(1.).into(); - let layout_id = cx.request_layout(&style, None); - let dragged_pane = state.unwrap_or_else(|| Rc::new(RefCell::new(None))); - (layout_id, dragged_pane) + (cx.request_layout(&style, None), ()) + } + + fn after_layout( + &mut self, + bounds: Bounds, + state: &mut Self::BeforeLayout, + cx: &mut ElementContext, + ) { + // we paint children below that need to be commited + todo!("implement commit bounds on pane axis element") } fn paint( &mut self, bounds: gpui::Bounds, - state: &mut Self::State, + _: &mut Self::BeforeLayout, + _: &mut Self::AfterLayout, cx: &mut ui::prelude::ElementContext, ) { + let state = cx.with_element_state::>>, _>( + Some(self.basis.into()), + |state, _cx| { + let state = state + .unwrap() + .unwrap_or_else(|| Rc::new(RefCell::new(None))); + (state.clone(), Some(state)) + }, + ); let flexes = self.flexes.lock().clone(); let len = self.children.len(); debug_assert!(flexes.len() == len); @@ -891,40 +893,34 @@ mod element { size: child_size, }; bounding_boxes.push(Some(child_bounds)); - cx.with_z_index(0, |cx| { - child.draw(origin, child_size.into(), cx); - }); + child.paint(cx); if active_pane_magnification.is_none() { - cx.with_z_index(1, |cx| { - if ix < len - 1 { - Self::push_handle( - self.flexes.clone(), - state.clone(), - self.axis, - ix, - child_bounds, - bounds, - self.workspace.clone(), - cx, - ); - } - }); + if ix < len - 1 { + Self::push_handle( + self.flexes.clone(), + state.clone(), + self.axis, + ix, + child_bounds, + bounds, + self.workspace.clone(), + cx, + ); + } } origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis)); } - cx.with_z_index(1, |cx| { - cx.on_mouse_event({ - let state = state.clone(); - move |_: &MouseUpEvent, phase, _cx| { - if phase.bubble() { - state.replace(None); - } + cx.on_mouse_event({ + let state = state.clone(); + move |_: &MouseUpEvent, phase, _cx| { + if phase.bubble() { + state.replace(None); } - }); - }) + } + }); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e31f508abf..51ba7de6cc 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2744,13 +2744,13 @@ impl Workspace { } fn render_notifications(&self, _cx: &ViewContext) -> Option

{ + // TODO! Put these in the right spo if self.notifications.is_empty() { None } else { Some( div() .absolute() - .z_index(100) .right_3() .bottom_3() .w_112() @@ -3825,18 +3825,15 @@ impl Render for Workspace { .border_t() .border_b() .border_color(colors.border) - .child( - canvas({ - let this = cx.view().clone(); - move |bounds, cx| { - this.update(cx, |this, _cx| { - this.bounds = *bounds; - }) - } - }) + .child({ + let this = cx.view().clone(); + canvas( + move |bounds, cx| this.update(cx, |this, _cx| this.bounds = bounds), + |_, _, _| {}, + ) .absolute() - .size_full(), - ) + .size_full() + }) .on_drag_move( cx.listener(|workspace, e: &DragMoveEvent, cx| { match e.drag(cx).0 { @@ -3914,7 +3911,6 @@ impl Render for Workspace { .children(self.zoomed.as_ref().and_then(|view| { let zoomed_view = view.upgrade()?; let div = div() - .z_index(1) .absolute() .overflow_hidden() .border_color(colors.border) @@ -4606,14 +4602,12 @@ pub fn titlebar_height(cx: &mut WindowContext) -> Pixels { struct DisconnectedOverlay; +// TODO! Actually render this on top impl Element for DisconnectedOverlay { - type State = AnyElement; + type BeforeLayout = AnyElement; + type AfterLayout = (); - fn request_layout( - &mut self, - _: Option, - cx: &mut ElementContext, - ) -> (LayoutId, Self::State) { + fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) { let mut background = cx.theme().colors().elevated_surface_background; background.fade_out(0.2); let mut overlay = div() @@ -4631,29 +4625,33 @@ impl Element for DisconnectedOverlay { "Your connection to the remote project has been lost.", )) .into_any(); - (overlay.request_layout(cx), overlay) + (overlay.before_layout(cx), overlay) + } + + fn after_layout( + &mut self, + bounds: Bounds, + overlay: &mut Self::BeforeLayout, + cx: &mut ElementContext, + ) { + cx.insert_hitbox(bounds, true); + overlay.after_layout(cx); } fn paint( &mut self, - bounds: Bounds, - overlay: &mut Self::State, + _: Bounds, + overlay: &mut Self::BeforeLayout, + _: &mut Self::AfterLayout, cx: &mut ElementContext, ) { - cx.with_z_index(u16::MAX, |cx| { - cx.add_opaque_layer(bounds); - overlay.paint(cx); - }) + overlay.paint(cx) } } impl IntoElement for DisconnectedOverlay { type Element = Self; - fn element_id(&self) -> Option { - None - } - fn into_element(self) -> Self::Element { self } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index c509a8d0c1..27e3a8ef50 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -268,7 +268,7 @@ fn main() { initialize_workspace(app_state.clone(), cx); if stdout_is_a_pty() { - //todo!(linux): unblock this + // todo(linux): unblock this #[cfg(not(target_os = "linux"))] upload_panics_and_crashes(http.clone(), cx); cx.activate(true); @@ -997,7 +997,7 @@ fn load_user_themes_in_background(fs: Arc, cx: &mut AppContext) { .detach_and_log_err(cx); } -//todo!(linux): Port fsevents to linux +// todo(linux): Port fsevents to linux /// Spawns a background task to watch the themes directory for changes. #[cfg(target_os = "macos")] fn watch_themes(fs: Arc, cx: &mut AppContext) {