From 4ace342cf0d47946b26f22c71cdbd67590856cc1 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 14 Dec 2023 11:59:24 -0500 Subject: [PATCH 01/27] Fix typo --- crates/storybook2/src/stories/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/storybook2/src/stories/text.rs b/crates/storybook2/src/stories/text.rs index ccd13cb4d80b1e739cef0671a3bfd54b78264fea..b279e004c7b37bc3c1d626028d86239df6e14d65 100644 --- a/crates/storybook2/src/stories/text.rs +++ b/crates/storybook2/src/stories/text.rs @@ -50,7 +50,7 @@ impl Render for TextStory { )))) // NOTE: When rendering text in a horizonal flex container, // Taffy will not pass width constraints down from the parent. - // To fix this, render text in a praent with overflow: hidden, which + // To fix this, render text in a parent with overflow: hidden .child(div().h_5()) .child(div().flex().w_96().bg(red()).child(concat!( "flex-row. width 96. The quick brown fox jumps over the lazy dog. ", From c041799c6ad32c046ac51b5233cdfe227958bd7c Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 14 Dec 2023 15:55:17 -0500 Subject: [PATCH 02/27] Extend Story components, allow linking to story file Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com> --- Cargo.lock | 2 + crates/story/Cargo.toml | 1 + crates/story/src/story.rs | 274 ++++++++++++++++++++++++-- crates/storybook2/Cargo.toml | 1 + crates/storybook2/src/stories/text.rs | 137 +++++++------ 5 files changed, 344 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4281553aff5f7100dc91be770eeda88bad9825ac..c5ec126de5934615c09f1e494b46b4f70808763e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9211,6 +9211,7 @@ name = "story" version = "0.1.0" dependencies = [ "gpui2", + "smallvec", ] [[package]] @@ -9224,6 +9225,7 @@ dependencies = [ "editor2", "fuzzy2", "gpui2", + "indoc", "itertools 0.11.0", "language2", "log", diff --git a/crates/story/Cargo.toml b/crates/story/Cargo.toml index 384447af8fe51472a7da9b614e23b364bb664d25..b042cbab807702b77610e885c997444ba5465fb8 100644 --- a/crates/story/Cargo.toml +++ b/crates/story/Cargo.toml @@ -8,3 +8,4 @@ publish = false [dependencies] gpui = { package = "gpui2", path = "../gpui2" } +smallvec.workspace = true diff --git a/crates/story/src/story.rs b/crates/story/src/story.rs index d95c879ce0068c00fd24ffb64179d4e3d46a5c7a..c656f3729db4c679ae81292f1ccaea10cb7eacaf 100644 --- a/crates/story/src/story.rs +++ b/crates/story/src/story.rs @@ -1,22 +1,109 @@ -use gpui::prelude::*; -use gpui::{div, hsla, Div, SharedString}; +use gpui::{div, hsla, AnyElement, Div, ElementId, Hsla, SharedString, Stateful, WindowContext}; +use gpui::{prelude::*, px}; +use smallvec::SmallVec; + +use std::path::PathBuf; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::{SystemTime, UNIX_EPOCH}; + +static COUNTER: AtomicUsize = AtomicUsize::new(0); + +pub fn reasonably_unique_id() -> String { + let now = SystemTime::now(); + let timestamp = now.duration_since(UNIX_EPOCH).unwrap(); + + let cnt = COUNTER.fetch_add(1, Ordering::Relaxed); + + let id = format!("{}_{}", timestamp.as_nanos(), cnt); + + id +} + +pub struct StoryColor { + pub primary: Hsla, + pub secondary: Hsla, + pub border: Hsla, + pub background: Hsla, + pub card_background: Hsla, + pub divider: Hsla, + pub link: Hsla, +} + +impl StoryColor { + pub fn new() -> Self { + Self { + primary: hsla(216. / 360., 11. / 100., 0. / 100., 1.), + secondary: hsla(216. / 360., 11. / 100., 16. / 100., 1.), + border: hsla(216. / 360., 11. / 100., 91. / 100., 1.), + background: hsla(0. / 360., 0. / 100., 100. / 100., 1.), + card_background: hsla(0. / 360., 0. / 100., 96. / 100., 1.), + divider: hsla(216. / 360., 11. / 100., 91. / 100., 1.), + link: hsla(206. / 360., 100. / 100., 50. / 100., 1.), + } + } +} + +pub fn story_color() -> StoryColor { + StoryColor::new() +} pub struct Story {} impl Story { pub fn container() -> Div { - div().size_full().flex().flex_col().pt_2().px_4().bg(hsla( - 0. / 360., - 0. / 100., - 100. / 100., - 1., - )) + div() + .size_full() + .flex() + .flex_col() + .bg(story_color().background) + } + + // TODO: Move all stories to container2, then rename + pub fn container2(relative_path: &'static str) -> Div { + div() + .size_full() + .flex() + .flex_col() + .bg(story_color().background) + .child( + div() + .flex() + .justify_between() + .p_2() + .border_b() + .border_color(story_color().border) + .child(Story::title_for::()) + .child( + div() + .text_xs() + .text_color(story_color().primary) + .child(Story::open_story_link(relative_path)), + ), + ) + } + + pub fn open_story_link(relative_path: &'static str) -> impl Element { + let path = PathBuf::from_iter([relative_path]); + div() + .id(SharedString::from(format!("id_{}", relative_path))) + .text_xs() + .text_color(story_color().primary) + .on_click({ + let path = path.clone(); + + move |_event, _cx| { + let path = format!("{}:0:0", path.to_string_lossy()); + + std::process::Command::new("zed").arg(path).spawn().ok(); + } + }) + .child(Story::link(path.to_string_lossy().to_string())) } pub fn title(title: impl Into) -> impl Element { div() - .text_xl() - .text_color(hsla(0. / 360., 0. / 100., 0. / 100., 1.)) + .text_xs() + .text_color(story_color().primary) .child(title.into()) } @@ -24,12 +111,173 @@ impl Story { Self::title(std::any::type_name::()) } + pub fn section() -> Div { + div().mt_4().mb_2() + } + + pub fn section_title() -> Div { + div().text_lg().text_color(story_color().primary) + } + + pub fn group() -> Div { + div().my_2().bg(story_color().background) + } + + pub fn code_block(code: impl Into) -> Div { + div() + .size_full() + .p_2() + .bg(gpui::black()) + .border() + .border_color(story_color().border) + .rounded_md() + .text_sm() + .text_color(gpui::white()) + .child(code.into()) + } + + pub fn divider() -> Div { + div().my_2().h(px(1.)).bg(story_color().divider) + } + + pub fn link(link: impl Into) -> impl Element { + div() + .id(ElementId::from(SharedString::from(reasonably_unique_id()))) + .text_xs() + .text_color(story_color().link) + .cursor(gpui::CursorStyle::PointingHand) + .child(link.into()) + } + + pub fn description(description: impl Into) -> impl Element { + div() + .text_sm() + .text_color(story_color().secondary) + .min_w_96() + .child(description.into()) + } + pub fn label(label: impl Into) -> impl Element { div() - .mt_4() - .mb_2() .text_xs() - .text_color(hsla(0. / 360., 0. / 100., 0. / 100., 1.)) + .text_color(story_color().primary) .child(label.into()) } + + /// Note: Not ui::v_stack() as the story crate doesn't depend on the ui crate. + pub fn v_stack() -> Div { + div().flex().flex_col().gap_1() + } +} + +#[derive(IntoElement)] +pub struct StoryItem { + label: SharedString, + item: AnyElement, + description: Option, + usage: Option, +} + +impl StoryItem { + pub fn new(label: impl Into, item: impl IntoElement) -> Self { + Self { + label: label.into(), + item: item.into_any_element(), + description: None, + usage: None, + } + } + + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } + + pub fn usage(mut self, code: impl Into) -> Self { + self.usage = Some(code.into()); + self + } +} + +impl RenderOnce for StoryItem { + type Rendered = Div; + + fn render(self, _cx: &mut WindowContext) -> Self::Rendered { + div() + .my_2() + .flex() + .w_full() + .child( + Story::v_stack() + .px_2() + .flex_none() + .w_1_2() + .min_h_px() + .child(Story::label(self.label)) + .child( + div() + .rounded_sm() + .bg(story_color().card_background) + .border() + .border_color(story_color().border) + .child(self.item), + ) + .when_some(self.description, |this, description| { + this.child(Story::description(description)) + }), + ) + .child( + Story::v_stack() + .px_2() + .flex_none() + .w_1_2() + .min_h_px() + .when_some(self.usage, |this, usage| { + this.child(Story::label("Usage")) + .child(Story::code_block(usage)) + }), + ) + } +} + +#[derive(IntoElement)] +pub struct StorySection { + description: Option, + children: SmallVec<[AnyElement; 2]>, +} + +impl StorySection { + pub fn new() -> Self { + Self { + description: None, + children: SmallVec::new(), + } + } + + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } +} + +impl RenderOnce for StorySection { + type Rendered = Div; + + fn render(self, _cx: &mut WindowContext) -> Self::Rendered { + Story::section() + // Section title + .py_2() + // Section description + .when_some(self.description.clone(), |section, description| { + section.child(Story::description(description)) + }) + .child(div().flex().flex_col().gap_2().children(self.children)) + .child(Story::divider()) + } +} + +impl ParentElement for StorySection { + fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { + &mut self.children + } } diff --git a/crates/storybook2/Cargo.toml b/crates/storybook2/Cargo.toml index 949d07b06f83bc4b18aaa7faa6b6c4ebfa2899d0..672bebe20e1ea59124833c00615c4da44cc65057 100644 --- a/crates/storybook2/Cargo.toml +++ b/crates/storybook2/Cargo.toml @@ -22,6 +22,7 @@ editor = { package = "editor2", path = "../editor2" } chrono = "0.4" fuzzy = { package = "fuzzy2", path = "../fuzzy2" } gpui = { package = "gpui2", path = "../gpui2" } +indoc.workspace = true itertools = "0.11.0" language = { package = "language2", path = "../language2" } log.workspace = true diff --git a/crates/storybook2/src/stories/text.rs b/crates/storybook2/src/stories/text.rs index b279e004c7b37bc3c1d626028d86239df6e14d65..279a2dbf4d24051aca3ec82263ea8821bb24ae7b 100644 --- a/crates/storybook2/src/stories/text.rs +++ b/crates/storybook2/src/stories/text.rs @@ -1,8 +1,6 @@ -use gpui::{ - blue, div, green, red, white, Div, HighlightStyle, InteractiveText, ParentElement, Render, - Styled, StyledText, View, VisualContext, WindowContext, -}; -use ui::v_stack; +use gpui::{div, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext}; +use indoc::indoc; +use story::*; pub struct TextStory; @@ -16,59 +14,82 @@ impl Render for TextStory { type Element = Div; fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { - v_stack() - .bg(blue()) - .child( - div() - .flex() - .child(div().max_w_96().bg(white()).child(concat!( - "max-width: 96. The quick brown fox jumps over the lazy dog. ", - "Meanwhile, the lazy dog decided it was time for a change. ", - "He started daily workout routines, ate healthier and became the fastest dog in town.", - ))), - ) - .child(div().h_5()) - .child(div().flex().flex_col().w_96().bg(white()).child(concat!( - "flex-col. width: 96; The quick brown fox jumps over the lazy dog. ", - "Meanwhile, the lazy dog decided it was time for a change. ", - "He started daily workout routines, ate healthier and became the fastest dog in town.", - ))) - .child(div().h_5()) - .child( - div() - .flex() - .child(div().min_w_96().bg(white()).child(concat!( - "min-width: 96. The quick brown fox jumps over the lazy dog. ", - "Meanwhile, the lazy dog decided it was time for a change. ", - "He started daily workout routines, ate healthier and became the fastest dog in town.", -)))) - .child(div().h_5()) - .child(div().flex().w_96().bg(white()).child(div().overflow_hidden().child(concat!( - "flex-row. width 96. overflow-hidden. The quick brown fox jumps over the lazy dog. ", - "Meanwhile, the lazy dog decided it was time for a change. ", - "He started daily workout routines, ate healthier and became the fastest dog in town.", - )))) - // NOTE: When rendering text in a horizonal flex container, - // Taffy will not pass width constraints down from the parent. - // To fix this, render text in a parent with overflow: hidden - .child(div().h_5()) - .child(div().flex().w_96().bg(red()).child(concat!( - "flex-row. width 96. The quick brown fox jumps over the lazy dog. ", - "Meanwhile, the lazy dog decided it was time for a change. ", - "He started daily workout routines, ate healthier and became the fastest dog in town.", - ))).child( - InteractiveText::new( - "interactive", - StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [ - (6..11, HighlightStyle { - background_color: Some(green()), - ..Default::default() - }), - ]), + // let # = "The quick brown fox jumps over the lazy dog. Meanwhile, the lazy dog decided it was time for a change. He started daily workout routines, ate healthier and became the fastest dog in town."; + + Story::container2::("crates/storybook2/src/stories/text.rs").child( + StorySection::new().child( + StoryItem::new( + "Default Text", + div().flex().child(div().max_w_96().child("foo")), ) - .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| { - println!("Clicked range {range_ix}"); - }) - ) + .description("Text with a max-width. Wraps based on set max-width.") + .usage(indoc! {r##" + div().max_w_96() + .child("Some text that you want to wrap.") + "## + }), + ), + ) } } + +// impl Render for TextStory { +// type Element = Div; + +// fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { +// v_stack() +// .bg(blue()) +// .child( +// div() +// .flex() +// .child(div().max_w_96().bg(white()).child(concat!( +// "max-width: 96. The quick brown fox jumps over the lazy dog. ", +// "Meanwhile, the lazy dog decided it was time for a change. ", +// "He started daily workout routines, ate healthier and became the fastest dog in town.", +// ))), +// ) +// .child(div().h_5()) +// .child(div().flex().flex_col().w_96().bg(white()).child(concat!( +// "flex-col. width: 96; The quick brown fox jumps over the lazy dog. ", +// "Meanwhile, the lazy dog decided it was time for a change. ", +// "He started daily workout routines, ate healthier and became the fastest dog in town.", +// ))) +// .child(div().h_5()) +// .child( +// div() +// .flex() +// .child(div().min_w_96().bg(white()).child(concat!( +// "min-width: 96. The quick brown fox jumps over the lazy dog. ", +// "Meanwhile, the lazy dog decided it was time for a change. ", +// "He started daily workout routines, ate healthier and became the fastest dog in town.", +// )))) +// .child(div().h_5()) +// .child(div().flex().w_96().bg(white()).child(div().overflow_hidden().child(concat!( +// "flex-row. width 96. overflow-hidden. The quick brown fox jumps over the lazy dog. ", +// "Meanwhile, the lazy dog decided it was time for a change. ", +// "He started daily workout routines, ate healthier and became the fastest dog in town.", +// )))) +// // NOTE: When rendering text in a horizonal flex container, +// // Taffy will not pass width constraints down from the parent. +// // To fix this, render text in a parent with overflow: hidden +// .child(div().h_5()) +// .child(div().flex().w_96().bg(red()).child(concat!( +// "flex-row. width 96. The quick brown fox jumps over the lazy dog. ", +// "Meanwhile, the lazy dog decided it was time for a change. ", +// "He started daily workout routines, ate healthier and became the fastest dog in town.", +// ))).child( +// InteractiveText::new( +// "interactive", +// StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [ +// (6..11, HighlightStyle { +// background_color: Some(green()), +// ..Default::default() +// }), +// ]), +// ) +// .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| { +// println!("Clicked range {range_ix}"); +// }) +// ) +// } +// } From 63c3edfb83a223c0822a160fc60e7a7de9c0d155 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 14 Dec 2023 16:52:05 -0500 Subject: [PATCH 03/27] Continue styling new story components --- Cargo.lock | 1 + crates/story/Cargo.toml | 1 + crates/story/src/story.rs | 34 +++++++++---- crates/storybook2/src/stories/text.rs | 72 +++++++++++++++++++++------ 4 files changed, 83 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5ec126de5934615c09f1e494b46b4f70808763e..1c74b49738e5703537ea5e43d0e0d067695b89c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9211,6 +9211,7 @@ name = "story" version = "0.1.0" dependencies = [ "gpui2", + "itertools 0.10.5", "smallvec", ] diff --git a/crates/story/Cargo.toml b/crates/story/Cargo.toml index b042cbab807702b77610e885c997444ba5465fb8..9c195f77f05f591474e739834a8b8965c3822fb4 100644 --- a/crates/story/Cargo.toml +++ b/crates/story/Cargo.toml @@ -9,3 +9,4 @@ publish = false [dependencies] gpui = { package = "gpui2", path = "../gpui2" } smallvec.workspace = true +itertools = {package = "itertools", version = "0.10"} diff --git a/crates/story/src/story.rs b/crates/story/src/story.rs index c656f3729db4c679ae81292f1ccaea10cb7eacaf..4985fb1961c3e4957d8ad9b8947929fee996fe52 100644 --- a/crates/story/src/story.rs +++ b/crates/story/src/story.rs @@ -1,5 +1,7 @@ -use gpui::{div, hsla, AnyElement, Div, ElementId, Hsla, SharedString, Stateful, WindowContext}; -use gpui::{prelude::*, px}; +use gpui::{ + div, hsla, prelude::*, px, rems, AnyElement, Div, ElementId, Hsla, SharedString, WindowContext, +}; +use itertools::Itertools; use smallvec::SmallVec; use std::path::PathBuf; @@ -37,7 +39,7 @@ impl StoryColor { border: hsla(216. / 360., 11. / 100., 91. / 100., 1.), background: hsla(0. / 360., 0. / 100., 100. / 100., 1.), card_background: hsla(0. / 360., 0. / 100., 96. / 100., 1.), - divider: hsla(216. / 360., 11. / 100., 91. / 100., 1.), + divider: hsla(216. / 360., 11. / 100., 86. / 100., 1.), link: hsla(206. / 360., 100. / 100., 50. / 100., 1.), } } @@ -112,7 +114,11 @@ impl Story { } pub fn section() -> Div { - div().mt_4().mb_2() + div() + .p_4() + .m_4() + .border() + .border_color(story_color().border) } pub fn section_title() -> Div { @@ -127,12 +133,12 @@ impl Story { div() .size_full() .p_2() + .max_w(rems(36.)) .bg(gpui::black()) - .border() - .border_color(story_color().border) .rounded_md() .text_sm() .text_color(gpui::white()) + .overflow_hidden() .child(code.into()) } @@ -206,20 +212,23 @@ impl RenderOnce for StoryItem { div() .my_2() .flex() + .gap_4() .w_full() .child( Story::v_stack() .px_2() - .flex_none() .w_1_2() .min_h_px() .child(Story::label(self.label)) .child( div() - .rounded_sm() + .rounded_md() .bg(story_color().card_background) .border() .border_color(story_color().border) + .py_1() + .px_2() + .overflow_hidden() .child(self.item), ) .when_some(self.description, |this, description| { @@ -233,7 +242,7 @@ impl RenderOnce for StoryItem { .w_1_2() .min_h_px() .when_some(self.usage, |this, usage| { - this.child(Story::label("Usage")) + this.child(Story::label("Example Usage")) .child(Story::code_block(usage)) }), ) @@ -264,6 +273,11 @@ impl RenderOnce for StorySection { type Rendered = Div; fn render(self, _cx: &mut WindowContext) -> Self::Rendered { + let children: SmallVec<[AnyElement; 2]> = SmallVec::from_iter(Itertools::intersperse_with( + self.children.into_iter(), + || Story::divider().into_any_element(), + )); + Story::section() // Section title .py_2() @@ -271,7 +285,7 @@ impl RenderOnce for StorySection { .when_some(self.description.clone(), |section, description| { section.child(Story::description(description)) }) - .child(div().flex().flex_col().gap_2().children(self.children)) + .child(div().flex().flex_col().gap_2().children(children)) .child(Story::divider()) } } diff --git a/crates/storybook2/src/stories/text.rs b/crates/storybook2/src/stories/text.rs index 279a2dbf4d24051aca3ec82263ea8821bb24ae7b..b48b9dd4b5ea438564499ec80654e6f052aa4396 100644 --- a/crates/storybook2/src/stories/text.rs +++ b/crates/storybook2/src/stories/text.rs @@ -1,4 +1,4 @@ -use gpui::{div, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext}; +use gpui::{div, red, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext}; use indoc::indoc; use story::*; @@ -13,22 +13,64 @@ impl TextStory { impl Render for TextStory { type Element = Div; - fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { - // let # = "The quick brown fox jumps over the lazy dog. Meanwhile, the lazy dog decided it was time for a change. He started daily workout routines, ate healthier and became the fastest dog in town."; - + fn render(&mut self, _cx: &mut gpui::ViewContext) -> Self::Element { Story::container2::("crates/storybook2/src/stories/text.rs").child( - StorySection::new().child( - StoryItem::new( - "Default Text", - div().flex().child(div().max_w_96().child("foo")), + StorySection::new() + .child( + StoryItem::new("Default", div().bg(gpui::blue()).child("Hello World!")) + .usage(indoc! {r##" + div() + .child("Hello World!") + "## + }), + ) + .child( + StoryItem::new("Wrapping Text", + div().max_w_96() + .child( + concat!( + "The quick brown fox jumps over the lazy dog. ", + "Meanwhile, the lazy dog decided it was time for a change. ", + "He started daily workout routines, ate healthier and became the fastest dog in town.", + ) + ) + ) + .description("Set a width or max-width to enable text wrapping.") + .usage(indoc! {r##" + div() + .max_w_96() + .child("Some text that you want to wrap.") + "## + }) + ) + .child( + StoryItem::new("tbd", + div().flex().w_96().child(div().overflow_hidden().child(concat!( + "flex-row. width 96. overflow-hidden. The quick brown fox jumps over the lazy dog. ", + "Meanwhile, the lazy dog decided it was time for a change. ", + "He started daily workout routines, ate healthier and became the fastest dog in town.", + ))) + ) + ) + .child( + StoryItem::new("Text in Horizontal Flex", + div().flex().w_96().bg(red()).child(concat!( + "flex-row. width 96. The quick brown fox jumps over the lazy dog. ", + "Meanwhile, the lazy dog decided it was time for a change. ", + "He started daily workout routines, ate healthier and became the fastest dog in town.", + )) + ) + .usage(indoc! {r##" + // NOTE: When rendering text in a horizonal flex container, + // Taffy will not pass width constraints down from the parent. + // To fix this, render text in a parent with overflow: hidden + + div() + .max_w_96() + .child("Some text that you want to wrap.") + "## + }) ) - .description("Text with a max-width. Wraps based on set max-width.") - .usage(indoc! {r##" - div().max_w_96() - .child("Some text that you want to wrap.") - "## - }), - ), ) } } From 3cf003763e3af497351f6294b10259cce081348c Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 14 Dec 2023 17:38:22 -0500 Subject: [PATCH 04/27] Use updated story container in Text story --- crates/story/src/story.rs | 118 ++++++++++++++++++++++---- crates/storybook2/src/stories/text.rs | 50 +++++++++-- 2 files changed, 148 insertions(+), 20 deletions(-) diff --git a/crates/story/src/story.rs b/crates/story/src/story.rs index 4985fb1961c3e4957d8ad9b8947929fee996fe52..3419af95b099807609b52b40b9775b17457975a0 100644 --- a/crates/story/src/story.rs +++ b/crates/story/src/story.rs @@ -1,5 +1,6 @@ use gpui::{ - div, hsla, prelude::*, px, rems, AnyElement, Div, ElementId, Hsla, SharedString, WindowContext, + div, hsla, prelude::*, px, rems, AnyElement, Div, ElementId, Hsla, SharedString, Stateful, + WindowContext, }; use itertools::Itertools; use smallvec::SmallVec; @@ -49,47 +50,134 @@ pub fn story_color() -> StoryColor { StoryColor::new() } -pub struct Story {} +#[derive(IntoElement)] +pub struct StoryContainer { + title: SharedString, + relative_path: &'static str, + children: SmallVec<[AnyElement; 2]>, +} -impl Story { - pub fn container() -> Div { - div() - .size_full() - .flex() - .flex_col() - .bg(story_color().background) +impl StoryContainer { + pub fn new(title: impl Into, relative_path: &'static str) -> Self { + Self { + title: title.into(), + relative_path, + children: SmallVec::new(), + } } +} - // TODO: Move all stories to container2, then rename - pub fn container2(relative_path: &'static str) -> Div { +impl ParentElement for StoryContainer { + fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { + &mut self.children + } +} + +impl RenderOnce for StoryContainer { + type Rendered = Stateful
; + + fn render(self, _cx: &mut WindowContext) -> Self::Rendered { div() .size_full() .flex() .flex_col() + .id("story_container") .bg(story_color().background) .child( div() .flex() + .flex_none() + .w_full() .justify_between() .p_2() + .bg(story_color().background) .border_b() .border_color(story_color().border) - .child(Story::title_for::()) + .child(Story::title(self.title)) .child( div() .text_xs() .text_color(story_color().primary) - .child(Story::open_story_link(relative_path)), + .child(Story::open_story_link(self.relative_path)), ), ) + .child( + div() + .w_full() + .h_px() + .flex_1() + .id("story_body") + .overflow_hidden_x() + .overflow_y_scroll() + .flex() + .flex_col() + .pb_4() + .children(self.children), + ) + } +} + +pub struct Story {} + +impl Story { + pub fn container() -> Div { + div().size_full().overflow_hidden().child( + div() + .id("story_container") + .overflow_y_scroll() + .w_full() + .min_h_full() + .flex() + .flex_col() + .bg(story_color().background), + ) + } + + // TODO: Move all stories to container2, then rename + pub fn container2(relative_path: &'static str) -> Div { + div().size_full().child( + div() + .size_full() + .id("story_container") + .overflow_y_scroll() + .flex() + .flex_col() + .flex_none() + .child( + div() + .flex() + .justify_between() + .p_2() + .border_b() + .border_color(story_color().border) + .child(Story::title_for::()) + .child( + div() + .text_xs() + .text_color(story_color().primary) + .child(Story::open_story_link(relative_path)), + ), + ) + .child( + div() + .w_full() + .min_h_full() + .flex() + .flex_col() + .bg(story_color().background), + ), + ) } pub fn open_story_link(relative_path: &'static str) -> impl Element { let path = PathBuf::from_iter([relative_path]); + div() - .id(SharedString::from(format!("id_{}", relative_path))) + .flex() + .gap_2() .text_xs() .text_color(story_color().primary) + .id(SharedString::from(format!("id_{}", relative_path))) .on_click({ let path = path.clone(); @@ -99,7 +187,7 @@ impl Story { std::process::Command::new("zed").arg(path).spawn().ok(); } }) - .child(Story::link(path.to_string_lossy().to_string())) + .children(vec![div().child(Story::link("Open in Zed →"))]) } pub fn title(title: impl Into) -> impl Element { diff --git a/crates/storybook2/src/stories/text.rs b/crates/storybook2/src/stories/text.rs index b48b9dd4b5ea438564499ec80654e6f052aa4396..7c1d29a2693dd6ba5dd83dfdbbe0030dea261d42 100644 --- a/crates/storybook2/src/stories/text.rs +++ b/crates/storybook2/src/stories/text.rs @@ -1,4 +1,7 @@ -use gpui::{div, red, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext}; +use gpui::{ + div, green, red, Component, Div, HighlightStyle, InteractiveText, IntoElement, ParentElement, + Render, Styled, StyledText, View, VisualContext, WindowContext, +}; use indoc::indoc; use story::*; @@ -11,10 +14,13 @@ impl TextStory { } impl Render for TextStory { - type Element = Div; + type Element = Component; + + fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { + StoryContainer::new("Text Story", "crates/storybook2/src/stories/text.rs") + .children( + vec![ - fn render(&mut self, _cx: &mut gpui::ViewContext) -> Self::Element { - Story::container2::("crates/storybook2/src/stories/text.rs").child( StorySection::new() .child( StoryItem::new("Default", div().bg(gpui::blue()).child("Hello World!")) @@ -71,10 +77,44 @@ impl Render for TextStory { "## }) ) - ) + .child( + StoryItem::new("Interactive Text", + InteractiveText::new( + "interactive", + StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [ + (6..11, HighlightStyle { + background_color: Some(green()), + ..Default::default() + }), + ]), + ) + .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| { + println!("Clicked range {range_ix}"); + }) + ) + .usage(indoc! {r##" + InteractiveText::new( + "interactive", + StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [ + (6..11, HighlightStyle { + background_color: Some(green()), + ..Default::default() + }), + ]), + ) + .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| { + println!("Clicked range {range_ix}"); + }) + "## + }) + ) + ] + ).into_element() } } +// TODO: Check all were updated to new style and remove + // impl Render for TextStory { // type Element = Div; From 936c78be941cbf6948b93c81e6e9ca07def9f4f2 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 14 Dec 2023 17:51:08 -0500 Subject: [PATCH 05/27] WIP IconButton story --- crates/storybook2/src/stories/text.rs | 2 +- .../ui2/src/components/stories/icon_button.rs | 214 +++++++++++++----- 2 files changed, 163 insertions(+), 53 deletions(-) diff --git a/crates/storybook2/src/stories/text.rs b/crates/storybook2/src/stories/text.rs index 7c1d29a2693dd6ba5dd83dfdbbe0030dea261d42..99d722988d086d932c6bd734992720273f02e2e3 100644 --- a/crates/storybook2/src/stories/text.rs +++ b/crates/storybook2/src/stories/text.rs @@ -1,5 +1,5 @@ use gpui::{ - div, green, red, Component, Div, HighlightStyle, InteractiveText, IntoElement, ParentElement, + div, green, red, Component, HighlightStyle, InteractiveText, IntoElement, ParentElement, Render, Styled, StyledText, View, VisualContext, WindowContext, }; use indoc::indoc; diff --git a/crates/ui2/src/components/stories/icon_button.rs b/crates/ui2/src/components/stories/icon_button.rs index 583f453d188b9df5aaaf71b5bb1897814c0a6f51..5391f0ac182c1a2a0d02bdead7350f46b45c1f6c 100644 --- a/crates/ui2/src/components/stories/icon_button.rs +++ b/crates/ui2/src/components/stories/icon_button.rs @@ -1,5 +1,5 @@ -use gpui::{Div, Render}; -use story::Story; +use gpui::{Component, Div, Render}; +use story::{Story, StoryContainer, StoryItem, StorySection}; use crate::{prelude::*, Tooltip}; use crate::{Icon, IconButton}; @@ -7,57 +7,167 @@ use crate::{Icon, IconButton}; pub struct IconButtonStory; impl Render for IconButtonStory { - type Element = Div; + type Element = Component; fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { - Story::container() - .child(Story::title_for::()) - .child(Story::label("Default")) - .child(div().w_8().child(IconButton::new("icon_a", Icon::Hash))) - .child(Story::label("Selected")) - .child( - div() - .w_8() - .child(IconButton::new("icon_a", Icon::Hash).selected(true)), - ) - .child(Story::label("Selected with `selected_icon`")) - .child( - div().w_8().child( - IconButton::new("icon_a", Icon::AudioOn) - .selected(true) - .selected_icon(Icon::AudioOff), - ), - ) - .child(Story::label("Disabled")) - .child( - div() - .w_8() - .child(IconButton::new("icon_a", Icon::Hash).disabled(true)), - ) - .child(Story::label("With `on_click`")) - .child( - div() - .w_8() - .child( - IconButton::new("with_on_click", Icon::Ai).on_click(|_event, _cx| { - println!("Clicked!"); - }), - ), - ) - .child(Story::label("With `tooltip`")) - .child( - div().w_8().child( - IconButton::new("with_tooltip", Icon::MessageBubbles) - .tooltip(|cx| Tooltip::text("Open messages", cx)), - ), - ) - .child(Story::label("Selected with `tooltip`")) - .child( - div().w_8().child( - IconButton::new("selected_with_tooltip", Icon::InlayHint) - .selected(true) - .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)), - ), - ) + let default_button = StoryItem::new( + "Default", + IconButton::new("default_icon_button", Icon::Hash), + ) + .description("Displays an icon button.") + .usage( + r#" + IconButton::new("default_icon_button", Icon::Hash) + "#, + ); + + let selected_button = StoryItem::new( + "Selected", + IconButton::new("selected_icon_button", Icon::Hash).selected(true), + ) + .description("Displays an icon button that is selected.") + .usage( + r#" + IconButton::new("selected_icon_button", Icon::Hash).selected(true) + "#, + ); + + let selected_with_selected_icon = StoryItem::new( + "Selected with `selected_icon`", + IconButton::new("selected_with_selected_icon_button", Icon::AudioOn) + .selected(true) + .selected_icon(Icon::AudioOff), + ) + .description( + "Displays an icon button that is selected and shows a different icon when selected.", + ) + .usage( + r#" + IconButton::new("selected_with_selected_icon_button", Icon::AudioOn) + .selected(true) + .selected_icon(Icon::AudioOff) + "#, + ); + + let disabled_button = StoryItem::new( + "Disabled", + IconButton::new("disabled_icon_button", Icon::Hash).disabled(true), + ) + .description("Displays an icon button that is disabled.") + .usage( + r#" + IconButton::new("disabled_icon_button", Icon::Hash).disabled(true) + "#, + ); + + let with_on_click_button = StoryItem::new( + "With `on_click`", + IconButton::new("with_on_click_button", Icon::Ai).on_click(|_event, _cx| { + println!("Clicked!"); + }), + ) + .description("Displays an icon button which triggers an event on click.") + .usage( + r#" + IconButton::new("with_on_click_button", Icon::Ai).on_click(|_event, _cx| { + println!("Clicked!"); + }) + "#, + ); + + let with_tooltip_button = StoryItem::new( + "With `tooltip`", + IconButton::new("with_tooltip_button", Icon::MessageBubbles) + .tooltip(|cx| Tooltip::text("Open messages", cx)), + ) + .description("Displays an icon button that has a tooltip when hovered.") + .usage( + r#" + IconButton::new("with_tooltip_button", Icon::MessageBubbles) + .tooltip(|cx| Tooltip::text("Open messages", cx)) + "#, + ); + + let selected_with_tooltip_button = StoryItem::new( + "Selected with `tooltip`", + IconButton::new("selected_with_tooltip_button", Icon::InlayHint) + .selected(true) + .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)), + ) + .description("Displays a selected icon button with tooltip.") + .usage( + r#" + IconButton::new("selected_with_tooltip_button", Icon::InlayHint) + .selected(true) + .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)) + "#, + ); + + let buttons = vec![ + default_button, + selected_button, + selected_with_selected_icon, + disabled_button, + with_on_click_button, + with_tooltip_button, + selected_with_tooltip_button, + ]; + + StoryContainer::new( + "Icon Button", + "crates/ui2/src/components/stories/icon_button.rs", + ) + .children(vec![StorySection::new().children(buttons)]) + .into_element() + + // Story::container() + // .child(Story::title_for::()) + // .child(Story::label("Default")) + // .child(div().w_8().child(IconButton::new("icon_a", Icon::Hash))) + // .child(Story::label("Selected")) + // .child( + // div() + // .w_8() + // .child(IconButton::new("icon_a", Icon::Hash).selected(true)), + // ) + // .child(Story::label("Selected with `selected_icon`")) + // .child( + // div().w_8().child( + // IconButton::new("icon_a", Icon::AudioOn) + // .selected(true) + // .selected_icon(Icon::AudioOff), + // ), + // ) + // .child(Story::label("Disabled")) + // .child( + // div() + // .w_8() + // .child(IconButton::new("icon_a", Icon::Hash).disabled(true)), + // ) + // .child(Story::label("With `on_click`")) + // .child( + // div() + // .w_8() + // .child( + // IconButton::new("with_on_click", Icon::Ai).on_click(|_event, _cx| { + // println!("Clicked!"); + // }), + // ), + // ) + // .child(Story::label("With `tooltip`")) + // .child( + // div().w_8().child( + // IconButton::new("with_tooltip", Icon::MessageBubbles) + // .tooltip(|cx| Tooltip::text("Open messages", cx)), + // ), + // ) + // .child(Story::label("Selected with `tooltip`")) + // .child( + // div().w_8().child( + // IconButton::new("selected_with_tooltip", Icon::InlayHint) + // .selected(true) + // .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)), + // ), + // ) } } From e4f9bddbab32140f23840037a3d8989bc665aecc Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 14 Dec 2023 17:56:42 -0500 Subject: [PATCH 06/27] Remove unused imports --- crates/ui2/src/components/stories/icon_button.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ui2/src/components/stories/icon_button.rs b/crates/ui2/src/components/stories/icon_button.rs index 5391f0ac182c1a2a0d02bdead7350f46b45c1f6c..0d219efbc2fe72ac4e7695e2f86bf305d26c5537 100644 --- a/crates/ui2/src/components/stories/icon_button.rs +++ b/crates/ui2/src/components/stories/icon_button.rs @@ -1,5 +1,5 @@ -use gpui::{Component, Div, Render}; -use story::{Story, StoryContainer, StoryItem, StorySection}; +use gpui::{Component, Render}; +use story::{StoryContainer, StoryItem, StorySection}; use crate::{prelude::*, Tooltip}; use crate::{Icon, IconButton}; From a6403aad1a246a5ca1013cac8c5c37407eada436 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 15 Dec 2023 11:28:48 +0200 Subject: [PATCH 07/27] Remove extra nits, do not panic on clicking the buffer separator --- crates/editor2/src/element.rs | 4 ++-- crates/gpui2/src/platform/mac/metal_renderer.rs | 5 ----- crates/gpui2/src/view.rs | 11 ----------- crates/gpui2/src/window.rs | 2 -- 4 files changed, 2 insertions(+), 20 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 0f1b565b9d818504e905ca2a668578ffafc04069..a6d4bc20b157d4f80a816d5dc78eb7980277cdc2 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -2284,8 +2284,8 @@ impl EditorElement { .cursor_pointer() .hover(|style| style.bg(cx.theme().colors().element_hover)) .on_click(cx.listener(|_editor, _event, _cx| { - // TODO: Implement collapsing path headers - todo!("Clicking path header") + // todo!() Implement collapsing path headers + // todo!("Clicking path header") })) .child( h_stack() diff --git a/crates/gpui2/src/platform/mac/metal_renderer.rs b/crates/gpui2/src/platform/mac/metal_renderer.rs index 3210a53c634e81e5410e732c2971c86a553c808f..68768521ee474cd25c2e98c8d38c7ea669fba1e4 100644 --- a/crates/gpui2/src/platform/mac/metal_renderer.rs +++ b/crates/gpui2/src/platform/mac/metal_renderer.rs @@ -187,8 +187,6 @@ impl MetalRenderer { } pub fn draw(&mut self, scene: &Scene) { - let start = std::time::Instant::now(); - let layer = self.layer.clone(); let viewport_size = layer.drawable_size(); let viewport_size: Size = size( @@ -306,9 +304,6 @@ impl MetalRenderer { command_buffer.commit(); self.sprite_atlas.clear_textures(AtlasTextureKind::Path); - let duration_since_start = start.elapsed(); - println!("renderer draw: {:?}", duration_since_start); - command_buffer.wait_until_completed(); drawable.present(); } diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index fb61190731bddf4859e3fcd5ebbc374976a4b9ca..1b4c2b6346fb56320737ab74a8057da589aba354 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -209,20 +209,9 @@ impl AnyView { cx: &mut WindowContext, ) { cx.with_absolute_element_offset(origin, |cx| { - let start_time = std::time::Instant::now(); let (layout_id, mut rendered_element) = (self.layout)(self, cx); - let duration = start_time.elapsed(); - println!("request layout: {:?}", duration); - - let start_time = std::time::Instant::now(); cx.compute_layout(layout_id, available_space); - let duration = start_time.elapsed(); - println!("compute layout: {:?}", duration); - - let start_time = std::time::Instant::now(); (self.paint)(self, &mut rendered_element, cx); - let duration = start_time.elapsed(); - println!("paint: {:?}", duration); }) } } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 1c0849785b69b3553bed6fff451c47ecec7c2570..74c7204048d781a2dd9d01ae95cef28c85b78ab6 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1255,7 +1255,6 @@ impl<'a> WindowContext<'a> { /// Draw pixels to the display for this window based on the contents of its scene. pub(crate) fn draw(&mut self) -> Scene { - let t0 = std::time::Instant::now(); self.window.dirty = false; self.window.drawing = true; @@ -1347,7 +1346,6 @@ impl<'a> WindowContext<'a> { } self.window.drawing = false; - eprintln!("window draw: {:?}", t0.elapsed()); scene } From 31ff7d40ed67ac55d19f1d4b051d27003765e86f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 15 Dec 2023 11:34:00 +0200 Subject: [PATCH 08/27] Adjust copy/paste buffer only on the copy error action trigger --- crates/editor2/src/editor.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index e58aa1000d475caa009b045b89e36145c69b3894..664d1d7380634426aa4a8264c4ed66aef008c265 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -9739,12 +9739,8 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend }; highlighted_lines.push(line); } - let message = diagnostic.message; Arc::new(move |cx: &mut BlockContext| { - let message = message.clone(); let copy_id: SharedString = format!("copy-{}", cx.block_id.clone()).to_string().into(); - let write_to_clipboard = cx.write_to_clipboard(ClipboardItem::new(message.clone())); - // TODO: Nate: We should tint the background of the block with the severity color // We need to extend the theme before we can do this v_stack() @@ -9754,7 +9750,6 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend .bg(gpui::red()) .children(highlighted_lines.iter().map(|(line, highlights)| { let group_id = cx.block_id.to_string(); - h_stack() .group(group_id.clone()) .gap_2() @@ -9769,7 +9764,12 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend .size(ButtonSize::Compact) .style(ButtonStyle::Transparent) .visible_on_hover(group_id) - .on_click(cx.listener(move |_, _, cx| write_to_clipboard)) + .on_click(cx.listener({ + let message = diagnostic.message.clone(); + move |_, _, cx| { + cx.write_to_clipboard(ClipboardItem::new(message.clone())) + } + })) .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)), ), ) From 4bfe46f53a6eaacb02a748e8c33ed6ed24e4929c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 15 Dec 2023 12:15:20 +0200 Subject: [PATCH 09/27] Restore zed1 behavior for buffer search deploy --- crates/search2/src/buffer_search.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/search2/src/buffer_search.rs b/crates/search2/src/buffer_search.rs index 45495e502700ee65c3015a9f6fd78bc22fe5053c..5db7aff73670161719188a8cff5051c4a4a46604 100644 --- a/crates/search2/src/buffer_search.rs +++ b/crates/search2/src/buffer_search.rs @@ -338,7 +338,9 @@ impl BufferSearchBar { pane.update(cx, |this, cx| { this.toolbar().update(cx, |this, cx| { if let Some(search_bar) = this.item_of_type::() { - search_bar.update(cx, |this, cx| this.toggle(deploy, cx)); + search_bar.update(cx, |this, cx| { + this.deploy(deploy, cx); + }); return; } let view = cx.build_view(|cx| BufferSearchBar::new(cx)); @@ -1483,9 +1485,9 @@ mod tests { search_bar.select_all_matches(&SelectAllMatches, cx); }); assert!( - editor.update(cx, |this, cx| !this.is_focused(cx.window_context())), - "Should not switch focus to editor if SelectAllMatches does not find any matches" - ); + editor.update(cx, |this, cx| !this.is_focused(cx.window_context())), + "Should not switch focus to editor if SelectAllMatches does not find any matches" + ); search_bar.update(cx, |search_bar, cx| { let all_selections = editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); @@ -1651,6 +1653,7 @@ mod tests { assert_eq!(search_bar.search_options, SearchOptions::NONE); }); } + #[gpui::test] async fn test_replace_simple(cx: &mut TestAppContext) { let (editor, search_bar, cx) = init_test(cx); From 2b3d9deabe66e601e8b984a07188ade678e2689d Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 15 Dec 2023 12:07:25 +0100 Subject: [PATCH 10/27] Dismiss Recent Projects & VCS modals on ESC (#3671) Release Notes: - N/A --- crates/recent_projects2/src/recent_projects.rs | 5 ++++- crates/vcs_menu2/src/lib.rs | 9 +++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/recent_projects2/src/recent_projects.rs b/crates/recent_projects2/src/recent_projects.rs index dff6aa12ccc30f43766451d244619159c2a7c8bb..3ecf1180af535ea23190bd41ad2c8dd0e329810d 100644 --- a/crates/recent_projects2/src/recent_projects.rs +++ b/crates/recent_projects2/src/recent_projects.rs @@ -76,7 +76,10 @@ impl RecentProjects { let delegate = RecentProjectsDelegate::new(weak_workspace, workspace_locations, true); - RecentProjects::new(delegate, cx) + let modal = RecentProjects::new(delegate, cx); + cx.subscribe(&modal.picker, |_, _, _, cx| cx.emit(DismissEvent)) + .detach(); + modal }); } else { workspace.show_notification(0, cx, |cx| { diff --git a/crates/vcs_menu2/src/lib.rs b/crates/vcs_menu2/src/lib.rs index e867e04dcdb96229eb7513f891d60cc31ce1ec26..ca3b685aa60ad93f63cc2ba203fa52a184ff564b 100644 --- a/crates/vcs_menu2/src/lib.rs +++ b/crates/vcs_menu2/src/lib.rs @@ -65,8 +65,13 @@ impl ModalBranchList { ) -> Result<()> { // Modal branch picker has a longer trailoff than a popover one. let delegate = BranchListDelegate::new(workspace, cx.view().clone(), 70, cx)?; - workspace.toggle_modal(cx, |cx| ModalBranchList { - picker: cx.build_view(|cx| Picker::new(delegate, cx)), + workspace.toggle_modal(cx, |cx| { + let modal = ModalBranchList { + picker: cx.build_view(|cx| Picker::new(delegate, cx)), + }; + cx.subscribe(&modal.picker, |_, _, _, cx| cx.emit(DismissEvent)) + .detach(); + modal }); Ok(()) From ff3f4f3027d7c4d03c8c163bac1158829e1af976 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 15 Dec 2023 12:20:54 +0100 Subject: [PATCH 11/27] search: Reintroduce whole word switch (#3672) It seems to have been lost in the recent styling pass. Release Notes: - N/A --- crates/search2/src/project_search.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/search2/src/project_search.rs b/crates/search2/src/project_search.rs index 167c6fece282f9d7619780497d8c357f7af2eebf..7bd60891ced8a78902d856a674c502c8c1546f4a 100644 --- a/crates/search2/src/project_search.rs +++ b/crates/search2/src/project_search.rs @@ -1536,13 +1536,30 @@ impl Render for ProjectSearchBar { cx, ) }) - .selected(self.is_option_enabled(SearchOptions::WHOLE_WORD, cx)) + .selected(self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx)) .on_click(cx.listener( |this, _, cx| { - this.toggle_search_option(SearchOptions::WHOLE_WORD, cx); + this.toggle_search_option( + SearchOptions::CASE_SENSITIVE, + cx, + ); }, )), ) + .child( + IconButton::new("project-search-whole-word", Icon::WholeWord) + .tooltip(|cx| { + Tooltip::for_action( + "Toggle whole word", + &ToggleWholeWord, + cx, + ) + }) + .selected(self.is_option_enabled(SearchOptions::WHOLE_WORD, cx)) + .on_click(cx.listener(|this, _, cx| { + this.toggle_search_option(SearchOptions::WHOLE_WORD, cx); + })), + ) }), ), ); From 3d9e051b07d20ca5009da8d363dea7307dad1d05 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Fri, 15 Dec 2023 10:25:07 -0500 Subject: [PATCH 12/27] Update storybook2.rs --- crates/storybook2/src/bin/storybook2.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/storybook2/src/bin/storybook2.rs b/crates/storybook2/src/bin/storybook2.rs index 43f91db1a471f01d5d48e47161f6163f84dde62b..b77bbcb0666ac9f3ddc4930e72ad2c2fd5c8c6b5 100644 --- a/crates/storybook2/src/bin/storybook2.rs +++ b/crates/storybook2/src/bin/storybook2.rs @@ -11,6 +11,7 @@ use simplelog::SimpleLogger; use theme2::{ThemeRegistry, ThemeSettings}; use ui::prelude::*; +pub use indoc::indoc; use storybook2::assets::Assets; pub use storybook2::story_selector::*; // pub use crate::story_selector::{ComponentStory, StorySelector}; From f459fc5e27c5e905f6be516be134edeca1aa911c Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Fri, 15 Dec 2023 10:33:27 -0500 Subject: [PATCH 13/27] Fix import --- crates/storybook2/src/storybook2.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/storybook2/src/storybook2.rs b/crates/storybook2/src/storybook2.rs index 13d9a75e2451d607820c9df3c845b285a0f6c40c..523e93cf5267e310d691ee4ca19db1cc67cfbb4b 100644 --- a/crates/storybook2/src/storybook2.rs +++ b/crates/storybook2/src/storybook2.rs @@ -17,10 +17,9 @@ use strum::IntoEnumIterator; use theme2::{ThemeRegistry, ThemeSettings}; use ui::prelude::*; -pub use indoc::indoc; -use storybook2::assets::Assets; use crate::assets::Assets; use crate::story_selector::{ComponentStory, StorySelector}; +pub use indoc::indoc; // gpui::actions! { // storybook, From 50a44dd8ba1d04bc9a69d9292b662e6d1e10d8e7 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Fri, 15 Dec 2023 11:07:17 -0500 Subject: [PATCH 14/27] Improve tooltip with keybinding styling --- crates/ui2/src/components/tooltip.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ui2/src/components/tooltip.rs b/crates/ui2/src/components/tooltip.rs index 7c502ac5cb13a4322db626602feedcd34edccdce..0a8eb8d6be5bc6f6e806a5ad153ea038a23f2b44 100644 --- a/crates/ui2/src/components/tooltip.rs +++ b/crates/ui2/src/components/tooltip.rs @@ -78,13 +78,13 @@ impl Render for Tooltip { v_stack() .elevation_2(cx) .font(ui_font) - .text_ui_sm() + .text_ui() .text_color(cx.theme().colors().text) .py_1() .px_2() .child( h_stack() - .gap_2() + .gap_4() .child(self.title.clone()) .when_some(self.key_binding.clone(), |this, key_binding| { this.justify_between().child(key_binding) From 47eaf1abd88863767233df4a843219d65944d2a8 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Fri, 15 Dec 2023 11:07:40 -0500 Subject: [PATCH 15/27] Remove red borders, improve left side padding --- crates/collab_ui2/src/collab_titlebar_item.rs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index b127708fa690dfc4976c8df246c9d3afa7b3f1a4..7ce3502c7dbc8f76fc8e04690282e45f64dddb03 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -74,12 +74,16 @@ impl Render for CollabTitlebarItem { // Set a non-scaling min-height here to ensure the titlebar is // always at least the height of the traffic lights. .min_h(px(32.)) - .when( - !matches!(cx.window_bounds(), WindowBounds::Fullscreen), - // Use pixels here instead of a rem-based size because the macOS traffic - // lights are a static size, and don't scale with the rest of the UI. - |s| s.pl(px(68.)), - ) + .pl_2() + .map(|this| { + if matches!(cx.window_bounds(), WindowBounds::Fullscreen) { + this.pl_2() + } else { + // Use pixels here instead of a rem-based size because the macOS traffic + // lights are a static size, and don't scale with the rest of the UI. + this.pl(px(72.)) + } + }) .bg(cx.theme().colors().title_bar_background) .on_click(|event, cx| { if event.up.click_count == 2 { @@ -325,8 +329,6 @@ impl CollabTitlebarItem { let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH); div() - .border() - .border_color(gpui::red()) .child( Button::new("project_name_trigger", name) .style(ButtonStyle::Subtle) @@ -365,10 +367,9 @@ impl CollabTitlebarItem { Some( div() - .border() - .border_color(gpui::red()) .child( Button::new("project_branch_trigger", branch_name) + .color(Color::Muted) .style(ButtonStyle::Subtle) .tooltip(move |cx| { Tooltip::with_meta( From d099d359488c9ffd890857c44dd986c052ca8d87 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Fri, 15 Dec 2023 11:20:56 -0500 Subject: [PATCH 16/27] Reduce intensity of project panel icons --- crates/project_panel2/src/project_panel.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index 2d18d2a6c769396d699ee3059e48f2736a85370a..d8b5c1551f4ef33495e7d84b168a702182479017 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -1389,7 +1389,9 @@ impl ProjectPanel { entry_id: *entry_id, }) }) - .drag_over::(|style| style.bg(cx.theme().colors().ghost_element_hover)) + .drag_over::(|style| { + style.bg(cx.theme().colors().drop_target_background) + }) .on_drop(cx.listener(move |this, dragged_id: &ProjectEntryId, cx| { this.move_entry(*dragged_id, entry_id, kind.is_file(), cx); })) @@ -1399,7 +1401,7 @@ impl ProjectPanel { .indent_step_size(px(settings.indent_size)) .selected(is_selected) .child(if let Some(icon) = &icon { - div().child(IconElement::from_path(icon.to_string())) + div().child(IconElement::from_path(icon.to_string()).color(Color::Muted)) } else { div() }) From 6345e6d4d2857a182d98875c24ea7daf4587b9ac Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Fri, 15 Dec 2023 11:25:24 -0500 Subject: [PATCH 17/27] Add some right side padding in titlebar. --- crates/collab_ui2/src/collab_titlebar_item.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index 7ce3502c7dbc8f76fc8e04690282e45f64dddb03..706749b28d349a2b9b30d51375c7818ec1031296 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -169,6 +169,7 @@ impl Render for CollabTitlebarItem { .child( h_stack() .gap_1() + .pr_1() .when_some(room, |this, room| { let room = room.read(cx); let is_shared = self.project.read(cx).is_shared(); From 6c10ff8548ccadd6749ab6b5680402942fed4187 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 15 Dec 2023 11:56:03 -0500 Subject: [PATCH 18/27] Render the collab panel using a `gpui::list` --- crates/collab_ui2/src/collab_panel.rs | 287 ++++++++------------------ 1 file changed, 87 insertions(+), 200 deletions(-) diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 4edf5ef35ba4495d5f3ef4922414d0f79cd46d7c..3a35c812861c429d5ae24aa45f293296969b433f 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -175,12 +175,12 @@ use editor::Editor; use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ - actions, canvas, div, fill, img, impl_actions, overlay, point, prelude::*, px, rems, + actions, canvas, div, fill, img, impl_actions, list, overlay, point, prelude::*, px, rems, serde_json, size, Action, AnyElement, AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, Focusable, FocusableView, Hsla, - InteractiveElement, IntoElement, Length, Model, MouseDownEvent, ParentElement, Pixels, Point, - PromptLevel, Quad, Render, RenderOnce, ScrollHandle, SharedString, Size, Stateful, Styled, - Subscription, Task, View, ViewContext, VisualContext, WeakView, + InteractiveElement, IntoElement, Length, ListState, Model, MouseDownEvent, ParentElement, + Pixels, Point, PromptLevel, Quad, Render, RenderOnce, ScrollHandle, SharedString, Size, + Stateful, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, }; use project::{Fs, Project}; use serde_derive::{Deserialize, Serialize}; @@ -303,6 +303,7 @@ pub struct CollabPanel { channel_clipboard: Option, pending_serialization: Task>, context_menu: Option<(View, Point, Subscription)>, + list_state: ListState, filter_editor: View, channel_name_editor: View, channel_editing_state: Option, @@ -398,7 +399,7 @@ enum ListEntry { impl CollabPanel { pub fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> View { cx.build_view(|cx| { - // let view_id = cx.view_id(); + let view = cx.view().clone(); let filter_editor = cx.build_view(|cx| { let mut editor = Editor::single_line(cx); @@ -445,136 +446,10 @@ impl CollabPanel { }) .detach(); - // let list_state = - // ListState::::new(0, Orientation::Top, 1000., move |this, ix, cx| { - // let theme = theme::current(cx).clone(); - // let is_selected = this.selection == Some(ix); - // let current_project_id = this.project.read(cx).remote_id(); - - // match &this.entries[ix] { - // ListEntry::Header(section) => { - // let is_collapsed = this.collapsed_sections.contains(section); - // this.render_header(*section, &theme, is_selected, is_collapsed, cx) - // } - // ListEntry::CallParticipant { - // user, - // peer_id, - // is_pending, - // } => Self::render_call_participant( - // user, - // *peer_id, - // this.user_store.clone(), - // *is_pending, - // is_selected, - // &theme, - // cx, - // ), - // ListEntry::ParticipantProject { - // project_id, - // worktree_root_names, - // host_user_id, - // is_last, - // } => Self::render_participant_project( - // *project_id, - // worktree_root_names, - // *host_user_id, - // Some(*project_id) == current_project_id, - // *is_last, - // is_selected, - // &theme, - // cx, - // ), - // ListEntry::ParticipantScreen { peer_id, is_last } => { - // Self::render_participant_screen( - // *peer_id, - // *is_last, - // is_selected, - // &theme.collab_panel, - // cx, - // ) - // } - // ListEntry::Channel { - // channel, - // depth, - // has_children, - // } => { - // let channel_row = this.render_channel( - // &*channel, - // *depth, - // &theme, - // is_selected, - // *has_children, - // ix, - // cx, - // ); - - // if is_selected && this.context_menu_on_selected { - // Stack::new() - // .with_child(channel_row) - // .with_child( - // ChildView::new(&this.context_menu, cx) - // .aligned() - // .bottom() - // .right(), - // ) - // .into_any() - // } else { - // return channel_row; - // } - // } - // ListEntry::ChannelNotes { channel_id } => this.render_channel_notes( - // *channel_id, - // &theme.collab_panel, - // is_selected, - // ix, - // cx, - // ), - // ListEntry::ChannelChat { channel_id } => this.render_channel_chat( - // *channel_id, - // &theme.collab_panel, - // is_selected, - // ix, - // cx, - // ), - // ListEntry::ChannelInvite(channel) => Self::render_channel_invite( - // channel.clone(), - // this.channel_store.clone(), - // &theme.collab_panel, - // is_selected, - // cx, - // ), - // ListEntry::IncomingRequest(user) => Self::render_contact_request( - // user.clone(), - // this.user_store.clone(), - // &theme.collab_panel, - // true, - // is_selected, - // cx, - // ), - // ListEntry::OutgoingRequest(user) => Self::render_contact_request( - // user.clone(), - // this.user_store.clone(), - // &theme.collab_panel, - // false, - // is_selected, - // cx, - // ), - // ListEntry::Contact { contact, calling } => Self::render_contact( - // contact, - // *calling, - // &this.project, - // &theme, - // is_selected, - // cx, - // ), - // ListEntry::ChannelEditor { depth } => { - // this.render_channel_editor(&theme, *depth, cx) - // } - // ListEntry::ContactPlaceholder => { - // this.render_contact_placeholder(&theme.collab_panel, is_selected, cx) - // } - // } - // }); + let list_state = + ListState::new(0, gpui::ListAlignment::Top, px(1000.), move |ix, cx| { + view.update(cx, |view, cx| view.render_list_entry(ix, cx)) + }); let mut this = Self { width: None, @@ -583,6 +458,7 @@ impl CollabPanel { fs: workspace.app_state().fs.clone(), pending_serialization: Task::ready(None), context_menu: None, + list_state, channel_name_editor, filter_editor, entries: Vec::default(), @@ -1084,6 +960,8 @@ impl CollabPanel { self.entries.push(ListEntry::ContactPlaceholder); } + self.list_state.reset(self.entries.len()); + if select_same_item { if let Some(prev_selected_entry) = prev_selected_entry { self.selection.take(); @@ -2158,77 +2036,86 @@ impl CollabPanel { ) } + fn render_list_entry( + &mut self, + // entry: &ListEntry, + ix: usize, + cx: &mut ViewContext, + ) -> AnyElement { + let entry = &self.entries[ix]; + + let is_selected = self.selection == Some(ix); + match entry { + ListEntry::Header(section) => { + let is_collapsed = self.collapsed_sections.contains(section); + self.render_header(*section, is_selected, is_collapsed, cx) + .into_any_element() + } + ListEntry::Contact { contact, calling } => self + .render_contact(contact, *calling, is_selected, cx) + .into_any_element(), + ListEntry::ContactPlaceholder => self + .render_contact_placeholder(is_selected, cx) + .into_any_element(), + ListEntry::IncomingRequest(user) => self + .render_contact_request(user, true, is_selected, cx) + .into_any_element(), + ListEntry::OutgoingRequest(user) => self + .render_contact_request(user, false, is_selected, cx) + .into_any_element(), + ListEntry::Channel { + channel, + depth, + has_children, + } => self + .render_channel(channel, *depth, *has_children, is_selected, ix, cx) + .into_any_element(), + ListEntry::ChannelEditor { depth } => { + self.render_channel_editor(*depth, cx).into_any_element() + } + ListEntry::CallParticipant { + user, + peer_id, + is_pending, + } => self + .render_call_participant(user, *peer_id, *is_pending, cx) + .into_any_element(), + ListEntry::ParticipantProject { + project_id, + worktree_root_names, + host_user_id, + is_last, + } => self + .render_participant_project( + *project_id, + &worktree_root_names, + *host_user_id, + *is_last, + cx, + ) + .into_any_element(), + ListEntry::ParticipantScreen { peer_id, is_last } => self + .render_participant_screen(*peer_id, *is_last, cx) + .into_any_element(), + ListEntry::ChannelNotes { channel_id } => self + .render_channel_notes(*channel_id, cx) + .into_any_element(), + ListEntry::ChannelChat { channel_id } => { + self.render_channel_chat(*channel_id, cx).into_any_element() + } + } + } + fn render_signed_in(&mut self, cx: &mut ViewContext) -> Div { v_stack() .size_full() .child( v_stack() .size_full() - .id("scroll") - .overflow_y_scroll() - .track_scroll(&self.scroll_handle) - .children(self.entries.iter().enumerate().map(|(ix, entry)| { - let is_selected = self.selection == Some(ix); - match entry { - ListEntry::Header(section) => { - let is_collapsed = self.collapsed_sections.contains(section); - self.render_header(*section, is_selected, is_collapsed, cx) - .into_any_element() - } - ListEntry::Contact { contact, calling } => self - .render_contact(contact, *calling, is_selected, cx) - .into_any_element(), - ListEntry::ContactPlaceholder => self - .render_contact_placeholder(is_selected, cx) - .into_any_element(), - ListEntry::IncomingRequest(user) => self - .render_contact_request(user, true, is_selected, cx) - .into_any_element(), - ListEntry::OutgoingRequest(user) => self - .render_contact_request(user, false, is_selected, cx) - .into_any_element(), - ListEntry::Channel { - channel, - depth, - has_children, - } => self - .render_channel(channel, *depth, *has_children, is_selected, ix, cx) - .into_any_element(), - ListEntry::ChannelEditor { depth } => { - self.render_channel_editor(*depth, cx).into_any_element() - } - ListEntry::CallParticipant { - user, - peer_id, - is_pending, - } => self - .render_call_participant(user, *peer_id, *is_pending, cx) - .into_any_element(), - ListEntry::ParticipantProject { - project_id, - worktree_root_names, - host_user_id, - is_last, - } => self - .render_participant_project( - *project_id, - &worktree_root_names, - *host_user_id, - *is_last, - cx, - ) - .into_any_element(), - ListEntry::ParticipantScreen { peer_id, is_last } => self - .render_participant_screen(*peer_id, *is_last, cx) - .into_any_element(), - ListEntry::ChannelNotes { channel_id } => self - .render_channel_notes(*channel_id, cx) - .into_any_element(), - ListEntry::ChannelChat { channel_id } => { - self.render_channel_chat(*channel_id, cx).into_any_element() - } - } - })), + // .id("scroll") + // .overflow_y_scroll() + // .track_scroll(&self.scroll_handle) + .child(list(self.list_state.clone()).full().into_any_element()), ) .child( div().p_2().child( From b478a4c4d5676f1d7e07961d29f41e24cf5e7602 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 15 Dec 2023 12:10:40 -0500 Subject: [PATCH 19/27] Perform scroll interactions through the `ListState` --- crates/collab_ui2/src/collab_panel.rs | 53 ++++++++++++++++----------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 3a35c812861c429d5ae24aa45f293296969b433f..31b1ef0237c9baacb8b40d07c0ad9be72bf0325c 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -178,9 +178,10 @@ use gpui::{ actions, canvas, div, fill, img, impl_actions, list, overlay, point, prelude::*, px, rems, serde_json, size, Action, AnyElement, AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, Focusable, FocusableView, Hsla, - InteractiveElement, IntoElement, Length, ListState, Model, MouseDownEvent, ParentElement, - Pixels, Point, PromptLevel, Quad, Render, RenderOnce, ScrollHandle, SharedString, Size, - Stateful, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, + InteractiveElement, IntoElement, Length, ListOffset, ListState, Model, MouseDownEvent, + ParentElement, Pixels, Point, PromptLevel, Quad, Render, RenderOnce, ScrollHandle, + SharedString, Size, Stateful, Styled, Subscription, Task, View, ViewContext, VisualContext, + WeakView, }; use project::{Fs, Project}; use serde_derive::{Deserialize, Serialize}; @@ -314,7 +315,6 @@ pub struct CollabPanel { client: Arc, project: Model, match_candidates: Vec, - scroll_handle: ScrollHandle, subscriptions: Vec, collapsed_sections: Vec
, collapsed_channels: Vec, @@ -469,7 +469,6 @@ impl CollabPanel { project: workspace.project().clone(), subscriptions: Vec::default(), match_candidates: Vec::default(), - scroll_handle: ScrollHandle::new(), collapsed_sections: vec![Section::Offline], collapsed_channels: Vec::default(), workspace: workspace.weak_handle(), @@ -585,6 +584,13 @@ impl CollabPanel { ); } + fn scroll_to_item(&mut self, ix: usize) { + self.list_state.scroll_to(ListOffset { + item_ix: ix, + offset_in_item: px(0.), + }) + } + fn update_entries(&mut self, select_same_item: bool, cx: &mut ViewContext) { let channel_store = self.channel_store.read(cx); let user_store = self.user_store.read(cx); @@ -968,7 +974,7 @@ impl CollabPanel { for (ix, entry) in self.entries.iter().enumerate() { if *entry == prev_selected_entry { self.selection = Some(ix); - self.scroll_handle.scroll_to_item(ix); + self.scroll_to_item(ix); break; } } @@ -979,16 +985,19 @@ impl CollabPanel { None } else { let ix = prev_selection.min(self.entries.len() - 1); - self.scroll_handle.scroll_to_item(ix); + self.scroll_to_item(ix); Some(ix) } }); } if scroll_to_top { - self.scroll_handle.scroll_to_item(0) + self.scroll_to_item(0) } else { - let (old_index, old_offset) = self.scroll_handle.logical_scroll_top(); + let ListOffset { + item_ix: old_index, + offset_in_item: old_offset, + } = self.list_state.logical_scroll_top(); // Attempt to maintain the same scroll position. if let Some(old_top_entry) = old_entries.get(old_index) { let (new_index, new_offset) = self @@ -1014,8 +1023,9 @@ impl CollabPanel { }) .unwrap_or_else(|| (old_index, old_offset)); - self.scroll_handle - .set_logical_scroll_top(new_index, new_offset); + // TODO: How to handle this with `list`? + // self.scroll_handle + // .set_logical_scroll_top(new_index, new_offset); } } @@ -1506,7 +1516,7 @@ impl CollabPanel { } if let Some(ix) = self.selection { - self.scroll_handle.scroll_to_item(ix) + self.scroll_to_item(ix) } cx.notify(); } @@ -1518,7 +1528,7 @@ impl CollabPanel { } if let Some(ix) = self.selection { - self.scroll_handle.scroll_to_item(ix) + self.scroll_to_item(ix) } cx.notify(); } @@ -1841,14 +1851,15 @@ impl CollabPanel { let Some(channel) = self.selected_channel() else { return; }; - let Some(bounds) = self - .selection - .and_then(|ix| self.scroll_handle.bounds_for_item(ix)) - else { - return; - }; - - self.deploy_channel_context_menu(bounds.center(), channel.id, self.selection.unwrap(), cx); + // TODO: How to handle now that we're using `list`? + // let Some(bounds) = self + // .selection + // .and_then(|ix| self.scroll_handle.bounds_for_item(ix)) + // else { + // return; + // }; + // + // self.deploy_channel_context_menu(bounds.center(), channel.id, self.selection.unwrap(), cx); cx.stop_propagation(); } From a2852e36ce8df6d1bc86a8cc2a59b31af60b2e22 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 15 Dec 2023 10:07:10 -0800 Subject: [PATCH 20/27] Fix pane resizing --- crates/workspace2/src/pane_group.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index 966e2c6341a5c9fa7808424a07ea0571d99a5bf6..d35c138d5c8217b32786a2b95cb7c5e9c9009030 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -693,7 +693,8 @@ mod element { use gpui::{ px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, IntoElement, - MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Style, WindowContext, + MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style, + WindowContext, }; use parking_lot::Mutex; use smallvec::SmallVec; @@ -736,7 +737,8 @@ mod element { e: &MouseMoveEvent, ix: usize, axis: Axis, - axis_bounds: Bounds, + child_start: Point, + container_size: Size, cx: &mut WindowContext, ) { let min_size = match axis { @@ -747,7 +749,7 @@ mod element { debug_assert!(flex_values_in_bounds(flexes.as_slice())); let size = move |ix, flexes: &[f32]| { - axis_bounds.size.along(axis) * (flexes[ix] / flexes.len() as f32) + container_size.along(axis) * (flexes[ix] / flexes.len() as f32) }; // Don't allow resizing to less than the minimum size, if elements are already too small @@ -756,10 +758,10 @@ mod element { } let mut proposed_current_pixel_change = - (e.position - axis_bounds.origin).along(axis) - size(ix, flexes.as_slice()); + (e.position - child_start).along(axis) - size(ix, flexes.as_slice()); let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| { - let flex_change = pixel_dx / axis_bounds.size.along(axis); + let flex_change = pixel_dx / container_size.along(axis); let current_target_flex = flexes[target_ix] + flex_change; let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change; (current_target_flex, next_target_flex) @@ -854,7 +856,15 @@ mod element { cx.on_mouse_event(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, axis_bounds, cx) + Self::compute_resize( + &flexes, + e, + ix, + axis, + pane_bounds.origin, + axis_bounds.size, + cx, + ) } }); }); From 47fc03ab98536b3ba2cbd75cf169ff516a5eb07f Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 15 Dec 2023 13:24:30 -0500 Subject: [PATCH 21/27] Fix positioning of the inline context menu Added a new `bounds_for_item` for `ListState`. Co-authored-by: Max --- crates/collab_ui2/src/collab_panel.rs | 55 ++++++++++----------------- crates/gpui2/src/elements/list.rs | 46 ++++++++++++++++++---- 2 files changed, 60 insertions(+), 41 deletions(-) diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 31b1ef0237c9baacb8b40d07c0ad9be72bf0325c..6f21649dd6a07f4d47ecb69e7f7183d4c4b789b3 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -165,7 +165,7 @@ struct ChannelMoveClipboard { const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel"; -use std::{iter::once, mem, sync::Arc}; +use std::{mem, sync::Arc}; use call::ActiveCall; use channel::{Channel, ChannelEvent, ChannelId, ChannelStore}; @@ -175,12 +175,11 @@ use editor::Editor; use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ - actions, canvas, div, fill, img, impl_actions, list, overlay, point, prelude::*, px, rems, - serde_json, size, Action, AnyElement, AppContext, AsyncWindowContext, Bounds, ClipboardItem, - DismissEvent, Div, EventEmitter, FocusHandle, Focusable, FocusableView, Hsla, - InteractiveElement, IntoElement, Length, ListOffset, ListState, Model, MouseDownEvent, - ParentElement, Pixels, Point, PromptLevel, Quad, Render, RenderOnce, ScrollHandle, - SharedString, Size, Stateful, Styled, Subscription, Task, View, ViewContext, VisualContext, + actions, canvas, div, fill, impl_actions, list, overlay, point, prelude::*, px, serde_json, + AnyElement, AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, + EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement, IntoElement, + ListOffset, ListState, Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, + Render, RenderOnce, SharedString, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, }; use project::{Fs, Project}; @@ -189,7 +188,7 @@ use settings::{Settings, SettingsStore}; use ui::prelude::*; use ui::{ h_stack, v_stack, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize, - Label, List, ListHeader, ListItem, Tooltip, + Label, ListHeader, ListItem, Tooltip, }; use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ @@ -1023,9 +1022,10 @@ impl CollabPanel { }) .unwrap_or_else(|| (old_index, old_offset)); - // TODO: How to handle this with `list`? - // self.scroll_handle - // .set_logical_scroll_top(new_index, new_offset); + self.list_state.scroll_to(ListOffset { + item_ix: new_index, + offset_in_item: new_offset, + }); } } @@ -1851,15 +1851,14 @@ impl CollabPanel { let Some(channel) = self.selected_channel() else { return; }; - // TODO: How to handle now that we're using `list`? - // let Some(bounds) = self - // .selection - // .and_then(|ix| self.scroll_handle.bounds_for_item(ix)) - // else { - // return; - // }; - // - // self.deploy_channel_context_menu(bounds.center(), channel.id, self.selection.unwrap(), cx); + let Some(bounds) = self + .selection + .and_then(|ix| self.list_state.bounds_for_item(ix)) + else { + return; + }; + + self.deploy_channel_context_menu(bounds.center(), channel.id, self.selection.unwrap(), cx); cx.stop_propagation(); } @@ -2047,12 +2046,7 @@ impl CollabPanel { ) } - fn render_list_entry( - &mut self, - // entry: &ListEntry, - ix: usize, - cx: &mut ViewContext, - ) -> AnyElement { + fn render_list_entry(&mut self, ix: usize, cx: &mut ViewContext) -> AnyElement { let entry = &self.entries[ix]; let is_selected = self.selection == Some(ix); @@ -2120,14 +2114,7 @@ impl CollabPanel { fn render_signed_in(&mut self, cx: &mut ViewContext) -> Div { v_stack() .size_full() - .child( - v_stack() - .size_full() - // .id("scroll") - // .overflow_y_scroll() - // .track_scroll(&self.scroll_handle) - .child(list(self.list_state.clone()).full().into_any_element()), - ) + .child(list(self.list_state.clone()).full()) .child( div().p_2().child( div() diff --git a/crates/gpui2/src/elements/list.rs b/crates/gpui2/src/elements/list.rs index 6818c5c7a2e62baab27aad701dfa27cfa80dbce4..108703370cf4ab12cb7cc2e7bbb6464fab7892f3 100644 --- a/crates/gpui2/src/elements/list.rs +++ b/crates/gpui2/src/elements/list.rs @@ -1,6 +1,7 @@ use crate::{ - px, AnyElement, AvailableSpace, BorrowAppContext, DispatchPhase, Element, IntoElement, Pixels, - Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled, WindowContext, + point, px, AnyElement, AvailableSpace, BorrowAppContext, Bounds, DispatchPhase, Element, + IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled, + WindowContext, }; use collections::VecDeque; use refineable::Refineable as _; @@ -23,7 +24,7 @@ pub struct List { pub struct ListState(Rc>); struct StateInner { - last_layout_width: Option, + last_layout_bounds: Option>, render_item: Box AnyElement>, items: SumTree, logical_scroll_top: Option, @@ -83,7 +84,7 @@ impl ListState { let mut items = SumTree::new(); items.extend((0..element_count).map(|_| ListItem::Unrendered), &()); Self(Rc::new(RefCell::new(StateInner { - last_layout_width: None, + last_layout_bounds: None, render_item: Box::new(render_item), items, logical_scroll_top: None, @@ -152,6 +153,35 @@ impl ListState { } state.logical_scroll_top = Some(scroll_top); } + + /// Get the bounds for the given item in window coordinates. + pub fn bounds_for_item(&self, ix: usize) -> Option> { + let state = &*self.0.borrow(); + let bounds = state.last_layout_bounds.unwrap_or_default(); + let scroll_top = state.logical_scroll_top.unwrap_or_default(); + + if ix < scroll_top.item_ix { + return None; + } + + let mut cursor = state.items.cursor::<(Count, Height)>(); + cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); + + let scroll_top = cursor.start().1 .0 + scroll_top.offset_in_item; + + cursor.seek_forward(&Count(ix), Bias::Right, &()); + if let Some(&ListItem::Rendered { height }) = cursor.item() { + let &(Count(count), Height(top)) = cursor.start(); + if count == ix { + let top = bounds.top() + top - scroll_top; + return Some(Bounds::from_corners( + point(bounds.left(), top), + point(bounds.right(), top + height), + )); + } + } + None + } } impl StateInner { @@ -234,7 +264,7 @@ impl std::fmt::Debug for ListItem { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Default)] pub struct ListOffset { pub item_ix: usize, pub offset_in_item: Pixels, @@ -265,7 +295,9 @@ impl Element for List { let state = &mut *self.state.0.borrow_mut(); // If the width of the list has changed, invalidate all cached item heights - if state.last_layout_width != Some(bounds.size.width) { + if state.last_layout_bounds.map_or(true, |last_bounds| { + last_bounds.size.width != bounds.size.width + }) { state.items = SumTree::from_iter( (0..state.items.summary().count).map(|_| ListItem::Unrendered), &(), @@ -392,7 +424,7 @@ impl Element for List { } state.items = new_items; - state.last_layout_width = Some(bounds.size.width); + state.last_layout_bounds = Some(bounds); let list_state = self.state.clone(); let height = bounds.size.height; From b4135dd2f1aa7f1da9daa3245f29d6b963ef0d6b Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 15 Dec 2023 10:26:41 -0800 Subject: [PATCH 22/27] Fix editor mouse event dispatch --- crates/editor2/src/editor.rs | 2 +- crates/editor2/src/element.rs | 106 ++++++++++++++++++---------------- 2 files changed, 57 insertions(+), 51 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 664d1d7380634426aa4a8264c4ed66aef008c265..c6d83ef24e498cc85b0280a5d0f1fbc9eed0ed93 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -9758,7 +9758,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend .px_1p5() .child(HighlightedLabel::new(line.clone(), highlights.clone())) .child( - div().border().border_color(gpui::red()).child( + div().z_index(1).child( IconButton::new(copy_id.clone(), Icon::Copy) .icon_color(Color::Muted) .size(ButtonSize::Compact) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index a6d4bc20b157d4f80a816d5dc78eb7980277cdc2..a04af377b432967ccf01046e824c027ace7549ec 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -2447,13 +2447,13 @@ impl EditorElement { let interactive_bounds = interactive_bounds.clone(); move |event: &ScrollWheelEvent, phase, cx| { - if phase != DispatchPhase::Bubble { - return; + if phase == DispatchPhase::Bubble + && interactive_bounds.visibly_contains(&event.position, cx) + { + editor.update(cx, |editor, cx| { + Self::scroll(editor, event, &position_map, &interactive_bounds, cx) + }); } - - editor.update(cx, |editor, cx| { - Self::scroll(editor, event, &position_map, &interactive_bounds, cx) - }); } }); @@ -2461,29 +2461,30 @@ impl EditorElement { 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 { - return; + 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); + }), + _ => {} + }; } - - 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); - }), - _ => {} - }; } }); @@ -2491,18 +2492,23 @@ impl EditorElement { 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| { - editor.update(cx, |editor, cx| { - Self::mouse_up( - editor, - event, - &position_map, - text_bounds, - &stacking_order, - cx, - ) - }); + if phase == DispatchPhase::Bubble + && interactive_bounds.visibly_contains(&event.position, cx) + { + editor.update(cx, |editor, cx| { + Self::mouse_up( + editor, + event, + &position_map, + text_bounds, + &stacking_order, + cx, + ) + }); + } } }); cx.on_mouse_event({ @@ -2511,21 +2517,21 @@ impl EditorElement { let stacking_order = cx.stacking_order().clone(); move |event: &MouseMoveEvent, phase, cx| { - if phase != DispatchPhase::Bubble { - return; + if phase == DispatchPhase::Bubble + && interactive_bounds.visibly_contains(&event.position, cx) + { + editor.update(cx, |editor, cx| { + Self::mouse_moved( + editor, + event, + &position_map, + text_bounds, + gutter_bounds, + &stacking_order, + cx, + ) + }); } - - editor.update(cx, |editor, cx| { - Self::mouse_moved( - editor, - event, - &position_map, - text_bounds, - gutter_bounds, - &stacking_order, - cx, - ) - }); } }); } From a40f04b71fd72e3818d3b9f23c4e31c30c4d47b6 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 15 Dec 2023 13:50:51 -0500 Subject: [PATCH 23/27] Only scroll enough to reveal the next channel if it isn't visible Co-authored-by: Max --- crates/collab_ui2/src/collab_panel.rs | 5 +--- crates/gpui2/src/elements/list.rs | 33 +++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 6f21649dd6a07f4d47ecb69e7f7183d4c4b789b3..1ca6101c54a19cfd51f4e05c34a0dcb0bbb4fc3d 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -584,10 +584,7 @@ impl CollabPanel { } fn scroll_to_item(&mut self, ix: usize) { - self.list_state.scroll_to(ListOffset { - item_ix: ix, - offset_in_item: px(0.), - }) + self.list_state.scroll_to_reveal_item(ix) } fn update_entries(&mut self, select_same_item: bool, cx: &mut ViewContext) { diff --git a/crates/gpui2/src/elements/list.rs b/crates/gpui2/src/elements/list.rs index 108703370cf4ab12cb7cc2e7bbb6464fab7892f3..415a3b7368bb6a88049ec5377bb1aafc21b3596e 100644 --- a/crates/gpui2/src/elements/list.rs +++ b/crates/gpui2/src/elements/list.rs @@ -154,11 +154,40 @@ impl ListState { state.logical_scroll_top = Some(scroll_top); } + pub fn scroll_to_reveal_item(&self, ix: usize) { + let state = &mut *self.0.borrow_mut(); + let mut scroll_top = state.logical_scroll_top(); + let height = state + .last_layout_bounds + .map_or(px(0.), |bounds| bounds.size.height); + + if ix <= scroll_top.item_ix { + scroll_top.item_ix = ix; + scroll_top.offset_in_item = px(0.); + } else { + let mut cursor = state.items.cursor::(); + cursor.seek(&Count(ix + 1), Bias::Right, &()); + let bottom = cursor.start().height; + let goal_top = px(0.).max(bottom - height); + + cursor.seek(&Height(goal_top), Bias::Left, &()); + let start_ix = cursor.start().count; + let start_item_top = cursor.start().height; + + if start_ix >= scroll_top.item_ix { + scroll_top.item_ix = start_ix; + scroll_top.offset_in_item = goal_top - start_item_top; + } + } + + state.logical_scroll_top = Some(scroll_top); + } + /// Get the bounds for the given item in window coordinates. pub fn bounds_for_item(&self, ix: usize) -> Option> { let state = &*self.0.borrow(); let bounds = state.last_layout_bounds.unwrap_or_default(); - let scroll_top = state.logical_scroll_top.unwrap_or_default(); + let scroll_top = state.logical_scroll_top(); if ix < scroll_top.item_ix { return None; @@ -264,7 +293,7 @@ impl std::fmt::Debug for ListItem { } } -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Copy)] pub struct ListOffset { pub item_ix: usize, pub offset_in_item: Pixels, From d0dbf8e1e269ae46ae456cf2485b70011134b8b7 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 15 Dec 2023 11:59:06 -0700 Subject: [PATCH 24/27] Fix bug where vim commands were copied on each update_matches In zed1, the array of commands was recalculated on every update_matches, In zed2 it's cached before we change the focus. --- crates/command_palette2/src/command_palette.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/command_palette2/src/command_palette.rs b/crates/command_palette2/src/command_palette.rs index 393fb16f8148b5f61d3591a1af5e7189fc033aeb..04efe1df53ae200273dc6d35e9231b55304571c2 100644 --- a/crates/command_palette2/src/command_palette.rs +++ b/crates/command_palette2/src/command_palette.rs @@ -101,6 +101,7 @@ pub struct CommandInterceptResult { pub struct CommandPaletteDelegate { command_palette: WeakView, + all_commands: Vec, commands: Vec, matches: Vec, selected_ix: usize, @@ -135,6 +136,7 @@ impl CommandPaletteDelegate { ) -> Self { Self { command_palette, + all_commands: commands.clone(), matches: vec![], commands, selected_ix: 0, @@ -167,7 +169,7 @@ impl PickerDelegate for CommandPaletteDelegate { query: String, cx: &mut ViewContext>, ) -> gpui::Task<()> { - let mut commands = self.commands.clone(); + let mut commands = self.all_commands.clone(); cx.spawn(move |picker, mut cx| async move { cx.read_global::(|hit_counts, _| { From 541712790295a6950afbf610a44d755b4c9ae143 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 15 Dec 2023 14:01:24 -0500 Subject: [PATCH 25/27] Fix channel drag and drop Also add the ability to unnest a channel by dragging it to the "Channels" header. This is currently not working due to a collab server issue. Co-authored-by: Max --- crates/collab_ui2/src/collab_panel.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 1ca6101c54a19cfd51f4e05c34a0dcb0bbb4fc3d..c2da29ba9f0ceffe488be53c3d1cb17938ccfd23 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -2225,14 +2225,14 @@ impl CollabPanel { .selected(is_selected), ) .when(section == Section::Channels, |el| { - el.drag_over::(|style| { + el.drag_over::(|style| { style.bg(cx.theme().colors().ghost_element_hover) }) .on_drop(cx.listener( - move |this, view: &View, cx| { + move |this, dragged_channel: &Channel, cx| { this.channel_store .update(cx, |channel_store, cx| { - channel_store.move_channel(view.read(cx).channel.id, None, cx) + channel_store.move_channel(dragged_channel.id, None, cx) }) .detach_and_log_err(cx) }, @@ -2451,15 +2451,15 @@ impl CollabPanel { width, }) }) - .drag_over::(|style| { + .drag_over::(|style| { style.bg(cx.theme().colors().ghost_element_hover) }) .on_drop( - cx.listener(move |this, view: &View, cx| { + cx.listener(move |this, dragged_channel: &Channel, cx| { this.channel_store .update(cx, |channel_store, cx| { channel_store.move_channel( - view.read(cx).channel.id, + dragged_channel.id, Some(channel_id), cx, ) From 5c060ceb1c2e3ae85503f89c57da34ac8e4446ad Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 15 Dec 2023 14:03:49 -0500 Subject: [PATCH 26/27] Format code --- crates/collab_ui2/src/collab_panel.rs | 34 +++++++++------------------ 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index c2da29ba9f0ceffe488be53c3d1cb17938ccfd23..ec723df25d740c5d020eecb378a35978dd179baf 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -2225,18 +2225,14 @@ impl CollabPanel { .selected(is_selected), ) .when(section == Section::Channels, |el| { - el.drag_over::(|style| { - style.bg(cx.theme().colors().ghost_element_hover) - }) - .on_drop(cx.listener( - move |this, dragged_channel: &Channel, cx| { + el.drag_over::(|style| style.bg(cx.theme().colors().ghost_element_hover)) + .on_drop(cx.listener(move |this, dragged_channel: &Channel, cx| { this.channel_store .update(cx, |channel_store, cx| { channel_store.move_channel(dragged_channel.id, None, cx) }) .detach_and_log_err(cx) - }, - )) + })) }); if section == Section::Offline { @@ -2451,22 +2447,14 @@ impl CollabPanel { width, }) }) - .drag_over::(|style| { - style.bg(cx.theme().colors().ghost_element_hover) - }) - .on_drop( - cx.listener(move |this, dragged_channel: &Channel, cx| { - this.channel_store - .update(cx, |channel_store, cx| { - channel_store.move_channel( - dragged_channel.id, - Some(channel_id), - cx, - ) - }) - .detach_and_log_err(cx) - }), - ) + .drag_over::(|style| style.bg(cx.theme().colors().ghost_element_hover)) + .on_drop(cx.listener(move |this, dragged_channel: &Channel, cx| { + this.channel_store + .update(cx, |channel_store, cx| { + channel_store.move_channel(dragged_channel.id, Some(channel_id), cx) + }) + .detach_and_log_err(cx) + })) .child( ListItem::new(channel_id as usize) // Offset the indent depth by one to give us room to show the disclosure. From b1a61ca21e9fd4b2b615f9c61012d54b9895832b Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 15 Dec 2023 14:44:29 -0500 Subject: [PATCH 27/27] Fix toolbar width (#3681) This PR fixes an issue with the toolbar width introduced in #3666. The lack of a flex container was making the toolbar contents not take up the full width, and thus not positions items correctly along its main axis. Release Notes: - N/A --- crates/workspace2/src/pane.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 11588212ef76e3891c57fc5a14778fd8e48a9e37..65e7f2a1063add17b8321f6594746871af418bdc 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -1897,19 +1897,14 @@ impl Render for Pane { .on_drag_move::(cx.listener(Self::handle_drag_move)) .map(|div| { if let Some(item) = self.active_item() { - div.flex_col() + div.v_flex() .child(self.toolbar.clone()) .child(item.to_any()) } else { - div.flex() - .flex_row() - .items_center() - .size_full() - .justify_center() - .child( - Label::new("Open a file or project to get started.") - .color(Color::Muted), - ) + div.h_flex().size_full().justify_center().child( + Label::new("Open a file or project to get started.") + .color(Color::Muted), + ) } }) .child(