From 176a68f90f6d14dd67e6ac3e34b9d3d55c6d56fa Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Mon, 20 Nov 2023 10:46:23 -0500 Subject: [PATCH 001/151] kb --- assets/icons/arrow_down.svg | 3 + assets/icons/arrow_left.svg | 4 +- assets/icons/arrow_right.svg | 4 +- assets/icons/arrow_up.svg | 3 + assets/icons/command.svg | 3 + assets/icons/control.svg | 3 + assets/icons/option.svg | 3 + assets/icons/return.svg | 3 + assets/icons/shift.svg | 3 + crates/theme2/src/default_colors.rs | 24 +++---- crates/theme2/src/one_themes.rs | 4 +- crates/ui2/src/components/icon.rs | 18 ++++- crates/ui2/src/components/keybinding.rs | 90 +++++++++++++++++++------ 13 files changed, 126 insertions(+), 39 deletions(-) create mode 100644 assets/icons/arrow_down.svg create mode 100644 assets/icons/arrow_up.svg create mode 100644 assets/icons/command.svg create mode 100644 assets/icons/control.svg create mode 100644 assets/icons/option.svg create mode 100644 assets/icons/return.svg create mode 100644 assets/icons/shift.svg diff --git a/assets/icons/arrow_down.svg b/assets/icons/arrow_down.svg new file mode 100644 index 0000000000000000000000000000000000000000..7d78497e6d28f3088f035762c39e38ea9b0e9f7b --- /dev/null +++ b/assets/icons/arrow_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/arrow_left.svg b/assets/icons/arrow_left.svg index 186c9c7457c48405508de337fa5d1904f2563f59..57ee7504906134de8662732207eeb261fc6359f2 100644 --- a/assets/icons/arrow_left.svg +++ b/assets/icons/arrow_left.svg @@ -1,3 +1,3 @@ - - + + diff --git a/assets/icons/arrow_right.svg b/assets/icons/arrow_right.svg index 7bae7f4801a10b0ee04dfab93048bbdaf526045a..7a5b1174eb5a98250be404e052f3e197d95756b8 100644 --- a/assets/icons/arrow_right.svg +++ b/assets/icons/arrow_right.svg @@ -1,3 +1,3 @@ - - + + diff --git a/assets/icons/arrow_up.svg b/assets/icons/arrow_up.svg new file mode 100644 index 0000000000000000000000000000000000000000..81dfee8042609ff2a2a8b26026cddb43557b2be2 --- /dev/null +++ b/assets/icons/arrow_up.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/command.svg b/assets/icons/command.svg new file mode 100644 index 0000000000000000000000000000000000000000..d38389aea4b08fb19d1641e8735c960de9073562 --- /dev/null +++ b/assets/icons/command.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/control.svg b/assets/icons/control.svg new file mode 100644 index 0000000000000000000000000000000000000000..94189dc07dbe761cd1cc71b22598e1d2a2869ed5 --- /dev/null +++ b/assets/icons/control.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/option.svg b/assets/icons/option.svg new file mode 100644 index 0000000000000000000000000000000000000000..9d54a6f34b1546bc7b1ed907f231eeb80f0c1264 --- /dev/null +++ b/assets/icons/option.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/return.svg b/assets/icons/return.svg new file mode 100644 index 0000000000000000000000000000000000000000..683519c3066fef61cb256a103dcd219fe59ff9f1 --- /dev/null +++ b/assets/icons/return.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/shift.svg b/assets/icons/shift.svg new file mode 100644 index 0000000000000000000000000000000000000000..02321147773469108283afbe65a68b303665f4d3 --- /dev/null +++ b/assets/icons/shift.svg @@ -0,0 +1,3 @@ + + + diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 91efecbfb310103deeafb683becf31b9b7732c42..4a47bc05366c5c8a9063e1b4f8fd4be560b11195 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -23,15 +23,15 @@ impl ThemeColors { surface_background: neutral().light().step_2(), background: neutral().light().step_1(), element_background: neutral().light().step_3(), - element_hover: neutral().light().step_4(), - element_active: neutral().light().step_5(), - element_selected: neutral().light().step_5(), + element_hover: neutral().light_alpha().step_4(), + element_active: neutral().light_alpha().step_5(), + element_selected: neutral().light_alpha().step_5(), element_disabled: neutral().light_alpha().step_3(), drop_target_background: blue().light_alpha().step_2(), ghost_element_background: system.transparent, - ghost_element_hover: neutral().light().step_4(), - ghost_element_active: neutral().light().step_5(), - ghost_element_selected: neutral().light().step_5(), + ghost_element_hover: neutral().light_alpha().step_4(), + ghost_element_active: neutral().light_alpha().step_5(), + ghost_element_selected: neutral().light_alpha().step_5(), ghost_element_disabled: neutral().light_alpha().step_3(), text: yellow().light().step_9(), text_muted: neutral().light().step_11(), @@ -95,15 +95,15 @@ impl ThemeColors { surface_background: neutral().dark().step_2(), background: neutral().dark().step_1(), element_background: neutral().dark().step_3(), - element_hover: neutral().dark().step_4(), - element_active: neutral().dark().step_5(), - element_selected: neutral().dark().step_5(), + element_hover: neutral().dark_alpha().step_4(), + element_active: neutral().dark_alpha().step_5(), + element_selected: neutral().dark_alpha().step_5(), element_disabled: neutral().dark_alpha().step_3(), drop_target_background: blue().dark_alpha().step_2(), ghost_element_background: system.transparent, - ghost_element_hover: neutral().dark().step_4(), - ghost_element_active: neutral().dark().step_5(), - ghost_element_selected: neutral().dark().step_5(), + ghost_element_hover: neutral().dark_alpha().step_4(), + ghost_element_active: neutral().dark_alpha().step_5(), + ghost_element_selected: neutral().dark_alpha().step_5(), ghost_element_disabled: neutral().dark_alpha().step_3(), text: neutral().dark().step_12(), text_muted: neutral().dark().step_11(), diff --git a/crates/theme2/src/one_themes.rs b/crates/theme2/src/one_themes.rs index 733cd6c40b6c789f484890b7a7591e5ae3cc6d40..533323ce51119170fb591e9c5847efa967f38145 100644 --- a/crates/theme2/src/one_themes.rs +++ b/crates/theme2/src/one_themes.rs @@ -20,7 +20,7 @@ pub fn one_family() -> ThemeFamily { pub(crate) fn one_dark() -> Theme { let bg = hsla(215. / 360., 12. / 100., 15. / 100., 1.); let editor = hsla(220. / 360., 12. / 100., 18. / 100., 1.); - let elevated_surface = hsla(220. / 360., 12. / 100., 18. / 100., 1.); + let elevated_surface = hsla(225. / 360., 12. / 100., 17. / 100., 1.); let blue = hsla(207.8 / 360., 81. / 100., 66. / 100., 1.0); let gray = hsla(218.8 / 360., 10. / 100., 40. / 100., 1.0); @@ -48,7 +48,7 @@ pub(crate) fn one_dark() -> Theme { elevated_surface_background: elevated_surface, surface_background: bg, background: bg, - element_background: elevated_surface, + element_background: hsla(223.0 / 360., 13. / 100., 21. / 100., 1.0), element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0), element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0), element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0), diff --git a/crates/ui2/src/components/icon.rs b/crates/ui2/src/components/icon.rs index 38b477792599920fc5cc90fc722f2fd4299018b0..48f60413c540570b0e068cae27d0a9969125cdda 100644 --- a/crates/ui2/src/components/icon.rs +++ b/crates/ui2/src/components/icon.rs @@ -14,6 +14,8 @@ pub enum IconSize { pub enum Icon { Ai, ArrowLeft, + ArrowUp, + ArrowDown, ArrowRight, ArrowUpRight, AtSign, @@ -66,6 +68,11 @@ pub enum Icon { SplitMessage, Terminal, XCircle, + Command, + Control, + Shift, + Option, + Return, } impl Icon { @@ -74,6 +81,8 @@ impl Icon { Icon::Ai => "icons/ai.svg", Icon::ArrowLeft => "icons/arrow_left.svg", Icon::ArrowRight => "icons/arrow_right.svg", + Icon::ArrowUp => "icons/arrow_up.svg", + Icon::ArrowDown => "icons/arrow_down.svg", Icon::ArrowUpRight => "icons/arrow_up_right.svg", Icon::AtSign => "icons/at-sign.svg", Icon::AudioOff => "icons/speaker-off.svg", @@ -125,6 +134,11 @@ impl Icon { Icon::SplitMessage => "icons/split_message.svg", Icon::Terminal => "icons/terminal.svg", Icon::XCircle => "icons/error.svg", + Icon::Command => "icons/command.svg", + Icon::Control => "icons/control.svg", + Icon::Shift => "icons/shift.svg", + Icon::Option => "icons/option.svg", + Icon::Return => "icons/return.svg", } } } @@ -165,8 +179,8 @@ impl IconElement { fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { let svg_size = match self.size { - IconSize::Small => rems(0.75), - IconSize::Medium => rems(0.9375), + IconSize::Small => rems(14. / 16.), + IconSize::Medium => rems(16. / 16.), }; svg() diff --git a/crates/ui2/src/components/keybinding.rs b/crates/ui2/src/components/keybinding.rs index 69396274faba449d8e32c6eace4536e35c7a2085..c0690ce1c718917fa4768e5da2d931a2d42584c6 100644 --- a/crates/ui2/src/components/keybinding.rs +++ b/crates/ui2/src/components/keybinding.rs @@ -1,7 +1,7 @@ -use gpui::{actions, Action}; +use gpui::{actions, relative, rems, Action, Styled}; use strum::EnumIter; -use crate::prelude::*; +use crate::{h_stack, prelude::*, Icon, IconElement, IconSize}; #[derive(Component, Clone)] pub struct KeyBinding { @@ -24,20 +24,46 @@ impl KeyBinding { Self { key_binding } } + fn icon_for_key(key: &str) -> Option { + match key { + "left" => Some(Icon::ArrowLeft), + "right" => Some(Icon::ArrowRight), + "up" => Some(Icon::ArrowUp), + "down" => Some(Icon::ArrowDown), + _ => None, + } + } + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - div() - .flex() - .gap_2() + h_stack() + .flex_none() + .gap_1() .children(self.key_binding.keystrokes().iter().map(|keystroke| { - div() - .flex() - .gap_1() + let key_icon = Self::icon_for_key(&keystroke.key); + + h_stack() + .flex_none() + .gap_0p5() + .bg(cx.theme().colors().element_background) + .p_0p5() + .rounded_sm() .when(keystroke.modifiers.function, |el| el.child(Key::new("fn"))) - .when(keystroke.modifiers.control, |el| el.child(Key::new("^"))) - .when(keystroke.modifiers.alt, |el| el.child(Key::new("⌥"))) - .when(keystroke.modifiers.command, |el| el.child(Key::new("⌘"))) - .when(keystroke.modifiers.shift, |el| el.child(Key::new("⇧"))) - .child(Key::new(keystroke.key.clone())) + .when(keystroke.modifiers.control, |el| { + el.child(KeyIcon::new(Icon::Control)) + }) + .when(keystroke.modifiers.alt, |el| { + el.child(KeyIcon::new(Icon::Option)) + }) + .when(keystroke.modifiers.command, |el| { + el.child(KeyIcon::new(Icon::Command)) + }) + .when(keystroke.modifiers.shift, |el| { + el.child(KeyIcon::new(Icon::Shift)) + }) + .when_some(key_icon, |el, icon| el.child(KeyIcon::new(icon))) + .when(key_icon.is_none(), |el| { + el.child(Key::new(keystroke.key.to_uppercase().clone())) + }) })) } } @@ -53,25 +79,51 @@ impl Key { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + let single_char = self.key.len() == 1; + div() - .px_2() + // .px_0p5() .py_0() - .rounded_md() - .text_ui_sm() + .when(single_char, |el| { + el.w(rems(14. / 16.)).flex().flex_none().justify_center() + }) + .when(!single_char, |el| el.px_0p5()) + .h(rems(14. / 16.)) + // .rounded_md() + .text_ui() + .line_height(relative(1.)) .text_color(cx.theme().colors().text) - .bg(cx.theme().colors().element_background) + // .bg(cx.theme().colors().element_background) .child(self.key.clone()) } } +#[derive(Component)] +pub struct KeyIcon { + icon: Icon, +} + +impl KeyIcon { + pub fn new(icon: Icon) -> Self { + Self { icon } + } + + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + div() + .w(rems(14. / 16.)) + // .bg(cx.theme().colors().element_background) + .child(IconElement::new(self.icon).size(IconSize::Small)) + } +} + // NOTE: The order the modifier keys appear in this enum impacts the order in // which they are rendered in the UI. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] pub enum ModifierKey { Control, - Alt, - Command, + Alt, // Option Shift, + Command, } actions!(NoAction); From 663bbb06d9e80d4f61ea5f2ae3598e6c930d04dd Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 21 Nov 2023 12:40:00 -0800 Subject: [PATCH 002/151] WIP --- Cargo.lock | 46 +++ Cargo.toml | 2 + crates/client2/src/client2.rs | 8 +- crates/gpui2/src/action.rs | 1 + crates/theme2/src/registry.rs | 4 + crates/theme_selector2/Cargo.toml | 28 ++ crates/theme_selector2/src/theme_selector.rs | 254 ++++++++++++++++ crates/welcome2/Cargo.toml | 36 +++ crates/welcome2/src/base_keymap_picker.rs | 152 ++++++++++ crates/welcome2/src/base_keymap_setting.rs | 65 +++++ crates/welcome2/src/welcome.rs | 287 +++++++++++++++++++ crates/zed2/Cargo.toml | 4 +- crates/zed2/src/main.rs | 83 +++--- script/crate-dep-graph | 2 +- 14 files changed, 921 insertions(+), 51 deletions(-) create mode 100644 crates/theme_selector2/Cargo.toml create mode 100644 crates/theme_selector2/src/theme_selector.rs create mode 100644 crates/welcome2/Cargo.toml create mode 100644 crates/welcome2/src/base_keymap_picker.rs create mode 100644 crates/welcome2/src/base_keymap_setting.rs create mode 100644 crates/welcome2/src/welcome.rs diff --git a/Cargo.lock b/Cargo.lock index 6aa94b08d05cd2cd2578f5f50f5c1ebf28ddf2f4..2184ba5ebb4c0de0fef9a02125a3e969a13921b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9405,6 +9405,26 @@ dependencies = [ "workspace", ] +[[package]] +name = "theme_selector2" +version = "0.1.0" +dependencies = [ + "editor2", + "feature_flags2", + "fs2", + "fuzzy2", + "gpui2", + "log", + "parking_lot 0.11.2", + "picker2", + "postage", + "settings2", + "smol", + "theme2", + "util", + "workspace2", +] + [[package]] name = "thiserror" version = "1.0.48" @@ -10978,6 +10998,30 @@ dependencies = [ "workspace", ] +[[package]] +name = "welcome2" +version = "0.1.0" +dependencies = [ + "anyhow", + "client2", + "db2", + "editor2", + "fs2", + "fuzzy2", + "gpui2", + "install_cli2", + "log", + "picker2", + "project2", + "schemars", + "serde", + "settings2", + "theme2", + "theme_selector2", + "util", + "workspace2", +] + [[package]] name = "which" version = "4.4.2" @@ -11640,6 +11684,7 @@ dependencies = [ "terminal_view2", "text2", "theme2", + "theme_selector2", "thiserror", "tiny_http", "toml 0.5.11", @@ -11676,6 +11721,7 @@ dependencies = [ "urlencoding", "util", "uuid 1.4.1", + "welcome2", "workspace2", "zed_actions2", ] diff --git a/Cargo.toml b/Cargo.toml index d7b9918f624a5f1461aef7768f3c98d9dfbef1af..6c1152cf9c3598d511636c946e8c34f3a592a3fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,6 +108,7 @@ members = [ "crates/theme2", "crates/theme_importer", "crates/theme_selector", + "crates/theme_selector2", "crates/ui2", "crates/util", "crates/semantic_index", @@ -115,6 +116,7 @@ members = [ "crates/vcs_menu", "crates/workspace2", "crates/welcome", + "crates/welcome2", "crates/xtask", "crates/zed", "crates/zed2", diff --git a/crates/client2/src/client2.rs b/crates/client2/src/client2.rs index b4279b023ecd7412d8ea0c4a69ddc0215be97fb2..028dec680306948eb13f1945c4125e9b8f20aa79 100644 --- a/crates/client2/src/client2.rs +++ b/crates/client2/src/client2.rs @@ -694,8 +694,8 @@ impl Client { } } - pub async fn has_keychain_credentials(&self, cx: &AsyncAppContext) -> bool { - read_credentials_from_keychain(cx).await.is_some() + pub fn has_keychain_credentials(&self, cx: &AsyncAppContext) -> bool { + read_credentials_from_keychain(cx).is_some() } #[async_recursion(?Send)] @@ -726,7 +726,7 @@ impl Client { let mut read_from_keychain = false; let mut credentials = self.state.read().credentials.clone(); if credentials.is_none() && try_keychain { - credentials = read_credentials_from_keychain(cx).await; + credentials = read_credentials_from_keychain(cx); read_from_keychain = credentials.is_some(); } if credentials.is_none() { @@ -1325,7 +1325,7 @@ impl Client { } } -async fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option { +fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option { if IMPERSONATE_LOGIN.is_some() { return None; } diff --git a/crates/gpui2/src/action.rs b/crates/gpui2/src/action.rs index 958eaabdb83076dcd8114ba1f06ae2ac4fc6c50b..03ef2d2281876ca101e210b1d06491413e1ce027 100644 --- a/crates/gpui2/src/action.rs +++ b/crates/gpui2/src/action.rs @@ -162,6 +162,7 @@ macro_rules! actions { ( $name:ident ) => { #[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, gpui::serde_derive::Deserialize, gpui::Action)] + #[serde(crate = "gpui::serde")] pub struct $name; }; diff --git a/crates/theme2/src/registry.rs b/crates/theme2/src/registry.rs index 919dd1b1099ecb35853142a2b5a66f14904a7653..b50eb831dda51b8357ad2b8c8ff9a7b6a86cfe81 100644 --- a/crates/theme2/src/registry.rs +++ b/crates/theme2/src/registry.rs @@ -86,6 +86,10 @@ impl ThemeRegistry { })); } + pub fn clear(&mut self) { + self.themes.clear(); + } + pub fn list_names(&self, _staff: bool) -> impl Iterator + '_ { self.themes.keys().cloned() } diff --git a/crates/theme_selector2/Cargo.toml b/crates/theme_selector2/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..89b7487a7bd4527eb0ea96d5e0c5e5bb6178d570 --- /dev/null +++ b/crates/theme_selector2/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "theme_selector2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/theme_selector.rs" +doctest = false + +[dependencies] +editor = { package = "editor2", path = "../editor2" } +fuzzy = { package = "fuzzy2", path = "../fuzzy2" } +fs = { package = "fs2", path = "../fs2" } +gpui = { package = "gpui2", path = "../gpui2" } +picker = { package = "picker2", path = "../picker2" } +theme = { package = "theme2", path = "../theme2" } +settings = { package = "settings2", path = "../settings2" } +feature_flags = { package = "feature_flags2", path = "../feature_flags2" } +workspace = { package = "workspace2", path = "../workspace2" } +util = { path = "../util" } +log.workspace = true +parking_lot.workspace = true +postage.workspace = true +smol.workspace = true + +[dev-dependencies] +editor = { package = "editor2", path = "../editor2", features = ["test-support"] } diff --git a/crates/theme_selector2/src/theme_selector.rs b/crates/theme_selector2/src/theme_selector.rs new file mode 100644 index 0000000000000000000000000000000000000000..6e660caf51bf486840d2c692203a76de277b5389 --- /dev/null +++ b/crates/theme_selector2/src/theme_selector.rs @@ -0,0 +1,254 @@ +use feature_flags::FeatureFlagAppExt; +use fs::Fs; +use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; +use gpui::{ + actions, div, AppContext, Div, EventEmitter, FocusableView, Manager, Render, SharedString, + View, ViewContext, VisualContext, +}; +use picker::{Picker, PickerDelegate}; +use settings::{update_settings_file, SettingsStore}; +use std::sync::Arc; +use theme::{ActiveTheme, Theme, ThemeRegistry, ThemeSettings}; +use util::ResultExt; +use workspace::{ui::HighlightedLabel, Workspace}; + +actions!(Toggle, Reload); + +pub fn init(cx: &mut AppContext) { + cx.observe_new_views( + |workspace: &mut Workspace, cx: &mut ViewContext| { + workspace.register_action(toggle); + }, + ); +} + +pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { + let fs = workspace.app_state().fs.clone(); + workspace.toggle_modal(cx, |cx| { + ThemeSelector::new(ThemeSelectorDelegate::new(fs, cx), cx) + }); +} + +#[cfg(debug_assertions)] +pub fn reload(cx: &mut AppContext) { + let current_theme_name = cx.theme().name.clone(); + let registry = cx.global::>(); + registry.clear(); + match registry.get(¤t_theme_name) { + Ok(theme) => { + ThemeSelectorDelegate::set_theme(theme, cx); + log::info!("reloaded theme {}", current_theme_name); + } + Err(error) => { + log::error!("failed to load theme {}: {:?}", current_theme_name, error) + } + } +} + +pub struct ThemeSelector { + picker: View>, +} + +impl EventEmitter for ThemeSelector {} + +impl FocusableView for ThemeSelector { + fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { + self.picker.focus_handle(cx) + } +} + +impl Render for ThemeSelector { + type Element = View>; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + self.picker.clone() + } +} + +impl ThemeSelector { + pub fn new(delegate: ThemeSelectorDelegate, cx: &mut ViewContext) -> Self { + let picker = cx.build_view(|cx| Picker::new(delegate, cx)); + Self { picker } + } +} + +pub struct ThemeSelectorDelegate { + fs: Arc, + theme_names: Vec, + matches: Vec, + original_theme: Arc, + selection_completed: bool, + selected_index: usize, +} + +impl ThemeSelectorDelegate { + fn new(fs: Arc, cx: &mut ViewContext) -> Self { + let original_theme = cx.theme().clone(); + + let staff_mode = cx.is_staff(); + let registry = cx.global::>(); + let mut theme_names = registry.list(staff_mode).collect::>(); + theme_names.sort_unstable_by(|a, b| a.is_light.cmp(&b.is_light).then(a.name.cmp(&b.name))); + let matches = theme_names + .iter() + .map(|meta| StringMatch { + candidate_id: 0, + score: 0.0, + positions: Default::default(), + string: meta.to_string(), + }) + .collect(); + let mut this = Self { + fs, + theme_names, + matches, + original_theme: original_theme.clone(), + selected_index: 0, + selection_completed: false, + }; + this.select_if_matching(&original_theme.meta.name); + this + } + + fn show_selected_theme(&mut self, cx: &mut ViewContext) { + if let Some(mat) = self.matches.get(self.selected_index) { + let registry = cx.global::>(); + match registry.get(&mat.string) { + Ok(theme) => { + Self::set_theme(theme, cx); + } + Err(error) => { + log::error!("error loading theme {}: {}", mat.string, error) + } + } + } + } + + fn select_if_matching(&mut self, theme_name: &str) { + self.selected_index = self + .matches + .iter() + .position(|mat| mat.string == theme_name) + .unwrap_or(self.selected_index); + } + + fn set_theme(theme: Arc, cx: &mut AppContext) { + cx.update_global::(|store, cx| { + let mut theme_settings = store.get::(None).clone(); + theme_settings.theme = theme; + store.override_global(theme_settings); + cx.refresh_windows(); + }); + } +} + +impl PickerDelegate for ThemeSelectorDelegate { + type ListItem = Div; + + fn placeholder_text(&self) -> Arc { + "Select Theme...".into() + } + + fn match_count(&self) -> usize { + self.matches.len() + } + + fn confirm(&mut self, _: bool, cx: &mut ViewContext) { + self.selection_completed = true; + + let theme_name = cx.theme().meta.name.clone(); + update_settings_file::(self.fs.clone(), cx, |settings| { + settings.theme = Some(theme_name); + }); + + cx.emit(Manager::Dismiss); + } + + fn dismissed(&mut self, cx: &mut ViewContext) { + if !self.selection_completed { + Self::set_theme(self.original_theme.clone(), cx); + self.selection_completed = true; + } + } + + fn selected_index(&self) -> usize { + self.selected_index + } + + fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext) { + self.selected_index = ix; + self.show_selected_theme(cx); + } + + fn update_matches( + &mut self, + query: String, + cx: &mut ViewContext, + ) -> gpui::Task<()> { + let background = cx.background().clone(); + let candidates = self + .theme_names + .iter() + .enumerate() + .map(|(id, meta)| StringMatchCandidate { + id, + char_bag: meta.name.as_str().into(), + string: meta.name.clone(), + }) + .collect::>(); + + cx.spawn(|this, mut cx| async move { + let matches = if query.is_empty() { + candidates + .into_iter() + .enumerate() + .map(|(index, candidate)| StringMatch { + candidate_id: index, + string: candidate.string, + positions: Vec::new(), + score: 0.0, + }) + .collect() + } else { + match_strings( + &candidates, + &query, + false, + 100, + &Default::default(), + background, + ) + .await + }; + + this.update(&mut cx, |this, cx| { + let delegate = this.delegate_mut(); + delegate.matches = matches; + delegate.selected_index = delegate + .selected_index + .min(delegate.matches.len().saturating_sub(1)); + delegate.show_selected_theme(cx); + }) + .log_err(); + }) + } + + fn render_match(&self, ix: usize, selected: bool, cx: &AppContext) -> Self::ListItem { + let theme = cx.theme(); + let colors = theme.colors(); + + let theme_match = &self.matches[ix]; + div() + .px_1() + .text_color(colors.text) + .text_ui() + .bg(colors.ghost_element_background) + .rounded_md() + .when(selected, |this| this.bg(colors.ghost_element_selected)) + .hover(|this| this.bg(colors.ghost_element_hover)) + .child(HighlightedLabel::new( + theme_match.string.clone(), + theme_match.positions.clone(), + )) + } +} diff --git a/crates/welcome2/Cargo.toml b/crates/welcome2/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..0a2d2fd781530634ea32ff93da7a516a64371397 --- /dev/null +++ b/crates/welcome2/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "welcome2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/welcome.rs" + +[features] +test-support = [] + +[dependencies] +client = { package = "client2", path = "../client2" } +editor = { package = "editor2", path = "../editor2" } +fs = { package = "fs2", path = "../fs2" } +fuzzy = { package = "fuzzy2", path = "../fuzzy2" } +gpui = { package = "gpui2", path = "../gpui2" } +db = { package = "db2", path = "../db2" } +install_cli = { package = "install_cli2", path = "../install_cli2" } +project = { package = "project2", path = "../project2" } +settings = { package = "settings2", path = "../settings2" } +theme = { package = "theme2", path = "../theme2" } +theme_selector = { package = "theme_selector2", path = "../theme_selector2" } +util = { path = "../util" } +picker = { package = "picker2", path = "../picker2" } +workspace = { package = "workspace2", path = "../workspace2" } +# vim = { package = "vim2", path = "../vim2" } + +anyhow.workspace = true +log.workspace = true +schemars.workspace = true +serde.workspace = true + +[dev-dependencies] +editor = { package = "editor2", path = "../editor2", features = ["test-support"] } diff --git a/crates/welcome2/src/base_keymap_picker.rs b/crates/welcome2/src/base_keymap_picker.rs new file mode 100644 index 0000000000000000000000000000000000000000..021e3b86a0c7c62b05d0f0474808fbb9a66b358a --- /dev/null +++ b/crates/welcome2/src/base_keymap_picker.rs @@ -0,0 +1,152 @@ +use super::base_keymap_setting::BaseKeymap; +use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; +use gpui::{ + actions, + elements::{Element as _, Label}, + AppContext, Task, ViewContext, +}; +use picker::{Picker, PickerDelegate, PickerEvent}; +use project::Fs; +use settings::update_settings_file; +use std::sync::Arc; +use util::ResultExt; +use workspace::Workspace; + +actions!(welcome, [ToggleBaseKeymapSelector]); + +pub fn init(cx: &mut AppContext) { + cx.add_action(toggle); + BaseKeymapSelector::init(cx); +} + +pub fn toggle( + workspace: &mut Workspace, + _: &ToggleBaseKeymapSelector, + cx: &mut ViewContext, +) { + workspace.toggle_modal(cx, |workspace, cx| { + let fs = workspace.app_state().fs.clone(); + cx.add_view(|cx| BaseKeymapSelector::new(BaseKeymapSelectorDelegate::new(fs, cx), cx)) + }); +} + +pub type BaseKeymapSelector = Picker; + +pub struct BaseKeymapSelectorDelegate { + matches: Vec, + selected_index: usize, + fs: Arc, +} + +impl BaseKeymapSelectorDelegate { + fn new(fs: Arc, cx: &mut ViewContext) -> Self { + let base = settings::get::(cx); + let selected_index = BaseKeymap::OPTIONS + .iter() + .position(|(_, value)| value == base) + .unwrap_or(0); + Self { + matches: Vec::new(), + selected_index, + fs, + } + } +} + +impl PickerDelegate for BaseKeymapSelectorDelegate { + fn placeholder_text(&self) -> Arc { + "Select a base keymap...".into() + } + + fn match_count(&self) -> usize { + self.matches.len() + } + + fn selected_index(&self) -> usize { + self.selected_index + } + + fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext) { + self.selected_index = ix; + } + + fn update_matches( + &mut self, + query: String, + cx: &mut ViewContext, + ) -> Task<()> { + let background = cx.background().clone(); + let candidates = BaseKeymap::names() + .enumerate() + .map(|(id, name)| StringMatchCandidate { + id, + char_bag: name.into(), + string: name.into(), + }) + .collect::>(); + + cx.spawn(|this, mut cx| async move { + let matches = if query.is_empty() { + candidates + .into_iter() + .enumerate() + .map(|(index, candidate)| StringMatch { + candidate_id: index, + string: candidate.string, + positions: Vec::new(), + score: 0.0, + }) + .collect() + } else { + match_strings( + &candidates, + &query, + false, + 100, + &Default::default(), + background, + ) + .await + }; + + this.update(&mut cx, |this, _| { + let delegate = this.delegate_mut(); + delegate.matches = matches; + delegate.selected_index = delegate + .selected_index + .min(delegate.matches.len().saturating_sub(1)); + }) + .log_err(); + }) + } + + fn confirm(&mut self, _: bool, cx: &mut ViewContext) { + if let Some(selection) = self.matches.get(self.selected_index) { + let base_keymap = BaseKeymap::from_names(&selection.string); + update_settings_file::(self.fs.clone(), cx, move |setting| { + *setting = Some(base_keymap) + }); + } + cx.emit(PickerEvent::Dismiss); + } + + fn dismissed(&mut self, _cx: &mut ViewContext) {} + + fn render_match( + &self, + ix: usize, + mouse_state: &mut gpui::MouseState, + selected: bool, + cx: &gpui::AppContext, + ) -> gpui::AnyElement> { + let theme = &theme::current(cx); + let keymap_match = &self.matches[ix]; + let style = theme.picker.item.in_state(selected).style_for(mouse_state); + + Label::new(keymap_match.string.clone(), style.label.clone()) + .with_highlights(keymap_match.positions.clone()) + .contained() + .with_style(style.container) + .into_any() + } +} diff --git a/crates/welcome2/src/base_keymap_setting.rs b/crates/welcome2/src/base_keymap_setting.rs new file mode 100644 index 0000000000000000000000000000000000000000..c5b6171f9b4fb809670d7e17f800f5d23699da61 --- /dev/null +++ b/crates/welcome2/src/base_keymap_setting.rs @@ -0,0 +1,65 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::Setting; + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] +pub enum BaseKeymap { + #[default] + VSCode, + JetBrains, + SublimeText, + Atom, + TextMate, +} + +impl BaseKeymap { + pub const OPTIONS: [(&'static str, Self); 5] = [ + ("VSCode (Default)", Self::VSCode), + ("Atom", Self::Atom), + ("JetBrains", Self::JetBrains), + ("Sublime Text", Self::SublimeText), + ("TextMate", Self::TextMate), + ]; + + pub fn asset_path(&self) -> Option<&'static str> { + match self { + BaseKeymap::JetBrains => Some("keymaps/jetbrains.json"), + BaseKeymap::SublimeText => Some("keymaps/sublime_text.json"), + BaseKeymap::Atom => Some("keymaps/atom.json"), + BaseKeymap::TextMate => Some("keymaps/textmate.json"), + BaseKeymap::VSCode => None, + } + } + + pub fn names() -> impl Iterator { + Self::OPTIONS.iter().map(|(name, _)| *name) + } + + pub fn from_names(option: &str) -> BaseKeymap { + Self::OPTIONS + .iter() + .copied() + .find_map(|(name, value)| (name == option).then(|| value)) + .unwrap_or_default() + } +} + +impl Setting for BaseKeymap { + const KEY: Option<&'static str> = Some("base_keymap"); + + type FileContent = Option; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &gpui::AppContext, + ) -> anyhow::Result + where + Self: Sized, + { + Ok(user_values + .first() + .and_then(|v| **v) + .unwrap_or(default_value.unwrap())) + } +} diff --git a/crates/welcome2/src/welcome.rs b/crates/welcome2/src/welcome.rs new file mode 100644 index 0000000000000000000000000000000000000000..a5d95429bdf00b97718d353b7a5f4916dff28f31 --- /dev/null +++ b/crates/welcome2/src/welcome.rs @@ -0,0 +1,287 @@ +mod base_keymap_picker; +mod base_keymap_setting; + +use crate::base_keymap_picker::ToggleBaseKeymapSelector; +use client::TelemetrySettings; +use db::kvp::KEY_VALUE_STORE; +use gpui::{ + elements::{Flex, Label, ParentElement}, + AnyElement, AppContext, Element, Entity, Subscription, View, ViewContext, WeakViewHandle, +}; +use settings::{update_settings_file, SettingsStore}; +use std::{borrow::Cow, sync::Arc}; +use vim::VimModeSetting; +use workspace::{ + dock::DockPosition, item::Item, open_new, AppState, PaneBackdrop, Welcome, Workspace, + WorkspaceId, +}; + +pub use base_keymap_setting::BaseKeymap; + +pub const FIRST_OPEN: &str = "first_open"; + +pub fn init(cx: &mut AppContext) { + settings::register::(cx); + + cx.add_action(|workspace: &mut Workspace, _: &Welcome, cx| { + let welcome_page = cx.add_view(|cx| WelcomePage::new(workspace, cx)); + workspace.add_item(Box::new(welcome_page), cx) + }); + + base_keymap_picker::init(cx); +} + +pub fn show_welcome_experience(app_state: &Arc, cx: &mut AppContext) { + open_new(&app_state, cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Left, cx); + let welcome_page = cx.add_view(|cx| WelcomePage::new(workspace, cx)); + workspace.add_item_to_center(Box::new(welcome_page.clone()), cx); + cx.focus(&welcome_page); + cx.notify(); + }) + .detach(); + + db::write_and_log(cx, || { + KEY_VALUE_STORE.write_kvp(FIRST_OPEN.to_string(), "false".to_string()) + }); +} + +pub struct WelcomePage { + workspace: WeakViewHandle, + _settings_subscription: Subscription, +} + +impl Entity for WelcomePage { + type Event = (); +} + +impl View for WelcomePage { + fn ui_name() -> &'static str { + "WelcomePage" + } + + fn render(&mut self, cx: &mut gpui::ViewContext) -> AnyElement { + let self_handle = cx.handle(); + let theme = theme::current(cx); + let width = theme.welcome.page_width; + + let telemetry_settings = *settings::get::(cx); + let vim_mode_setting = settings::get::(cx).0; + + enum Metrics {} + enum Diagnostics {} + + PaneBackdrop::new( + self_handle.id(), + Flex::column() + .with_child( + Flex::column() + .with_child( + theme::ui::svg(&theme.welcome.logo) + .aligned() + .contained() + .aligned(), + ) + .with_child( + Label::new( + "Code at the speed of thought", + theme.welcome.logo_subheading.text.clone(), + ) + .aligned() + .contained() + .with_style(theme.welcome.logo_subheading.container), + ) + .contained() + .with_style(theme.welcome.heading_group) + .constrained() + .with_width(width), + ) + .with_child( + Flex::column() + .with_child(theme::ui::cta_button::( + "Choose a theme", + width, + &theme.welcome.button, + cx, + |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + theme_selector::toggle(workspace, &Default::default(), cx) + }) + } + }, + )) + .with_child(theme::ui::cta_button::( + "Choose a keymap", + width, + &theme.welcome.button, + cx, + |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + base_keymap_picker::toggle( + workspace, + &Default::default(), + cx, + ) + }) + } + }, + )) + .with_child(theme::ui::cta_button::( + "Install the CLI", + width, + &theme.welcome.button, + cx, + |_, _, cx| { + cx.app_context() + .spawn(|cx| async move { install_cli::install_cli(&cx).await }) + .detach_and_log_err(cx); + }, + )) + .contained() + .with_style(theme.welcome.button_group) + .constrained() + .with_width(width), + ) + .with_child( + Flex::column() + .with_child( + theme::ui::checkbox::( + "Enable vim mode", + &theme.welcome.checkbox, + vim_mode_setting, + 0, + cx, + |this, checked, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + let fs = workspace.read(cx).app_state().fs.clone(); + update_settings_file::( + fs, + cx, + move |setting| *setting = Some(checked), + ) + } + }, + ) + .contained() + .with_style(theme.welcome.checkbox_container), + ) + .with_child( + theme::ui::checkbox_with_label::( + Flex::column() + .with_child( + Label::new( + "Send anonymous usage data", + theme.welcome.checkbox.label.text.clone(), + ) + .contained() + .with_style(theme.welcome.checkbox.label.container), + ) + .with_child( + Label::new( + "Help > View Telemetry", + theme.welcome.usage_note.text.clone(), + ) + .contained() + .with_style(theme.welcome.usage_note.container), + ), + &theme.welcome.checkbox, + telemetry_settings.metrics, + 0, + cx, + |this, checked, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + let fs = workspace.read(cx).app_state().fs.clone(); + update_settings_file::( + fs, + cx, + move |setting| setting.metrics = Some(checked), + ) + } + }, + ) + .contained() + .with_style(theme.welcome.checkbox_container), + ) + .with_child( + theme::ui::checkbox::( + "Send crash reports", + &theme.welcome.checkbox, + telemetry_settings.diagnostics, + 1, + cx, + |this, checked, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + let fs = workspace.read(cx).app_state().fs.clone(); + update_settings_file::( + fs, + cx, + move |setting| setting.diagnostics = Some(checked), + ) + } + }, + ) + .contained() + .with_style(theme.welcome.checkbox_container), + ) + .contained() + .with_style(theme.welcome.checkbox_group) + .constrained() + .with_width(width), + ) + .constrained() + .with_max_width(width) + .contained() + .with_uniform_padding(10.) + .aligned() + .into_any(), + ) + .into_any_named("welcome page") + } +} + +impl WelcomePage { + pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + WelcomePage { + workspace: workspace.weak_handle(), + _settings_subscription: cx.observe_global::(move |_, cx| cx.notify()), + } + } +} + +impl Item for WelcomePage { + fn tab_tooltip_text(&self, _: &AppContext) -> Option> { + Some("Welcome to Zed!".into()) + } + + fn tab_content( + &self, + _detail: Option, + style: &theme::Tab, + _cx: &gpui::AppContext, + ) -> AnyElement { + Flex::row() + .with_child( + Label::new("Welcome to Zed!", style.label.clone()) + .aligned() + .contained(), + ) + .into_any() + } + + fn show_toolbar(&self) -> bool { + false + } + + fn clone_on_split( + &self, + _workspace_id: WorkspaceId, + cx: &mut ViewContext, + ) -> Option { + Some(WelcomePage { + workspace: self.workspace.clone(), + _settings_subscription: cx.observe_global::(move |_, cx| cx.notify()), + }) + } +} diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index 24648f87f1d69587cb8b73d6584159a13c91788e..5aba7faaa02fe86b8f9df00a918ce6ec05559719 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -66,12 +66,12 @@ shellexpand = "2.1.0" text = { package = "text2", path = "../text2" } terminal_view = { package = "terminal_view2", path = "../terminal_view2" } theme = { package = "theme2", path = "../theme2" } -# theme_selector = { path = "../theme_selector" } +theme_selector = { package = "theme_selector2", path = "../theme_selector2" } util = { path = "../util" } # semantic_index = { path = "../semantic_index" } # vim = { path = "../vim" } workspace = { package = "workspace2", path = "../workspace2" } -# welcome = { path = "../welcome" } +welcome = { package = "welcome2", path = "../welcome2" } zed_actions = {package = "zed_actions2", path = "../zed_actions2"} anyhow.workspace = true async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] } diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 9c42badb8517f42b502e8af5b61c4296bb990ee5..648c4108d7eb621107eff07b96795f7ae526d363 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -8,7 +8,7 @@ use anyhow::{anyhow, Context as _, Result}; use backtrace::Backtrace; use chrono::Utc; use cli::FORCE_CLI_MODE_ENV_VAR_NAME; -use client::UserStore; +use client::{Client, UserStore}; use db::kvp::KEY_VALUE_STORE; use editor::Editor; use fs::RealFs; @@ -36,7 +36,7 @@ use std::{ path::{Path, PathBuf}, sync::{ atomic::{AtomicU32, Ordering}, - Arc, + Arc, Weak, }, thread, }; @@ -99,16 +99,15 @@ fn main() { let listener = Arc::new(listener); let open_listener = listener.clone(); app.on_open_urls(move |urls, _| open_listener.open_urls(&urls)); - app.on_reopen(move |_cx| { - // todo!("workspace") - // if cx.has_global::>() { - // if let Some(app_state) = cx.global::>().upgrade() { - // workspace::open_new(&app_state, cx, |workspace, cx| { - // Editor::new_file(workspace, &Default::default(), cx) - // }) - // .detach(); - // } - // } + app.on_reopen(move |cx| { + if cx.has_global::>() { + if let Some(app_state) = cx.global::>().upgrade() { + workspace::open_new(&app_state, cx, |workspace, cx| { + Editor::new_file(workspace, &Default::default(), cx) + }) + .detach(); + } + } }); app.run(move |cx| { @@ -180,7 +179,6 @@ fn main() { user_store, fs, build_window_options, - // background_actions: todo!("ask Mikayla"), workspace_store, node_runtime, }); @@ -236,7 +234,7 @@ fn main() { } } - let mut _triggered_authentication = false; + let mut triggered_authentication = false; fn open_paths_and_log_errs( paths: &[PathBuf], @@ -266,17 +264,17 @@ fn main() { .detach(); } Ok(Some(OpenRequest::JoinChannel { channel_id: _ })) => { - todo!() - // triggered_authentication = true; - // let app_state = app_state.clone(); - // let client = client.clone(); - // cx.spawn(|mut cx| async move { - // // ignore errors here, we'll show a generic "not signed in" - // let _ = authenticate(client, &cx).await; - // cx.update(|cx| workspace::join_channel(channel_id, app_state, None, cx)) - // .await - // }) - // .detach_and_log_err(cx) + triggered_authentication = true; + let app_state = app_state.clone(); + let client = client.clone(); + cx.spawn(|mut cx| async move { + // ignore errors here, we'll show a generic "not signed in" + let _ = authenticate(client, &cx).await; + // cx.update(|cx| workspace::join_channel(channel_id, app_state, None, cx)) + // .await + anyhow::Ok(()) + }) + .detach_and_log_err(cx) } Ok(Some(OpenRequest::OpenChannelNotes { channel_id: _ })) => { todo!() @@ -315,23 +313,23 @@ fn main() { }) .detach(); - // if !triggered_authentication { - // cx.spawn(|cx| async move { authenticate(client, &cx).await }) - // .detach_and_log_err(cx); - // } + if !triggered_authentication { + cx.spawn(|cx| async move { authenticate(client, &cx).await }) + .detach_and_log_err(cx); + } }); } -// async fn authenticate(client: Arc, cx: &AsyncAppContext) -> Result<()> { -// if stdout_is_a_pty() { -// if client::IMPERSONATE_LOGIN.is_some() { -// client.authenticate_and_connect(false, &cx).await?; -// } -// } else if client.has_keychain_credentials(&cx) { -// client.authenticate_and_connect(true, &cx).await?; -// } -// Ok::<_, anyhow::Error>(()) -// } +async fn authenticate(client: Arc, cx: &AsyncAppContext) -> Result<()> { + if stdout_is_a_pty() { + if client::IMPERSONATE_LOGIN.is_some() { + client.authenticate_and_connect(false, &cx).await?; + } + } else if client.has_keychain_credentials(&cx) { + client.authenticate_and_connect(true, &cx).await?; + } + Ok::<_, anyhow::Error>(()) +} async fn installation_id() -> Result { let legacy_key_name = "device_id"; @@ -355,11 +353,8 @@ async fn restore_or_create_workspace(app_state: &Arc, mut cx: AsyncApp cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx))? .await .log_err(); - } else if matches!(KEY_VALUE_STORE.read_kvp("******* THIS IS A BAD KEY PLEASE UNCOMMENT BELOW TO FIX THIS VERY LONG LINE *******"), Ok(None)) { - // todo!(welcome) - //} else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { - //todo!() - // cx.update(|cx| show_welcome_experience(app_state, cx)); + } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { + cx.update(|cx| show_welcome_experience(app_state, cx)); } else { cx.update(|cx| { workspace::open_new(app_state, cx, |workspace, cx| { diff --git a/script/crate-dep-graph b/script/crate-dep-graph index 25285cc097c01331e36ea34a9b5a9ef622f43342..74ea36683cde45a4453b7ee72531a4b83eef1b60 100755 --- a/script/crate-dep-graph +++ b/script/crate-dep-graph @@ -11,7 +11,7 @@ graph_file=target/crate-graph.html cargo depgraph \ --workspace-only \ --offline \ - --root=zed,cli,collab \ + --root=zed2,cli,collab2 \ --dedup-transitive-deps \ | dot -Tsvg > $graph_file From c1919438494b181ee812a6fd7992d0a355add5e9 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 23 Nov 2023 15:00:13 +0100 Subject: [PATCH 003/151] Add basic call/user UI in top-right corner. Allow ui::Avatar to take custom data instead of always relying on URI resolution --- crates/collab_ui2/src/collab_titlebar_item.rs | 45 +++++++++- crates/gpui2/src/elements/img.rs | 86 +++++++++++++------ crates/ui2/src/components/avatar.rs | 8 +- crates/ui2/src/components/stories/avatar.rs | 4 +- crates/util/src/channel.rs | 2 +- crates/workspace2/src/workspace2.rs | 4 + 6 files changed, 113 insertions(+), 36 deletions(-) diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index 94db2f4d9f60659cc2e520a2ed3533caa508f9af..d9eff16e8e4c5db81923deca5256bec3a1132485 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -37,7 +37,7 @@ use gpui::{ }; use project::Project; use theme::ActiveTheme; -use ui::{h_stack, Button, ButtonVariant, Color, KeyBinding, Label, Tooltip}; +use ui::{h_stack, Button, ButtonVariant, Color, IconButton, KeyBinding, Tooltip}; use workspace::Workspace; // const MAX_PROJECT_NAME_LENGTH: usize = 40; @@ -85,6 +85,13 @@ impl Render for CollabTitlebarItem { type Element = Stateful
; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + let is_in_room = self + .workspace + .update(cx, |this, cx| this.call_state().is_in_room(cx)) + .unwrap_or_default(); + let is_shared = is_in_room && self.project.read(cx).is_shared(); + let current_user = self.user_store.read(cx).current_user(); + let client = self.client.clone(); h_stack() .id("titlebar") .justify_between() @@ -149,8 +156,40 @@ impl Render for CollabTitlebarItem { .into() }), ), - ) // self.titlebar_item - .child(h_stack().child(Label::new("Right side titlebar item"))) + ) + .map(|this| { + if let Some(user) = current_user { + this.when_some(user.avatar.clone(), |this, avatar| { + this.child(ui::Avatar::new(avatar)) + }) + } else { + this.child(Button::new("Sign in").on_click(move |_, cx| { + let client = client.clone(); + cx.spawn(move |cx| async move { + client.authenticate_and_connect(true, &cx).await?; + Ok::<(), anyhow::Error>(()) + }) + .detach_and_log_err(cx); + })) + } + }) // that's obviously wrong as we should check for current call,not current user + .when(is_in_room, |this| { + this.child( + h_stack() + .child( + h_stack() + .child(Button::new(if is_shared { "Unshare" } else { "Share" })) + .child(IconButton::new("leave-call", ui::Icon::Exit)), + ) + .child( + h_stack() + .child(IconButton::new("mute-microphone", ui::Icon::Mic)) + .child(IconButton::new("mute-sound", ui::Icon::AudioOn)) + .child(IconButton::new("screen-share", ui::Icon::Screen)) + .pl_2(), + ), + ) + }) } } diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index 3c0f4c00be852ed2df5295ce83d47b583582f747..c28f0dca3091488dbcd500ffd3a691b0ff9e6ee6 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -1,30 +1,59 @@ +use std::sync::Arc; + use crate::{ - Bounds, Element, InteractiveElement, InteractiveElementState, Interactivity, LayoutId, Pixels, - RenderOnce, SharedString, StyleRefinement, Styled, WindowContext, + Bounds, Element, ImageData, InteractiveElement, InteractiveElementState, Interactivity, + LayoutId, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, WindowContext, }; use futures::FutureExt; use util::ResultExt; +#[derive(Clone, Debug)] +pub enum ImageSource { + /// Image content will be loaded from provided URI at render time. + Uri(SharedString), + Data(Arc), +} + +impl From for ImageSource { + fn from(value: SharedString) -> Self { + Self::Uri(value) + } +} + +impl From> for ImageSource { + fn from(value: Arc) -> Self { + Self::Data(value) + } +} + pub struct Img { interactivity: Interactivity, - uri: Option, + source: Option, grayscale: bool, } pub fn img() -> Img { Img { interactivity: Interactivity::default(), - uri: None, + source: None, grayscale: false, } } impl Img { pub fn uri(mut self, uri: impl Into) -> Self { - self.uri = Some(uri.into()); + self.source = Some(ImageSource::from(uri.into())); + self + } + pub fn data(mut self, data: Arc) -> Self { + self.source = Some(ImageSource::from(data)); self } + pub fn source(mut self, source: impl Into) -> Self { + self.source = Some(source.into()); + self + } pub fn grayscale(mut self, grayscale: bool) -> Self { self.grayscale = grayscale; self @@ -58,28 +87,33 @@ impl Element for Img { |style, _scroll_offset, cx| { let corner_radii = style.corner_radii; - if let Some(uri) = self.uri.clone() { - // eprintln!(">>> image_cache.get({uri}"); - let image_future = cx.image_cache.get(uri.clone()); - // eprintln!("<<< image_cache.get({uri}"); - if let Some(data) = image_future - .clone() - .now_or_never() - .and_then(|result| result.ok()) - { - let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size()); - cx.with_z_index(1, |cx| { - cx.paint_image(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.notify()); + if let Some(source) = self.source { + let image = match source { + ImageSource::Uri(uri) => { + let image_future = cx.image_cache.get(uri.clone()); + if let Some(data) = image_future + .clone() + .now_or_never() + .and_then(|result| result.ok()) + { + data + } else { + cx.spawn(|mut cx| async move { + if image_future.await.ok().is_some() { + cx.on_next_frame(|cx| cx.notify()); + } + }) + .detach(); + return; } - }) - .detach() - } + } + ImageSource::Data(image) => image, + }; + let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size()); + cx.with_z_index(1, |cx| { + cx.paint_image(bounds, corner_radii, image, self.grayscale) + .log_err() + }); } }, ) diff --git a/crates/ui2/src/components/avatar.rs b/crates/ui2/src/components/avatar.rs index 364a1454946d51970723938da672b21993f9484f..872d62fa129e083b3c692cc920f5673e6a0767cf 100644 --- a/crates/ui2/src/components/avatar.rs +++ b/crates/ui2/src/components/avatar.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use gpui::{img, Img, RenderOnce}; +use gpui::{img, ImageSource, Img, RenderOnce}; #[derive(Debug, Default, PartialEq, Clone)] pub enum Shape { @@ -10,7 +10,7 @@ pub enum Shape { #[derive(RenderOnce)] pub struct Avatar { - src: SharedString, + src: ImageSource, shape: Shape, } @@ -26,7 +26,7 @@ impl Component for Avatar { img = img.rounded_md(); } - img.uri(self.src.clone()) + img.source(self.src.clone()) .size_4() // todo!(Pull the avatar fallback background from the theme.) .bg(gpui::red()) @@ -34,7 +34,7 @@ impl Component for Avatar { } impl Avatar { - pub fn new(src: impl Into) -> Self { + pub fn new(src: impl Into) -> Self { Self { src: src.into(), shape: Shape::Circle, diff --git a/crates/ui2/src/components/stories/avatar.rs b/crates/ui2/src/components/stories/avatar.rs index ad9c3ccb3928b43cdb7ee066f9a296d96ef59b68..177065cfcb96aaab262bb35001c63eb75b500842 100644 --- a/crates/ui2/src/components/stories/avatar.rs +++ b/crates/ui2/src/components/stories/avatar.rs @@ -14,10 +14,10 @@ impl Render for AvatarStory { .child(Story::title_for::()) .child(Story::label("Default")) .child(Avatar::new( - "https://avatars.githubusercontent.com/u/1714999?v=4", + "https://avatars.githubusercontent.com/u/1714999?v=4".into(), )) .child(Avatar::new( - "https://avatars.githubusercontent.com/u/326587?v=4", + "https://avatars.githubusercontent.com/u/326587?v=4".into(), )) } } diff --git a/crates/util/src/channel.rs b/crates/util/src/channel.rs index 55f13df0846ff1b9c9396a28826ae488ce074304..94260c71db3e9255aa0089070e75b54bde098568 100644 --- a/crates/util/src/channel.rs +++ b/crates/util/src/channel.rs @@ -19,7 +19,7 @@ lazy_static! { pub struct AppCommitSha(pub String); -#[derive(Copy, Clone, PartialEq, Eq, Default)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] pub enum ReleaseChannel { #[default] Dev, diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index b09b47d24c47551e530ff3d9d08c3e129b3f1a22..761b09365dab48012824d6fd29efab0dc47bec05 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -3408,6 +3408,10 @@ impl Workspace { self.modal_layer .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build)) } + + pub fn call_state(&mut self) -> &mut dyn CallHandler { + &mut *self.call_handler + } } fn window_bounds_env_override(cx: &AsyncAppContext) -> Option { From b463454062fae91775e4604e5a4c4ab42bf0ee7d Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 23 Nov 2023 15:03:54 +0100 Subject: [PATCH 004/151] Remove redundant comment --- crates/collab_ui2/src/collab_titlebar_item.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index 13a3ee67321d646b07a75a7a71d09e998f19b0b8..7c9cb9f45347284cc34b56ba9acee3ff995fd0b3 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -172,7 +172,7 @@ impl Render for CollabTitlebarItem { .detach_and_log_err(cx); })) } - }) // that's obviously wrong as we should check for current call,not current user + }) .when(is_in_room, |this| { this.child( h_stack() From 2a2b3b5e918a8f47acd01b3bfdb371c7e34ac21f Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 23 Nov 2023 15:08:21 +0100 Subject: [PATCH 005/151] Authenticate on app startup --- crates/zed2/src/main.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index bbf7c3ae9c1125cc3717416033de4012c535f9bc..b18eccace798b701f44bd9925d5eb74b0c3d01c1 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -8,7 +8,7 @@ use anyhow::{anyhow, Context as _, Result}; use backtrace::Backtrace; use chrono::Utc; use cli::FORCE_CLI_MODE_ENV_VAR_NAME; -use client::UserStore; +use client::{Client, UserStore}; use db::kvp::KEY_VALUE_STORE; use editor::Editor; use fs::RealFs; @@ -249,7 +249,7 @@ fn main() { } } - let mut _triggered_authentication = false; + let mut triggered_authentication = false; fn open_paths_and_log_errs( paths: &[PathBuf], @@ -328,23 +328,23 @@ fn main() { }) .detach(); - // if !triggered_authentication { - // cx.spawn(|cx| async move { authenticate(client, &cx).await }) - // .detach_and_log_err(cx); - // } + if !triggered_authentication { + cx.spawn(|cx| async move { authenticate(client, &cx).await }) + .detach_and_log_err(cx); + } }); } -// async fn authenticate(client: Arc, cx: &AsyncAppContext) -> Result<()> { -// if stdout_is_a_pty() { -// if client::IMPERSONATE_LOGIN.is_some() { -// client.authenticate_and_connect(false, &cx).await?; -// } -// } else if client.has_keychain_credentials(&cx) { -// client.authenticate_and_connect(true, &cx).await?; -// } -// Ok::<_, anyhow::Error>(()) -// } +async fn authenticate(client: Arc, cx: &AsyncAppContext) -> Result<()> { + if stdout_is_a_pty() { + if client::IMPERSONATE_LOGIN.is_some() { + client.authenticate_and_connect(false, &cx).await?; + } + } else if client.has_keychain_credentials(&cx).await { + client.authenticate_and_connect(true, &cx).await?; + } + Ok::<_, anyhow::Error>(()) +} async fn installation_id() -> Result<(String, bool)> { let legacy_key_name = "device_id"; From e754c6626de7c8a61f4a2010fbd2ba6e176e560a Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:19:25 +0100 Subject: [PATCH 006/151] Allow calling an user, render contacts in collab panel --- crates/call2/src/call2.rs | 74 +++++++++++++++------------ crates/collab_ui2/src/collab_panel.rs | 42 ++++++++++++--- crates/workspace2/src/workspace2.rs | 6 +++ 3 files changed, 82 insertions(+), 40 deletions(-) diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index 8e553a9b166ba34bfd6f85070f2b062070315a35..9aa8c1142850aad3e438d5f076733a48bf0814ff 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -549,6 +549,40 @@ impl Call { #[async_trait(?Send)] impl CallHandler for Call { + fn peer_state( + &mut self, + leader_id: PeerId, + cx: &mut ViewContext, + ) -> Option<(bool, bool)> { + let (call, _) = self.active_call.as_ref()?; + let room = call.read(cx).room()?.read(cx); + let participant = room.remote_participant_for_peer_id(leader_id)?; + + let leader_in_this_app; + let leader_in_this_project; + match participant.location { + ParticipantLocation::SharedProject { project_id } => { + leader_in_this_app = true; + leader_in_this_project = Some(project_id) + == self + .parent_workspace + .update(cx, |this, cx| this.project().read(cx).remote_id()) + .log_err() + .flatten(); + } + ParticipantLocation::UnsharedProject => { + leader_in_this_app = true; + leader_in_this_project = false; + } + ParticipantLocation::External => { + leader_in_this_app = false; + leader_in_this_project = false; + } + }; + + Some((leader_in_this_project, leader_in_this_app)) + } + fn shared_screen_for_peer( &self, peer_id: PeerId, @@ -571,7 +605,6 @@ impl CallHandler for Call { // SharedScreen::new(&track, peer_id, user.clone(), cx) // }))) } - fn room_id(&self, cx: &AppContext) -> Option { Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id()) } @@ -585,38 +618,15 @@ impl CallHandler for Call { fn active_project(&self, cx: &AppContext) -> Option> { ActiveCall::global(cx).read(cx).location().cloned() } - fn peer_state( + fn invite( &mut self, - leader_id: PeerId, - cx: &mut ViewContext, - ) -> Option<(bool, bool)> { - let (call, _) = self.active_call.as_ref()?; - let room = call.read(cx).room()?.read(cx); - let participant = room.remote_participant_for_peer_id(leader_id)?; - - let leader_in_this_app; - let leader_in_this_project; - match participant.location { - ParticipantLocation::SharedProject { project_id } => { - leader_in_this_app = true; - leader_in_this_project = Some(project_id) - == self - .parent_workspace - .update(cx, |this, cx| this.project().read(cx).remote_id()) - .log_err() - .flatten(); - } - ParticipantLocation::UnsharedProject => { - leader_in_this_app = true; - leader_in_this_project = false; - } - ParticipantLocation::External => { - leader_in_this_app = false; - leader_in_this_project = false; - } - }; - - Some((leader_in_this_project, leader_in_this_app)) + called_user_id: u64, + initial_project: Option>, + cx: &mut AppContext, + ) -> Task> { + ActiveCall::global(cx).update(cx, |this, cx| { + this.invite(called_user_id, initial_project, cx) + }) } } diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 6af188dfd200c82d21771a603b905a5e2377f182..abeb481d533a200f9c60cb8d9d74b182ac701173 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -157,15 +157,17 @@ const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel"; use std::sync::Arc; +use client::{Client, Contact, UserStore}; use db::kvp::KEY_VALUE_STORE; use gpui::{ actions, div, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle, - Focusable, FocusableView, InteractiveElement, ParentElement, Render, View, ViewContext, - VisualContext, WeakView, + Focusable, FocusableView, InteractiveElement, Model, ParentElement, Render, Styled, View, + ViewContext, VisualContext, WeakView, }; use project::Fs; use serde_derive::{Deserialize, Serialize}; use settings::Settings; +use ui::{h_stack, Avatar, Label}; use util::ResultExt; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, @@ -299,8 +301,8 @@ pub struct CollabPanel { // channel_editing_state: Option, // entries: Vec, // selection: Option, - // user_store: ModelHandle, - // client: Arc, + user_store: Model, + client: Arc, // channel_store: ModelHandle, // project: ModelHandle, // match_candidates: Vec, @@ -595,7 +597,7 @@ impl CollabPanel { // entries: Vec::default(), // channel_editing_state: None, // selection: None, - // user_store: workspace.user_store().clone(), + user_store: workspace.user_store().clone(), // channel_store: ChannelStore::global(cx), // project: workspace.project().clone(), // subscriptions: Vec::default(), @@ -603,7 +605,7 @@ impl CollabPanel { // collapsed_sections: vec![Section::Offline], // collapsed_channels: Vec::default(), _workspace: workspace.weak_handle(), - // client: workspace.app_state().client.clone(), + client: workspace.app_state().client.clone(), // context_menu_on_selected: true, // drag_target_channel: ChannelDragTarget::None, // list_state, @@ -663,6 +665,9 @@ impl CollabPanel { }) } + fn contacts(&self, cx: &AppContext) -> Option>> { + Some(self.user_store.read(cx).contacts().to_owned()) + } pub async fn load( workspace: WeakView, mut cx: AsyncWindowContext, @@ -3297,11 +3302,32 @@ impl CollabPanel { impl Render for CollabPanel { type Element = Focusable
; - fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + let contacts = self.contacts(cx).unwrap_or_default(); + let workspace = self._workspace.clone(); div() .key_context("CollabPanel") .track_focus(&self.focus_handle) - .child("COLLAB PANEL") + .children(contacts.into_iter().map(|contact| { + let id = contact.user.id; + h_stack() + .p_2() + .gap_2() + .children( + contact + .user + .avatar + .as_ref() + .map(|avatar| Avatar::new(avatar.clone())), + ) + .child(Label::new(contact.user.github_login.clone())) + .on_mouse_down(gpui::MouseButton::Left, { + let workspace = workspace.clone(); + move |event, cx| { + workspace.update(cx, |this, cx| this.call_state().invite(id, None, cx)); + } + }) + })) } } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 761b09365dab48012824d6fd29efab0dc47bec05..8c687becd0c57d4afa92ba15689b26288f24a3c5 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -454,6 +454,12 @@ pub trait CallHandler { } fn hang_up(&self, cx: AsyncWindowContext) -> Result>>; fn active_project(&self, cx: &AppContext) -> Option>; + fn invite( + &mut self, + called_user_id: u64, + initial_project: Option>, + cx: &mut AppContext, + ) -> Task>; } pub struct Workspace { From f2b62c3946d7392469bebcf0857c893e0e8135bc Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 24 Nov 2023 00:52:44 +0100 Subject: [PATCH 007/151] Start emitting notifications for calls --- Cargo.lock | 1 + crates/collab_ui2/src/collab_ui.rs | 58 +-- crates/collab_ui2/src/notifications.rs | 16 +- .../incoming_call_notification.rs | 346 ++++++++++-------- crates/zed2/Cargo.toml | 2 +- crates/zed2/src/main.rs | 2 +- 6 files changed, 239 insertions(+), 186 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 99d4827966c7ef4c1e5427d59d01c144906fb7a2..693f2d9156e4d8ae683b77f445bb06242e2a36c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11644,6 +11644,7 @@ dependencies = [ "async-recursion 0.3.2", "async-tar", "async-trait", + "audio2", "auto_update2", "backtrace", "call2", diff --git a/crates/collab_ui2/src/collab_ui.rs b/crates/collab_ui2/src/collab_ui.rs index d2e6b28115dacf147d1a2ac7dff25e9de9dd8de8..d1e4bea7d62464d7bc10517ac60f632fa80b8a06 100644 --- a/crates/collab_ui2/src/collab_ui.rs +++ b/crates/collab_ui2/src/collab_ui.rs @@ -7,11 +7,14 @@ pub mod notification_panel; pub mod notifications; mod panel_settings; -use std::sync::Arc; +use std::{rc::Rc, sync::Arc}; pub use collab_panel::CollabPanel; pub use collab_titlebar_item::CollabTitlebarItem; -use gpui::AppContext; +use gpui::{ + point, px, AppContext, GlobalPixels, Pixels, PlatformDisplay, Point, Size, WindowBounds, + WindowKind, WindowOptions, +}; pub use panel_settings::{ ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings, }; @@ -23,7 +26,7 @@ use workspace::AppState; // [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall] // ); -pub fn init(_app_state: &Arc, cx: &mut AppContext) { +pub fn init(app_state: &Arc, cx: &mut AppContext) { CollaborationPanelSettings::register(cx); ChatPanelSettings::register(cx); NotificationPanelSettings::register(cx); @@ -32,7 +35,7 @@ pub fn init(_app_state: &Arc, cx: &mut AppContext) { collab_titlebar_item::init(cx); collab_panel::init(cx); // chat_panel::init(cx); - // notifications::init(&app_state, cx); + notifications::init(&app_state, cx); // cx.add_global_action(toggle_screen_sharing); // cx.add_global_action(toggle_mute); @@ -95,31 +98,30 @@ pub fn init(_app_state: &Arc, cx: &mut AppContext) { // } // } -// fn notification_window_options( -// screen: Rc, -// window_size: Vector2F, -// ) -> WindowOptions<'static> { -// const NOTIFICATION_PADDING: f32 = 16.; +fn notification_window_options( + screen: Rc, + window_size: Size, +) -> WindowOptions { + let notification_padding = Pixels::from(16.); -// let screen_bounds = screen.content_bounds(); -// WindowOptions { -// bounds: WindowBounds::Fixed(RectF::new( -// screen_bounds.upper_right() -// + vec2f( -// -NOTIFICATION_PADDING - window_size.x(), -// NOTIFICATION_PADDING, -// ), -// window_size, -// )), -// titlebar: None, -// center: false, -// focus: false, -// show: true, -// kind: WindowKind::PopUp, -// is_movable: false, -// screen: Some(screen), -// } -// } + let screen_bounds = screen.bounds(); + let size: Size = window_size.into(); + + let bounds = gpui::Bounds:: { + origin: screen_bounds.origin, + size: window_size.into(), + }; + WindowOptions { + bounds: WindowBounds::Fixed(bounds), + titlebar: None, + center: false, + focus: false, + show: true, + kind: WindowKind::PopUp, + is_movable: false, + display_id: Some(screen.id()), + } +} // fn render_avatar( // avatar: Option>, diff --git a/crates/collab_ui2/src/notifications.rs b/crates/collab_ui2/src/notifications.rs index bc5d7ad3bf0f80fa1f16e8f60337e8abdbde5018..b58473476acb40b29ecb9754133ce6ce6e8fbf5c 100644 --- a/crates/collab_ui2/src/notifications.rs +++ b/crates/collab_ui2/src/notifications.rs @@ -1,11 +1,11 @@ -// use gpui::AppContext; -// use std::sync::Arc; -// use workspace::AppState; +use gpui::AppContext; +use std::sync::Arc; +use workspace::AppState; -// pub mod incoming_call_notification; +pub mod incoming_call_notification; // pub mod project_shared_notification; -// pub fn init(app_state: &Arc, cx: &mut AppContext) { -// incoming_call_notification::init(app_state, cx); -// project_shared_notification::init(app_state, cx); -// } +pub fn init(app_state: &Arc, cx: &mut AppContext) { + incoming_call_notification::init(app_state, cx); + //project_shared_notification::init(app_state, cx); +} diff --git a/crates/collab_ui2/src/notifications/incoming_call_notification.rs b/crates/collab_ui2/src/notifications/incoming_call_notification.rs index c614a814caf1757b37306cfca5b7d570fc0fac0f..d400b14f5f362c7c60658a2dd857b65785ccc693 100644 --- a/crates/collab_ui2/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui2/src/notifications/incoming_call_notification.rs @@ -3,12 +3,12 @@ use call::{ActiveCall, IncomingCall}; use client::proto; use futures::StreamExt; use gpui::{ - elements::*, - geometry::vector::vec2f, - platform::{CursorStyle, MouseButton}, - AnyElement, AppContext, Entity, View, ViewContext, WindowHandle, + blue, div, green, px, red, AnyElement, AppContext, Component, Context, Div, Element, Entity, + EventEmitter, GlobalPixels, ParentElement, Render, RenderOnce, StatefulInteractiveElement, + Styled, View, ViewContext, VisualContext as _, WindowHandle, }; use std::sync::{Arc, Weak}; +use ui::{h_stack, v_stack, Avatar, Button, Label}; use util::ResultExt; use workspace::AppState; @@ -19,23 +19,44 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { let mut notification_windows: Vec> = Vec::new(); while let Some(incoming_call) = incoming_call.next().await { for window in notification_windows.drain(..) { - window.remove(&mut cx); + window.update(&mut cx, |this, cx| { + //cx.remove_window(); + }); + //cx.update_window(window.into(), |this, cx| cx.remove_window()); + //window.remove(&mut cx); } if let Some(incoming_call) = incoming_call { - let window_size = cx.read(|cx| { - let theme = &theme::current(cx).incoming_call_notification; - vec2f(theme.window_width, theme.window_height) - }); + let unique_screens = cx.update(|cx| cx.displays()).unwrap(); + let window_size = gpui::Size { + width: px(380.), + height: px(64.), + }; - for screen in cx.platform().screens() { + for window in unique_screens { + let options = notification_window_options(window, window_size); + dbg!(&options); let window = cx - .add_window(notification_window_options(screen, window_size), |_| { - IncomingCallNotification::new(incoming_call.clone(), app_state.clone()) - }); - + .open_window(options, |cx| { + cx.build_view(|_| { + IncomingCallNotification::new( + incoming_call.clone(), + app_state.clone(), + ) + }) + }) + .unwrap(); notification_windows.push(window); } + + // for screen in cx.platform().screens() { + // let window = cx + // .add_window(notification_window_options(screen, window_size), |_| { + // IncomingCallNotification::new(incoming_call.clone(), app_state.clone()) + // }); + + // notification_windows.push(window); + // } } } }) @@ -47,167 +68,196 @@ struct RespondToCall { accept: bool, } -pub struct IncomingCallNotification { +struct IncomingCallNotificationState { call: IncomingCall, app_state: Weak, } -impl IncomingCallNotification { +pub struct IncomingCallNotification { + state: Arc, +} +impl IncomingCallNotificationState { pub fn new(call: IncomingCall, app_state: Weak) -> Self { Self { call, app_state } } - fn respond(&mut self, accept: bool, cx: &mut ViewContext) { + fn respond(&self, accept: bool, cx: &mut AppContext) { let active_call = ActiveCall::global(cx); if accept { let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx)); let caller_user_id = self.call.calling_user.id; let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id); let app_state = self.app_state.clone(); - cx.app_context() - .spawn(|mut cx| async move { - join.await?; - if let Some(project_id) = initial_project_id { - cx.update(|cx| { - if let Some(app_state) = app_state.upgrade() { - workspace::join_remote_project( - project_id, - caller_user_id, - app_state, - cx, - ) - .detach_and_log_err(cx); - } - }); - } - anyhow::Ok(()) - }) - .detach_and_log_err(cx); + let cx: &mut AppContext = cx; + cx.spawn(|mut cx| async move { + join.await?; + if let Some(project_id) = initial_project_id { + cx.update(|cx| { + if let Some(app_state) = app_state.upgrade() { + // workspace::join_remote_project( + // project_id, + // caller_user_id, + // app_state, + // cx, + // ) + // .detach_and_log_err(cx); + } + }); + } + anyhow::Ok(()) + }) + .detach_and_log_err(cx); } else { active_call.update(cx, |active_call, cx| { active_call.decline_incoming(cx).log_err(); }); } } +} - fn render_caller(&self, cx: &mut ViewContext) -> AnyElement { - let theme = &theme::current(cx).incoming_call_notification; - let default_project = proto::ParticipantProject::default(); - let initial_project = self - .call - .initial_project - .as_ref() - .unwrap_or(&default_project); - Flex::row() - .with_children(self.call.calling_user.avatar.clone().map(|avatar| { - Image::from_data(avatar) - .with_style(theme.caller_avatar) - .aligned() - })) - .with_child( - Flex::column() - .with_child( - Label::new( - self.call.calling_user.github_login.clone(), - theme.caller_username.text.clone(), - ) - .contained() - .with_style(theme.caller_username.container), - ) - .with_child( - Label::new( - format!( - "is sharing a project in Zed{}", - if initial_project.worktree_root_names.is_empty() { - "" - } else { - ":" - } - ), - theme.caller_message.text.clone(), - ) - .contained() - .with_style(theme.caller_message.container), - ) - .with_children(if initial_project.worktree_root_names.is_empty() { - None - } else { - Some( - Label::new( - initial_project.worktree_root_names.join(", "), - theme.worktree_roots.text.clone(), - ) - .contained() - .with_style(theme.worktree_roots.container), - ) - }) - .contained() - .with_style(theme.caller_metadata) - .aligned(), - ) - .contained() - .with_style(theme.caller_container) - .flex(1., true) - .into_any() +impl IncomingCallNotification { + pub fn new(call: IncomingCall, app_state: Weak) -> Self { + Self { + state: Arc::new(IncomingCallNotificationState::new(call, app_state)), + } } - - fn render_buttons(&self, cx: &mut ViewContext) -> AnyElement { - enum Accept {} - enum Decline {} - - let theme = theme::current(cx); - Flex::column() - .with_child( - MouseEventHandler::new::(0, cx, |_, _| { - let theme = &theme.incoming_call_notification; - Label::new("Accept", theme.accept_button.text.clone()) - .aligned() - .contained() - .with_style(theme.accept_button.container) - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, this, cx| { - this.respond(true, cx); - }) - .flex(1., true), + fn render_caller(&self, cx: &mut ViewContext) -> impl Element { + h_stack() + .children( + self.state + .call + .calling_user + .avatar + .as_ref() + .map(|avatar| Avatar::new(avatar.clone())), ) - .with_child( - MouseEventHandler::new::(0, cx, |_, _| { - let theme = &theme.incoming_call_notification; - Label::new("Decline", theme.decline_button.text.clone()) - .aligned() - .contained() - .with_style(theme.decline_button.container) - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, this, cx| { - this.respond(false, cx); - }) - .flex(1., true), + .child( + v_stack() + .child(Label::new(format!( + "{} is sharing a project in Zed", + self.state.call.calling_user.github_login + ))) + .child(self.render_buttons(cx)), ) - .constrained() - .with_width(theme.incoming_call_notification.button_width) - .into_any() + // let theme = &theme::current(cx).incoming_call_notification; + // let default_project = proto::ParticipantProject::default(); + // let initial_project = self + // .call + // .initial_project + // .as_ref() + // .unwrap_or(&default_project); + // Flex::row() + // .with_children(self.call.calling_user.avatar.clone().map(|avatar| { + // Image::from_data(avatar) + // .with_style(theme.caller_avatar) + // .aligned() + // })) + // .with_child( + // Flex::column() + // .with_child( + // Label::new( + // self.call.calling_user.github_login.clone(), + // theme.caller_username.text.clone(), + // ) + // .contained() + // .with_style(theme.caller_username.container), + // ) + // .with_child( + // Label::new( + // format!( + // "is sharing a project in Zed{}", + // if initial_project.worktree_root_names.is_empty() { + // "" + // } else { + // ":" + // } + // ), + // theme.caller_message.text.clone(), + // ) + // .contained() + // .with_style(theme.caller_message.container), + // ) + // .with_children(if initial_project.worktree_root_names.is_empty() { + // None + // } else { + // Some( + // Label::new( + // initial_project.worktree_root_names.join(", "), + // theme.worktree_roots.text.clone(), + // ) + // .contained() + // .with_style(theme.worktree_roots.container), + // ) + // }) + // .contained() + // .with_style(theme.caller_metadata) + // .aligned(), + // ) + // .contained() + // .with_style(theme.caller_container) + // .flex(1., true) + // .into_any() } -} -impl Entity for IncomingCallNotification { - type Event = (); -} + fn render_buttons(&self, cx: &mut ViewContext) -> impl Element { + h_stack() + .child(Button::new("Accept").render(cx).bg(green()).on_click({ + let state = self.state.clone(); + move |_, cx| state.respond(true, cx) + })) + .child(Button::new("Decline").render(cx).bg(red()).on_click({ + let state = self.state.clone(); + move |_, cx| state.respond(false, cx) + })) -impl View for IncomingCallNotification { - fn ui_name() -> &'static str { - "IncomingCallNotification" - } + // enum Accept {} + // enum Decline {} - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let background = theme::current(cx).incoming_call_notification.background; - Flex::row() - .with_child(self.render_caller(cx)) - .with_child(self.render_buttons(cx)) - .contained() - .with_background_color(background) - .expanded() - .into_any() + // let theme = theme::current(cx); + // Flex::column() + // .with_child( + // MouseEventHandler::new::(0, cx, |_, _| { + // let theme = &theme.incoming_call_notification; + // Label::new("Accept", theme.accept_button.text.clone()) + // .aligned() + // .contained() + // .with_style(theme.accept_button.container) + // }) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_click(MouseButton::Left, |_, this, cx| { + // this.respond(true, cx); + // }) + // .flex(1., true), + // ) + // .with_child( + // MouseEventHandler::new::(0, cx, |_, _| { + // let theme = &theme.incoming_call_notification; + // Label::new("Decline", theme.decline_button.text.clone()) + // .aligned() + // .contained() + // .with_style(theme.decline_button.container) + // }) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_click(MouseButton::Left, |_, this, cx| { + // this.respond(false, cx); + // }) + // .flex(1., true), + // ) + // .constrained() + // .with_width(theme.incoming_call_notification.button_width) + // .into_any() + } +} +impl Render for IncomingCallNotification { + type Element = Div; + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + div().bg(red()).flex_none().child(self.render_caller(cx)) + // Flex::row() + // .with_child() + // .with_child(self.render_buttons(cx)) + // .contained() + // .with_background_color(background) + // .expanded() + // .into_any() } } diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index 24648f87f1d69587cb8b73d6584159a13c91788e..3212b6182b7b64fe1d79a2f20097fffd64c603b5 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -16,7 +16,7 @@ path = "src/main.rs" [dependencies] ai = { package = "ai2", path = "../ai2"} -# audio = { path = "../audio" } +audio = { package = "audio2", path = "../audio2" } # activity_indicator = { path = "../activity_indicator" } auto_update = { package = "auto_update2", path = "../auto_update2" } # breadcrumbs = { path = "../breadcrumbs" } diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index b18eccace798b701f44bd9925d5eb74b0c3d01c1..c9ed26436ab8e07529c2365d8bb91ec989eed51a 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -199,7 +199,7 @@ fn main() { }); cx.set_global(Arc::downgrade(&app_state)); - // audio::init(Assets, cx); + audio::init(Assets, cx); auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx); workspace::init(app_state.clone(), cx); From 6ebe5d5053af7b9d95cd28c2b1742a00a0d140fd Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 24 Nov 2023 15:16:03 +0100 Subject: [PATCH 008/151] Add mute handling --- crates/call2/src/call2.rs | 37 +++++++++ crates/call2/src/room.rs | 3 +- crates/client2/src/client2.rs | 1 - crates/collab_ui2/src/collab_titlebar_item.rs | 77 ++++++++++++++----- crates/workspace2/src/workspace2.rs | 25 +++++- 5 files changed, 122 insertions(+), 21 deletions(-) diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index 9aa8c1142850aad3e438d5f076733a48bf0814ff..e778316d59545f2ec81b553ddbb604cc3afcff1b 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -17,6 +17,7 @@ use gpui::{ Subscription, Task, View, ViewContext, WeakModel, WeakView, }; pub use participant::ParticipantLocation; +use participant::RemoteParticipant; use postage::watch; use project::Project; use room::Event; @@ -628,6 +629,42 @@ impl CallHandler for Call { this.invite(called_user_id, initial_project, cx) }) } + fn remote_participants(&self, cx: &AppContext) -> Option>> { + self.active_call + .as_ref() + .map(|call| { + call.0.read(cx).room().map(|room| { + room.read(cx) + .remote_participants() + .iter() + .map(|participant| participant.1.user.clone()) + .collect() + }) + }) + .flatten() + } + fn is_muted(&self, cx: &AppContext) -> Option { + self.active_call + .as_ref() + .map(|call| { + call.0 + .read(cx) + .room() + .map(|room| room.read(cx).is_muted(cx)) + }) + .flatten() + } + fn toggle_mute(&self, cx: &mut AppContext) { + self.active_call.as_ref().map(|call| { + call.0.update(cx, |this, cx| { + this.room().map(|room| { + room.update(cx, |this, cx| { + this.toggle_mute(cx); + }) + }) + }) + }); + } } #[cfg(test)] diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index 87118764fdc717620521b32e5b77aa7e1c4aca9f..d73a522937615b9239877f7205d8f30d1bd82ad8 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -333,7 +333,8 @@ impl Room { } pub fn mute_on_join(cx: &AppContext) -> bool { - CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some() + false + //CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some() } fn from_join_response( diff --git a/crates/client2/src/client2.rs b/crates/client2/src/client2.rs index 4ad354f2f91bd56cdb0c1f657137d1beac9a3e4f..b31451aa87a55f5ed65a7a9cdb6a0149d52cfe80 100644 --- a/crates/client2/src/client2.rs +++ b/crates/client2/src/client2.rs @@ -551,7 +551,6 @@ impl Client { F: 'static + Future>, { let message_type_id = TypeId::of::(); - let mut state = self.state.write(); state .models_by_message_type diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index 7c9cb9f45347284cc34b56ba9acee3ff995fd0b3..bcb43d4972b270b454e2e95741f1261643e51bfd 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -37,7 +37,8 @@ use gpui::{ }; use project::Project; use theme::ActiveTheme; -use ui::{h_stack, Button, ButtonVariant, Color, IconButton, KeyBinding, Tooltip}; +use ui::{h_stack, Avatar, Button, ButtonVariant, Color, IconButton, KeyBinding, Label, Tooltip}; +use util::ResultExt; use workspace::Workspace; // const MAX_PROJECT_NAME_LENGTH: usize = 40; @@ -92,6 +93,23 @@ impl Render for CollabTitlebarItem { let is_shared = is_in_room && self.project.read(cx).is_shared(); let current_user = self.user_store.read(cx).current_user(); let client = self.client.clone(); + let users = self + .workspace + .update(cx, |this, cx| this.call_state().remote_participants(cx)) + .log_err() + .flatten(); + let mic_icon = if self + .workspace + .update(cx, |this, cx| this.call_state().is_muted(cx)) + .log_err() + .flatten() + .unwrap_or_default() + { + ui::Icon::MicMute + } else { + ui::Icon::Mic + }; + let workspace = self.workspace.clone(); h_stack() .id("titlebar") .justify_between() @@ -157,22 +175,23 @@ impl Render for CollabTitlebarItem { }), ), ) - .map(|this| { - if let Some(user) = current_user { - this.when_some(user.avatar.clone(), |this, avatar| { - this.child(ui::Avatar::new(avatar)) - }) - } else { - this.child(Button::new("Sign in").on_click(move |_, cx| { - let client = client.clone(); - cx.spawn(move |cx| async move { - client.authenticate_and_connect(true, &cx).await?; - Ok::<(), anyhow::Error>(()) - }) - .detach_and_log_err(cx); - })) - } - }) + .when_some( + users.zip(current_user.clone()), + |this, (remote_participants, current_user)| { + this.children( + current_user + .avatar + .clone() + .map(|avatar| Avatar::new(avatar.clone())) + .into_iter() + .chain(remote_participants.into_iter().flat_map(|user| { + user.avatar + .as_ref() + .map(|avatar| Avatar::new(avatar.clone())) + })), + ) + }, + ) .when(is_in_room, |this| { this.child( h_stack() @@ -183,13 +202,35 @@ impl Render for CollabTitlebarItem { ) .child( h_stack() - .child(IconButton::new("mute-microphone", ui::Icon::Mic)) + .child(IconButton::new("mute-microphone", mic_icon).on_click( + move |_, cx| { + workspace.update(cx, |this, cx| { + this.call_state().toggle_mute(cx); + }); + }, + )) .child(IconButton::new("mute-sound", ui::Icon::AudioOn)) .child(IconButton::new("screen-share", ui::Icon::Screen)) .pl_2(), ), ) }) + .map(|this| { + if let Some(user) = current_user { + this.when_some(user.avatar.clone(), |this, avatar| { + this.child(ui::Avatar::new(avatar)) + }) + } else { + this.child(Button::new("Sign in").on_click(move |_, cx| { + let client = client.clone(); + cx.spawn(move |cx| async move { + client.authenticate_and_connect(true, &cx).await?; + Ok::<(), anyhow::Error>(()) + }) + .detach_and_log_err(cx); + })) + } + }) } } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 8c687becd0c57d4afa92ba15689b26288f24a3c5..bccb25b64ddc3a0cedce22b5e71a8ebcfccf551f 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -19,7 +19,7 @@ use anyhow::{anyhow, Context as _, Result}; use async_trait::async_trait; use client2::{ proto::{self, PeerId}, - Client, TypedEnvelope, UserStore, + Client, TypedEnvelope, User, UserStore, }; use collections::{hash_map, HashMap, HashSet}; use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle}; @@ -351,7 +351,27 @@ impl CallHandler for TestCallHandler { fn active_project(&self, cx: &AppContext) -> Option> { None } + + fn invite( + &mut self, + called_user_id: u64, + initial_project: Option>, + cx: &mut AppContext, + ) -> Task> { + unimplemented!() + } + + fn remote_participants(&self, cx: &AppContext) -> Option> { + None + } + + fn is_muted(&self, cx: &AppContext) -> Option { + None + } + + fn toggle_mute(&self, cx: &mut AppContext) {} } + impl AppState { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut AppContext) -> Arc { @@ -460,6 +480,9 @@ pub trait CallHandler { initial_project: Option>, cx: &mut AppContext, ) -> Task>; + fn remote_participants(&self, cx: &AppContext) -> Option>>; + fn is_muted(&self, cx: &AppContext) -> Option; + fn toggle_mute(&self, cx: &mut AppContext); } pub struct Workspace { From 481c19fbaf0a6265ae80c5368f11619d3af73fd3 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 24 Nov 2023 17:31:47 +0100 Subject: [PATCH 009/151] WIP, frames are being sent to the other end --- Cargo.lock | 2 + crates/call2/Cargo.toml | 2 + crates/call2/src/call2.rs | 60 +++++-- crates/call2/src/participant.rs | 2 +- crates/call2/src/room.rs | 2 + crates/call2/src/shared_screen.rs | 159 ++++++++++++++++++ crates/collab_ui2/src/collab_titlebar_item.rs | 39 +++-- crates/workspace2/src/workspace2.rs | 71 ++++---- 8 files changed, 276 insertions(+), 61 deletions(-) create mode 100644 crates/call2/src/shared_screen.rs diff --git a/Cargo.lock b/Cargo.lock index 693f2d9156e4d8ae683b77f445bb06242e2a36c1..27ff88f68a49f249a35291673a9420164db29c2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1193,6 +1193,7 @@ dependencies = [ "fs2", "futures 0.3.28", "gpui2", + "image", "language2", "live_kit_client2", "log", @@ -1204,6 +1205,7 @@ dependencies = [ "serde_derive", "serde_json", "settings2", + "smallvec", "util", "workspace2", ] diff --git a/crates/call2/Cargo.toml b/crates/call2/Cargo.toml index 43e19b4ccb4738c42654dffaf7797d75dc88c084..a258cbe2cf3edd10931022067627106d99890ac8 100644 --- a/crates/call2/Cargo.toml +++ b/crates/call2/Cargo.toml @@ -36,11 +36,13 @@ async-trait.workspace = true anyhow.workspace = true async-broadcast = "0.4" futures.workspace = true +image = "0.23" postage.workspace = true schemars.workspace = true serde.workspace = true serde_json.workspace = true serde_derive.workspace = true +smallvec.workspace = true [dev-dependencies] client = { package = "client2", path = "../client2", features = ["test-support"] } diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index e778316d59545f2ec81b553ddbb604cc3afcff1b..5933f7404f42d9dcab96261cea0cbdd5070ab073 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -1,6 +1,7 @@ pub mod call_settings; pub mod participant; pub mod room; +mod shared_screen; use anyhow::{anyhow, bail, Result}; use async_trait::async_trait; @@ -14,7 +15,7 @@ use collections::HashSet; use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use gpui::{ AppContext, AsyncAppContext, AsyncWindowContext, Context, EventEmitter, Model, ModelContext, - Subscription, Task, View, ViewContext, WeakModel, WeakView, + Subscription, Task, View, ViewContext, VisualContext, WeakModel, WeakView, }; pub use participant::ParticipantLocation; use participant::RemoteParticipant; @@ -23,6 +24,7 @@ use project::Project; use room::Event; pub use room::Room; use settings::Settings; +use shared_screen::SharedScreen; use std::sync::Arc; use util::ResultExt; use workspace::{item::ItemHandle, CallHandler, Pane, Workspace}; @@ -587,24 +589,27 @@ impl CallHandler for Call { fn shared_screen_for_peer( &self, peer_id: PeerId, - _pane: &View, + pane: &View, cx: &mut ViewContext, ) -> Option> { let (call, _) = self.active_call.as_ref()?; + dbg!("A"); let room = call.read(cx).room()?.read(cx); + dbg!("B"); let participant = room.remote_participant_for_peer_id(peer_id)?; - let _track = participant.video_tracks.values().next()?.clone(); - let _user = participant.user.clone(); - todo!(); - // for item in pane.read(cx).items_of_type::() { - // if item.read(cx).peer_id == peer_id { - // return Box::new(Some(item)); - // } - // } - - // Some(Box::new(cx.build_view(|cx| { - // SharedScreen::new(&track, peer_id, user.clone(), cx) - // }))) + dbg!("C"); + let track = participant.video_tracks.values().next()?.clone(); + dbg!("D"); + let user = participant.user.clone(); + for item in pane.read(cx).items_of_type::() { + if item.read(cx).peer_id == peer_id { + return Some(Box::new(item)); + } + } + + Some(Box::new(cx.build_view(|cx| { + SharedScreen::new(&track, peer_id, user.clone(), cx) + }))) } fn room_id(&self, cx: &AppContext) -> Option { Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id()) @@ -629,7 +634,7 @@ impl CallHandler for Call { this.invite(called_user_id, initial_project, cx) }) } - fn remote_participants(&self, cx: &AppContext) -> Option>> { + fn remote_participants(&self, cx: &AppContext) -> Option, PeerId)>> { self.active_call .as_ref() .map(|call| { @@ -637,7 +642,9 @@ impl CallHandler for Call { room.read(cx) .remote_participants() .iter() - .map(|participant| participant.1.user.clone()) + .map(|participant| { + (participant.1.user.clone(), participant.1.peer_id.clone()) + }) .collect() }) }) @@ -665,6 +672,27 @@ impl CallHandler for Call { }) }); } + fn toggle_screen_share(&self, cx: &mut AppContext) { + self.active_call.as_ref().map(|call| { + call.0.update(cx, |this, cx| { + this.room().map(|room| { + room.update(cx, |this, cx| { + if this.is_screen_sharing() { + dbg!("Unsharing"); + this.unshare_screen(cx); + } else { + dbg!("Sharing"); + let t = this.share_screen(cx); + cx.spawn(move |_, cx| async move { + t.await.log_err(); + }) + .detach(); + } + }) + }) + }) + }); + } } #[cfg(test)] diff --git a/crates/call2/src/participant.rs b/crates/call2/src/participant.rs index f62d103f1758d85f6e4a12cc10c1797c7cd80f50..325a4f812b2f58c1b1bb0cc56f042e891df435d4 100644 --- a/crates/call2/src/participant.rs +++ b/crates/call2/src/participant.rs @@ -4,7 +4,7 @@ use client::{proto, User}; use collections::HashMap; use gpui::WeakModel; pub use live_kit_client::Frame; -use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack}; +pub(crate) use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack}; use project::Project; use std::sync::Arc; diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index d73a522937615b9239877f7205d8f30d1bd82ad8..5bba4cc75063532125d6cd12fa97187241142138 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -1345,6 +1345,8 @@ impl Room { let display = displays .first() .ok_or_else(|| anyhow!("no display found"))?; + dbg!("Been there"); + dbg!(displays.len()); let track = LocalVideoTrack::screen_share_for_display(&display); this.upgrade() .ok_or_else(|| anyhow!("room was dropped"))? diff --git a/crates/call2/src/shared_screen.rs b/crates/call2/src/shared_screen.rs new file mode 100644 index 0000000000000000000000000000000000000000..a281948d6f10d682090d5c8d71c309d403424d01 --- /dev/null +++ b/crates/call2/src/shared_screen.rs @@ -0,0 +1,159 @@ +use crate::participant::{Frame, RemoteVideoTrack}; +use anyhow::Result; +use client::{proto::PeerId, User}; +use futures::StreamExt; +use gpui::{ + div, img, AppContext, Div, Element, Entity, EventEmitter, FocusHandle, FocusableView, + ImageData, Img, MouseButton, ParentElement, Render, SharedString, Task, View, ViewContext, + VisualContext, WindowContext, +}; +use smallvec::SmallVec; +use std::{ + borrow::Cow, + sync::{Arc, Weak}, +}; +use workspace::{ + item::{Item, ItemEvent}, + ItemNavHistory, WorkspaceId, +}; + +pub enum Event { + Close, +} + +pub struct SharedScreen { + track: Weak, + frame: Option, + pub peer_id: PeerId, + user: Arc, + nav_history: Option, + _maintain_frame: Task>, + focus: FocusHandle, +} + +impl SharedScreen { + pub fn new( + track: &Arc, + peer_id: PeerId, + user: Arc, + cx: &mut ViewContext, + ) -> Self { + cx.focus_handle(); + let mut frames = track.frames(); + Self { + track: Arc::downgrade(track), + frame: None, + peer_id, + user, + nav_history: Default::default(), + _maintain_frame: cx.spawn(|this, mut cx| async move { + while let Some(frame) = frames.next().await { + this.update(&mut cx, |this, cx| { + this.frame = Some(frame); + cx.notify(); + })?; + } + this.update(&mut cx, |_, cx| cx.emit(Event::Close))?; + Ok(()) + }), + focus: cx.focus_handle(), + } + } +} + +impl EventEmitter for SharedScreen {} +impl EventEmitter for SharedScreen {} + +impl FocusableView for SharedScreen { + fn focus_handle(&self, cx: &AppContext) -> FocusHandle { + self.focus.clone() + } +} +impl Render for SharedScreen { + type Element = Div; + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + let frame = self.frame.clone(); + div().children(frame.map(|frame| { + img().data(Arc::new(ImageData::new(image::ImageBuffer::new( + frame.width() as u32, + frame.height() as u32, + )))) + })) + } +} +// impl View for SharedScreen { +// fn ui_name() -> &'static str { +// "SharedScreen" +// } + +// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { +// enum Focus {} + +// let frame = self.frame.clone(); +// MouseEventHandler::new::(0, cx, |_, cx| { +// Canvas::new(move |bounds, _, _, cx| { +// if let Some(frame) = frame.clone() { +// let size = constrain_size_preserving_aspect_ratio( +// bounds.size(), +// vec2f(frame.width() as f32, frame.height() as f32), +// ); +// let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.; +// cx.scene().push_surface(gpui::platform::mac::Surface { +// bounds: RectF::new(origin, size), +// image_buffer: frame.image(), +// }); +// } +// }) +// .contained() +// .with_style(theme::current(cx).shared_screen) +// }) +// .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent()) +// .into_any() +// } +// } + +impl Item for SharedScreen { + fn tab_tooltip_text(&self, _: &AppContext) -> Option { + Some(format!("{}'s screen", self.user.github_login).into()) + } + fn deactivated(&mut self, cx: &mut ViewContext) { + if let Some(nav_history) = self.nav_history.as_mut() { + nav_history.push::<()>(None, cx); + } + } + + fn tab_content(&self, _: Option, _: &WindowContext<'_>) -> gpui::AnyElement { + div().child("Shared screen").into_any() + // Flex::row() + // .with_child( + // Svg::new("icons/desktop.svg") + // .with_color(style.label.text.color) + // .constrained() + // .with_width(style.type_icon_width) + // .aligned() + // .contained() + // .with_margin_right(style.spacing), + // ) + // .with_child( + // Label::new( + // format!("{}'s screen", self.user.github_login), + // style.label.clone(), + // ) + // .aligned(), + // ) + // .into_any() + } + + fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext) { + self.nav_history = Some(history); + } + + fn clone_on_split( + &self, + _workspace_id: WorkspaceId, + cx: &mut ViewContext, + ) -> Option> { + let track = self.track.upgrade()?; + Some(cx.build_view(|cx| Self::new(&track, self.peer_id, self.user.clone(), cx))) + } +} diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index bcb43d4972b270b454e2e95741f1261643e51bfd..aad908531ac3a8b1362494778c27f2a99ea42391 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -31,9 +31,9 @@ use std::sync::Arc; use call::ActiveCall; use client::{Client, UserStore}; use gpui::{ - div, px, rems, AppContext, Div, InteractiveElement, IntoElement, Model, ParentElement, Render, - Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext, - WeakView, WindowBounds, + div, px, rems, AppContext, Div, InteractiveElement, IntoElement, Model, MouseButton, + ParentElement, Render, Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, + VisualContext, WeakView, WindowBounds, }; use project::Project; use theme::ActiveTheme; @@ -182,12 +182,22 @@ impl Render for CollabTitlebarItem { current_user .avatar .clone() - .map(|avatar| Avatar::new(avatar.clone())) + .map(|avatar| div().child(Avatar::new(avatar.clone()))) .into_iter() - .chain(remote_participants.into_iter().flat_map(|user| { - user.avatar - .as_ref() - .map(|avatar| Avatar::new(avatar.clone())) + .chain(remote_participants.into_iter().flat_map(|(user, peer_id)| { + user.avatar.as_ref().map(|avatar| { + div() + .child(Avatar::new(avatar.clone()).into_element()) + .on_mouse_down(MouseButton::Left, { + let workspace = workspace.clone(); + let id = peer_id.clone(); + move |_, cx| { + workspace.update(cx, |this, cx| { + this.open_shared_screen(peer_id, cx); + }); + } + }) + }) })), ) }, @@ -202,15 +212,22 @@ impl Render for CollabTitlebarItem { ) .child( h_stack() - .child(IconButton::new("mute-microphone", mic_icon).on_click( + .child(IconButton::new("mute-microphone", mic_icon).on_click({ + let workspace = workspace.clone(); move |_, cx| { workspace.update(cx, |this, cx| { this.call_state().toggle_mute(cx); }); + } + })) + .child(IconButton::new("mute-sound", ui::Icon::AudioOn)) + .child(IconButton::new("screen-share", ui::Icon::Screen).on_click( + move |_, cx| { + workspace.update(cx, |this, cx| { + this.call_state().toggle_screen_share(cx); + }); }, )) - .child(IconButton::new("mute-sound", ui::Icon::AudioOn)) - .child(IconButton::new("screen-share", ui::Icon::Screen)) .pl_2(), ), ) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index bccb25b64ddc3a0cedce22b5e71a8ebcfccf551f..ac759022960bad85902dae3ff299b59fe3f17081 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -361,7 +361,7 @@ impl CallHandler for TestCallHandler { unimplemented!() } - fn remote_participants(&self, cx: &AppContext) -> Option> { + fn remote_participants(&self, cx: &AppContext) -> Option, PeerId)>> { None } @@ -370,6 +370,8 @@ impl CallHandler for TestCallHandler { } fn toggle_mute(&self, cx: &mut AppContext) {} + + fn toggle_screen_share(&self, cx: &mut AppContext) {} } impl AppState { @@ -480,9 +482,10 @@ pub trait CallHandler { initial_project: Option>, cx: &mut AppContext, ) -> Task>; - fn remote_participants(&self, cx: &AppContext) -> Option>>; + fn remote_participants(&self, cx: &AppContext) -> Option, PeerId)>>; fn is_muted(&self, cx: &AppContext) -> Option; fn toggle_mute(&self, cx: &mut AppContext); + fn toggle_screen_share(&self, cx: &mut AppContext); } pub struct Workspace { @@ -1996,13 +1999,15 @@ impl Workspace { item } - // pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext) { - // if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) { - // self.active_pane.update(cx, |pane, cx| { - // pane.add_item(Box::new(shared_screen), false, true, None, cx) - // }); - // } - // } + pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext) { + if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) { + self.active_pane.update(cx, |pane, cx| { + pane.add_item(shared_screen, false, true, None, cx) + }); + } else { + dbg!("WTF"); + } + } pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext) -> bool { let result = self.panes.iter().find_map(|pane| { @@ -2877,10 +2882,10 @@ impl Workspace { } continue; } - // todo!() - // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { - // items_to_activate.push((pane.clone(), Box::new(shared_screen))); - // } + + if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { + items_to_activate.push((pane.clone(), shared_screen)); + } } for (pane, item) in items_to_activate { @@ -2901,27 +2906,27 @@ impl Workspace { None } - // todo!() - // fn shared_screen_for_peer( - // &self, - // peer_id: PeerId, - // pane: &View, - // cx: &mut ViewContext, - // ) -> Option> { - // let call = self.active_call()?; - // let room = call.read(cx).room()?.read(cx); - // let participant = room.remote_participant_for_peer_id(peer_id)?; - // let track = participant.video_tracks.values().next()?.clone(); - // let user = participant.user.clone(); - - // for item in pane.read(cx).items_of_type::() { - // if item.read(cx).peer_id == peer_id { - // return Some(item); - // } - // } + fn shared_screen_for_peer( + &self, + peer_id: PeerId, + pane: &View, + cx: &mut ViewContext, + ) -> Option> { + self.call_handler.shared_screen_for_peer(peer_id, pane, cx) + // let call = self.active_call()?; + // let room = call.read(cx).room()?.read(cx); + // let participant = room.remote_participant_for_peer_id(peer_id)?; + // let track = participant.video_tracks.values().next()?.clone(); + // let user = participant.user.clone(); + + // for item in pane.read(cx).items_of_type::() { + // if item.read(cx).peer_id == peer_id { + // return Some(item); + // } + // } - // Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx))) - // } + // Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx))) + } pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext) { if cx.is_window_active() { From 039c933d8e51df7cbeec87af8e373dfee9b196ac Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sun, 26 Nov 2023 22:27:33 -0700 Subject: [PATCH 010/151] gpui2: Notifications --- .../auto_update2/src/update_notification.rs | 9 +- .../command_palette2/src/command_palette.rs | 9 +- crates/file_finder2/src/file_finder.rs | 10 +- crates/go_to_line2/src/go_to_line.rs | 13 +- crates/gpui2/src/app/async_context.rs | 8 +- crates/gpui2/src/app/test_context.rs | 2 +- crates/gpui2/src/window.rs | 10 +- crates/ui2/src/components/context_menu.rs | 16 +-- crates/workspace/src/workspace.rs | 20 ++- crates/workspace2/src/notifications.rs | 133 ++++++++++++------ crates/workspace2/src/workspace2.rs | 71 ++++++---- 11 files changed, 190 insertions(+), 111 deletions(-) diff --git a/crates/auto_update2/src/update_notification.rs b/crates/auto_update2/src/update_notification.rs index e6a22b73248a8fce898c6871abb06d602a3a8e7a..9cb1550bd431eb81d7c0e0c8dc4a49655f5d73ce 100644 --- a/crates/auto_update2/src/update_notification.rs +++ b/crates/auto_update2/src/update_notification.rs @@ -1,12 +1,13 @@ -use gpui::{div, Div, EventEmitter, ParentElement, Render, SemanticVersion, ViewContext}; +use gpui::{ + div, DismissEvent, Div, EventEmitter, ParentElement, Render, SemanticVersion, ViewContext, +}; use menu::Cancel; -use workspace::notifications::NotificationEvent; pub struct UpdateNotification { _version: SemanticVersion, } -impl EventEmitter for UpdateNotification {} +impl EventEmitter for UpdateNotification {} impl Render for UpdateNotification { type Element = Div; @@ -82,6 +83,6 @@ impl UpdateNotification { } pub fn _dismiss(&mut self, _: &Cancel, cx: &mut ViewContext) { - cx.emit(NotificationEvent::Dismiss); + cx.emit(DismissEvent::Dismiss); } } diff --git a/crates/command_palette2/src/command_palette.rs b/crates/command_palette2/src/command_palette.rs index 3c6f2fff92150fd302576635e7589eb238e5d01b..07b819d3a10c054ea20cddac787d28e792d3b187 100644 --- a/crates/command_palette2/src/command_palette.rs +++ b/crates/command_palette2/src/command_palette.rs @@ -1,8 +1,9 @@ use collections::{CommandPaletteFilter, HashMap}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - actions, div, prelude::*, Action, AppContext, Div, EventEmitter, FocusHandle, FocusableView, - Keystroke, Manager, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView, + actions, div, prelude::*, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, + FocusableView, Keystroke, ParentElement, Render, Styled, View, ViewContext, VisualContext, + WeakView, }; use picker::{Picker, PickerDelegate}; use std::{ @@ -68,7 +69,7 @@ impl CommandPalette { } } -impl EventEmitter for CommandPalette {} +impl EventEmitter for CommandPalette {} impl FocusableView for CommandPalette { fn focus_handle(&self, cx: &AppContext) -> FocusHandle { @@ -268,7 +269,7 @@ impl PickerDelegate for CommandPaletteDelegate { fn dismissed(&mut self, cx: &mut ViewContext>) { self.command_palette - .update(cx, |_, cx| cx.emit(Manager::Dismiss)) + .update(cx, |_, cx| cx.emit(DismissEvent::Dismiss)) .log_err(); } diff --git a/crates/file_finder2/src/file_finder.rs b/crates/file_finder2/src/file_finder.rs index 873054a68c77c81ecac723b2ad1169ef9eca3775..ea578fbb0ebb5703f3f31ed142f5589a0345c814 100644 --- a/crates/file_finder2/src/file_finder.rs +++ b/crates/file_finder2/src/file_finder.rs @@ -2,8 +2,8 @@ use collections::HashMap; use editor::{scroll::autoscroll::Autoscroll, Bias, Editor}; use fuzzy::{CharBag, PathMatch, PathMatchCandidate}; use gpui::{ - actions, div, AppContext, Div, EventEmitter, FocusHandle, FocusableView, InteractiveElement, - IntoElement, Manager, Model, ParentElement, Render, Styled, Task, View, ViewContext, + actions, div, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, + InteractiveElement, IntoElement, Model, ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView, }; use picker::{Picker, PickerDelegate}; @@ -111,7 +111,7 @@ impl FileFinder { } } -impl EventEmitter for FileFinder {} +impl EventEmitter for FileFinder {} impl FocusableView for FileFinder { fn focus_handle(&self, cx: &AppContext) -> FocusHandle { self.picker.focus_handle(cx) @@ -690,7 +690,7 @@ impl PickerDelegate for FileFinderDelegate { } } finder - .update(&mut cx, |_, cx| cx.emit(Manager::Dismiss)) + .update(&mut cx, |_, cx| cx.emit(DismissEvent::Dismiss)) .ok()?; Some(()) @@ -702,7 +702,7 @@ impl PickerDelegate for FileFinderDelegate { fn dismissed(&mut self, cx: &mut ViewContext>) { self.file_finder - .update(cx, |_, cx| cx.emit(Manager::Dismiss)) + .update(cx, |_, cx| cx.emit(DismissEvent::Dismiss)) .log_err(); } diff --git a/crates/go_to_line2/src/go_to_line.rs b/crates/go_to_line2/src/go_to_line.rs index 61f5742750a08ea35ffec2592c526e47f8b0a377..d1119de9b454ed83eb809fb6a657669ee061dfcb 100644 --- a/crates/go_to_line2/src/go_to_line.rs +++ b/crates/go_to_line2/src/go_to_line.rs @@ -1,7 +1,8 @@ use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor}; use gpui::{ - actions, div, prelude::*, AppContext, Div, EventEmitter, FocusHandle, FocusableView, Manager, - Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WindowContext, + actions, div, prelude::*, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, + FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, + WindowContext, }; use text::{Bias, Point}; use theme::ActiveTheme; @@ -28,7 +29,7 @@ impl FocusableView for GoToLine { self.active_editor.focus_handle(cx) } } -impl EventEmitter for GoToLine {} +impl EventEmitter for GoToLine {} impl GoToLine { fn register(workspace: &mut Workspace, _: &mut ViewContext) { @@ -88,7 +89,7 @@ impl GoToLine { ) { match event { // todo!() this isn't working... - editor::EditorEvent::Blurred => cx.emit(Manager::Dismiss), + editor::EditorEvent::Blurred => cx.emit(DismissEvent::Dismiss), editor::EditorEvent::BufferEdited { .. } => self.highlight_current_line(cx), _ => {} } @@ -123,7 +124,7 @@ impl GoToLine { } fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { - cx.emit(Manager::Dismiss); + cx.emit(DismissEvent::Dismiss); } fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext) { @@ -140,7 +141,7 @@ impl GoToLine { self.prev_scroll_position.take(); } - cx.emit(Manager::Dismiss); + cx.emit(DismissEvent::Dismiss); } } diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index cc3b0ace57b37d639656ef06fbc5a2c2344b7877..11420bee69d2c8acd9cb179df5e68bcd7c3e9e5b 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -1,7 +1,7 @@ use crate::{ - AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, Context, FocusableView, - ForegroundExecutor, Manager, Model, ModelContext, Render, Result, Task, View, ViewContext, - VisualContext, WindowContext, WindowHandle, + AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, Context, DismissEvent, + FocusableView, ForegroundExecutor, Model, ModelContext, Render, Result, Task, View, + ViewContext, VisualContext, WindowContext, WindowHandle, }; use anyhow::{anyhow, Context as _}; use derive_more::{Deref, DerefMut}; @@ -326,7 +326,7 @@ impl VisualContext for AsyncWindowContext { V: crate::ManagedView, { self.window.update(self, |_, cx| { - view.update(cx, |_, cx| cx.emit(Manager::Dismiss)) + view.update(cx, |_, cx| cx.emit(DismissEvent::Dismiss)) }) } } diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 2bd3a069caa28c22deb84c156ee590c14029d67d..71bc8e3d8172713e1f2c0ab7d7f5ffb33fe4171e 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -611,7 +611,7 @@ impl<'a> VisualContext for VisualTestContext<'a> { { self.window .update(self.cx, |_, cx| { - view.update(cx, |_, cx| cx.emit(crate::Manager::Dismiss)) + view.update(cx, |_, cx| cx.emit(crate::DismissEvent::Dismiss)) }) .unwrap() } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 7b39089ae0773295f8cf5d34090acdaa67f10003..de86e427571624fa2014827a51064eaa955fc73f 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -193,11 +193,11 @@ pub trait FocusableView: 'static + Render { /// ManagedView is a view (like a Modal, Popover, Menu, etc.) /// where the lifecycle of the view is handled by another view. -pub trait ManagedView: FocusableView + EventEmitter {} +pub trait ManagedView: FocusableView + EventEmitter {} -impl> ManagedView for M {} +impl> ManagedView for M {} -pub enum Manager { +pub enum DismissEvent { Dismiss, } @@ -1663,7 +1663,7 @@ impl VisualContext for WindowContext<'_> { where V: ManagedView, { - self.update_view(view, |_, cx| cx.emit(Manager::Dismiss)) + self.update_view(view, |_, cx| cx.emit(DismissEvent::Dismiss)) } } @@ -2349,7 +2349,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { where V: ManagedView, { - self.defer(|_, cx| cx.emit(Manager::Dismiss)) + self.defer(|_, cx| cx.emit(DismissEvent::Dismiss)) } pub fn listener( diff --git a/crates/ui2/src/components/context_menu.rs b/crates/ui2/src/components/context_menu.rs index d5adaf586be080d3c07f166710a7c254c18760a4..a92c08d82fae0164d0488e350f273df86f7ae48f 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -4,9 +4,9 @@ use std::rc::Rc; use crate::{prelude::*, v_stack, Label, List}; use crate::{ListItem, ListSeparator, ListSubHeader}; use gpui::{ - overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, ClickEvent, DispatchPhase, - Div, EventEmitter, FocusHandle, FocusableView, IntoElement, LayoutId, ManagedView, Manager, - MouseButton, MouseDownEvent, Pixels, Point, Render, View, VisualContext, + overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, ClickEvent, DismissEvent, + DispatchPhase, Div, EventEmitter, FocusHandle, FocusableView, IntoElement, LayoutId, + ManagedView, MouseButton, MouseDownEvent, Pixels, Point, Render, View, VisualContext, }; pub enum ContextMenuItem { @@ -26,7 +26,7 @@ impl FocusableView for ContextMenu { } } -impl EventEmitter for ContextMenu {} +impl EventEmitter for ContextMenu {} impl ContextMenu { pub fn build( @@ -74,11 +74,11 @@ impl ContextMenu { pub fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext) { // todo!() - cx.emit(Manager::Dismiss); + cx.emit(DismissEvent::Dismiss); } pub fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { - cx.emit(Manager::Dismiss); + cx.emit(DismissEvent::Dismiss); } } @@ -111,7 +111,7 @@ impl Render for ContextMenu { } ContextMenuItem::Entry(entry, callback) => { let callback = callback.clone(); - let dismiss = cx.listener(|_, _, cx| cx.emit(Manager::Dismiss)); + let dismiss = cx.listener(|_, _, cx| cx.emit(DismissEvent::Dismiss)); ListItem::new(entry.clone()) .child(Label::new(entry.clone())) @@ -265,7 +265,7 @@ impl Element for MenuHandle { let new_menu = (builder)(cx); let menu2 = menu.clone(); cx.subscribe(&new_menu, move |modal, e, cx| match e { - &Manager::Dismiss => { + &DismissEvent::Dismiss => { *menu2.borrow_mut() = None; cx.notify(); } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a19d2c5b58b083619af3c080a379fd2febb67593..268c4f2ca0ea8fa26ecc36d04687f4527692a4f1 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -63,7 +63,7 @@ use crate::{ }; use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle}; use lazy_static::lazy_static; -use notifications::{NotificationHandle, NotifyResultExt}; +use notifications::{simple_message_notification, NotificationHandle, NotifyResultExt}; pub use pane::*; pub use pane_group::*; use persistence::{model::SerializedItem, DB}; @@ -776,7 +776,23 @@ impl Workspace { }), ]; - cx.defer(|this, cx| this.update_window_title(cx)); + cx.defer(|this, cx| { + this.update_window_title(cx); + + this.show_notification(0, cx, |cx| { + cx.add_view(|_cx| { + simple_message_notification::MessageNotification::new(format!( + "Error: what happens if this message is very very very very very long " + )) + .with_click_message("Click here because!") + }) + }); + this.show_notification(1, cx, |cx| { + cx.add_view(|_cx| { + simple_message_notification::MessageNotification::new(format!("Nope")) + }) + }); + }); Workspace { weak_self: weak_handle.clone(), modal: None, diff --git a/crates/workspace2/src/notifications.rs b/crates/workspace2/src/notifications.rs index 9b8557c62c0367a5280edcae015e22a3f4960a20..def13c518e17308ef91df1b5cc940c71beae6e77 100644 --- a/crates/workspace2/src/notifications.rs +++ b/crates/workspace2/src/notifications.rs @@ -1,6 +1,9 @@ use crate::{Toast, Workspace}; use collections::HashMap; -use gpui::{AnyView, AppContext, Entity, EntityId, EventEmitter, Render, View, ViewContext}; +use gpui::{ + AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter, Render, + View, ViewContext, VisualContext, +}; use std::{any::TypeId, ops::DerefMut}; pub fn init(cx: &mut AppContext) { @@ -9,13 +12,9 @@ pub fn init(cx: &mut AppContext) { // simple_message_notification::init(cx); } -pub enum NotificationEvent { - Dismiss, -} - -pub trait Notification: EventEmitter + Render {} +pub trait Notification: EventEmitter + Render {} -impl + Render> Notification for V {} +impl + Render> Notification for V {} pub trait NotificationHandle: Send { fn id(&self) -> EntityId; @@ -107,8 +106,8 @@ impl Workspace { let notification = build_notification(cx); cx.subscribe( ¬ification, - move |this, handle, event: &NotificationEvent, cx| match event { - NotificationEvent::Dismiss => { + move |this, handle, event: &DismissEvent, cx| match event { + DismissEvent::Dismiss => { this.dismiss_notification_internal(type_id, id, cx); } }, @@ -120,6 +119,17 @@ impl Workspace { } } + pub fn show_error(&mut self, err: &E, cx: &mut ViewContext) + where + E: std::fmt::Debug, + { + self.show_notification(0, cx, |cx| { + cx.build_view(|_cx| { + simple_message_notification::MessageNotification::new(format!("Error: {err:?}")) + }) + }); + } + pub fn dismiss_notification(&mut self, id: usize, cx: &mut ViewContext) { let type_id = TypeId::of::(); @@ -166,13 +176,14 @@ impl Workspace { } pub mod simple_message_notification { - use super::NotificationEvent; - use gpui::{AnyElement, AppContext, Div, EventEmitter, Render, TextStyle, ViewContext}; + use gpui::{ + div, AnyElement, AppContext, DismissEvent, Div, EventEmitter, InteractiveElement, + ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, TextStyle, + ViewContext, + }; use serde::Deserialize; use std::{borrow::Cow, sync::Arc}; - - // todo!() - // actions!(message_notifications, [CancelMessageNotification]); + use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt}; #[derive(Clone, Default, Deserialize, PartialEq)] pub struct OsOpen(pub Cow<'static, str>); @@ -197,22 +208,22 @@ pub mod simple_message_notification { // } enum NotificationMessage { - Text(Cow<'static, str>), + Text(SharedString), Element(fn(TextStyle, &AppContext) -> AnyElement), } pub struct MessageNotification { message: NotificationMessage, on_click: Option) + Send + Sync>>, - click_message: Option>, + click_message: Option, } - impl EventEmitter for MessageNotification {} + impl EventEmitter for MessageNotification {} impl MessageNotification { pub fn new(message: S) -> MessageNotification where - S: Into>, + S: Into, { Self { message: NotificationMessage::Text(message.into()), @@ -221,19 +232,20 @@ pub mod simple_message_notification { } } - pub fn new_element( - message: fn(TextStyle, &AppContext) -> AnyElement, - ) -> MessageNotification { - Self { - message: NotificationMessage::Element(message), - on_click: None, - click_message: None, - } - } + // not needed I think (only for the "new panel" toast, which is outdated now) + // pub fn new_element( + // message: fn(TextStyle, &AppContext) -> AnyElement, + // ) -> MessageNotification { + // Self { + // message: NotificationMessage::Element(message), + // on_click: None, + // click_message: None, + // } + // } pub fn with_click_message(mut self, message: S) -> Self where - S: Into>, + S: Into, { self.click_message = Some(message.into()); self @@ -247,17 +259,43 @@ pub mod simple_message_notification { self } - // todo!() - // pub fn dismiss(&mut self, _: &CancelMessageNotification, cx: &mut ViewContext) { - // cx.emit(MessageNotificationEvent::Dismiss); - // } + pub fn dismiss(&mut self, cx: &mut ViewContext) { + cx.emit(DismissEvent::Dismiss); + } } impl Render for MessageNotification { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - todo!() + v_stack() + .elevation_3(cx) + .p_4() + .child( + h_stack() + .justify_between() + .child(div().max_w_80().child(match &self.message { + NotificationMessage::Text(text) => Label::new(text.clone()), + NotificationMessage::Element(element) => { + todo!() + } + })) + .child( + div() + .id("cancel") + .child(IconElement::new(Icon::Close)) + .cursor_pointer() + .on_click(cx.listener(|this, event, cx| this.dismiss(cx))), + ), + ) + .children(self.click_message.iter().map(|message| { + Button::new(message.clone()).on_click(cx.listener(|this, _, cx| { + if let Some(on_click) = this.on_click.as_ref() { + (on_click)(cx) + }; + this.dismiss(cx) + })) + })) } } // todo!() @@ -359,8 +397,6 @@ pub mod simple_message_notification { // .into_any() // } // } - - impl EventEmitter for MessageNotification {} } pub trait NotifyResultExt { @@ -371,6 +407,8 @@ pub trait NotifyResultExt { workspace: &mut Workspace, cx: &mut ViewContext, ) -> Option; + + fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option; } impl NotifyResultExt for Result @@ -384,14 +422,23 @@ where Ok(value) => Some(value), Err(err) => { log::error!("TODO {err:?}"); - // todo!() - // workspace.show_notification(0, cx, |cx| { - // cx.add_view(|_cx| { - // simple_message_notification::MessageNotification::new(format!( - // "Error: {err:?}", - // )) - // }) - // }); + workspace.show_error(&err, cx); + None + } + } + } + + fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option { + match self { + Ok(value) => Some(value), + Err(err) => { + log::error!("TODO {err:?}"); + cx.update(|view, cx| { + if let Ok(workspace) = view.downcast::() { + workspace.update(cx, |workspace, cx| workspace.show_error(&err, cx)) + } + }) + .ok(); None } } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index b09b47d24c47551e530ff3d9d08c3e129b3f1a22..5480ac4d3c597b161cf6f14cf7ee7b00cfb5eca5 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -683,7 +683,21 @@ impl Workspace { }), ]; - cx.defer(|this, cx| this.update_window_title(cx)); + cx.defer(|this, cx| { + this.update_window_title(cx); + // todo! @nate - these are useful for testing notifications + // this.show_error( + // &anyhow::anyhow!("what happens if this message is very very very very very long"), + // cx, + // ); + + // this.show_notification(1, cx, |cx| { + // cx.build_view(|_cx| { + // simple_message_notification::MessageNotification::new(format!("Error:")) + // .with_click_message("click here because!") + // }) + // }); + }); Workspace { window_self: window_handle, weak_self: weak_handle.clone(), @@ -2566,32 +2580,31 @@ impl Workspace { // } // } - // fn render_notifications( - // &self, - // theme: &theme::Workspace, - // cx: &AppContext, - // ) -> Option> { - // if self.notifications.is_empty() { - // None - // } else { - // Some( - // Flex::column() - // .with_children(self.notifications.iter().map(|(_, _, notification)| { - // ChildView::new(notification.as_any(), cx) - // .contained() - // .with_style(theme.notification) - // })) - // .constrained() - // .with_width(theme.notifications.width) - // .contained() - // .with_style(theme.notifications.container) - // .aligned() - // .bottom() - // .right() - // .into_any(), - // ) - // } - // } + fn render_notifications(&self, cx: &ViewContext) -> Option
{ + if self.notifications.is_empty() { + None + } else { + Some( + div() + .absolute() + .z_index(100) + .right_3() + .bottom_3() + .w_96() + .h_full() + .flex() + .flex_col() + .justify_end() + .gap_2() + .children(self.notifications.iter().map(|(_, _, notification)| { + div() + .on_any_mouse_down(|_, cx| cx.stop_propagation()) + .on_any_mouse_up(|_, cx| cx.stop_propagation()) + .child(notification.to_any()) + })), + ) + } + } // // RPC handlers @@ -3653,7 +3666,6 @@ impl Render for Workspace { .bg(cx.theme().colors().background) .children(self.titlebar_item.clone()) .child( - // todo! should this be a component a view? div() .id("workspace") .relative() @@ -3703,7 +3715,8 @@ impl Render for Workspace { .overflow_hidden() .child(self.right_dock.clone()), ), - ), + ) + .children(self.render_notifications(cx)), ) .child(self.status_bar.clone()) } From fad1f8db96cbdcb355c25c2a74eb4f6d1dacb3b1 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 27 Nov 2023 12:09:36 +0100 Subject: [PATCH 011/151] Remove shared_screen from workspace --- crates/workspace2/src/shared_screen.rs | 151 ------------------------- 1 file changed, 151 deletions(-) delete mode 100644 crates/workspace2/src/shared_screen.rs diff --git a/crates/workspace2/src/shared_screen.rs b/crates/workspace2/src/shared_screen.rs deleted file mode 100644 index b99c5f3ab99477ff651a1d28186901ed12b99de3..0000000000000000000000000000000000000000 --- a/crates/workspace2/src/shared_screen.rs +++ /dev/null @@ -1,151 +0,0 @@ -use crate::{ - item::{Item, ItemEvent}, - ItemNavHistory, WorkspaceId, -}; -use anyhow::Result; -use call::participant::{Frame, RemoteVideoTrack}; -use client::{proto::PeerId, User}; -use futures::StreamExt; -use gpui::{ - elements::*, - geometry::{rect::RectF, vector::vec2f}, - platform::MouseButton, - AppContext, Entity, Task, View, ViewContext, -}; -use smallvec::SmallVec; -use std::{ - borrow::Cow, - sync::{Arc, Weak}, -}; - -pub enum Event { - Close, -} - -pub struct SharedScreen { - track: Weak, - frame: Option, - pub peer_id: PeerId, - user: Arc, - nav_history: Option, - _maintain_frame: Task>, -} - -impl SharedScreen { - pub fn new( - track: &Arc, - peer_id: PeerId, - user: Arc, - cx: &mut ViewContext, - ) -> Self { - let mut frames = track.frames(); - Self { - track: Arc::downgrade(track), - frame: None, - peer_id, - user, - nav_history: Default::default(), - _maintain_frame: cx.spawn(|this, mut cx| async move { - while let Some(frame) = frames.next().await { - this.update(&mut cx, |this, cx| { - this.frame = Some(frame); - cx.notify(); - })?; - } - this.update(&mut cx, |_, cx| cx.emit(Event::Close))?; - Ok(()) - }), - } - } -} - -impl Entity for SharedScreen { - type Event = Event; -} - -impl View for SharedScreen { - fn ui_name() -> &'static str { - "SharedScreen" - } - - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - enum Focus {} - - let frame = self.frame.clone(); - MouseEventHandler::new::(0, cx, |_, cx| { - Canvas::new(move |bounds, _, _, cx| { - if let Some(frame) = frame.clone() { - let size = constrain_size_preserving_aspect_ratio( - bounds.size(), - vec2f(frame.width() as f32, frame.height() as f32), - ); - let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.; - cx.scene().push_surface(gpui::platform::mac::Surface { - bounds: RectF::new(origin, size), - image_buffer: frame.image(), - }); - } - }) - .contained() - .with_style(theme::current(cx).shared_screen) - }) - .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent()) - .into_any() - } -} - -impl Item for SharedScreen { - fn tab_tooltip_text(&self, _: &AppContext) -> Option> { - Some(format!("{}'s screen", self.user.github_login).into()) - } - fn deactivated(&mut self, cx: &mut ViewContext) { - if let Some(nav_history) = self.nav_history.as_mut() { - nav_history.push::<()>(None, cx); - } - } - - fn tab_content( - &self, - _: Option, - style: &theme::Tab, - _: &AppContext, - ) -> gpui::AnyElement { - Flex::row() - .with_child( - Svg::new("icons/desktop.svg") - .with_color(style.label.text.color) - .constrained() - .with_width(style.type_icon_width) - .aligned() - .contained() - .with_margin_right(style.spacing), - ) - .with_child( - Label::new( - format!("{}'s screen", self.user.github_login), - style.label.clone(), - ) - .aligned(), - ) - .into_any() - } - - fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext) { - self.nav_history = Some(history); - } - - fn clone_on_split( - &self, - _workspace_id: WorkspaceId, - cx: &mut ViewContext, - ) -> Option { - let track = self.track.upgrade()?; - Some(Self::new(&track, self.peer_id, self.user.clone(), cx)) - } - - fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { - match event { - Event::Close => smallvec::smallvec!(ItemEvent::CloseItem), - } - } -} From 4c1514edc43fd212377c67ec3199d5bd4d966df0 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 27 Nov 2023 12:10:07 +0100 Subject: [PATCH 012/151] fixup! Remove shared_screen from workspace --- crates/workspace2/src/workspace2.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index ac759022960bad85902dae3ff299b59fe3f17081..fd52276ef46b505d7f0ac1ddef1781dcc1cf8758 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -9,7 +9,6 @@ pub mod pane_group; mod persistence; pub mod searchable; // todo!() -// pub mod shared_screen; mod modal_layer; mod status_bar; mod toolbar; From 5cbe8deb50faeee5376a1174baea021333586759 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 27 Nov 2023 12:33:44 +0100 Subject: [PATCH 013/151] Fix up tests --- crates/collab_ui2/src/collab_panel.rs | 2 +- crates/collab_ui2/src/collab_titlebar_item.rs | 6 +++--- .../src/notifications/incoming_call_notification.rs | 2 +- crates/ui2/src/components/avatar.rs | 12 ++++++++++-- crates/ui2/src/components/list.rs | 4 ++-- crates/ui2/src/components/stories/avatar.rs | 8 ++++---- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index abeb481d533a200f9c60cb8d9d74b182ac701173..22a8a2cbba6b18806f3e612c7dd29e7677dcd536 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -3318,7 +3318,7 @@ impl Render for CollabPanel { .user .avatar .as_ref() - .map(|avatar| Avatar::new(avatar.clone())), + .map(|avatar| Avatar::data(avatar.clone())), ) .child(Label::new(contact.user.github_login.clone())) .on_mouse_down(gpui::MouseButton::Left, { diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index aad908531ac3a8b1362494778c27f2a99ea42391..9386d3ff35e540ab209cc44c8331bc1414871aa3 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -182,12 +182,12 @@ impl Render for CollabTitlebarItem { current_user .avatar .clone() - .map(|avatar| div().child(Avatar::new(avatar.clone()))) + .map(|avatar| div().child(Avatar::data(avatar.clone()))) .into_iter() .chain(remote_participants.into_iter().flat_map(|(user, peer_id)| { user.avatar.as_ref().map(|avatar| { div() - .child(Avatar::new(avatar.clone()).into_element()) + .child(Avatar::data(avatar.clone()).into_element()) .on_mouse_down(MouseButton::Left, { let workspace = workspace.clone(); let id = peer_id.clone(); @@ -235,7 +235,7 @@ impl Render for CollabTitlebarItem { .map(|this| { if let Some(user) = current_user { this.when_some(user.avatar.clone(), |this, avatar| { - this.child(ui::Avatar::new(avatar)) + this.child(ui::Avatar::data(avatar)) }) } else { this.child(Button::new("Sign in").on_click(move |_, cx| { diff --git a/crates/collab_ui2/src/notifications/incoming_call_notification.rs b/crates/collab_ui2/src/notifications/incoming_call_notification.rs index d400b14f5f362c7c60658a2dd857b65785ccc693..f47fcf07515195d325a663621fa9d34a55571452 100644 --- a/crates/collab_ui2/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui2/src/notifications/incoming_call_notification.rs @@ -129,7 +129,7 @@ impl IncomingCallNotification { .calling_user .avatar .as_ref() - .map(|avatar| Avatar::new(avatar.clone())), + .map(|avatar| Avatar::data(avatar.clone())), ) .child( v_stack() diff --git a/crates/ui2/src/components/avatar.rs b/crates/ui2/src/components/avatar.rs index 0ea7702f52081c924dfd3124898cd33336ff241b..d358b221da9287f488bd68dd95bcff659191b5a5 100644 --- a/crates/ui2/src/components/avatar.rs +++ b/crates/ui2/src/components/avatar.rs @@ -1,5 +1,7 @@ +use std::sync::Arc; + use crate::prelude::*; -use gpui::{img, ImageSource, Img, IntoElement}; +use gpui::{img, ImageData, ImageSource, Img, IntoElement}; #[derive(Debug, Default, PartialEq, Clone)] pub enum Shape { @@ -34,7 +36,13 @@ impl RenderOnce for Avatar { } impl Avatar { - pub fn new(src: impl Into) -> Self { + pub fn uri(src: impl Into) -> Self { + Self { + src: src.into().into(), + shape: Shape::Circle, + } + } + pub fn data(src: Arc) -> Self { Self { src: src.into(), shape: Shape::Circle, diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index a994583666ef98404097299824835a28f82be86c..875ab6d97e2032ae8c6be9bb9e0445d99897d099 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -323,8 +323,8 @@ impl RenderOnce for ListItem { .color(Color::Muted), ), ), - Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::new(src))), - Some(GraphicSlot::PublicActor(src)) => Some(h_stack().child(Avatar::new(src))), + Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::uri(src))), + Some(GraphicSlot::PublicActor(src)) => Some(h_stack().child(Avatar::uri(src))), None => None, }; diff --git a/crates/ui2/src/components/stories/avatar.rs b/crates/ui2/src/components/stories/avatar.rs index 177065cfcb96aaab262bb35001c63eb75b500842..505ede4ecc98d1248533f806b8fb47fc932a0028 100644 --- a/crates/ui2/src/components/stories/avatar.rs +++ b/crates/ui2/src/components/stories/avatar.rs @@ -13,11 +13,11 @@ impl Render for AvatarStory { Story::container() .child(Story::title_for::()) .child(Story::label("Default")) - .child(Avatar::new( - "https://avatars.githubusercontent.com/u/1714999?v=4".into(), + .child(Avatar::uri( + "https://avatars.githubusercontent.com/u/1714999?v=4", )) - .child(Avatar::new( - "https://avatars.githubusercontent.com/u/326587?v=4".into(), + .child(Avatar::uri( + "https://avatars.githubusercontent.com/u/326587?v=4", )) } } From 9590f253a924334cd451db30516ff72a9ea209ff Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 27 Nov 2023 12:46:07 +0100 Subject: [PATCH 014/151] Fix warnings --- crates/call2/src/call2.rs | 9 ++---- crates/call2/src/room.rs | 9 ++---- crates/call2/src/shared_screen.rs | 20 ++++--------- crates/collab_ui2/src/collab_panel.rs | 14 ++++++--- crates/collab_ui2/src/collab_titlebar_item.rs | 27 ++++++++++------- crates/collab_ui2/src/collab_ui.rs | 8 ++--- .../incoming_call_notification.rs | 29 +++++++++---------- 7 files changed, 56 insertions(+), 60 deletions(-) diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index 5933f7404f42d9dcab96261cea0cbdd5070ab073..f9a7e4bcfa845edfa8a38e3fc2b92d473ed1399e 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -18,7 +18,6 @@ use gpui::{ Subscription, Task, View, ViewContext, VisualContext, WeakModel, WeakView, }; pub use participant::ParticipantLocation; -use participant::RemoteParticipant; use postage::watch; use project::Project; use room::Event; @@ -666,7 +665,7 @@ impl CallHandler for Call { call.0.update(cx, |this, cx| { this.room().map(|room| { room.update(cx, |this, cx| { - this.toggle_mute(cx); + this.toggle_mute(cx).log_err(); }) }) }) @@ -678,12 +677,10 @@ impl CallHandler for Call { this.room().map(|room| { room.update(cx, |this, cx| { if this.is_screen_sharing() { - dbg!("Unsharing"); - this.unshare_screen(cx); + this.unshare_screen(cx).log_err(); } else { - dbg!("Sharing"); let t = this.share_screen(cx); - cx.spawn(move |_, cx| async move { + cx.spawn(move |_, _| async move { t.await.log_err(); }) .detach(); diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index 5bba4cc75063532125d6cd12fa97187241142138..1f299ff6cb9342bd6d390ec5717379961db95052 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -1,7 +1,4 @@ -use crate::{ - call_settings::CallSettings, - participant::{LocalParticipant, ParticipantLocation, RemoteParticipant}, -}; +use crate::participant::{LocalParticipant, ParticipantLocation, RemoteParticipant}; use anyhow::{anyhow, Result}; use audio::{Audio, Sound}; use client::{ @@ -21,7 +18,6 @@ use live_kit_client::{ }; use postage::{sink::Sink, stream::Stream, watch}; use project::Project; -use settings::Settings; use std::{future::Future, mem, sync::Arc, time::Duration}; use util::{post_inc, ResultExt, TryFutureExt}; @@ -332,7 +328,8 @@ impl Room { } } - pub fn mute_on_join(cx: &AppContext) -> bool { + pub fn mute_on_join(_cx: &AppContext) -> bool { + // todo!() po: This should be uncommented, though then unmuting does not work false //CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some() } diff --git a/crates/call2/src/shared_screen.rs b/crates/call2/src/shared_screen.rs index a281948d6f10d682090d5c8d71c309d403424d01..6f0024b63da13894b4130c077ef94f66481cec7e 100644 --- a/crates/call2/src/shared_screen.rs +++ b/crates/call2/src/shared_screen.rs @@ -3,19 +3,11 @@ use anyhow::Result; use client::{proto::PeerId, User}; use futures::StreamExt; use gpui::{ - div, img, AppContext, Div, Element, Entity, EventEmitter, FocusHandle, FocusableView, - ImageData, Img, MouseButton, ParentElement, Render, SharedString, Task, View, ViewContext, - VisualContext, WindowContext, -}; -use smallvec::SmallVec; -use std::{ - borrow::Cow, - sync::{Arc, Weak}, -}; -use workspace::{ - item::{Item, ItemEvent}, - ItemNavHistory, WorkspaceId, + div, img, AppContext, Div, Element, EventEmitter, FocusHandle, FocusableView, ImageData, + ParentElement, Render, SharedString, Task, View, ViewContext, VisualContext, WindowContext, }; +use std::sync::{Arc, Weak}; +use workspace::{item::Item, ItemNavHistory, WorkspaceId}; pub enum Event { Close, @@ -65,13 +57,13 @@ impl EventEmitter for SharedScreen {} impl EventEmitter for SharedScreen {} impl FocusableView for SharedScreen { - fn focus_handle(&self, cx: &AppContext) -> FocusHandle { + fn focus_handle(&self, _: &AppContext) -> FocusHandle { self.focus.clone() } } impl Render for SharedScreen { type Element = Div; - fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + fn render(&mut self, _: &mut ViewContext) -> Self::Element { let frame = self.frame.clone(); div().children(frame.map(|frame| { img().data(Arc::new(ImageData::new(image::ImageBuffer::new( diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 22a8a2cbba6b18806f3e612c7dd29e7677dcd536..e9c17a6589dd79c73edc9b6e29bea4c76740b20e 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -302,7 +302,7 @@ pub struct CollabPanel { // entries: Vec, // selection: Option, user_store: Model, - client: Arc, + _client: Arc, // channel_store: ModelHandle, // project: ModelHandle, // match_candidates: Vec, @@ -605,7 +605,7 @@ impl CollabPanel { // collapsed_sections: vec![Section::Offline], // collapsed_channels: Vec::default(), _workspace: workspace.weak_handle(), - client: workspace.app_state().client.clone(), + _client: workspace.app_state().client.clone(), // context_menu_on_selected: true, // drag_target_channel: ChannelDragTarget::None, // list_state, @@ -3323,8 +3323,14 @@ impl Render for CollabPanel { .child(Label::new(contact.user.github_login.clone())) .on_mouse_down(gpui::MouseButton::Left, { let workspace = workspace.clone(); - move |event, cx| { - workspace.update(cx, |this, cx| this.call_state().invite(id, None, cx)); + move |_, cx| { + workspace + .update(cx, |this, cx| { + this.call_state() + .invite(id, None, cx) + .detach_and_log_err(cx) + }) + .log_err(); } }) })) diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index 9386d3ff35e540ab209cc44c8331bc1414871aa3..25a4db8c90ff235e1aaf3197809363fcee14906d 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -37,7 +37,7 @@ use gpui::{ }; use project::Project; use theme::ActiveTheme; -use ui::{h_stack, Avatar, Button, ButtonVariant, Color, IconButton, KeyBinding, Label, Tooltip}; +use ui::{h_stack, Avatar, Button, ButtonVariant, Color, IconButton, KeyBinding, Tooltip}; use util::ResultExt; use workspace::Workspace; @@ -190,11 +190,12 @@ impl Render for CollabTitlebarItem { .child(Avatar::data(avatar.clone()).into_element()) .on_mouse_down(MouseButton::Left, { let workspace = workspace.clone(); - let id = peer_id.clone(); move |_, cx| { - workspace.update(cx, |this, cx| { - this.open_shared_screen(peer_id, cx); - }); + workspace + .update(cx, |this, cx| { + this.open_shared_screen(peer_id, cx); + }) + .log_err(); } }) }) @@ -215,17 +216,21 @@ impl Render for CollabTitlebarItem { .child(IconButton::new("mute-microphone", mic_icon).on_click({ let workspace = workspace.clone(); move |_, cx| { - workspace.update(cx, |this, cx| { - this.call_state().toggle_mute(cx); - }); + workspace + .update(cx, |this, cx| { + this.call_state().toggle_mute(cx); + }) + .log_err(); } })) .child(IconButton::new("mute-sound", ui::Icon::AudioOn)) .child(IconButton::new("screen-share", ui::Icon::Screen).on_click( move |_, cx| { - workspace.update(cx, |this, cx| { - this.call_state().toggle_screen_share(cx); - }); + workspace + .update(cx, |this, cx| { + this.call_state().toggle_screen_share(cx); + }) + .log_err(); }, )) .pl_2(), diff --git a/crates/collab_ui2/src/collab_ui.rs b/crates/collab_ui2/src/collab_ui.rs index d1e4bea7d62464d7bc10517ac60f632fa80b8a06..679a6a135b13832936b381bbff71015eb6636682 100644 --- a/crates/collab_ui2/src/collab_ui.rs +++ b/crates/collab_ui2/src/collab_ui.rs @@ -12,8 +12,8 @@ use std::{rc::Rc, sync::Arc}; pub use collab_panel::CollabPanel; pub use collab_titlebar_item::CollabTitlebarItem; use gpui::{ - point, px, AppContext, GlobalPixels, Pixels, PlatformDisplay, Point, Size, WindowBounds, - WindowKind, WindowOptions, + AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, WindowBounds, WindowKind, + WindowOptions, }; pub use panel_settings::{ ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings, @@ -102,10 +102,10 @@ fn notification_window_options( screen: Rc, window_size: Size, ) -> WindowOptions { - let notification_padding = Pixels::from(16.); + let _notification_padding = Pixels::from(16.); let screen_bounds = screen.bounds(); - let size: Size = window_size.into(); + let _size: Size = window_size.into(); let bounds = gpui::Bounds:: { origin: screen_bounds.origin, diff --git a/crates/collab_ui2/src/notifications/incoming_call_notification.rs b/crates/collab_ui2/src/notifications/incoming_call_notification.rs index f47fcf07515195d325a663621fa9d34a55571452..b139afcc2f4ed91857b412bd29a123f3381fb536 100644 --- a/crates/collab_ui2/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui2/src/notifications/incoming_call_notification.rs @@ -1,11 +1,9 @@ use crate::notification_window_options; use call::{ActiveCall, IncomingCall}; -use client::proto; use futures::StreamExt; use gpui::{ - blue, div, green, px, red, AnyElement, AppContext, Component, Context, Div, Element, Entity, - EventEmitter, GlobalPixels, ParentElement, Render, RenderOnce, StatefulInteractiveElement, - Styled, View, ViewContext, VisualContext as _, WindowHandle, + div, green, px, red, AppContext, Div, Element, ParentElement, Render, RenderOnce, + StatefulInteractiveElement, Styled, ViewContext, VisualContext as _, WindowHandle, }; use std::sync::{Arc, Weak}; use ui::{h_stack, v_stack, Avatar, Button, Label}; @@ -19,11 +17,12 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { let mut notification_windows: Vec> = Vec::new(); while let Some(incoming_call) = incoming_call.next().await { for window in notification_windows.drain(..) { - window.update(&mut cx, |this, cx| { - //cx.remove_window(); - }); - //cx.update_window(window.into(), |this, cx| cx.remove_window()); - //window.remove(&mut cx); + window + .update(&mut cx, |_, _| { + // todo!() + //cx.remove_window(); + }) + .log_err(); } if let Some(incoming_call) = incoming_call { @@ -85,15 +84,14 @@ impl IncomingCallNotificationState { let active_call = ActiveCall::global(cx); if accept { let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx)); - let caller_user_id = self.call.calling_user.id; let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id); let app_state = self.app_state.clone(); let cx: &mut AppContext = cx; - cx.spawn(|mut cx| async move { + cx.spawn(|cx| async move { join.await?; - if let Some(project_id) = initial_project_id { - cx.update(|cx| { - if let Some(app_state) = app_state.upgrade() { + if let Some(_project_id) = initial_project_id { + cx.update(|_cx| { + if let Some(_app_state) = app_state.upgrade() { // workspace::join_remote_project( // project_id, // caller_user_id, @@ -102,7 +100,8 @@ impl IncomingCallNotificationState { // ) // .detach_and_log_err(cx); } - }); + }) + .log_err(); } anyhow::Ok(()) }) From 714b45157b74d8b6c191423f03e26341489d4f4a Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 27 Nov 2023 13:02:21 +0100 Subject: [PATCH 015/151] Replace unrendered frame with a frame counter Move facepile to the left hand side --- Cargo.lock | 1 + crates/call2/Cargo.toml | 1 + crates/call2/src/call2.rs | 4 ---- crates/call2/src/room.rs | 2 -- crates/call2/src/shared_screen.rs | 20 ++++++++++++------- crates/collab_ui2/src/collab_titlebar_item.rs | 1 + 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27ff88f68a49f249a35291673a9420164db29c2f..90cc0460ff22193eac28119ec9f8ff9e2dbb752c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1206,6 +1206,7 @@ dependencies = [ "serde_json", "settings2", "smallvec", + "ui2", "util", "workspace2", ] diff --git a/crates/call2/Cargo.toml b/crates/call2/Cargo.toml index a258cbe2cf3edd10931022067627106d99890ac8..8dc37f68dd7bd9b91c1d1fab240448eb25119cbf 100644 --- a/crates/call2/Cargo.toml +++ b/crates/call2/Cargo.toml @@ -31,6 +31,7 @@ media = { path = "../media" } project = { package = "project2", path = "../project2" } settings = { package = "settings2", path = "../settings2" } util = { path = "../util" } +ui = {package = "ui2", path = "../ui2"} workspace = {package = "workspace2", path = "../workspace2"} async-trait.workspace = true anyhow.workspace = true diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index f9a7e4bcfa845edfa8a38e3fc2b92d473ed1399e..1cffc44f93a02da41afd4dbe79e74c493ab9570d 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -592,13 +592,9 @@ impl CallHandler for Call { cx: &mut ViewContext, ) -> Option> { let (call, _) = self.active_call.as_ref()?; - dbg!("A"); let room = call.read(cx).room()?.read(cx); - dbg!("B"); let participant = room.remote_participant_for_peer_id(peer_id)?; - dbg!("C"); let track = participant.video_tracks.values().next()?.clone(); - dbg!("D"); let user = participant.user.clone(); for item in pane.read(cx).items_of_type::() { if item.read(cx).peer_id == peer_id { diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index 1f299ff6cb9342bd6d390ec5717379961db95052..b55d3348dcc37103b2b76e540ce878106d424fa2 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -1342,8 +1342,6 @@ impl Room { let display = displays .first() .ok_or_else(|| anyhow!("no display found"))?; - dbg!("Been there"); - dbg!(displays.len()); let track = LocalVideoTrack::screen_share_for_display(&display); this.upgrade() .ok_or_else(|| anyhow!("room was dropped"))? diff --git a/crates/call2/src/shared_screen.rs b/crates/call2/src/shared_screen.rs index 6f0024b63da13894b4130c077ef94f66481cec7e..7b7cd7d9df33045759ae9bda2f16e016f959c9d6 100644 --- a/crates/call2/src/shared_screen.rs +++ b/crates/call2/src/shared_screen.rs @@ -3,8 +3,8 @@ use anyhow::Result; use client::{proto::PeerId, User}; use futures::StreamExt; use gpui::{ - div, img, AppContext, Div, Element, EventEmitter, FocusHandle, FocusableView, ImageData, - ParentElement, Render, SharedString, Task, View, ViewContext, VisualContext, WindowContext, + div, AppContext, Div, Element, EventEmitter, FocusHandle, FocusableView, ParentElement, Render, + SharedString, Task, View, ViewContext, VisualContext, WindowContext, }; use std::sync::{Arc, Weak}; use workspace::{item::Item, ItemNavHistory, WorkspaceId}; @@ -16,6 +16,8 @@ pub enum Event { pub struct SharedScreen { track: Weak, frame: Option, + // temporary addition just to render something interactive. + current_frame_id: usize, pub peer_id: PeerId, user: Arc, nav_history: Option, @@ -49,6 +51,7 @@ impl SharedScreen { Ok(()) }), focus: cx.focus_handle(), + current_frame_id: 0, } } } @@ -65,11 +68,14 @@ impl Render for SharedScreen { type Element = Div; fn render(&mut self, _: &mut ViewContext) -> Self::Element { let frame = self.frame.clone(); - div().children(frame.map(|frame| { - img().data(Arc::new(ImageData::new(image::ImageBuffer::new( - frame.width() as u32, - frame.height() as u32, - )))) + let frame_id = self.current_frame_id; + self.current_frame_id = self.current_frame_id.wrapping_add(1); + div().children(frame.map(|_| { + ui::Label::new(frame_id.to_string()).color(ui::Color::Error) + // img().data(Arc::new(ImageData::new(image::ImageBuffer::new( + // frame.width() as u32, + // frame.height() as u32, + // )))) })) } } diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index 25a4db8c90ff235e1aaf3197809363fcee14906d..67b7d8d1a28367a6fc2ed683314a60fc0cc46a85 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -203,6 +203,7 @@ impl Render for CollabTitlebarItem { ) }, ) + .child(div().flex_1()) .when(is_in_room, |this| { this.child( h_stack() From bf4211b03a1357590f98b96f3cc2c859ebc0b194 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 27 Nov 2023 14:56:51 +0100 Subject: [PATCH 016/151] Use facepile for avatars --- crates/collab_ui2/src/collab_titlebar_item.rs | 23 ++-- crates/collab_ui2/src/face_pile.rs | 107 +++++++----------- crates/workspace2/src/workspace2.rs | 2 - 3 files changed, 54 insertions(+), 78 deletions(-) diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index 67b7d8d1a28367a6fc2ed683314a60fc0cc46a85..0ba5c3e7071aca1f61e710b0f859920e3f952a92 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -31,9 +31,9 @@ use std::sync::Arc; use call::ActiveCall; use client::{Client, UserStore}; use gpui::{ - div, px, rems, AppContext, Div, InteractiveElement, IntoElement, Model, MouseButton, - ParentElement, Render, Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, - VisualContext, WeakView, WindowBounds, + div, px, rems, AppContext, Div, Element, InteractiveElement, IntoElement, Model, MouseButton, + ParentElement, Render, RenderOnce, Stateful, StatefulInteractiveElement, Styled, Subscription, + ViewContext, VisualContext, WeakView, WindowBounds, }; use project::Project; use theme::ActiveTheme; @@ -41,6 +41,8 @@ use ui::{h_stack, Avatar, Button, ButtonVariant, Color, IconButton, KeyBinding, use util::ResultExt; use workspace::Workspace; +use crate::face_pile::FacePile; + // const MAX_PROJECT_NAME_LENGTH: usize = 40; // const MAX_BRANCH_NAME_LENGTH: usize = 40; @@ -178,16 +180,21 @@ impl Render for CollabTitlebarItem { .when_some( users.zip(current_user.clone()), |this, (remote_participants, current_user)| { - this.children( + let mut pile = FacePile::default(); + pile.extend( current_user .avatar .clone() - .map(|avatar| div().child(Avatar::data(avatar.clone()))) + .map(|avatar| { + div().child(Avatar::data(avatar.clone())).into_any_element() + }) .into_iter() .chain(remote_participants.into_iter().flat_map(|(user, peer_id)| { user.avatar.as_ref().map(|avatar| { div() - .child(Avatar::data(avatar.clone()).into_element()) + .child( + Avatar::data(avatar.clone()).into_element().into_any(), + ) .on_mouse_down(MouseButton::Left, { let workspace = workspace.clone(); move |_, cx| { @@ -198,9 +205,11 @@ impl Render for CollabTitlebarItem { .log_err(); } }) + .into_any_element() }) })), - ) + ); + this.child(pile.render(cx)) }, ) .child(div().flex_1()) diff --git a/crates/collab_ui2/src/face_pile.rs b/crates/collab_ui2/src/face_pile.rs index 077b813fbd9124ad2ae585632e0870c4f233d51a..953470b85402ab5f07508fdb6410ae119e9cf093 100644 --- a/crates/collab_ui2/src/face_pile.rs +++ b/crates/collab_ui2/src/face_pile.rs @@ -1,54 +1,50 @@ -// use std::ops::Range; +use gpui::{ + div, AnyElement, Div, IntoElement as _, ParentElement as _, Render, RenderOnce, Styled, + ViewContext, WindowContext, +}; +use ui::Avatar; -// use gpui::{ -// geometry::{ -// rect::RectF, -// vector::{vec2f, Vector2F}, -// }, -// json::ToJson, -// serde_json::{self, json}, -// AnyElement, Axis, Element, View, ViewContext, -// }; +#[derive(Default)] +pub(crate) struct FacePile { + faces: Vec, +} -// pub(crate) struct FacePile { -// overlap: f32, -// faces: Vec>, -// } +impl RenderOnce for FacePile { + type Rendered = Div; -// impl FacePile { -// pub fn new(overlap: f32) -> Self { -// Self { -// overlap, -// faces: Vec::new(), -// } -// } -// } + fn render(self, _: &mut WindowContext) -> Self::Rendered { + let player_count = self.faces.len(); + let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| { + let isnt_last = ix < player_count - 1; -// impl Element for FacePile { -// type LayoutState = (); -// type PaintState = (); + div().when(isnt_last, |div| div.neg_mr_2()).child(player) + }); + div().p_1().flex().items_center().children(player_list) + } +} +// impl Element for FacePile { +// type State = (); // fn layout( // &mut self, -// constraint: gpui::SizeConstraint, -// view: &mut V, -// cx: &mut ViewContext, -// ) -> (Vector2F, Self::LayoutState) { -// debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY); - +// state: Option, +// cx: &mut WindowContext, +// ) -> (LayoutId, Self::State) { // let mut width = 0.; // let mut max_height = 0.; +// let mut faces = Vec::with_capacity(self.faces.len()); // for face in &mut self.faces { -// let layout = face.layout(constraint, view, cx); +// let layout = face.layout(cx); // width += layout.x(); // max_height = f32::max(max_height, layout.y()); +// faces.push(layout); // } // width -= self.overlap * self.faces.len().saturating_sub(1) as f32; - -// ( -// Vector2F::new(width, max_height.clamp(1., constraint.max.y())), -// (), -// ) +// (cx.request_layout(&Style::default(), faces), ()) +// // ( +// // Vector2F::new(width, max_height.clamp(1., constraint.max.y())), +// // (), +// // )) // } // fn paint( @@ -77,37 +73,10 @@ // () // } - -// fn rect_for_text_range( -// &self, -// _: Range, -// _: RectF, -// _: RectF, -// _: &Self::LayoutState, -// _: &Self::PaintState, -// _: &V, -// _: &ViewContext, -// ) -> Option { -// None -// } - -// fn debug( -// &self, -// bounds: RectF, -// _: &Self::LayoutState, -// _: &Self::PaintState, -// _: &V, -// _: &ViewContext, -// ) -> serde_json::Value { -// json!({ -// "type": "FacePile", -// "bounds": bounds.to_json() -// }) -// } // } -// impl Extend> for FacePile { -// fn extend>>(&mut self, children: T) { -// self.faces.extend(children); -// } -// } +impl Extend for FacePile { + fn extend>(&mut self, children: T) { + self.faces.extend(children); + } +} diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index fd52276ef46b505d7f0ac1ddef1781dcc1cf8758..73bfc633328d0d180d326afaae5a46b84d436839 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -2003,8 +2003,6 @@ impl Workspace { self.active_pane.update(cx, |pane, cx| { pane.add_item(shared_screen, false, true, None, cx) }); - } else { - dbg!("WTF"); } } From a71180257d1c63c1dca670290d50776b91d75730 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 27 Nov 2023 15:12:13 +0100 Subject: [PATCH 017/151] Add deafening support --- crates/call2/src/call2.rs | 23 +++++++++++++++++++ crates/collab_ui2/src/collab_titlebar_item.rs | 22 +++++++++++++++++- crates/collab_ui2/src/face_pile.rs | 2 +- crates/workspace2/src/workspace2.rs | 8 +++++++ 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index 1cffc44f93a02da41afd4dbe79e74c493ab9570d..d983af50b76bf754aac95cfa90f530968de92ece 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -686,6 +686,29 @@ impl CallHandler for Call { }) }); } + fn toggle_deafen(&self, cx: &mut AppContext) { + self.active_call.as_ref().map(|call| { + call.0.update(cx, |this, cx| { + this.room().map(|room| { + room.update(cx, |this, cx| { + this.toggle_deafen(cx).log_err(); + }) + }) + }) + }); + } + fn is_deafened(&self, cx: &AppContext) -> Option { + self.active_call + .as_ref() + .map(|call| { + call.0 + .read(cx) + .room() + .map(|room| room.read(cx).is_deafened()) + }) + .flatten() + .flatten() + } } #[cfg(test)] diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index 0ba5c3e7071aca1f61e710b0f859920e3f952a92..a58186d0f0d6064395a7fb762ee6e385266f1f02 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -111,6 +111,17 @@ impl Render for CollabTitlebarItem { } else { ui::Icon::Mic }; + let speakers_icon = if self + .workspace + .update(cx, |this, cx| this.call_state().is_deafened(cx)) + .log_err() + .flatten() + .unwrap_or_default() + { + ui::Icon::AudioOff + } else { + ui::Icon::AudioOn + }; let workspace = self.workspace.clone(); h_stack() .id("titlebar") @@ -233,7 +244,16 @@ impl Render for CollabTitlebarItem { .log_err(); } })) - .child(IconButton::new("mute-sound", ui::Icon::AudioOn)) + .child(IconButton::new("mute-sound", speakers_icon).on_click({ + let workspace = workspace.clone(); + move |_, cx| { + workspace + .update(cx, |this, cx| { + this.call_state().toggle_deafen(cx); + }) + .log_err(); + } + })) .child(IconButton::new("screen-share", ui::Icon::Screen).on_click( move |_, cx| { workspace diff --git a/crates/collab_ui2/src/face_pile.rs b/crates/collab_ui2/src/face_pile.rs index 953470b85402ab5f07508fdb6410ae119e9cf093..61a2ef85b5b20ad95974a11a48ddb24d62c7e62a 100644 --- a/crates/collab_ui2/src/face_pile.rs +++ b/crates/collab_ui2/src/face_pile.rs @@ -17,7 +17,7 @@ impl RenderOnce for FacePile { let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| { let isnt_last = ix < player_count - 1; - div().when(isnt_last, |div| div.neg_mr_2()).child(player) + div().when(isnt_last, |div| div.neg_mr_1()).child(player) }); div().p_1().flex().items_center().children(player_list) } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 73bfc633328d0d180d326afaae5a46b84d436839..f906a194983f48f05d922c941ea74c77aaf99103 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -371,6 +371,12 @@ impl CallHandler for TestCallHandler { fn toggle_mute(&self, cx: &mut AppContext) {} fn toggle_screen_share(&self, cx: &mut AppContext) {} + + fn toggle_deafen(&self, cx: &mut AppContext) {} + + fn is_deafened(&self, cx: &AppContext) -> Option { + None + } } impl AppState { @@ -483,7 +489,9 @@ pub trait CallHandler { ) -> Task>; fn remote_participants(&self, cx: &AppContext) -> Option, PeerId)>>; fn is_muted(&self, cx: &AppContext) -> Option; + fn is_deafened(&self, cx: &AppContext) -> Option; fn toggle_mute(&self, cx: &mut AppContext); + fn toggle_deafen(&self, cx: &mut AppContext); fn toggle_screen_share(&self, cx: &mut AppContext); } From 44876062c95838a05bf3f506319dfe3da6d261eb Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 27 Nov 2023 15:44:19 +0100 Subject: [PATCH 018/151] Use AppContext instead of AsyncWindowContext to quit Fixes refcell-related panic --- crates/call2/src/call2.rs | 6 +++--- crates/collab_ui2/src/collab_titlebar_item.rs | 9 ++++++++- crates/workspace2/src/workspace2.rs | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index d983af50b76bf754aac95cfa90f530968de92ece..534964b3c27b9fc214b8909752a4757b3c745e36 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -609,12 +609,12 @@ impl CallHandler for Call { fn room_id(&self, cx: &AppContext) -> Option { Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id()) } - fn hang_up(&self, mut cx: AsyncWindowContext) -> Result>> { + fn hang_up(&self, mut cx: &mut AppContext) -> Task> { let Some((call, _)) = self.active_call.as_ref() else { - bail!("Cannot exit a call; not in a call"); + return Task::ready(Err(anyhow!("Cannot exit a call; not in a call"))); }; - call.update(&mut cx, |this, cx| this.hang_up(cx)) + call.update(cx, |this, cx| this.hang_up(cx)) } fn active_project(&self, cx: &AppContext) -> Option> { ActiveCall::global(cx).read(cx).location().cloned() diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index a58186d0f0d6064395a7fb762ee6e385266f1f02..bfa2932aee237ba5fdb95df13400981e31092f0e 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -230,7 +230,14 @@ impl Render for CollabTitlebarItem { .child( h_stack() .child(Button::new(if is_shared { "Unshare" } else { "Share" })) - .child(IconButton::new("leave-call", ui::Icon::Exit)), + .child(IconButton::new("leave-call", ui::Icon::Exit).on_click({ + let workspace = workspace.clone(); + move |_, cx| { + workspace.update(cx, |this, cx| { + this.call_state().hang_up(cx).detach(); + }); + } + })), ) .child( h_stack() diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index f906a194983f48f05d922c941ea74c77aaf99103..135a713661725017ee99f4ff93170ade724d70eb 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -479,7 +479,7 @@ pub trait CallHandler { fn is_in_room(&self, cx: &mut ViewContext) -> bool { self.room_id(cx).is_some() } - fn hang_up(&self, cx: AsyncWindowContext) -> Result>>; + fn hang_up(&self, cx: &mut AppContext) -> Task>; fn active_project(&self, cx: &AppContext) -> Option>; fn invite( &mut self, @@ -1222,7 +1222,7 @@ impl Workspace { if answer.await.log_err() == Some(1) { return anyhow::Ok(false); } else { - this.update(&mut cx, |this, cx| this.call_handler.hang_up(cx.to_async()))?? + this.update(&mut cx, |this, cx| this.call_handler.hang_up(cx))? .await .log_err(); } From 30af3ffaf39c689a154b5756d76722acc3d14c32 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 27 Nov 2023 15:47:00 +0100 Subject: [PATCH 019/151] fixup! Use AppContext instead of AsyncWindowContext to quit --- crates/workspace2/src/workspace2.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 135a713661725017ee99f4ff93170ade724d70eb..a649a8696a0641161618458d7754f001998babc6 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -343,8 +343,8 @@ impl CallHandler for TestCallHandler { None } - fn hang_up(&self, cx: AsyncWindowContext) -> Result>> { - anyhow::bail!("TestCallHandler should not be hanging up") + fn hang_up(&self, cx: &mut AppContext) -> Task> { + Task::ready(Err(anyhow!("TestCallHandler should not be hanging up"))) } fn active_project(&self, cx: &AppContext) -> Option> { From 8ca9f4e12a6a69ff63b748c320b10f9a42ec37da Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 27 Nov 2023 15:49:47 +0100 Subject: [PATCH 020/151] Fix some more warnings --- crates/call2/src/call2.rs | 8 ++++---- crates/collab_ui2/src/collab_titlebar_item.rs | 8 +++++--- crates/collab_ui2/src/face_pile.rs | 4 +--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index 534964b3c27b9fc214b8909752a4757b3c745e36..9a89ec7167f776ab2cb96ad60875c69293d6f0d0 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -3,7 +3,7 @@ pub mod participant; pub mod room; mod shared_screen; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, Result}; use async_trait::async_trait; use audio::Audio; use call_settings::CallSettings; @@ -14,8 +14,8 @@ use client::{ use collections::HashSet; use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use gpui::{ - AppContext, AsyncAppContext, AsyncWindowContext, Context, EventEmitter, Model, ModelContext, - Subscription, Task, View, ViewContext, VisualContext, WeakModel, WeakView, + AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task, + View, ViewContext, VisualContext, WeakModel, WeakView, }; pub use participant::ParticipantLocation; use postage::watch; @@ -609,7 +609,7 @@ impl CallHandler for Call { fn room_id(&self, cx: &AppContext) -> Option { Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id()) } - fn hang_up(&self, mut cx: &mut AppContext) -> Task> { + fn hang_up(&self, cx: &mut AppContext) -> Task> { let Some((call, _)) = self.active_call.as_ref() else { return Task::ready(Err(anyhow!("Cannot exit a call; not in a call"))); }; diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index bfa2932aee237ba5fdb95df13400981e31092f0e..b5ed8b09199f2c254bf04827c8d2b8ecf9cd8142 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -233,9 +233,11 @@ impl Render for CollabTitlebarItem { .child(IconButton::new("leave-call", ui::Icon::Exit).on_click({ let workspace = workspace.clone(); move |_, cx| { - workspace.update(cx, |this, cx| { - this.call_state().hang_up(cx).detach(); - }); + workspace + .update(cx, |this, cx| { + this.call_state().hang_up(cx).detach(); + }) + .log_err(); } })), ) diff --git a/crates/collab_ui2/src/face_pile.rs b/crates/collab_ui2/src/face_pile.rs index 61a2ef85b5b20ad95974a11a48ddb24d62c7e62a..e235f33ce6178d6cc1034c874e0f17e7072f8005 100644 --- a/crates/collab_ui2/src/face_pile.rs +++ b/crates/collab_ui2/src/face_pile.rs @@ -1,8 +1,6 @@ use gpui::{ - div, AnyElement, Div, IntoElement as _, ParentElement as _, Render, RenderOnce, Styled, - ViewContext, WindowContext, + div, AnyElement, Div, IntoElement as _, ParentElement as _, RenderOnce, Styled, WindowContext, }; -use ui::Avatar; #[derive(Default)] pub(crate) struct FacePile { From 1acc6b462f4c671031faefe02c240ed32dcec0dd Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Mon, 27 Nov 2023 10:29:29 -0500 Subject: [PATCH 021/151] Start on completions styles --- crates/editor2/src/editor.rs | 22 +++++++++++++++++----- crates/ui2/src/components/popover.rs | 2 ++ crates/ui2/src/styled_ext.rs | 4 ++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 3a9a40328d2b6dc0a11cbc75004f2d9c36b865ee..a0f074221c4ba0eb2f2db2b148a59796e5ef140a 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -1273,6 +1273,13 @@ impl CompletionsMenu { multiline_docs.map(|div| { div.id("multiline_docs") .max_h(max_height) + .flex_1() + .px_1p5() + .py_1() + .min_w(px(260.)) + .max_w(px(640.)) + .w(px(500.)) + .text_ui() .overflow_y_scroll() // Prevent a mouse down on documentation from being propagated to the editor, // because that would move the cursor. @@ -1327,13 +1334,18 @@ impl CompletionsMenu { div() .id(mat.candidate_id) - .min_w(px(300.)) - .max_w(px(700.)) + .min_w(px(220.)) + .max_w(px(640.)) .whitespace_nowrap() .overflow_hidden() - .bg(gpui::green()) - .hover(|style| style.bg(gpui::blue())) - .when(item_ix == selected_item, |div| div.bg(gpui::red())) + .text_ui() + .px_1() + .rounded(px(4.)) + .bg(cx.theme().colors().ghost_element_background) + .hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) + .when(item_ix == selected_item, |div| { + div.bg(cx.theme().colors().ghost_element_selected) + }) .on_mouse_down( MouseButton::Left, cx.listener(move |editor, event, cx| { diff --git a/crates/ui2/src/components/popover.rs b/crates/ui2/src/components/popover.rs index ac407d23353bfb65e031b651d37b639d06c0fd42..3838e40bec527fb191c6a0e187c6982dcb643c59 100644 --- a/crates/ui2/src/components/popover.rs +++ b/crates/ui2/src/components/popover.rs @@ -3,6 +3,7 @@ use gpui::{ WindowContext, }; use smallvec::SmallVec; +use theme2::ActiveTheme; use crate::{v_stack, StyledExt}; @@ -58,6 +59,7 @@ impl RenderOnce for Popover { .ml_1() .absolute() .elevation_2(cx) + .bg(cx.theme().colors().surface_background) .p_1() .child(aside), ) diff --git a/crates/ui2/src/styled_ext.rs b/crates/ui2/src/styled_ext.rs index 9e81ab19ee37b4bebb4ba6845a27b8c28498a47d..d064312c327bc5cddfc7784b3ef0793690e0da79 100644 --- a/crates/ui2/src/styled_ext.rs +++ b/crates/ui2/src/styled_ext.rs @@ -1,4 +1,4 @@ -use gpui::{Styled, WindowContext}; +use gpui::{px, Styled, WindowContext}; use theme2::ActiveTheme; use crate::{ElevationIndex, UITextSize}; @@ -6,7 +6,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()) - .rounded_lg() + .rounded(px(8.)) .border() .border_color(cx.theme().colors().border_variant) .shadow(index.shadow()) From 8a35a028637e943d594526ef9b29e7c30b5a465d Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Mon, 27 Nov 2023 11:22:19 -0500 Subject: [PATCH 022/151] Checkpoint - try using overlay for completions popover [no ci] --- crates/editor2/src/editor.rs | 37 +++++++++++++++++++--------- crates/gpui2/src/elements/overlay.rs | 1 + crates/ui2/src/components/popover.rs | 19 ++++++-------- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index a0f074221c4ba0eb2f2db2b148a59796e5ef140a..3aa53077182a26d0c8f813db3ef18fd8d8d048ff 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -39,12 +39,13 @@ use futures::FutureExt; use fuzzy::{StringMatch, StringMatchCandidate}; use git::diff_hunk_to_display; use gpui::{ - actions, div, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement, - AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context, ElementId, - EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle, - Hsla, InputHandler, InteractiveText, KeyContext, Model, MouseButton, ParentElement, Pixels, - Render, RenderOnce, SharedString, Styled, StyledText, Subscription, Task, TextRun, TextStyle, - UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext, + actions, div, overlay, point, prelude::*, px, relative, rems, size, uniform_list, Action, + AnyElement, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context, + ElementId, EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, + HighlightStyle, Hsla, InputHandler, InteractiveText, KeyContext, Model, MouseButton, + ParentElement, Pixels, Render, RenderOnce, SharedString, Styled, StyledText, Subscription, + Task, TextRun, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, + WhiteSpace, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -1335,7 +1336,7 @@ impl CompletionsMenu { div() .id(mat.candidate_id) .min_w(px(220.)) - .max_w(px(640.)) + .max_w(px(540.)) .whitespace_nowrap() .overflow_hidden() .text_ui() @@ -1370,11 +1371,23 @@ impl CompletionsMenu { .track_scroll(self.scroll_handle.clone()) .with_width_from_item(widest_completion_ix); - Popover::new() - .child(list) - .when_some(multiline_docs, |popover, multiline_docs| { - popover.aside(multiline_docs) - }) + // Old: + // Popover::new() + // .child(list) + // .when_some(multiline_docs, |popover, multiline_docs| { + // popover.aside(multiline_docs) + // }) + // .into_any_element() + + overlay() + .anchor(gpui::AnchorCorner::TopLeft) + .child( + Popover::new() + .child(list) + .when_some(multiline_docs, |popover, multiline_docs| { + popover.aside(multiline_docs) + }), + ) .into_any_element() } diff --git a/crates/gpui2/src/elements/overlay.rs b/crates/gpui2/src/elements/overlay.rs index d8aad4a42f9413f60e05d59bfab5d0606d4dd5d2..3d5c7ac0b551d36a3045240c7afc1d783f684dcb 100644 --- a/crates/gpui2/src/elements/overlay.rs +++ b/crates/gpui2/src/elements/overlay.rs @@ -105,6 +105,7 @@ impl Element for Overlay { origin: Point::zero(), size: cx.viewport_size(), }; + dbg!(limits); match self.fit_mode { OverlayFitMode::SnapToWindow => { diff --git a/crates/ui2/src/components/popover.rs b/crates/ui2/src/components/popover.rs index 3838e40bec527fb191c6a0e187c6982dcb643c59..62b624de0e7291e5b62238ea8745ce059694ede3 100644 --- a/crates/ui2/src/components/popover.rs +++ b/crates/ui2/src/components/popover.rs @@ -1,11 +1,11 @@ use gpui::{ - AnyElement, Div, Element, ElementId, IntoElement, ParentElement, RenderOnce, Styled, + div, AnyElement, Div, Element, ElementId, IntoElement, ParentElement, RenderOnce, Styled, WindowContext, }; use smallvec::SmallVec; use theme2::ActiveTheme; -use crate::{v_stack, StyledExt}; +use crate::{h_stack, v_stack, StyledExt}; /// A popover is used to display a menu or show some options. /// @@ -44,21 +44,18 @@ impl RenderOnce for Popover { type Rendered = Div; fn render(self, cx: &mut WindowContext) -> Self::Rendered { - v_stack() - .relative() - .elevation_2(cx) - .p_1() - .children(self.children) + div() + .flex() + .flex_none() + .gap_1() + .child(v_stack().elevation_2(cx).p_1().children(self.children)) .when_some(self.aside, |this, aside| { // TODO: This will statically position the aside to the top right of the popover. // We should update this to use gpui2::overlay avoid collisions with the window edges. this.child( v_stack() - .top_0() - .left_full() - .ml_1() - .absolute() .elevation_2(cx) + .flex_1() .bg(cx.theme().colors().surface_background) .p_1() .child(aside), From ab83f4319baa5af2307802aba88e901bce51b673 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Mon, 27 Nov 2023 11:51:42 -0500 Subject: [PATCH 023/151] revert popover changes [no ci] --- crates/ui2/src/components/popover.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/ui2/src/components/popover.rs b/crates/ui2/src/components/popover.rs index 62b624de0e7291e5b62238ea8745ce059694ede3..925fb30a5e81cbbb4048cee8309dec51458ecd3f 100644 --- a/crates/ui2/src/components/popover.rs +++ b/crates/ui2/src/components/popover.rs @@ -44,18 +44,21 @@ impl RenderOnce for Popover { type Rendered = Div; fn render(self, cx: &mut WindowContext) -> Self::Rendered { - div() - .flex() - .flex_none() - .gap_1() - .child(v_stack().elevation_2(cx).p_1().children(self.children)) + v_stack() + .relative() + .elevation_2(cx) + .p_1() + .children(self.children) .when_some(self.aside, |this, aside| { // TODO: This will statically position the aside to the top right of the popover. // We should update this to use gpui2::overlay avoid collisions with the window edges. this.child( v_stack() + .top_0() + .left_full() + .ml_1() + .absolute() .elevation_2(cx) - .flex_1() .bg(cx.theme().colors().surface_background) .p_1() .child(aside), From a5951df21fa87b7c13e322b03dc40305a347d4b3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 27 Nov 2023 18:56:37 +0100 Subject: [PATCH 024/151] Start on hover popover --- crates/editor2/src/editor.rs | 2 - crates/editor2/src/element.rs | 198 ++- crates/editor2/src/hover_popover.rs | 1792 ++++++++++++++------------- 3 files changed, 987 insertions(+), 1005 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 3a9a40328d2b6dc0a11cbc75004f2d9c36b865ee..332035944047c8a416a6cae657d5a7607c272327 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -530,8 +530,6 @@ pub fn init(cx: &mut AppContext) { // cx.register_action_type(Editor::context_menu_next); // cx.register_action_type(Editor::context_menu_last); - hover_popover::init(cx); - workspace::register_project_item::(cx); workspace::register_followable_item::(cx); workspace::register_deserializable_item::(cx); diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index e591dd84cf55da1ed71292dd39ac102d38736554..56f4ecd2bedc8d743de78f9ef3a798997842e0c4 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -5,7 +5,9 @@ use crate::{ }, editor_settings::ShowScrollbar, git::{diff_hunk_to_display, DisplayDiffHunk}, - hover_popover::hover_at, + hover_popover::{ + self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, + }, link_go_to_definition::{ go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link, update_inlay_link_and_hover_points, GoToDefinitionTrigger, @@ -257,6 +259,7 @@ impl EditorElement { // on_action(cx, Editor::open_excerpts); todo!() register_action(view, cx, Editor::toggle_soft_wrap); register_action(view, cx, Editor::toggle_inlay_hints); + register_action(view, cx, hover_popover::hover); register_action(view, cx, Editor::reveal_in_finder); register_action(view, cx, Editor::copy_path); register_action(view, cx, Editor::copy_relative_path); @@ -1024,8 +1027,8 @@ impl EditorElement { } }); - if let Some((position, mut context_menu)) = layout.context_menu.take() { - cx.with_z_index(1, |cx| { + cx.with_z_index(1, |cx| { + if let Some((position, mut context_menu)) = layout.context_menu.take() { let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); let context_menu_size = context_menu.measure(available_space, cx); @@ -1053,80 +1056,72 @@ impl EditorElement { } context_menu.draw(list_origin, available_space, cx); - }) - } + } - // if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() { - // cx.scene().push_stacking_context(None, None); - - // // This is safe because we check on layout whether the required row is available - // let hovered_row_layout = - // &layout.position_map.line_layouts[(position.row() - start_row) as usize].line; - - // // Minimum required size: Take the first popover, and add 1.5 times the minimum popover - // // height. This is the size we will use to decide whether to render popovers above or below - // // the hovered line. - // let first_size = hover_popovers[0].size(); - // let height_to_reserve = first_size.y - // + 1.5 * MIN_POPOVER_LINE_HEIGHT as f32 * layout.position_map.line_height; - - // // Compute Hovered Point - // let x = hovered_row_layout.x_for_index(position.column() as usize) - scroll_left; - // let y = position.row() as f32 * layout.position_map.line_height - scroll_top; - // let hovered_point = content_origin + point(x, y); - - // if hovered_point.y - height_to_reserve > 0.0 { - // // There is enough space above. Render popovers above the hovered point - // let mut current_y = hovered_point.y; - // for hover_popover in hover_popovers { - // let size = hover_popover.size(); - // let mut popover_origin = point(hovered_point.x, current_y - size.y); - - // let x_out_of_bounds = bounds.max_x - (popover_origin.x + size.x); - // if x_out_of_bounds < 0.0 { - // popover_origin.set_x(popover_origin.x + x_out_of_bounds); - // } - - // hover_popover.paint( - // popover_origin, - // Bounds::::from_points( - // gpui::Point::::zero(), - // point(f32::MAX, f32::MAX), - // ), // Let content bleed outside of editor - // editor, - // cx, - // ); - - // 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 hover_popover in hover_popovers { - // let size = hover_popover.size(); - // let mut popover_origin = point(hovered_point.x, current_y); - - // let x_out_of_bounds = bounds.max_x - (popover_origin.x + size.x); - // if x_out_of_bounds < 0.0 { - // popover_origin.set_x(popover_origin.x + x_out_of_bounds); - // } - - // hover_popover.paint( - // popover_origin, - // Bounds::::from_points( - // gpui::Point::::zero(), - // point(f32::MAX, f32::MAX), - // ), // Let content bleed outside of editor - // editor, - // cx, - // ); - - // current_y = popover_origin.y + size.y + HOVER_POPOVER_GAP; - // } - // } - - // cx.scene().pop_stacking_context(); - // } + if let Some((position, mut hover_popovers)) = layout.hover_popovers.take() { + let available_space = + size(AvailableSpace::MinContent, AvailableSpace::MinContent); + // cx.scene().push_stacking_context(None, None); + + // This is safe because we check on layout whether the required row is available + let hovered_row_layout = &layout.position_map.line_layouts + [(position.row() - start_row) as usize] + .line; + + // Minimum required size: Take the first popover, and add 1.5 times the minimum popover + // height. This is the size we will use to decide whether to render popovers above or below + // the hovered line. + let first_size = hover_popovers[0].measure(available_space, cx); + let height_to_reserve = first_size.height + + 1.5 * MIN_POPOVER_LINE_HEIGHT * layout.position_map.line_height; + + // Compute Hovered Point + let x = hovered_row_layout.x_for_index(position.column() as usize) + - layout.position_map.scroll_position.x; + let y = position.row() as f32 * layout.position_map.line_height + - layout.position_map.scroll_position.y; + let hovered_point = content_origin + point(x, y); + + if hovered_point.y - height_to_reserve > Pixels::ZERO { + // 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); + + 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); + + 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); + + 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); + + current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP; + } + } + + // cx.scene().pop_stacking_context(); + } + }) }, ) } @@ -1992,15 +1987,23 @@ impl EditorElement { } let visible_rows = start_row..start_row + line_layouts.len() as u32; - // todo!("hover") - // let mut hover = editor.hover_state.render( - // &snapshot, - // &style, - // visible_rows, - // editor.workspace.as_ref().map(|(w, _)| w.clone()), - // cx, - // ); - // let mode = editor.mode; + 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 mut hover = editor.hover_state.render( + &snapshot, + &style, + visible_rows, + max_size, + editor.workspace.as_ref().map(|(w, _)| w.clone()), + cx, + ); let mut fold_indicators = cx.with_element_id(Some("gutter_fold_indicators"), |cx| { editor.render_fold_indicators( @@ -2013,27 +2016,6 @@ impl EditorElement { ) }); - // todo!("hover popovers") - // if let Some((_, hover_popovers)) = hover.as_mut() { - // for hover_popover in hover_popovers.iter_mut() { - // hover_popover.layout( - // SizeConstraint { - // min: gpui::Point::::zero(), - // max: point( - // (120. * em_width) // Default size - // .min(size.x / 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(size.y / 2.) // Shrink to half of the editor height - // .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines - // ), - // }, - // editor, - // cx, - // ); - // } - // } - let invisible_symbol_font_size = font_size / 2.; let tab_invisible = cx .text_system() @@ -2102,7 +2084,7 @@ impl EditorElement { fold_indicators, tab_invisible, space_invisible, - // hover_popovers: hover, + hover_popovers: hover, } }) } @@ -3287,7 +3269,7 @@ pub struct LayoutState { max_row: u32, context_menu: Option<(DisplayPoint, AnyElement)>, code_actions_indicator: Option, - // hover_popovers: Option<(DisplayPoint, Vec)>, + hover_popovers: Option<(DisplayPoint, Vec)>, fold_indicators: Vec>, tab_invisible: ShapedLine, space_invisible: ShapedLine, diff --git a/crates/editor2/src/hover_popover.rs b/crates/editor2/src/hover_popover.rs index 07d108cd6525babd12bf55404ba1b561cf2d67f4..948b391ecf0cf5a4976210a1f3b94b9f096a1408 100644 --- a/crates/editor2/src/hover_popover.rs +++ b/crates/editor2/src/hover_popover.rs @@ -1,11 +1,14 @@ use crate::{ - display_map::InlayOffset, + display_map::{InlayOffset, ToDisplayPoint}, link_go_to_definition::{InlayHighlight, RangeInEditor}, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, ExcerptId, RangeToAnchorExt, }; use futures::FutureExt; -use gpui::{AnyElement, AppContext, Model, Task, ViewContext, WeakView}; +use gpui::{ + actions, div, px, AnyElement, AppContext, InteractiveElement, IntoElement, Model, MouseButton, + ParentElement, Pixels, Size, StatefulInteractiveElement, Styled, Task, ViewContext, WeakView, +}; use language::{markdown, Bias, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown}; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; use settings::Settings; @@ -17,22 +20,17 @@ pub const HOVER_DELAY_MILLIS: u64 = 350; pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200; pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.; -pub const MIN_POPOVER_LINE_HEIGHT: f32 = 4.; -pub const HOVER_POPOVER_GAP: f32 = 10.; +pub const MIN_POPOVER_LINE_HEIGHT: Pixels = px(4.); +pub const HOVER_POPOVER_GAP: Pixels = px(10.); -// actions!(editor, [Hover]); +actions!(Hover); -pub fn init(cx: &mut AppContext) { - // cx.add_action(hover); +/// Bindable action which uses the most recent selection head to trigger a hover +pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext) { + let head = editor.selections.newest_display(cx).head(); + show_hover(editor, head, true, cx); } -// todo!() -// /// Bindable action which uses the most recent selection head to trigger a hover -// pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext) { -// let head = editor.selections.newest_display(cx).head(); -// show_hover(editor, head, true, cx); -// } - /// The internal hover action dispatches between `show_hover` or `hide_hover` /// depending on whether a point to hover over is provided. pub fn hover_at(editor: &mut Editor, point: Option, cx: &mut ViewContext) { @@ -74,64 +72,63 @@ pub fn find_hovered_hint_part( } pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut ViewContext) { - todo!() - // if EditorSettings::get_global(cx).hover_popover_enabled { - // if editor.pending_rename.is_some() { - // return; - // } - - // let Some(project) = editor.project.clone() else { - // return; - // }; - - // if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover { - // if let RangeInEditor::Inlay(range) = symbol_range { - // if range == &inlay_hover.range { - // // Hover triggered from same location as last time. Don't show again. - // return; - // } - // } - // hide_hover(editor, cx); - // } - - // let task = cx.spawn(|this, mut cx| { - // async move { - // cx.background_executor() - // .timer(Duration::from_millis(HOVER_DELAY_MILLIS)) - // .await; - // this.update(&mut cx, |this, _| { - // this.hover_state.diagnostic_popover = None; - // })?; - - // let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?; - // let blocks = vec![inlay_hover.tooltip]; - // let parsed_content = parse_blocks(&blocks, &language_registry, None).await; - - // let hover_popover = InfoPopover { - // project: project.clone(), - // symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()), - // blocks, - // parsed_content, - // }; - - // this.update(&mut cx, |this, cx| { - // // Highlight the selected symbol using a background highlight - // this.highlight_inlay_background::( - // vec![inlay_hover.range], - // |theme| theme.editor.hover_popover.highlight, - // cx, - // ); - // this.hover_state.info_popover = Some(hover_popover); - // cx.notify(); - // })?; - - // anyhow::Ok(()) - // } - // .log_err() - // }); - - // editor.hover_state.info_task = Some(task); - // } + if EditorSettings::get_global(cx).hover_popover_enabled { + if editor.pending_rename.is_some() { + return; + } + + let Some(project) = editor.project.clone() else { + return; + }; + + if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover { + if let RangeInEditor::Inlay(range) = symbol_range { + if range == &inlay_hover.range { + // Hover triggered from same location as last time. Don't show again. + return; + } + } + hide_hover(editor, cx); + } + + let task = cx.spawn(|this, mut cx| { + async move { + cx.background_executor() + .timer(Duration::from_millis(HOVER_DELAY_MILLIS)) + .await; + this.update(&mut cx, |this, _| { + this.hover_state.diagnostic_popover = None; + })?; + + let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?; + let blocks = vec![inlay_hover.tooltip]; + let parsed_content = parse_blocks(&blocks, &language_registry, None).await; + + let hover_popover = InfoPopover { + project: project.clone(), + symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()), + blocks, + parsed_content, + }; + + this.update(&mut cx, |this, cx| { + // Highlight the selected symbol using a background highlight + this.highlight_inlay_background::( + vec![inlay_hover.range], + |theme| gpui::red(), // todo!("use a proper background here") + cx, + ); + this.hover_state.info_popover = Some(hover_popover); + cx.notify(); + })?; + + anyhow::Ok(()) + } + .log_err() + }); + + editor.hover_state.info_task = Some(task); + } } /// Hides the type information popup. @@ -420,43 +417,42 @@ impl HoverState { snapshot: &EditorSnapshot, style: &EditorStyle, visible_rows: Range, + max_size: Size, workspace: Option>, cx: &mut ViewContext, ) -> Option<(DisplayPoint, Vec)> { - todo!("old version below") + // If there is a diagnostic, position the popovers based on that. + // Otherwise use the start of the hover range + let anchor = self + .diagnostic_popover + .as_ref() + .map(|diagnostic_popover| &diagnostic_popover.local_diagnostic.range.start) + .or_else(|| { + self.info_popover + .as_ref() + .map(|info_popover| match &info_popover.symbol_range { + RangeInEditor::Text(range) => &range.start, + RangeInEditor::Inlay(range) => &range.inlay_position, + }) + })?; + let point = anchor.to_display_point(&snapshot.display_snapshot); + + // Don't render if the relevant point isn't on screen + if !self.visible() || !visible_rows.contains(&point.row()) { + return None; + } + + let mut elements = Vec::new(); + + if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() { + elements.push(diagnostic_popover.render(style, max_size, cx)); + } + if let Some(info_popover) = self.info_popover.as_mut() { + elements.push(info_popover.render(style, max_size, workspace, cx)); + } + + Some((point, elements)) } - // // If there is a diagnostic, position the popovers based on that. - // // Otherwise use the start of the hover range - // let anchor = self - // .diagnostic_popover - // .as_ref() - // .map(|diagnostic_popover| &diagnostic_popover.local_diagnostic.range.start) - // .or_else(|| { - // self.info_popover - // .as_ref() - // .map(|info_popover| match &info_popover.symbol_range { - // RangeInEditor::Text(range) => &range.start, - // RangeInEditor::Inlay(range) => &range.inlay_position, - // }) - // })?; - // let point = anchor.to_display_point(&snapshot.display_snapshot); - - // // Don't render if the relevant point isn't on screen - // if !self.visible() || !visible_rows.contains(&point.row()) { - // return None; - // } - - // let mut elements = Vec::new(); - - // if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() { - // elements.push(diagnostic_popover.render(style, cx)); - // } - // if let Some(info_popover) = self.info_popover.as_mut() { - // elements.push(info_popover.render(style, workspace, cx)); - // } - - // Some((point, elements)) - // } } #[derive(Debug, Clone)] @@ -467,35 +463,36 @@ pub struct InfoPopover { parsed_content: ParsedMarkdown, } -// impl InfoPopover { -// pub fn render( -// &mut self, -// style: &EditorStyle, -// workspace: Option>, -// cx: &mut ViewContext, -// ) -> AnyElement { -// MouseEventHandler::new::(0, cx, |_, cx| { -// Flex::column() -// .scrollable::(0, None, cx) -// .with_child(crate::render_parsed_markdown::( -// &self.parsed_content, -// style, -// workspace, -// cx, -// )) -// .contained() -// .with_style(style.hover_popover.container) -// }) -// .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath. -// .with_cursor_style(CursorStyle::Arrow) -// .with_padding(Padding { -// bottom: HOVER_POPOVER_GAP, -// top: HOVER_POPOVER_GAP, -// ..Default::default() -// }) -// .into_any() -// } -// } +impl InfoPopover { + pub fn render( + &mut self, + style: &EditorStyle, + max_size: Size, + workspace: Option>, + cx: &mut ViewContext, + ) -> AnyElement { + div() + .id("info_popover") + .overflow_y_scroll() + .bg(gpui::red()) + .max_w(max_size.width) + .max_h(max_size.height) + // Prevent a mouse move on the popover from being propagated to the editor, + // because that would dismiss the popover. + .on_mouse_move(|_, cx| cx.stop_propagation()) + // Prevent a mouse down on the popover from being propagated to the editor, + // because that would move the cursor. + .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation()) + .child(crate::render_parsed_markdown( + "content", + &self.parsed_content, + style, + workspace, + cx, + )) + .into_any_element() + } +} #[derive(Debug, Clone)] pub struct DiagnosticPopover { @@ -504,7 +501,12 @@ pub struct DiagnosticPopover { } impl DiagnosticPopover { - pub fn render(&self, style: &EditorStyle, cx: &mut ViewContext) -> AnyElement { + pub fn render( + &self, + style: &EditorStyle, + max_size: Size, + cx: &mut ViewContext, + ) -> AnyElement { todo!() // enum PrimaryDiagnostic {} @@ -567,763 +569,763 @@ impl DiagnosticPopover { } } -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::{ -// editor_tests::init_test, -// element::PointForPosition, -// inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, -// link_go_to_definition::update_inlay_link_and_hover_points, -// test::editor_lsp_test_context::EditorLspTestContext, -// InlayId, -// }; -// use collections::BTreeSet; -// use gpui::fonts::{HighlightStyle, Underline, Weight}; -// use indoc::indoc; -// use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; -// use lsp::LanguageServerId; -// use project::{HoverBlock, HoverBlockKind}; -// use smol::stream::StreamExt; -// use unindent::Unindent; -// use util::test::marked_text_ranges; - -// #[gpui::test] -// async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) { -// init_test(cx, |_| {}); - -// let mut cx = EditorLspTestContext::new_rust( -// lsp::ServerCapabilities { -// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), -// ..Default::default() -// }, -// cx, -// ) -// .await; - -// // Basic hover delays and then pops without moving the mouse -// cx.set_state(indoc! {" -// fn ˇtest() { println!(); } -// "}); -// let hover_point = cx.display_point(indoc! {" -// fn test() { printˇln!(); } -// "}); - -// cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx)); -// assert!(!cx.editor(|editor, _| editor.hover_state.visible())); - -// // After delay, hover should be visible. -// let symbol_range = cx.lsp_range(indoc! {" -// fn test() { «println!»(); } -// "}); -// let mut requests = -// cx.handle_request::(move |_, _, _| async move { -// Ok(Some(lsp::Hover { -// contents: lsp::HoverContents::Markup(lsp::MarkupContent { -// kind: lsp::MarkupKind::Markdown, -// value: "some basic docs".to_string(), -// }), -// range: Some(symbol_range), -// })) -// }); -// cx.foreground() -// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); -// requests.next().await; - -// cx.editor(|editor, _| { -// assert!(editor.hover_state.visible()); -// assert_eq!( -// editor.hover_state.info_popover.clone().unwrap().blocks, -// vec![HoverBlock { -// text: "some basic docs".to_string(), -// kind: HoverBlockKind::Markdown, -// },] -// ) -// }); - -// // Mouse moved with no hover response dismisses -// let hover_point = cx.display_point(indoc! {" -// fn teˇst() { println!(); } -// "}); -// let mut request = cx -// .lsp -// .handle_request::(|_, _| async move { Ok(None) }); -// cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx)); -// cx.foreground() -// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); -// request.next().await; -// cx.editor(|editor, _| { -// assert!(!editor.hover_state.visible()); -// }); -// } - -// #[gpui::test] -// async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) { -// init_test(cx, |_| {}); - -// let mut cx = EditorLspTestContext::new_rust( -// lsp::ServerCapabilities { -// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), -// ..Default::default() -// }, -// cx, -// ) -// .await; - -// // Hover with keyboard has no delay -// cx.set_state(indoc! {" -// fˇn test() { println!(); } -// "}); -// cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); -// let symbol_range = cx.lsp_range(indoc! {" -// «fn» test() { println!(); } -// "}); -// cx.handle_request::(move |_, _, _| async move { -// Ok(Some(lsp::Hover { -// contents: lsp::HoverContents::Markup(lsp::MarkupContent { -// kind: lsp::MarkupKind::Markdown, -// value: "some other basic docs".to_string(), -// }), -// range: Some(symbol_range), -// })) -// }) -// .next() -// .await; - -// cx.condition(|editor, _| editor.hover_state.visible()).await; -// cx.editor(|editor, _| { -// assert_eq!( -// editor.hover_state.info_popover.clone().unwrap().blocks, -// vec![HoverBlock { -// text: "some other basic docs".to_string(), -// kind: HoverBlockKind::Markdown, -// }] -// ) -// }); -// } - -// #[gpui::test] -// async fn test_empty_hovers_filtered(cx: &mut gpui::TestAppContext) { -// init_test(cx, |_| {}); - -// let mut cx = EditorLspTestContext::new_rust( -// lsp::ServerCapabilities { -// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), -// ..Default::default() -// }, -// cx, -// ) -// .await; - -// // Hover with keyboard has no delay -// cx.set_state(indoc! {" -// fˇn test() { println!(); } -// "}); -// cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); -// let symbol_range = cx.lsp_range(indoc! {" -// «fn» test() { println!(); } -// "}); -// cx.handle_request::(move |_, _, _| async move { -// Ok(Some(lsp::Hover { -// contents: lsp::HoverContents::Array(vec![ -// lsp::MarkedString::String("regular text for hover to show".to_string()), -// lsp::MarkedString::String("".to_string()), -// lsp::MarkedString::LanguageString(lsp::LanguageString { -// language: "Rust".to_string(), -// value: "".to_string(), -// }), -// ]), -// range: Some(symbol_range), -// })) -// }) -// .next() -// .await; - -// cx.condition(|editor, _| editor.hover_state.visible()).await; -// cx.editor(|editor, _| { -// assert_eq!( -// editor.hover_state.info_popover.clone().unwrap().blocks, -// vec![HoverBlock { -// text: "regular text for hover to show".to_string(), -// kind: HoverBlockKind::Markdown, -// }], -// "No empty string hovers should be shown" -// ); -// }); -// } - -// #[gpui::test] -// async fn test_line_ends_trimmed(cx: &mut gpui::TestAppContext) { -// init_test(cx, |_| {}); - -// let mut cx = EditorLspTestContext::new_rust( -// lsp::ServerCapabilities { -// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), -// ..Default::default() -// }, -// cx, -// ) -// .await; - -// // Hover with keyboard has no delay -// cx.set_state(indoc! {" -// fˇn test() { println!(); } -// "}); -// cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); -// let symbol_range = cx.lsp_range(indoc! {" -// «fn» test() { println!(); } -// "}); - -// let code_str = "\nlet hovered_point: Vector2F // size = 8, align = 0x4\n"; -// let markdown_string = format!("\n```rust\n{code_str}```"); - -// let closure_markdown_string = markdown_string.clone(); -// cx.handle_request::(move |_, _, _| { -// let future_markdown_string = closure_markdown_string.clone(); -// async move { -// Ok(Some(lsp::Hover { -// contents: lsp::HoverContents::Markup(lsp::MarkupContent { -// kind: lsp::MarkupKind::Markdown, -// value: future_markdown_string, -// }), -// range: Some(symbol_range), -// })) -// } -// }) -// .next() -// .await; - -// cx.condition(|editor, _| editor.hover_state.visible()).await; -// cx.editor(|editor, _| { -// let blocks = editor.hover_state.info_popover.clone().unwrap().blocks; -// assert_eq!( -// blocks, -// vec![HoverBlock { -// text: markdown_string, -// kind: HoverBlockKind::Markdown, -// }], -// ); - -// let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); -// assert_eq!( -// rendered.text, -// code_str.trim(), -// "Should not have extra line breaks at end of rendered hover" -// ); -// }); -// } - -// #[gpui::test] -// async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) { -// init_test(cx, |_| {}); - -// let mut cx = EditorLspTestContext::new_rust( -// lsp::ServerCapabilities { -// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), -// ..Default::default() -// }, -// cx, -// ) -// .await; - -// // Hover with just diagnostic, pops DiagnosticPopover immediately and then -// // info popover once request completes -// cx.set_state(indoc! {" -// fn teˇst() { println!(); } -// "}); - -// // Send diagnostic to client -// let range = cx.text_anchor_range(indoc! {" -// fn «test»() { println!(); } -// "}); -// cx.update_buffer(|buffer, cx| { -// let snapshot = buffer.text_snapshot(); -// let set = DiagnosticSet::from_sorted_entries( -// vec![DiagnosticEntry { -// range, -// diagnostic: Diagnostic { -// message: "A test diagnostic message.".to_string(), -// ..Default::default() -// }, -// }], -// &snapshot, -// ); -// buffer.update_diagnostics(LanguageServerId(0), set, cx); -// }); - -// // Hover pops diagnostic immediately -// cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); -// cx.foreground().run_until_parked(); - -// cx.editor(|Editor { hover_state, .. }, _| { -// assert!(hover_state.diagnostic_popover.is_some() && hover_state.info_popover.is_none()) -// }); - -// // Info Popover shows after request responded to -// let range = cx.lsp_range(indoc! {" -// fn «test»() { println!(); } -// "}); -// cx.handle_request::(move |_, _, _| async move { -// Ok(Some(lsp::Hover { -// contents: lsp::HoverContents::Markup(lsp::MarkupContent { -// kind: lsp::MarkupKind::Markdown, -// value: "some new docs".to_string(), -// }), -// range: Some(range), -// })) -// }); -// cx.foreground() -// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); - -// cx.foreground().run_until_parked(); -// cx.editor(|Editor { hover_state, .. }, _| { -// hover_state.diagnostic_popover.is_some() && hover_state.info_task.is_some() -// }); -// } - -// #[gpui::test] -// fn test_render_blocks(cx: &mut gpui::TestAppContext) { -// init_test(cx, |_| {}); - -// cx.add_window(|cx| { -// let editor = Editor::single_line(None, cx); -// let style = editor.style(cx); - -// struct Row { -// blocks: Vec, -// expected_marked_text: String, -// expected_styles: Vec, -// } - -// let rows = &[ -// // Strong emphasis -// Row { -// blocks: vec![HoverBlock { -// text: "one **two** three".to_string(), -// kind: HoverBlockKind::Markdown, -// }], -// expected_marked_text: "one «two» three".to_string(), -// expected_styles: vec![HighlightStyle { -// weight: Some(Weight::BOLD), -// ..Default::default() -// }], -// }, -// // Links -// Row { -// blocks: vec![HoverBlock { -// text: "one [two](https://the-url) three".to_string(), -// kind: HoverBlockKind::Markdown, -// }], -// expected_marked_text: "one «two» three".to_string(), -// expected_styles: vec![HighlightStyle { -// underline: Some(Underline { -// thickness: 1.0.into(), -// ..Default::default() -// }), -// ..Default::default() -// }], -// }, -// // Lists -// Row { -// blocks: vec![HoverBlock { -// text: " -// lists: -// * one -// - a -// - b -// * two -// - [c](https://the-url) -// - d" -// .unindent(), -// kind: HoverBlockKind::Markdown, -// }], -// expected_marked_text: " -// lists: -// - one -// - a -// - b -// - two -// - «c» -// - d" -// .unindent(), -// expected_styles: vec![HighlightStyle { -// underline: Some(Underline { -// thickness: 1.0.into(), -// ..Default::default() -// }), -// ..Default::default() -// }], -// }, -// // Multi-paragraph list items -// Row { -// blocks: vec![HoverBlock { -// text: " -// * one two -// three - -// * four five -// * six seven -// eight - -// nine -// * ten -// * six" -// .unindent(), -// kind: HoverBlockKind::Markdown, -// }], -// expected_marked_text: " -// - one two three -// - four five -// - six seven eight - -// nine -// - ten -// - six" -// .unindent(), -// expected_styles: vec![HighlightStyle { -// underline: Some(Underline { -// thickness: 1.0.into(), -// ..Default::default() -// }), -// ..Default::default() -// }], -// }, -// ]; - -// for Row { -// blocks, -// expected_marked_text, -// expected_styles, -// } in &rows[0..] -// { -// let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); - -// let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); -// let expected_highlights = ranges -// .into_iter() -// .zip(expected_styles.iter().cloned()) -// .collect::>(); -// assert_eq!( -// rendered.text, expected_text, -// "wrong text for input {blocks:?}" -// ); - -// let rendered_highlights: Vec<_> = rendered -// .highlights -// .iter() -// .filter_map(|(range, highlight)| { -// let highlight = highlight.to_highlight_style(&style.syntax)?; -// Some((range.clone(), highlight)) -// }) -// .collect(); - -// assert_eq!( -// rendered_highlights, expected_highlights, -// "wrong highlights for input {blocks:?}" -// ); -// } - -// editor -// }); -// } - -// #[gpui::test] -// async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) { -// init_test(cx, |settings| { -// settings.defaults.inlay_hints = Some(InlayHintSettings { -// enabled: true, -// show_type_hints: true, -// show_parameter_hints: true, -// show_other_hints: true, -// }) -// }); - -// let mut cx = EditorLspTestContext::new_rust( -// lsp::ServerCapabilities { -// inlay_hint_provider: Some(lsp::OneOf::Right( -// lsp::InlayHintServerCapabilities::Options(lsp::InlayHintOptions { -// resolve_provider: Some(true), -// ..Default::default() -// }), -// )), -// ..Default::default() -// }, -// cx, -// ) -// .await; - -// cx.set_state(indoc! {" -// struct TestStruct; - -// // ================== - -// struct TestNewType(T); - -// fn main() { -// let variableˇ = TestNewType(TestStruct); -// } -// "}); - -// let hint_start_offset = cx.ranges(indoc! {" -// struct TestStruct; - -// // ================== - -// struct TestNewType(T); - -// fn main() { -// let variableˇ = TestNewType(TestStruct); -// } -// "})[0] -// .start; -// let hint_position = cx.to_lsp(hint_start_offset); -// let new_type_target_range = cx.lsp_range(indoc! {" -// struct TestStruct; - -// // ================== - -// struct «TestNewType»(T); - -// fn main() { -// let variable = TestNewType(TestStruct); -// } -// "}); -// let struct_target_range = cx.lsp_range(indoc! {" -// struct «TestStruct»; - -// // ================== - -// struct TestNewType(T); - -// fn main() { -// let variable = TestNewType(TestStruct); -// } -// "}); - -// let uri = cx.buffer_lsp_url.clone(); -// let new_type_label = "TestNewType"; -// let struct_label = "TestStruct"; -// let entire_hint_label = ": TestNewType"; -// let closure_uri = uri.clone(); -// cx.lsp -// .handle_request::(move |params, _| { -// let task_uri = closure_uri.clone(); -// async move { -// assert_eq!(params.text_document.uri, task_uri); -// Ok(Some(vec![lsp::InlayHint { -// position: hint_position, -// label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart { -// value: entire_hint_label.to_string(), -// ..Default::default() -// }]), -// kind: Some(lsp::InlayHintKind::TYPE), -// text_edits: None, -// tooltip: None, -// padding_left: Some(false), -// padding_right: Some(false), -// data: None, -// }])) -// } -// }) -// .next() -// .await; -// cx.foreground().run_until_parked(); -// cx.update_editor(|editor, cx| { -// let expected_layers = vec![entire_hint_label.to_string()]; -// assert_eq!(expected_layers, cached_hint_labels(editor)); -// assert_eq!(expected_layers, visible_hint_labels(editor, cx)); -// }); - -// let inlay_range = cx -// .ranges(indoc! {" -// struct TestStruct; - -// // ================== - -// struct TestNewType(T); - -// fn main() { -// let variable« »= TestNewType(TestStruct); -// } -// "}) -// .get(0) -// .cloned() -// .unwrap(); -// let new_type_hint_part_hover_position = cx.update_editor(|editor, cx| { -// let snapshot = editor.snapshot(cx); -// let previous_valid = inlay_range.start.to_display_point(&snapshot); -// let next_valid = inlay_range.end.to_display_point(&snapshot); -// assert_eq!(previous_valid.row(), next_valid.row()); -// assert!(previous_valid.column() < next_valid.column()); -// let exact_unclipped = DisplayPoint::new( -// previous_valid.row(), -// previous_valid.column() -// + (entire_hint_label.find(new_type_label).unwrap() + new_type_label.len() / 2) -// as u32, -// ); -// PointForPosition { -// previous_valid, -// next_valid, -// exact_unclipped, -// column_overshoot_after_line_end: 0, -// } -// }); -// cx.update_editor(|editor, cx| { -// update_inlay_link_and_hover_points( -// &editor.snapshot(cx), -// new_type_hint_part_hover_position, -// editor, -// true, -// false, -// cx, -// ); -// }); - -// let resolve_closure_uri = uri.clone(); -// cx.lsp -// .handle_request::( -// move |mut hint_to_resolve, _| { -// let mut resolved_hint_positions = BTreeSet::new(); -// let task_uri = resolve_closure_uri.clone(); -// async move { -// let inserted = resolved_hint_positions.insert(hint_to_resolve.position); -// assert!(inserted, "Hint {hint_to_resolve:?} was resolved twice"); - -// // `: TestNewType` -// hint_to_resolve.label = lsp::InlayHintLabel::LabelParts(vec![ -// lsp::InlayHintLabelPart { -// value: ": ".to_string(), -// ..Default::default() -// }, -// lsp::InlayHintLabelPart { -// value: new_type_label.to_string(), -// location: Some(lsp::Location { -// uri: task_uri.clone(), -// range: new_type_target_range, -// }), -// tooltip: Some(lsp::InlayHintLabelPartTooltip::String(format!( -// "A tooltip for `{new_type_label}`" -// ))), -// ..Default::default() -// }, -// lsp::InlayHintLabelPart { -// value: "<".to_string(), -// ..Default::default() -// }, -// lsp::InlayHintLabelPart { -// value: struct_label.to_string(), -// location: Some(lsp::Location { -// uri: task_uri, -// range: struct_target_range, -// }), -// tooltip: Some(lsp::InlayHintLabelPartTooltip::MarkupContent( -// lsp::MarkupContent { -// kind: lsp::MarkupKind::Markdown, -// value: format!("A tooltip for `{struct_label}`"), -// }, -// )), -// ..Default::default() -// }, -// lsp::InlayHintLabelPart { -// value: ">".to_string(), -// ..Default::default() -// }, -// ]); - -// Ok(hint_to_resolve) -// } -// }, -// ) -// .next() -// .await; -// cx.foreground().run_until_parked(); - -// cx.update_editor(|editor, cx| { -// update_inlay_link_and_hover_points( -// &editor.snapshot(cx), -// new_type_hint_part_hover_position, -// editor, -// true, -// false, -// cx, -// ); -// }); -// cx.foreground() -// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); -// cx.foreground().run_until_parked(); -// cx.update_editor(|editor, cx| { -// let hover_state = &editor.hover_state; -// assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); -// let popover = hover_state.info_popover.as_ref().unwrap(); -// let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); -// assert_eq!( -// popover.symbol_range, -// RangeInEditor::Inlay(InlayHighlight { -// inlay: InlayId::Hint(0), -// inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), -// range: ": ".len()..": ".len() + new_type_label.len(), -// }), -// "Popover range should match the new type label part" -// ); -// assert_eq!( -// popover.parsed_content.text, -// format!("A tooltip for `{new_type_label}`"), -// "Rendered text should not anyhow alter backticks" -// ); -// }); - -// let struct_hint_part_hover_position = cx.update_editor(|editor, cx| { -// let snapshot = editor.snapshot(cx); -// let previous_valid = inlay_range.start.to_display_point(&snapshot); -// let next_valid = inlay_range.end.to_display_point(&snapshot); -// assert_eq!(previous_valid.row(), next_valid.row()); -// assert!(previous_valid.column() < next_valid.column()); -// let exact_unclipped = DisplayPoint::new( -// previous_valid.row(), -// previous_valid.column() -// + (entire_hint_label.find(struct_label).unwrap() + struct_label.len() / 2) -// as u32, -// ); -// PointForPosition { -// previous_valid, -// next_valid, -// exact_unclipped, -// column_overshoot_after_line_end: 0, -// } -// }); -// cx.update_editor(|editor, cx| { -// update_inlay_link_and_hover_points( -// &editor.snapshot(cx), -// struct_hint_part_hover_position, -// editor, -// true, -// false, -// cx, -// ); -// }); -// cx.foreground() -// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); -// cx.foreground().run_until_parked(); -// cx.update_editor(|editor, cx| { -// let hover_state = &editor.hover_state; -// assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); -// let popover = hover_state.info_popover.as_ref().unwrap(); -// let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); -// assert_eq!( -// popover.symbol_range, -// RangeInEditor::Inlay(InlayHighlight { -// inlay: InlayId::Hint(0), -// inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), -// range: ": ".len() + new_type_label.len() + "<".len() -// ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(), -// }), -// "Popover range should match the struct label part" -// ); -// assert_eq!( -// popover.parsed_content.text, -// format!("A tooltip for {struct_label}"), -// "Rendered markdown element should remove backticks from text" -// ); -// }); -// } -// } +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + editor_tests::init_test, + element::PointForPosition, + inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, + link_go_to_definition::update_inlay_link_and_hover_points, + test::editor_lsp_test_context::EditorLspTestContext, + InlayId, + }; + use collections::BTreeSet; + use gpui::{FontWeight, HighlightStyle, UnderlineStyle}; + use indoc::indoc; + use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; + use lsp::LanguageServerId; + use project::{HoverBlock, HoverBlockKind}; + use smol::stream::StreamExt; + use unindent::Unindent; + use util::test::marked_text_ranges; + + #[gpui::test] + async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), + ..Default::default() + }, + cx, + ) + .await; + + // Basic hover delays and then pops without moving the mouse + cx.set_state(indoc! {" + fn ˇtest() { println!(); } + "}); + let hover_point = cx.display_point(indoc! {" + fn test() { printˇln!(); } + "}); + + cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx)); + assert!(!cx.editor(|editor, _| editor.hover_state.visible())); + + // After delay, hover should be visible. + let symbol_range = cx.lsp_range(indoc! {" + fn test() { «println!»(); } + "}); + let mut requests = + cx.handle_request::(move |_, _, _| async move { + Ok(Some(lsp::Hover { + contents: lsp::HoverContents::Markup(lsp::MarkupContent { + kind: lsp::MarkupKind::Markdown, + value: "some basic docs".to_string(), + }), + range: Some(symbol_range), + })) + }); + cx.background_executor + .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); + requests.next().await; + + cx.editor(|editor, _| { + assert!(editor.hover_state.visible()); + assert_eq!( + editor.hover_state.info_popover.clone().unwrap().blocks, + vec![HoverBlock { + text: "some basic docs".to_string(), + kind: HoverBlockKind::Markdown, + },] + ) + }); + + // Mouse moved with no hover response dismisses + let hover_point = cx.display_point(indoc! {" + fn teˇst() { println!(); } + "}); + let mut request = cx + .lsp + .handle_request::(|_, _| async move { Ok(None) }); + cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx)); + cx.background_executor + .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); + request.next().await; + cx.editor(|editor, _| { + assert!(!editor.hover_state.visible()); + }); + } + + #[gpui::test] + async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), + ..Default::default() + }, + cx, + ) + .await; + + // Hover with keyboard has no delay + cx.set_state(indoc! {" + fˇn test() { println!(); } + "}); + cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); + let symbol_range = cx.lsp_range(indoc! {" + «fn» test() { println!(); } + "}); + cx.handle_request::(move |_, _, _| async move { + Ok(Some(lsp::Hover { + contents: lsp::HoverContents::Markup(lsp::MarkupContent { + kind: lsp::MarkupKind::Markdown, + value: "some other basic docs".to_string(), + }), + range: Some(symbol_range), + })) + }) + .next() + .await; + + cx.condition(|editor, _| editor.hover_state.visible()).await; + cx.editor(|editor, _| { + assert_eq!( + editor.hover_state.info_popover.clone().unwrap().blocks, + vec![HoverBlock { + text: "some other basic docs".to_string(), + kind: HoverBlockKind::Markdown, + }] + ) + }); + } + + #[gpui::test] + async fn test_empty_hovers_filtered(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), + ..Default::default() + }, + cx, + ) + .await; + + // Hover with keyboard has no delay + cx.set_state(indoc! {" + fˇn test() { println!(); } + "}); + cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); + let symbol_range = cx.lsp_range(indoc! {" + «fn» test() { println!(); } + "}); + cx.handle_request::(move |_, _, _| async move { + Ok(Some(lsp::Hover { + contents: lsp::HoverContents::Array(vec![ + lsp::MarkedString::String("regular text for hover to show".to_string()), + lsp::MarkedString::String("".to_string()), + lsp::MarkedString::LanguageString(lsp::LanguageString { + language: "Rust".to_string(), + value: "".to_string(), + }), + ]), + range: Some(symbol_range), + })) + }) + .next() + .await; + + cx.condition(|editor, _| editor.hover_state.visible()).await; + cx.editor(|editor, _| { + assert_eq!( + editor.hover_state.info_popover.clone().unwrap().blocks, + vec![HoverBlock { + text: "regular text for hover to show".to_string(), + kind: HoverBlockKind::Markdown, + }], + "No empty string hovers should be shown" + ); + }); + } + + #[gpui::test] + async fn test_line_ends_trimmed(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), + ..Default::default() + }, + cx, + ) + .await; + + // Hover with keyboard has no delay + cx.set_state(indoc! {" + fˇn test() { println!(); } + "}); + cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); + let symbol_range = cx.lsp_range(indoc! {" + «fn» test() { println!(); } + "}); + + let code_str = "\nlet hovered_point: Vector2F // size = 8, align = 0x4\n"; + let markdown_string = format!("\n```rust\n{code_str}```"); + + let closure_markdown_string = markdown_string.clone(); + cx.handle_request::(move |_, _, _| { + let future_markdown_string = closure_markdown_string.clone(); + async move { + Ok(Some(lsp::Hover { + contents: lsp::HoverContents::Markup(lsp::MarkupContent { + kind: lsp::MarkupKind::Markdown, + value: future_markdown_string, + }), + range: Some(symbol_range), + })) + } + }) + .next() + .await; + + cx.condition(|editor, _| editor.hover_state.visible()).await; + cx.editor(|editor, _| { + let blocks = editor.hover_state.info_popover.clone().unwrap().blocks; + assert_eq!( + blocks, + vec![HoverBlock { + text: markdown_string, + kind: HoverBlockKind::Markdown, + }], + ); + + let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); + assert_eq!( + rendered.text, + code_str.trim(), + "Should not have extra line breaks at end of rendered hover" + ); + }); + } + + #[gpui::test] + async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), + ..Default::default() + }, + cx, + ) + .await; + + // Hover with just diagnostic, pops DiagnosticPopover immediately and then + // info popover once request completes + cx.set_state(indoc! {" + fn teˇst() { println!(); } + "}); + + // Send diagnostic to client + let range = cx.text_anchor_range(indoc! {" + fn «test»() { println!(); } + "}); + cx.update_buffer(|buffer, cx| { + let snapshot = buffer.text_snapshot(); + let set = DiagnosticSet::from_sorted_entries( + vec![DiagnosticEntry { + range, + diagnostic: Diagnostic { + message: "A test diagnostic message.".to_string(), + ..Default::default() + }, + }], + &snapshot, + ); + buffer.update_diagnostics(LanguageServerId(0), set, cx); + }); + + // Hover pops diagnostic immediately + cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); + cx.background_executor.run_until_parked(); + + cx.editor(|Editor { hover_state, .. }, _| { + assert!(hover_state.diagnostic_popover.is_some() && hover_state.info_popover.is_none()) + }); + + // Info Popover shows after request responded to + let range = cx.lsp_range(indoc! {" + fn «test»() { println!(); } + "}); + cx.handle_request::(move |_, _, _| async move { + Ok(Some(lsp::Hover { + contents: lsp::HoverContents::Markup(lsp::MarkupContent { + kind: lsp::MarkupKind::Markdown, + value: "some new docs".to_string(), + }), + range: Some(range), + })) + }); + cx.background_executor + .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); + + cx.background_executor.run_until_parked(); + cx.editor(|Editor { hover_state, .. }, _| { + hover_state.diagnostic_popover.is_some() && hover_state.info_task.is_some() + }); + } + + #[gpui::test] + fn test_render_blocks(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + cx.add_window(|cx| { + let editor = Editor::single_line(cx); + let style = editor.style.clone().unwrap(); + + struct Row { + blocks: Vec, + expected_marked_text: String, + expected_styles: Vec, + } + + let rows = &[ + // Strong emphasis + Row { + blocks: vec![HoverBlock { + text: "one **two** three".to_string(), + kind: HoverBlockKind::Markdown, + }], + expected_marked_text: "one «two» three".to_string(), + expected_styles: vec![HighlightStyle { + font_weight: Some(FontWeight::BOLD), + ..Default::default() + }], + }, + // Links + Row { + blocks: vec![HoverBlock { + text: "one [two](https://the-url) three".to_string(), + kind: HoverBlockKind::Markdown, + }], + expected_marked_text: "one «two» three".to_string(), + expected_styles: vec![HighlightStyle { + underline: Some(UnderlineStyle { + thickness: 1.0.into(), + ..Default::default() + }), + ..Default::default() + }], + }, + // Lists + Row { + blocks: vec![HoverBlock { + text: " + lists: + * one + - a + - b + * two + - [c](https://the-url) + - d" + .unindent(), + kind: HoverBlockKind::Markdown, + }], + expected_marked_text: " + lists: + - one + - a + - b + - two + - «c» + - d" + .unindent(), + expected_styles: vec![HighlightStyle { + underline: Some(UnderlineStyle { + thickness: 1.0.into(), + ..Default::default() + }), + ..Default::default() + }], + }, + // Multi-paragraph list items + Row { + blocks: vec![HoverBlock { + text: " + * one two + three + + * four five + * six seven + eight + + nine + * ten + * six" + .unindent(), + kind: HoverBlockKind::Markdown, + }], + expected_marked_text: " + - one two three + - four five + - six seven eight + + nine + - ten + - six" + .unindent(), + expected_styles: vec![HighlightStyle { + underline: Some(UnderlineStyle { + thickness: 1.0.into(), + ..Default::default() + }), + ..Default::default() + }], + }, + ]; + + for Row { + blocks, + expected_marked_text, + expected_styles, + } in &rows[0..] + { + let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); + + let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); + let expected_highlights = ranges + .into_iter() + .zip(expected_styles.iter().cloned()) + .collect::>(); + assert_eq!( + rendered.text, expected_text, + "wrong text for input {blocks:?}" + ); + + let rendered_highlights: Vec<_> = rendered + .highlights + .iter() + .filter_map(|(range, highlight)| { + let highlight = highlight.to_highlight_style(&style.syntax)?; + Some((range.clone(), highlight)) + }) + .collect(); + + assert_eq!( + rendered_highlights, expected_highlights, + "wrong highlights for input {blocks:?}" + ); + } + + editor + }); + } + + #[gpui::test] + async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) { + init_test(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: true, + show_parameter_hints: true, + show_other_hints: true, + }) + }); + + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + inlay_hint_provider: Some(lsp::OneOf::Right( + lsp::InlayHintServerCapabilities::Options(lsp::InlayHintOptions { + resolve_provider: Some(true), + ..Default::default() + }), + )), + ..Default::default() + }, + cx, + ) + .await; + + cx.set_state(indoc! {" + struct TestStruct; + + // ================== + + struct TestNewType(T); + + fn main() { + let variableˇ = TestNewType(TestStruct); + } + "}); + + let hint_start_offset = cx.ranges(indoc! {" + struct TestStruct; + + // ================== + + struct TestNewType(T); + + fn main() { + let variableˇ = TestNewType(TestStruct); + } + "})[0] + .start; + let hint_position = cx.to_lsp(hint_start_offset); + let new_type_target_range = cx.lsp_range(indoc! {" + struct TestStruct; + + // ================== + + struct «TestNewType»(T); + + fn main() { + let variable = TestNewType(TestStruct); + } + "}); + let struct_target_range = cx.lsp_range(indoc! {" + struct «TestStruct»; + + // ================== + + struct TestNewType(T); + + fn main() { + let variable = TestNewType(TestStruct); + } + "}); + + let uri = cx.buffer_lsp_url.clone(); + let new_type_label = "TestNewType"; + let struct_label = "TestStruct"; + let entire_hint_label = ": TestNewType"; + let closure_uri = uri.clone(); + cx.lsp + .handle_request::(move |params, _| { + let task_uri = closure_uri.clone(); + async move { + assert_eq!(params.text_document.uri, task_uri); + Ok(Some(vec![lsp::InlayHint { + position: hint_position, + label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart { + value: entire_hint_label.to_string(), + ..Default::default() + }]), + kind: Some(lsp::InlayHintKind::TYPE), + text_edits: None, + tooltip: None, + padding_left: Some(false), + padding_right: Some(false), + data: None, + }])) + } + }) + .next() + .await; + cx.background_executor.run_until_parked(); + cx.update_editor(|editor, cx| { + let expected_layers = vec![entire_hint_label.to_string()]; + assert_eq!(expected_layers, cached_hint_labels(editor)); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + }); + + let inlay_range = cx + .ranges(indoc! {" + struct TestStruct; + + // ================== + + struct TestNewType(T); + + fn main() { + let variable« »= TestNewType(TestStruct); + } + "}) + .get(0) + .cloned() + .unwrap(); + let new_type_hint_part_hover_position = cx.update_editor(|editor, cx| { + let snapshot = editor.snapshot(cx); + let previous_valid = inlay_range.start.to_display_point(&snapshot); + let next_valid = inlay_range.end.to_display_point(&snapshot); + assert_eq!(previous_valid.row(), next_valid.row()); + assert!(previous_valid.column() < next_valid.column()); + let exact_unclipped = DisplayPoint::new( + previous_valid.row(), + previous_valid.column() + + (entire_hint_label.find(new_type_label).unwrap() + new_type_label.len() / 2) + as u32, + ); + PointForPosition { + previous_valid, + next_valid, + exact_unclipped, + column_overshoot_after_line_end: 0, + } + }); + cx.update_editor(|editor, cx| { + update_inlay_link_and_hover_points( + &editor.snapshot(cx), + new_type_hint_part_hover_position, + editor, + true, + false, + cx, + ); + }); + + let resolve_closure_uri = uri.clone(); + cx.lsp + .handle_request::( + move |mut hint_to_resolve, _| { + let mut resolved_hint_positions = BTreeSet::new(); + let task_uri = resolve_closure_uri.clone(); + async move { + let inserted = resolved_hint_positions.insert(hint_to_resolve.position); + assert!(inserted, "Hint {hint_to_resolve:?} was resolved twice"); + + // `: TestNewType` + hint_to_resolve.label = lsp::InlayHintLabel::LabelParts(vec![ + lsp::InlayHintLabelPart { + value: ": ".to_string(), + ..Default::default() + }, + lsp::InlayHintLabelPart { + value: new_type_label.to_string(), + location: Some(lsp::Location { + uri: task_uri.clone(), + range: new_type_target_range, + }), + tooltip: Some(lsp::InlayHintLabelPartTooltip::String(format!( + "A tooltip for `{new_type_label}`" + ))), + ..Default::default() + }, + lsp::InlayHintLabelPart { + value: "<".to_string(), + ..Default::default() + }, + lsp::InlayHintLabelPart { + value: struct_label.to_string(), + location: Some(lsp::Location { + uri: task_uri, + range: struct_target_range, + }), + tooltip: Some(lsp::InlayHintLabelPartTooltip::MarkupContent( + lsp::MarkupContent { + kind: lsp::MarkupKind::Markdown, + value: format!("A tooltip for `{struct_label}`"), + }, + )), + ..Default::default() + }, + lsp::InlayHintLabelPart { + value: ">".to_string(), + ..Default::default() + }, + ]); + + Ok(hint_to_resolve) + } + }, + ) + .next() + .await; + cx.background_executor.run_until_parked(); + + cx.update_editor(|editor, cx| { + update_inlay_link_and_hover_points( + &editor.snapshot(cx), + new_type_hint_part_hover_position, + editor, + true, + false, + cx, + ); + }); + cx.background_executor + .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); + cx.background_executor.run_until_parked(); + cx.update_editor(|editor, cx| { + let hover_state = &editor.hover_state; + assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); + let popover = hover_state.info_popover.as_ref().unwrap(); + let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); + assert_eq!( + popover.symbol_range, + RangeInEditor::Inlay(InlayHighlight { + inlay: InlayId::Hint(0), + inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), + range: ": ".len()..": ".len() + new_type_label.len(), + }), + "Popover range should match the new type label part" + ); + assert_eq!( + popover.parsed_content.text, + format!("A tooltip for `{new_type_label}`"), + "Rendered text should not anyhow alter backticks" + ); + }); + + let struct_hint_part_hover_position = cx.update_editor(|editor, cx| { + let snapshot = editor.snapshot(cx); + let previous_valid = inlay_range.start.to_display_point(&snapshot); + let next_valid = inlay_range.end.to_display_point(&snapshot); + assert_eq!(previous_valid.row(), next_valid.row()); + assert!(previous_valid.column() < next_valid.column()); + let exact_unclipped = DisplayPoint::new( + previous_valid.row(), + previous_valid.column() + + (entire_hint_label.find(struct_label).unwrap() + struct_label.len() / 2) + as u32, + ); + PointForPosition { + previous_valid, + next_valid, + exact_unclipped, + column_overshoot_after_line_end: 0, + } + }); + cx.update_editor(|editor, cx| { + update_inlay_link_and_hover_points( + &editor.snapshot(cx), + struct_hint_part_hover_position, + editor, + true, + false, + cx, + ); + }); + cx.background_executor + .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); + cx.background_executor.run_until_parked(); + cx.update_editor(|editor, cx| { + let hover_state = &editor.hover_state; + assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); + let popover = hover_state.info_popover.as_ref().unwrap(); + let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); + assert_eq!( + popover.symbol_range, + RangeInEditor::Inlay(InlayHighlight { + inlay: InlayId::Hint(0), + inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), + range: ": ".len() + new_type_label.len() + "<".len() + ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(), + }), + "Popover range should match the struct label part" + ); + assert_eq!( + popover.parsed_content.text, + format!("A tooltip for {struct_label}"), + "Rendered markdown element should remove backticks from text" + ); + }); + } +} From b2b5df442889b9820d447e48d29a8fe5121afd4c Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 27 Nov 2023 18:57:25 +0100 Subject: [PATCH 025/151] close notification handler proper on accept/decline Co-authored-by: Conrad --- .../src/notifications/incoming_call_notification.rs | 4 ++-- crates/gpui2/src/app.rs | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/collab_ui2/src/notifications/incoming_call_notification.rs b/crates/collab_ui2/src/notifications/incoming_call_notification.rs index b139afcc2f4ed91857b412bd29a123f3381fb536..24db39683de2daedf409dd911b89f1279e3b9fb9 100644 --- a/crates/collab_ui2/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui2/src/notifications/incoming_call_notification.rs @@ -18,9 +18,9 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { while let Some(incoming_call) = incoming_call.next().await { for window in notification_windows.drain(..) { window - .update(&mut cx, |_, _| { + .update(&mut cx, |_, cx| { // todo!() - //cx.remove_window(); + cx.remove_window(); }) .log_err(); } diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 617c0b5600742a07029863468b785376c1d53224..e8f2a60a6a6efd611eac67a649cf1d0da6a8c789 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -580,7 +580,7 @@ impl AppContext { .windows .iter() .filter_map(|(_, window)| { - let window = window.as_ref().unwrap(); + let window = window.as_ref()?; if window.dirty { Some(window.handle.clone()) } else { @@ -1049,7 +1049,9 @@ impl Context for AppContext { let root_view = window.root_view.clone().unwrap(); let result = update(root_view, &mut WindowContext::new(cx, &mut window)); - if !window.removed { + if window.removed { + cx.windows.remove(handle.id); + } else { cx.windows .get_mut(handle.id) .ok_or_else(|| anyhow!("window not found"))? From 1e6214440d344a3ca28acfa5e70b9a73e8d759cd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 27 Nov 2023 19:07:55 +0100 Subject: [PATCH 026/151] Show diagnostic hover popover --- crates/editor2/src/hover_popover.rs | 85 +++++++++++------------------ 1 file changed, 33 insertions(+), 52 deletions(-) diff --git a/crates/editor2/src/hover_popover.rs b/crates/editor2/src/hover_popover.rs index 948b391ecf0cf5a4976210a1f3b94b9f096a1408..d4886120435c6e7b298b10c3f9c2580ecf8ab9fd 100644 --- a/crates/editor2/src/hover_popover.rs +++ b/crates/editor2/src/hover_popover.rs @@ -6,13 +6,16 @@ use crate::{ }; use futures::FutureExt; use gpui::{ - actions, div, px, AnyElement, AppContext, InteractiveElement, IntoElement, Model, MouseButton, - ParentElement, Pixels, Size, StatefulInteractiveElement, Styled, Task, ViewContext, WeakView, + actions, div, px, AnyElement, AppContext, CursorStyle, InteractiveElement, IntoElement, Model, + MouseButton, ParentElement, Pixels, SharedString, Size, StatefulInteractiveElement, Styled, + Task, ViewContext, WeakView, }; use language::{markdown, Bias, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown}; +use lsp::DiagnosticSeverity; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; use settings::Settings; use std::{ops::Range, sync::Arc, time::Duration}; +use ui::Tooltip; use util::TryFutureExt; use workspace::Workspace; @@ -507,56 +510,34 @@ impl DiagnosticPopover { max_size: Size, cx: &mut ViewContext, ) -> AnyElement { - todo!() - // enum PrimaryDiagnostic {} - - // let mut text_style = style.hover_popover.prose.clone(); - // text_style.font_size = style.text.font_size; - // let diagnostic_source_style = style.hover_popover.diagnostic_source_highlight.clone(); - - // let text = match &self.local_diagnostic.diagnostic.source { - // Some(source) => Text::new( - // format!("{source}: {}", self.local_diagnostic.diagnostic.message), - // text_style, - // ) - // .with_highlights(vec![(0..source.len(), diagnostic_source_style)]), - - // None => Text::new(self.local_diagnostic.diagnostic.message.clone(), text_style), - // }; - - // let container_style = match self.local_diagnostic.diagnostic.severity { - // DiagnosticSeverity::HINT => style.hover_popover.info_container, - // DiagnosticSeverity::INFORMATION => style.hover_popover.info_container, - // DiagnosticSeverity::WARNING => style.hover_popover.warning_container, - // DiagnosticSeverity::ERROR => style.hover_popover.error_container, - // _ => style.hover_popover.container, - // }; - - // let tooltip_style = theme::current(cx).tooltip.clone(); - - // MouseEventHandler::new::(0, cx, |_, _| { - // text.with_soft_wrap(true) - // .contained() - // .with_style(container_style) - // }) - // .with_padding(Padding { - // top: HOVER_POPOVER_GAP, - // bottom: HOVER_POPOVER_GAP, - // ..Default::default() - // }) - // .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath. - // .on_click(MouseButton::Left, |_, this, cx| { - // this.go_to_diagnostic(&Default::default(), cx) - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .with_tooltip::( - // 0, - // "Go To Diagnostic".to_string(), - // Some(Box::new(crate::GoToDiagnostic)), - // tooltip_style, - // cx, - // ) - // .into_any() + let text = match &self.local_diagnostic.diagnostic.source { + Some(source) => format!("{source}: {}", self.local_diagnostic.diagnostic.message), + None => self.local_diagnostic.diagnostic.message.clone(), + }; + + let container_bg = crate::diagnostic_style( + self.local_diagnostic.diagnostic.severity, + true, + &style.diagnostic_style, + ); + + div() + .id("diagnostic") + .overflow_y_scroll() + .bg(container_bg) + .max_w(max_size.width) + .max_h(max_size.height) + .cursor(CursorStyle::PointingHand) + .tooltip(move |cx| Tooltip::for_action("Go To Diagnostic", &crate::GoToDiagnostic, cx)) + // Prevent a mouse move on the popover from being propagated to the editor, + // because that would dismiss the popover. + .on_mouse_move(|_, cx| cx.stop_propagation()) + // Prevent a mouse down on the popover from being propagated to the editor, + // because that would move the cursor. + .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation()) + .on_click(cx.listener(|editor, _, cx| editor.go_to_diagnostic(&Default::default(), cx))) + .child(SharedString::from(text)) + .into_any_element() } pub fn activation_info(&self) -> (usize, Anchor) { From 8c53f1b9c205a3a2db01af9866a71f32df9bc31b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 27 Nov 2023 19:14:58 +0100 Subject: [PATCH 027/151] Uncomment hover popover tests --- crates/editor2/src/hover_popover.rs | 206 ++++++++++++++-------------- 1 file changed, 103 insertions(+), 103 deletions(-) diff --git a/crates/editor2/src/hover_popover.rs b/crates/editor2/src/hover_popover.rs index d4886120435c6e7b298b10c3f9c2580ecf8ab9fd..e7980758e046cda1adb4c6eb43ff272f864c92b9 100644 --- a/crates/editor2/src/hover_popover.rs +++ b/crates/editor2/src/hover_popover.rs @@ -868,48 +868,49 @@ mod tests { fn test_render_blocks(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - cx.add_window(|cx| { - let editor = Editor::single_line(cx); - let style = editor.style.clone().unwrap(); - - struct Row { - blocks: Vec, - expected_marked_text: String, - expected_styles: Vec, - } + let editor = cx.add_window(|cx| Editor::single_line(cx)); + editor + .update(cx, |editor, cx| { + let style = editor.style.clone().unwrap(); + + struct Row { + blocks: Vec, + expected_marked_text: String, + expected_styles: Vec, + } - let rows = &[ - // Strong emphasis - Row { - blocks: vec![HoverBlock { - text: "one **two** three".to_string(), - kind: HoverBlockKind::Markdown, - }], - expected_marked_text: "one «two» three".to_string(), - expected_styles: vec![HighlightStyle { - font_weight: Some(FontWeight::BOLD), - ..Default::default() - }], - }, - // Links - Row { - blocks: vec![HoverBlock { - text: "one [two](https://the-url) three".to_string(), - kind: HoverBlockKind::Markdown, - }], - expected_marked_text: "one «two» three".to_string(), - expected_styles: vec![HighlightStyle { - underline: Some(UnderlineStyle { - thickness: 1.0.into(), + let rows = &[ + // Strong emphasis + Row { + blocks: vec![HoverBlock { + text: "one **two** three".to_string(), + kind: HoverBlockKind::Markdown, + }], + expected_marked_text: "one «two» three".to_string(), + expected_styles: vec![HighlightStyle { + font_weight: Some(FontWeight::BOLD), ..Default::default() - }), - ..Default::default() - }], - }, - // Lists - Row { - blocks: vec![HoverBlock { - text: " + }], + }, + // Links + Row { + blocks: vec![HoverBlock { + text: "one [two](https://the-url) three".to_string(), + kind: HoverBlockKind::Markdown, + }], + expected_marked_text: "one «two» three".to_string(), + expected_styles: vec![HighlightStyle { + underline: Some(UnderlineStyle { + thickness: 1.0.into(), + ..Default::default() + }), + ..Default::default() + }], + }, + // Lists + Row { + blocks: vec![HoverBlock { + text: " lists: * one - a @@ -917,10 +918,10 @@ mod tests { * two - [c](https://the-url) - d" - .unindent(), - kind: HoverBlockKind::Markdown, - }], - expected_marked_text: " + .unindent(), + kind: HoverBlockKind::Markdown, + }], + expected_marked_text: " lists: - one - a @@ -928,19 +929,19 @@ mod tests { - two - «c» - d" - .unindent(), - expected_styles: vec![HighlightStyle { - underline: Some(UnderlineStyle { - thickness: 1.0.into(), + .unindent(), + expected_styles: vec![HighlightStyle { + underline: Some(UnderlineStyle { + thickness: 1.0.into(), + ..Default::default() + }), ..Default::default() - }), - ..Default::default() - }], - }, - // Multi-paragraph list items - Row { - blocks: vec![HoverBlock { - text: " + }], + }, + // Multi-paragraph list items + Row { + blocks: vec![HoverBlock { + text: " * one two three @@ -951,10 +952,10 @@ mod tests { nine * ten * six" - .unindent(), - kind: HoverBlockKind::Markdown, - }], - expected_marked_text: " + .unindent(), + kind: HoverBlockKind::Markdown, + }], + expected_marked_text: " - one two three - four five - six seven eight @@ -962,52 +963,51 @@ mod tests { nine - ten - six" - .unindent(), - expected_styles: vec![HighlightStyle { - underline: Some(UnderlineStyle { - thickness: 1.0.into(), + .unindent(), + expected_styles: vec![HighlightStyle { + underline: Some(UnderlineStyle { + thickness: 1.0.into(), + ..Default::default() + }), ..Default::default() - }), - ..Default::default() - }], - }, - ]; - - for Row { - blocks, - expected_marked_text, - expected_styles, - } in &rows[0..] - { - let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); - - let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); - let expected_highlights = ranges - .into_iter() - .zip(expected_styles.iter().cloned()) - .collect::>(); - assert_eq!( - rendered.text, expected_text, - "wrong text for input {blocks:?}" - ); - - let rendered_highlights: Vec<_> = rendered - .highlights - .iter() - .filter_map(|(range, highlight)| { - let highlight = highlight.to_highlight_style(&style.syntax)?; - Some((range.clone(), highlight)) - }) - .collect(); + }], + }, + ]; - assert_eq!( - rendered_highlights, expected_highlights, - "wrong highlights for input {blocks:?}" - ); - } + for Row { + blocks, + expected_marked_text, + expected_styles, + } in &rows[0..] + { + let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); + + let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); + let expected_highlights = ranges + .into_iter() + .zip(expected_styles.iter().cloned()) + .collect::>(); + assert_eq!( + rendered.text, expected_text, + "wrong text for input {blocks:?}" + ); - editor - }); + let rendered_highlights: Vec<_> = rendered + .highlights + .iter() + .filter_map(|(range, highlight)| { + let highlight = highlight.to_highlight_style(&style.syntax)?; + Some((range.clone(), highlight)) + }) + .collect(); + + assert_eq!( + rendered_highlights, expected_highlights, + "wrong highlights for input {blocks:?}" + ); + } + }) + .unwrap(); } #[gpui::test] From fc4b621b326b3939ba70766798927f62ec7bb47a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 27 Nov 2023 19:16:06 +0100 Subject: [PATCH 028/151] Use element_hover instead of red --- crates/editor2/src/hover_popover.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor2/src/hover_popover.rs b/crates/editor2/src/hover_popover.rs index e7980758e046cda1adb4c6eb43ff272f864c92b9..37c7df650b126c859c28339a09077a96f4844bf2 100644 --- a/crates/editor2/src/hover_popover.rs +++ b/crates/editor2/src/hover_popover.rs @@ -118,7 +118,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| gpui::red(), // 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); From 3ea12ad0d7ad391e9f8855d9e7a84668fbc688e6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 27 Nov 2023 19:19:56 +0100 Subject: [PATCH 029/151] Remove commented out code --- crates/editor2/src/element.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 56f4ecd2bedc8d743de78f9ef3a798997842e0c4..1a888e21f7f79265a5551d1b1fe9e6fe062ff8db 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -1061,7 +1061,6 @@ impl EditorElement { if let Some((position, mut hover_popovers)) = layout.hover_popovers.take() { let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); - // cx.scene().push_stacking_context(None, None); // This is safe because we check on layout whether the required row is available let hovered_row_layout = &layout.position_map.line_layouts @@ -1118,8 +1117,6 @@ impl EditorElement { current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP; } } - - // cx.scene().pop_stacking_context(); } }) }, From 1c2f906e4837ce897f8d3569942c47981e4efc86 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:37:04 +0100 Subject: [PATCH 030/151] Position incoming call notification on right hand side. Co-authored-by: Conrad --- crates/collab_ui2/src/collab_ui.rs | 14 ++++++++++---- .../notifications/incoming_call_notification.rs | 1 - 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/collab_ui2/src/collab_ui.rs b/crates/collab_ui2/src/collab_ui.rs index 679a6a135b13832936b381bbff71015eb6636682..57a33c6790868bcd97a597da5a68a2608d0a684a 100644 --- a/crates/collab_ui2/src/collab_ui.rs +++ b/crates/collab_ui2/src/collab_ui.rs @@ -12,7 +12,7 @@ use std::{rc::Rc, sync::Arc}; pub use collab_panel::CollabPanel; pub use collab_titlebar_item::CollabTitlebarItem; use gpui::{ - AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, WindowBounds, WindowKind, + point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, WindowBounds, WindowKind, WindowOptions, }; pub use panel_settings::{ @@ -102,13 +102,19 @@ fn notification_window_options( screen: Rc, window_size: Size, ) -> WindowOptions { - let _notification_padding = Pixels::from(16.); + let notification_margin_width = GlobalPixels::from(16.); + let notification_margin_height = GlobalPixels::from(-0.) - GlobalPixels::from(48.); let screen_bounds = screen.bounds(); - let _size: Size = window_size.into(); + let size: Size = window_size.into(); + // todo!() use content bounds instead of screen.bounds and get rid of magics in point's 2nd argument. let bounds = gpui::Bounds:: { - origin: screen_bounds.origin, + origin: screen_bounds.upper_right() + - point( + size.width + notification_margin_width, + notification_margin_height, + ), size: window_size.into(), }; WindowOptions { diff --git a/crates/collab_ui2/src/notifications/incoming_call_notification.rs b/crates/collab_ui2/src/notifications/incoming_call_notification.rs index 24db39683de2daedf409dd911b89f1279e3b9fb9..0519b6fc4af1dc5b0f1418a19f2e956dffcbda4e 100644 --- a/crates/collab_ui2/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui2/src/notifications/incoming_call_notification.rs @@ -34,7 +34,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { for window in unique_screens { let options = notification_window_options(window, window_size); - dbg!(&options); let window = cx .open_window(options, |cx| { cx.build_view(|_| { From 7a8aba329bca459a73ec9cb42949198d06d59834 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 27 Nov 2023 11:43:14 -0700 Subject: [PATCH 031/151] Break content mask for hoverables --- crates/editor2/src/element.rs | 8 ++++++-- crates/gpui2/src/elements/overlay.rs | 8 +++++--- crates/gpui2/src/window.rs | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 1a888e21f7f79265a5551d1b1fe9e6fe062ff8db..5b510095ff116745d020e5ecb8604d06e9af0a02 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -1055,7 +1055,9 @@ impl EditorElement { list_origin.y -= layout.position_map.line_height + list_height; } - context_menu.draw(list_origin, available_space, cx); + cx.break_content_mask(|cx| { + context_menu.draw(list_origin, available_space, cx) + }); } if let Some((position, mut hover_popovers)) = layout.hover_popovers.take() { @@ -1095,7 +1097,9 @@ impl EditorElement { popover_origin.x = popover_origin.x + x_out_of_bounds; } - hover_popover.draw(popover_origin, available_space, cx); + cx.break_content_mask(|cx| { + hover_popover.draw(popover_origin, available_space, cx) + }); current_y = popover_origin.y - HOVER_POPOVER_GAP; } diff --git a/crates/gpui2/src/elements/overlay.rs b/crates/gpui2/src/elements/overlay.rs index d8aad4a42f9413f60e05d59bfab5d0606d4dd5d2..764bdfabcd6695d3ee0b4dd71cc51e472567c09e 100644 --- a/crates/gpui2/src/elements/overlay.rs +++ b/crates/gpui2/src/elements/overlay.rs @@ -144,9 +144,11 @@ impl Element for Overlay { } cx.with_element_offset(desired.origin - bounds.origin, |cx| { - for child in self.children { - child.paint(cx); - } + cx.break_content_mask(|cx| { + for child in self.children { + child.paint(cx); + } + }) }) } } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index de86e427571624fa2014827a51064eaa955fc73f..5d33f0161c687325d77db16d6dc5ec316236ff2c 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1752,6 +1752,24 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { } } + /// Invoke the given function with the content mask reset to that + /// of the window. + 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, + }, + }; + self.window_mut() + .current_frame + .content_mask_stack + .push(mask); + let result = f(self); + self.window_mut().current_frame.content_mask_stack.pop(); + result + } + /// Update the global element offset relative to the current offset. This is used to implement /// scrolling. fn with_element_offset( From 82f6f771172e123f1d853a2fe9de0d4dda03e51c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 27 Nov 2023 11:48:10 -0700 Subject: [PATCH 032/151] Use editor's overlay implementation --- crates/editor2/src/editor.rs | 35 +++++++++------------------- crates/ui2/src/components/popover.rs | 4 ++-- 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index a15d18f030852101b36dfbe501b399849c19cae4..c713ab085b7e761af4108abb5a106c92c1c5deca 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -39,13 +39,12 @@ use futures::FutureExt; use fuzzy::{StringMatch, StringMatchCandidate}; use git::diff_hunk_to_display; use gpui::{ - actions, div, overlay, point, prelude::*, px, relative, rems, size, uniform_list, Action, - AnyElement, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context, - ElementId, EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, - HighlightStyle, Hsla, InputHandler, InteractiveText, KeyContext, Model, MouseButton, - ParentElement, Pixels, Render, RenderOnce, SharedString, Styled, StyledText, Subscription, - Task, TextRun, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, - WhiteSpace, WindowContext, + actions, div, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement, + AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context, ElementId, + EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle, + Hsla, InputHandler, InteractiveText, KeyContext, Model, MouseButton, ParentElement, Pixels, + Render, RenderOnce, SharedString, Styled, StyledText, Subscription, Task, TextRun, TextStyle, + UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -1369,23 +1368,11 @@ impl CompletionsMenu { .track_scroll(self.scroll_handle.clone()) .with_width_from_item(widest_completion_ix); - // Old: - // Popover::new() - // .child(list) - // .when_some(multiline_docs, |popover, multiline_docs| { - // popover.aside(multiline_docs) - // }) - // .into_any_element() - - overlay() - .anchor(gpui::AnchorCorner::TopLeft) - .child( - Popover::new() - .child(list) - .when_some(multiline_docs, |popover, multiline_docs| { - popover.aside(multiline_docs) - }), - ) + Popover::new() + .child(list) + .when_some(multiline_docs, |popover, multiline_docs| { + popover.aside(multiline_docs) + }) .into_any_element() } diff --git a/crates/ui2/src/components/popover.rs b/crates/ui2/src/components/popover.rs index 925fb30a5e81cbbb4048cee8309dec51458ecd3f..3838e40bec527fb191c6a0e187c6982dcb643c59 100644 --- a/crates/ui2/src/components/popover.rs +++ b/crates/ui2/src/components/popover.rs @@ -1,11 +1,11 @@ use gpui::{ - div, AnyElement, Div, Element, ElementId, IntoElement, ParentElement, RenderOnce, Styled, + AnyElement, Div, Element, ElementId, IntoElement, ParentElement, RenderOnce, Styled, WindowContext, }; use smallvec::SmallVec; use theme2::ActiveTheme; -use crate::{h_stack, v_stack, StyledExt}; +use crate::{v_stack, StyledExt}; /// A popover is used to display a menu or show some options. /// From e31a8f05367857e30795bc4d1ff8cb2d8a9de4d3 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 27 Nov 2023 12:04:08 -0700 Subject: [PATCH 033/151] Scaffolding for update notification too --- crates/auto_update2/src/auto_update.rs | 11 +- .../auto_update2/src/update_notification.rs | 102 ++++++------------ 2 files changed, 43 insertions(+), 70 deletions(-) diff --git a/crates/auto_update2/src/auto_update.rs b/crates/auto_update2/src/auto_update.rs index aeff68965fd07ce7eda4cc0aac9bb8a7aaeb4649..d2eab15d09967a84c5c11004ec70419795e0bf01 100644 --- a/crates/auto_update2/src/auto_update.rs +++ b/crates/auto_update2/src/auto_update.rs @@ -84,8 +84,8 @@ impl Settings for AutoUpdateSetting { pub fn init(http_client: Arc, server_url: String, cx: &mut AppContext) { AutoUpdateSetting::register(cx); - cx.observe_new_views(|wokrspace: &mut Workspace, _cx| { - wokrspace + cx.observe_new_views(|workspace: &mut Workspace, _cx| { + workspace .register_action(|_, action: &Check, cx| check(action, cx)) .register_action(|_, _action: &CheckThatAutoUpdaterWorks, cx| { let prompt = cx.prompt(gpui::PromptLevel::Info, "It does!", &["Ok"]); @@ -94,6 +94,11 @@ pub fn init(http_client: Arc, server_url: String, cx: &mut AppCo }) .detach(); }); + + // @nate - code to trigger update notification on launch + // workspace.show_notification(0, _cx, |cx| { + // cx.build_view(|_| UpdateNotification::new(SemanticVersion::from_str("1.1.1").unwrap())) + // }); }) .detach(); @@ -131,7 +136,7 @@ pub fn check(_: &Check, cx: &mut AppContext) { } } -fn _view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) { +pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) { if let Some(auto_updater) = AutoUpdater::get(cx) { let auto_updater = auto_updater.read(cx); let server_url = &auto_updater.server_url; diff --git a/crates/auto_update2/src/update_notification.rs b/crates/auto_update2/src/update_notification.rs index 9cb1550bd431eb81d7c0e0c8dc4a49655f5d73ce..d15d82e112e612143ef2aaff509965899818e95a 100644 --- a/crates/auto_update2/src/update_notification.rs +++ b/crates/auto_update2/src/update_notification.rs @@ -1,10 +1,12 @@ use gpui::{ - div, DismissEvent, Div, EventEmitter, ParentElement, Render, SemanticVersion, ViewContext, + div, DismissEvent, Div, EventEmitter, InteractiveElement, ParentElement, Render, + SemanticVersion, StatefulInteractiveElement, Styled, ViewContext, }; -use menu::Cancel; +use util::channel::ReleaseChannel; +use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt}; pub struct UpdateNotification { - _version: SemanticVersion, + version: SemanticVersion, } impl EventEmitter for UpdateNotification {} @@ -12,77 +14,43 @@ impl EventEmitter for UpdateNotification {} impl Render for UpdateNotification { type Element = Div; - fn render(&mut self, _cx: &mut gpui::ViewContext) -> Self::Element { - div().child("Updated zed!") - // let theme = theme::current(cx).clone(); - // let theme = &theme.update_notification; - - // let app_name = cx.global::().display_name(); - - // MouseEventHandler::new::(0, cx, |state, cx| { - // Flex::column() - // .with_child( - // Flex::row() - // .with_child( - // Text::new( - // format!("Updated to {app_name} {}", self.version), - // theme.message.text.clone(), - // ) - // .contained() - // .with_style(theme.message.container) - // .aligned() - // .top() - // .left() - // .flex(1., true), - // ) - // .with_child( - // MouseEventHandler::new::(0, cx, |state, _| { - // let style = theme.dismiss_button.style_for(state); - // Svg::new("icons/x.svg") - // .with_color(style.color) - // .constrained() - // .with_width(style.icon_width) - // .aligned() - // .contained() - // .with_style(style.container) - // .constrained() - // .with_width(style.button_width) - // .with_height(style.button_width) - // }) - // .with_padding(Padding::uniform(5.)) - // .on_click(MouseButton::Left, move |_, this, cx| { - // this.dismiss(&Default::default(), cx) - // }) - // .aligned() - // .constrained() - // .with_height(cx.font_cache().line_height(theme.message.text.font_size)) - // .aligned() - // .top() - // .flex_float(), - // ), - // ) - // .with_child({ - // let style = theme.action_message.style_for(state); - // Text::new("View the release notes", style.text.clone()) - // .contained() - // .with_style(style.container) - // }) - // .contained() - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .on_click(MouseButton::Left, |_, _, cx| { - // crate::view_release_notes(&Default::default(), cx) - // }) - // .into_any_named("update notification") + fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { + let app_name = cx.global::().display_name(); + + v_stack() + .elevation_3(cx) + .p_4() + .child( + h_stack() + .justify_between() + .child(Label::new(format!( + "Updated to {app_name} {}", + self.version + ))) + .child( + div() + .id("cancel") + .child(IconElement::new(Icon::Close)) + .cursor_pointer() + .on_click(cx.listener(|this, _, cx| this.dismiss(cx))), + ), + ) + .child( + div() + .id("notes") + .child(Label::new("View the release notes")) + .cursor_pointer() + .on_click(|_, cx| crate::view_release_notes(&Default::default(), cx)), + ) } } impl UpdateNotification { pub fn new(version: SemanticVersion) -> Self { - Self { _version: version } + Self { version } } - pub fn _dismiss(&mut self, _: &Cancel, cx: &mut ViewContext) { + pub fn dismiss(&mut self, cx: &mut ViewContext) { cx.emit(DismissEvent::Dismiss); } } From feb7753a73755b32d348a9655d8fcc3bd651b474 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 27 Nov 2023 11:54:56 -0500 Subject: [PATCH 034/151] Fix typo in `uniform_list`'s doc comment --- crates/gpui2/src/elements/uniform_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/gpui2/src/elements/uniform_list.rs b/crates/gpui2/src/elements/uniform_list.rs index 1ddddf9c783fd5fbca5cdee60a5860f9e2a9c29d..8e61f247bda3b9247c515cd9e2714d30da5b1291 100644 --- a/crates/gpui2/src/elements/uniform_list.rs +++ b/crates/gpui2/src/elements/uniform_list.rs @@ -9,7 +9,7 @@ use taffy::style::Overflow; /// uniform_list provides lazy rendering for a set of items that are of uniform height. /// When rendered into a container with overflow-y: hidden and a fixed (or max) height, -/// uniform_list will only render the visibile subset of items. +/// uniform_list will only render the visible subset of items. pub fn uniform_list( view: View, id: I, From 19ecccb107d2e5ffae4dd99af8b9fdddab8b4273 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 27 Nov 2023 11:55:23 -0500 Subject: [PATCH 035/151] Add `ListItem` story --- crates/storybook2/src/story_selector.rs | 13 +++++----- crates/ui2/src/components/stories.rs | 2 ++ .../ui2/src/components/stories/list_item.rs | 26 +++++++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 crates/ui2/src/components/stories/list_item.rs diff --git a/crates/storybook2/src/story_selector.rs b/crates/storybook2/src/story_selector.rs index 71d7f80a8e44c90a32dd9b2b550c2b8d858f706d..0c4abf9a136a9f1c294b0428cef3e451df4f8f86 100644 --- a/crates/storybook2/src/story_selector.rs +++ b/crates/storybook2/src/story_selector.rs @@ -8,7 +8,6 @@ use clap::ValueEnum; use gpui::{AnyView, VisualContext}; use strum::{EnumIter, EnumString, IntoEnumIterator}; use ui::prelude::*; -use ui::{AvatarStory, ButtonStory, IconStory, InputStory, LabelStory}; #[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)] #[strum(serialize_all = "snake_case")] @@ -22,6 +21,7 @@ pub enum ComponentStory { Input, Keybinding, Label, + ListItem, Scroll, Text, ZIndex, @@ -31,15 +31,16 @@ pub enum ComponentStory { impl ComponentStory { pub fn story(&self, cx: &mut WindowContext) -> AnyView { match self { - Self::Avatar => cx.build_view(|_| AvatarStory).into(), - Self::Button => cx.build_view(|_| ButtonStory).into(), + Self::Avatar => cx.build_view(|_| ui::AvatarStory).into(), + Self::Button => cx.build_view(|_| ui::ButtonStory).into(), Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(), Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into(), Self::Focus => FocusStory::view(cx).into(), - Self::Icon => cx.build_view(|_| IconStory).into(), - Self::Input => cx.build_view(|_| InputStory).into(), + Self::Icon => cx.build_view(|_| ui::IconStory).into(), + Self::Input => cx.build_view(|_| ui::InputStory).into(), Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(), - Self::Label => cx.build_view(|_| LabelStory).into(), + Self::Label => cx.build_view(|_| ui::LabelStory).into(), + Self::ListItem => cx.build_view(|_| ui::ListItemStory).into(), Self::Scroll => ScrollStory::view(cx).into(), Self::Text => TextStory::view(cx).into(), Self::ZIndex => cx.build_view(|_| ZIndexStory).into(), diff --git a/crates/ui2/src/components/stories.rs b/crates/ui2/src/components/stories.rs index 032a3d23d6fcdf96d90b6235321f8b7bc39bcb9a..6211bfff31955583e2405457488876385f13a00a 100644 --- a/crates/ui2/src/components/stories.rs +++ b/crates/ui2/src/components/stories.rs @@ -6,6 +6,7 @@ mod icon; mod input; mod keybinding; mod label; +mod list_item; pub use avatar::*; pub use button::*; @@ -15,3 +16,4 @@ pub use icon::*; pub use input::*; pub use keybinding::*; pub use label::*; +pub use list_item::*; diff --git a/crates/ui2/src/components/stories/list_item.rs b/crates/ui2/src/components/stories/list_item.rs new file mode 100644 index 0000000000000000000000000000000000000000..ee1b4d0be6d274f79191c42542a01ee8c7b77931 --- /dev/null +++ b/crates/ui2/src/components/stories/list_item.rs @@ -0,0 +1,26 @@ +use gpui::{Div, Render}; +use story::Story; + +use crate::prelude::*; +use crate::ListItem; + +pub struct ListItemStory; + +impl Render for ListItemStory { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + Story::container() + .child(Story::title_for::()) + .child(Story::label("Default")) + .child(ListItem::new("hello_world").child("Hello, world!")) + .child(Story::label("With `on_click`")) + .child( + ListItem::new("with_on_click") + .child("Click me") + .on_click(|_event, _cx| { + println!("Clicked!"); + }), + ) + } +} From 6f839a1b480684449fc6b5bc0ce0f7aeb7c04b70 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 27 Nov 2023 13:00:12 -0700 Subject: [PATCH 036/151] Add logged out collab panel --- crates/collab_ui2/src/collab_panel.rs | 91 ++++++++++++------- crates/collab_ui2/src/collab_titlebar_item.rs | 12 ++- 2 files changed, 65 insertions(+), 38 deletions(-) diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index e9c17a6589dd79c73edc9b6e29bea4c76740b20e..c1c966972131ce3e065fb063b85a31be5d74317c 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -167,10 +167,11 @@ use gpui::{ use project::Fs; use serde_derive::{Deserialize, Serialize}; use settings::Settings; -use ui::{h_stack, Avatar, Label}; +use ui::{h_stack, v_stack, Avatar, Button, Label}; use util::ResultExt; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, + notifications::NotifyResultExt, Workspace, }; @@ -302,7 +303,7 @@ pub struct CollabPanel { // entries: Vec, // selection: Option, user_store: Model, - _client: Arc, + client: Arc, // channel_store: ModelHandle, // project: ModelHandle, // match_candidates: Vec, @@ -311,7 +312,7 @@ pub struct CollabPanel { // collapsed_sections: Vec
, // collapsed_channels: Vec, // drag_target_channel: ChannelDragTarget, - _workspace: WeakView, + workspace: WeakView, // context_menu_on_selected: bool, } @@ -604,8 +605,8 @@ impl CollabPanel { // match_candidates: Vec::default(), // collapsed_sections: vec![Section::Offline], // collapsed_channels: Vec::default(), - _workspace: workspace.weak_handle(), - _client: workspace.app_state().client.clone(), + workspace: workspace.weak_handle(), + client: workspace.app_state().client.clone(), // context_menu_on_selected: true, // drag_target_channel: ChannelDragTarget::None, // list_state, @@ -3252,6 +3253,53 @@ impl CollabPanel { // let item = ClipboardItem::new(channel.link()); // cx.write_to_clipboard(item) // } + + fn render_signed_out(&mut self, cx: &mut ViewContext) -> Div { + v_stack().child(Button::new("Sign in to collaborate").on_click(cx.listener( + |this, _, cx| { + let client = this.client.clone(); + cx.spawn(|_, mut cx| async move { + client + .authenticate_and_connect(true, &cx) + .await + .notify_async_err(&mut cx); + }) + .detach() + }, + ))) + } + + fn render_signed_in(&mut self, cx: &mut ViewContext) -> Div { + let contacts = self.contacts(cx).unwrap_or_default(); + let workspace = self.workspace.clone(); + + v_stack().children(contacts.into_iter().map(|contact| { + let id = contact.user.id; + h_stack() + .p_2() + .gap_2() + .children( + contact + .user + .avatar + .as_ref() + .map(|avatar| Avatar::data(avatar.clone())), + ) + .child(Label::new(contact.user.github_login.clone())) + .on_mouse_down(gpui::MouseButton::Left, { + let workspace = workspace.clone(); + move |_, cx| { + workspace + .update(cx, |this, cx| { + this.call_state() + .invite(id, None, cx) + .detach_and_log_err(cx) + }) + .log_err(); + } + }) + })) + } } // fn render_tree_branch( @@ -3303,37 +3351,14 @@ impl Render for CollabPanel { type Element = Focusable
; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - let contacts = self.contacts(cx).unwrap_or_default(); - let workspace = self._workspace.clone(); div() .key_context("CollabPanel") .track_focus(&self.focus_handle) - .children(contacts.into_iter().map(|contact| { - let id = contact.user.id; - h_stack() - .p_2() - .gap_2() - .children( - contact - .user - .avatar - .as_ref() - .map(|avatar| Avatar::data(avatar.clone())), - ) - .child(Label::new(contact.user.github_login.clone())) - .on_mouse_down(gpui::MouseButton::Left, { - let workspace = workspace.clone(); - move |_, cx| { - workspace - .update(cx, |this, cx| { - this.call_state() - .invite(id, None, cx) - .detach_and_log_err(cx) - }) - .log_err(); - } - }) - })) + .child(if self.user_store.read(cx).current_user().is_none() { + self.render_signed_out(cx) + } else { + self.render_signed_in(cx) + }) } } diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index d208eb91f18e7bdeef40ddf58c1f49628d9facb2..2307ba2fcbfb3d67b732cddae0c6305fdb4a92de 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -39,7 +39,7 @@ use project::Project; use theme::ActiveTheme; use ui::{h_stack, Avatar, Button, ButtonVariant, Color, IconButton, KeyBinding, Tooltip}; use util::ResultExt; -use workspace::Workspace; +use workspace::{notifications::NotifyResultExt, Workspace}; use crate::face_pile::FacePile; @@ -290,11 +290,13 @@ impl Render for CollabTitlebarItem { } else { this.child(Button::new("Sign in").on_click(move |_, cx| { let client = client.clone(); - cx.spawn(move |cx| async move { - client.authenticate_and_connect(true, &cx).await?; - Ok::<(), anyhow::Error>(()) + cx.spawn(move |mut cx| async move { + client + .authenticate_and_connect(true, &cx) + .await + .notify_async_err(&mut cx); }) - .detach_and_log_err(cx); + .detach(); })) } }) From 52119ca203c3d6a7b9e7deda382aedc93412117c Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 27 Nov 2023 21:06:06 +0100 Subject: [PATCH 037/151] call: Restore mute_on_join behaviour --- crates/call2/src/room.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index b55d3348dcc37103b2b76e540ce878106d424fa2..e54455a87e5b9e5257c08a754e8fbed6ec25eb23 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -1,4 +1,7 @@ -use crate::participant::{LocalParticipant, ParticipantLocation, RemoteParticipant}; +use crate::{ + call_settings::CallSettings, + participant::{LocalParticipant, ParticipantLocation, RemoteParticipant}, +}; use anyhow::{anyhow, Result}; use audio::{Audio, Sound}; use client::{ @@ -18,6 +21,7 @@ use live_kit_client::{ }; use postage::{sink::Sink, stream::Stream, watch}; use project::Project; +use settings::Settings as _; use std::{future::Future, mem, sync::Arc, time::Duration}; use util::{post_inc, ResultExt, TryFutureExt}; @@ -328,10 +332,9 @@ impl Room { } } - pub fn mute_on_join(_cx: &AppContext) -> bool { + pub fn mute_on_join(cx: &AppContext) -> bool { // todo!() po: This should be uncommented, though then unmuting does not work - false - //CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some() + CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some() } fn from_join_response( From b0d9e3c8fad09e62fc07d3aef9ec75c5b78dd9c9 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 27 Nov 2023 21:44:53 +0100 Subject: [PATCH 038/151] Await toggle of mute --- crates/call2/src/call2.rs | 7 +++++-- crates/call2/src/room.rs | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index 9a89ec7167f776ab2cb96ad60875c69293d6f0d0..7885ef6e3f3b92fc3b6947ce3407b092ee1bb82c 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -660,9 +660,12 @@ impl CallHandler for Call { self.active_call.as_ref().map(|call| { call.0.update(cx, |this, cx| { this.room().map(|room| { - room.update(cx, |this, cx| { - this.toggle_mute(cx).log_err(); + let room = room.clone(); + cx.spawn(|_, mut cx| async move { + room.update(&mut cx, |this, cx| this.toggle_mute(cx))?? + .await }) + .detach_and_log_err(cx); }) }) }); diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index e54455a87e5b9e5257c08a754e8fbed6ec25eb23..d091f801f86140a353e4fec02e76012c32a4763a 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -1268,7 +1268,6 @@ impl Room { .ok_or_else(|| anyhow!("live-kit was not initialized"))? .await }; - let publication = publish_track.await; this.upgrade() .ok_or_else(|| anyhow!("room was dropped"))? From f8614b5909ae973dddc18cacdeb7ab746684cb14 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 27 Nov 2023 21:46:03 +0100 Subject: [PATCH 039/151] fixup! Await toggle of mute --- crates/call2/src/room.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index d091f801f86140a353e4fec02e76012c32a4763a..694966abe9d509f5dc9cad5353a63aac074eaf50 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -333,7 +333,6 @@ impl Room { } pub fn mute_on_join(cx: &AppContext) -> bool { - // todo!() po: This should be uncommented, though then unmuting does not work CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some() } From 1c62abbf7901b31387c5b8eaa7ac789daf8f32b8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 27 Nov 2023 13:16:50 -0800 Subject: [PATCH 040/151] Upgrade Tree-sitter for stack-overflow bugfix --- Cargo.lock | 2 +- Cargo.toml | 3 ++- crates/language/src/highlight_map.rs | 8 ++++---- crates/language/src/language.rs | 4 ++-- crates/language/src/syntax_map/syntax_map_tests.rs | 2 +- crates/language2/src/highlight_map.rs | 8 ++++---- crates/language2/src/language2.rs | 4 ++-- crates/language2/src/syntax_map/syntax_map_tests.rs | 2 +- 8 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90cc0460ff22193eac28119ec9f8ff9e2dbb752c..4b63011f08ebced0e716dec978b01be30d1b8c77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9930,7 +9930,7 @@ dependencies = [ [[package]] name = "tree-sitter" version = "0.20.10" -source = "git+https://github.com/tree-sitter/tree-sitter?rev=35a6052fbcafc5e5fc0f9415b8652be7dcaf7222#35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" +source = "git+https://github.com/tree-sitter/tree-sitter?rev=3b0159d25559b603af566ade3c83d930bf466db1#3b0159d25559b603af566ade3c83d930bf466db1" dependencies = [ "cc", "regex", diff --git a/Cargo.toml b/Cargo.toml index 874b9e8de56eec0e44f3e04c1125d7471e6bd1b8..c5c75eacecfe6febe2da8f022a262a03d1e07559 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -195,8 +195,9 @@ tree-sitter-lua = "0.0.14" tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" } tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"} tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"} + [patch.crates-io] -tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" } +tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "3b0159d25559b603af566ade3c83d930bf466db1" } async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 diff --git a/crates/language/src/highlight_map.rs b/crates/language/src/highlight_map.rs index 109d79cf708218345f0ac8705b770f6c4088a846..cf790e803e0b0ed46b72d394967b6fa329c361c6 100644 --- a/crates/language/src/highlight_map.rs +++ b/crates/language/src/highlight_map.rs @@ -11,7 +11,7 @@ pub struct HighlightId(pub u32); const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX); impl HighlightMap { - pub fn new(capture_names: &[String], theme: &SyntaxTheme) -> Self { + pub fn new(capture_names: &[&str], theme: &SyntaxTheme) -> Self { // For each capture name in the highlight query, find the longest // key in the theme's syntax styles that matches all of the // dot-separated components of the capture name. @@ -98,9 +98,9 @@ mod tests { ); let capture_names = &[ - "function.special".to_string(), - "function.async.rust".to_string(), - "variable.builtin.self".to_string(), + "function.special", + "function.async.rust", + "variable.builtin.self", ]; let map = HighlightMap::new(capture_names, &theme); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 1d22d7773bad2ca8a2bc666957f0dd78ad05a273..af7504529cefbee215fb96e53798242da340a4d6 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1383,7 +1383,7 @@ impl Language { let query = Query::new(self.grammar_mut().ts_language, source)?; let mut override_configs_by_id = HashMap::default(); - for (ix, name) in query.capture_names().iter().enumerate() { + for (ix, name) in query.capture_names().iter().copied().enumerate() { if !name.starts_with('_') { let value = self.config.overrides.remove(name).unwrap_or_default(); for server_name in &value.opt_into_language_servers { @@ -1396,7 +1396,7 @@ impl Language { } } - override_configs_by_id.insert(ix as u32, (name.clone(), value)); + override_configs_by_id.insert(ix as u32, (name.into(), value)); } } diff --git a/crates/language/src/syntax_map/syntax_map_tests.rs b/crates/language/src/syntax_map/syntax_map_tests.rs index bd50608122b80e9dd3ceba0a20d6b29dbb9f07c4..f20f481613eabfffddee791873d78d6383086ade 100644 --- a/crates/language/src/syntax_map/syntax_map_tests.rs +++ b/crates/language/src/syntax_map/syntax_map_tests.rs @@ -1300,7 +1300,7 @@ fn assert_capture_ranges( .collect::>(); for capture in captures { let name = &queries[capture.grammar_index].capture_names()[capture.index as usize]; - if highlight_query_capture_names.contains(&name.as_str()) { + if highlight_query_capture_names.contains(&name) { actual_ranges.push(capture.node.byte_range()); } } diff --git a/crates/language2/src/highlight_map.rs b/crates/language2/src/highlight_map.rs index 1421ef672da935a7d2ff540e85591e4ff7d41be1..8e7a35233cf2e702536241099619cb0bff53459e 100644 --- a/crates/language2/src/highlight_map.rs +++ b/crates/language2/src/highlight_map.rs @@ -11,7 +11,7 @@ pub struct HighlightId(pub u32); const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX); impl HighlightMap { - pub fn new(capture_names: &[String], theme: &SyntaxTheme) -> Self { + pub fn new(capture_names: &[&str], theme: &SyntaxTheme) -> Self { // For each capture name in the highlight query, find the longest // key in the theme's syntax styles that matches all of the // dot-separated components of the capture name. @@ -100,9 +100,9 @@ mod tests { }; let capture_names = &[ - "function.special".to_string(), - "function.async.rust".to_string(), - "variable.builtin.self".to_string(), + "function.special", + "function.async.rust", + "variable.builtin.self", ]; let map = HighlightMap::new(capture_names, &theme); diff --git a/crates/language2/src/language2.rs b/crates/language2/src/language2.rs index 311049f0328dac56df8e04720b7f2c74136234af..5c17592f0ca3ff2a1a352954b79e2ed0a937079e 100644 --- a/crates/language2/src/language2.rs +++ b/crates/language2/src/language2.rs @@ -1391,7 +1391,7 @@ impl Language { let mut override_configs_by_id = HashMap::default(); for (ix, name) in query.capture_names().iter().enumerate() { if !name.starts_with('_') { - let value = self.config.overrides.remove(name).unwrap_or_default(); + let value = self.config.overrides.remove(*name).unwrap_or_default(); for server_name in &value.opt_into_language_servers { if !self .config @@ -1402,7 +1402,7 @@ impl Language { } } - override_configs_by_id.insert(ix as u32, (name.clone(), value)); + override_configs_by_id.insert(ix as u32, (name.to_string(), value)); } } diff --git a/crates/language2/src/syntax_map/syntax_map_tests.rs b/crates/language2/src/syntax_map/syntax_map_tests.rs index bd50608122b80e9dd3ceba0a20d6b29dbb9f07c4..f20f481613eabfffddee791873d78d6383086ade 100644 --- a/crates/language2/src/syntax_map/syntax_map_tests.rs +++ b/crates/language2/src/syntax_map/syntax_map_tests.rs @@ -1300,7 +1300,7 @@ fn assert_capture_ranges( .collect::>(); for capture in captures { let name = &queries[capture.grammar_index].capture_names()[capture.index as usize]; - if highlight_query_capture_names.contains(&name.as_str()) { + if highlight_query_capture_names.contains(&name) { actual_ranges.push(capture.node.byte_range()); } } From 107c3d7f678e166d436a901ac5dcd2499fd05d38 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 27 Nov 2023 14:32:06 -0700 Subject: [PATCH 041/151] Fix esc in command palette Also: add editor.register_action --- crates/editor2/src/editor.rs | 41 +++++-- crates/editor2/src/element.rs | 7 +- crates/go_to_line2/src/go_to_line.rs | 21 ++-- crates/picker2/src/picker2.rs | 1 + crates/project_panel2/src/project_panel.rs | 1 + crates/search2/src/buffer_search.rs | 119 ++++++++++++++------- crates/workspace2/src/modal_layer.rs | 1 + crates/workspace2/src/workspace2.rs | 5 + 8 files changed, 140 insertions(+), 56 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 332035944047c8a416a6cae657d5a7607c272327..39d7d9ed0995a97dcd87426f3e199b441fd915e1 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -40,11 +40,12 @@ use fuzzy::{StringMatch, StringMatchCandidate}; use git::diff_hunk_to_display; use gpui::{ actions, div, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement, - AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context, ElementId, - EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle, - Hsla, InputHandler, InteractiveText, KeyContext, Model, MouseButton, ParentElement, Pixels, - Render, RenderOnce, SharedString, Styled, StyledText, Subscription, Task, TextRun, TextStyle, - UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext, + AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context, + DispatchPhase, Div, ElementId, EventEmitter, FocusHandle, FocusableView, FontFeatures, + FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, InteractiveText, KeyContext, Model, + MouseButton, ParentElement, Pixels, Render, RenderOnce, SharedString, Styled, StyledText, + Subscription, Task, TextRun, TextStyle, UniformListScrollHandle, View, ViewContext, + VisualContext, WeakView, WhiteSpace, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -103,7 +104,7 @@ use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::{ item::{ItemEvent, ItemHandle}, searchable::SearchEvent, - ItemNavHistory, SplitDirection, ViewId, Workspace, + ItemNavHistory, Pane, SplitDirection, ViewId, Workspace, }; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); @@ -662,6 +663,7 @@ pub struct Editor { pixel_position_of_newest_cursor: Option>, gutter_width: Pixels, style: Option, + editor_actions: Vec)>>, } pub struct EditorSnapshot { @@ -1890,6 +1892,7 @@ impl Editor { pixel_position_of_newest_cursor: None, gutter_width: Default::default(), style: None, + editor_actions: Default::default(), _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), cx.subscribe(&buffer, Self::on_buffer_event), @@ -2021,10 +2024,14 @@ impl Editor { &self.buffer } - fn workspace(&self) -> Option> { + pub fn workspace(&self) -> Option> { self.workspace.as_ref()?.0.upgrade() } + pub fn pane(&self, cx: &AppContext) -> Option> { + self.workspace()?.read(cx).pane_for(&self.handle.upgrade()?) + } + pub fn title<'a>(&self, cx: &'a AppContext) -> Cow<'a, str> { self.buffer().read(cx).title(cx) } @@ -9181,6 +9188,26 @@ impl Editor { cx.emit(EditorEvent::Blurred); cx.notify(); } + + pub fn register_action( + &mut self, + listener: impl Fn(&A, &mut WindowContext) + 'static, + ) -> &mut Self { + let listener = Arc::new(listener); + + self.editor_actions.push(Box::new(move |cx| { + let view = cx.view().clone(); + let cx = cx.window_context(); + let listener = listener.clone(); + cx.on_action(TypeId::of::(), move |action, phase, cx| { + let action = action.downcast_ref().unwrap(); + if phase == DispatchPhase::Bubble { + listener(action, cx) + } + }) + })); + self + } } pub trait CollaborationHub { diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 5b510095ff116745d020e5ecb8604d06e9af0a02..6a6afd5461b6abb1b2944635216b5fd8ff5270a2 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -128,6 +128,11 @@ impl EditorElement { fn register_actions(&self, cx: &mut WindowContext) { let view = &self.editor; + self.editor.update(cx, |editor, cx| { + for action in editor.editor_actions.iter() { + (action)(cx) + } + }); register_action(view, cx, Editor::move_left); register_action(view, cx, Editor::move_right); register_action(view, cx, Editor::move_down); @@ -4068,7 +4073,7 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 { // } // } -fn register_action( +pub fn register_action( view: &View, cx: &mut WindowContext, listener: impl Fn(&mut Editor, &T, &mut ViewContext) + 'static, diff --git a/crates/go_to_line2/src/go_to_line.rs b/crates/go_to_line2/src/go_to_line.rs index d1119de9b454ed83eb809fb6a657669ee061dfcb..c734281d22d731b0c55d617c8cf7f58e60077a45 100644 --- a/crates/go_to_line2/src/go_to_line.rs +++ b/crates/go_to_line2/src/go_to_line.rs @@ -8,7 +8,6 @@ use text::{Bias, Point}; use theme::ActiveTheme; use ui::{h_stack, v_stack, Color, Label, StyledExt}; use util::paths::FILE_ROW_COLUMN_DELIMITER; -use workspace::Workspace; actions!(Toggle); @@ -26,22 +25,24 @@ pub struct GoToLine { impl FocusableView for GoToLine { fn focus_handle(&self, cx: &AppContext) -> FocusHandle { - self.active_editor.focus_handle(cx) + self.line_editor.focus_handle(cx) } } impl EventEmitter for GoToLine {} impl GoToLine { - fn register(workspace: &mut Workspace, _: &mut ViewContext) { - workspace.register_action(|workspace, _: &Toggle, cx| { - let Some(editor) = workspace - .active_item(cx) - .and_then(|active_item| active_item.downcast::()) - else { + fn register(editor: &mut Editor, cx: &mut ViewContext) { + let handle = cx.view().downgrade(); + editor.register_action(move |_: &Toggle, cx| { + let Some(editor) = handle.upgrade() else { return; }; - - workspace.toggle_modal(cx, move |cx| GoToLine::new(editor, cx)); + let Some(workspace) = editor.read(cx).workspace() else { + return; + }; + workspace.update(cx, |workspace, cx| { + workspace.toggle_modal(cx, move |cx| GoToLine::new(editor, cx)); + }) }); } diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs index 70a8df21e138e85c6d9cfbc3528fba22667f1405..dc6b77c7c7b21db00cc3cb1b6ae23588e07d6c36 100644 --- a/crates/picker2/src/picker2.rs +++ b/crates/picker2/src/picker2.rs @@ -114,6 +114,7 @@ impl Picker { } fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { + dbg!("canceling!"); self.delegate.dismissed(cx); } diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index d2d30556ebcb1d7864840cb1a72d5e338bd4da58..b0272098702f7006649850c93f57148e557e6115 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -644,6 +644,7 @@ impl ProjectPanel { } fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { + dbg!("odd"); self.edit_state = None; self.update_visible_entries(None, cx); cx.focus(&self.focus_handle); diff --git a/crates/search2/src/buffer_search.rs b/crates/search2/src/buffer_search.rs index ad549256f9e0f277ee9c050a12c39d68f7c202ad..d80d9f5d50750e3d1a4ecfd76b1ccae23a28ae8c 100644 --- a/crates/search2/src/buffer_search.rs +++ b/crates/search2/src/buffer_search.rs @@ -7,12 +7,12 @@ use crate::{ ToggleCaseSensitive, ToggleReplace, ToggleWholeWord, }; use collections::HashMap; -use editor::Editor; +use editor::{Editor, EditorMode}; use futures::channel::oneshot; use gpui::{ actions, div, red, Action, AppContext, Div, EventEmitter, InteractiveElement as _, IntoElement, ParentElement as _, Render, Styled, Subscription, Task, View, ViewContext, VisualContext as _, - WindowContext, + WeakView, WindowContext, }; use project::search::SearchQuery; use serde::Deserialize; @@ -23,7 +23,7 @@ use util::ResultExt; use workspace::{ item::ItemHandle, searchable::{Direction, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle}, - ToolbarItemLocation, ToolbarItemView, Workspace, + ToolbarItemLocation, ToolbarItemView, }; #[derive(PartialEq, Clone, Deserialize, Default, Action)] @@ -38,7 +38,7 @@ pub enum Event { } pub fn init(cx: &mut AppContext) { - cx.observe_new_views(|workspace: &mut Workspace, _| BufferSearchBar::register(workspace)) + cx.observe_new_views(|editor: &mut Editor, cx| BufferSearchBar::register(editor, cx)) .detach(); } @@ -187,6 +187,7 @@ impl Render for BufferSearchBar { }) .on_action(cx.listener(Self::previous_history_query)) .on_action(cx.listener(Self::next_history_query)) + .on_action(cx.listener(Self::dismiss)) .w_full() .p_1() .child( @@ -294,9 +295,19 @@ impl ToolbarItemView for BufferSearchBar { } impl BufferSearchBar { - pub fn register(workspace: &mut Workspace) { - workspace.register_action(|workspace, a: &Deploy, cx| { - workspace.active_pane().update(cx, |this, cx| { + pub fn register(editor: &mut Editor, cx: &mut ViewContext) { + if editor.mode() != EditorMode::Full { + return; + }; + + let handle = cx.view().downgrade(); + + editor.register_action(move |a: &Deploy, cx| { + let Some(pane) = handle.upgrade().and_then(|editor| editor.read(cx).pane(cx)) else { + return; + }; + + 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| { @@ -316,11 +327,16 @@ impl BufferSearchBar { }); }); fn register_action( - workspace: &mut Workspace, + editor: &mut Editor, + handle: WeakView, update: fn(&mut BufferSearchBar, &A, &mut ViewContext), ) { - workspace.register_action(move |workspace, action: &A, cx| { - workspace.active_pane().update(cx, move |this, cx| { + editor.register_action(move |action: &A, cx| { + let Some(pane) = handle.upgrade().and_then(|editor| editor.read(cx).pane(cx)) + else { + return; + }; + pane.update(cx, move |this, cx| { this.toolbar().update(cx, move |this, cx| { if let Some(search_bar) = this.item_of_type::() { search_bar.update(cx, move |this, cx| update(this, action, cx)); @@ -331,49 +347,76 @@ impl BufferSearchBar { }); } - register_action(workspace, |this, action: &ToggleCaseSensitive, cx| { - if this.supported_options().case { - this.toggle_case_sensitive(action, cx); - } - }); - register_action(workspace, |this, action: &ToggleWholeWord, cx| { - if this.supported_options().word { - this.toggle_whole_word(action, cx); - } - }); - register_action(workspace, |this, action: &ToggleReplace, cx| { - if this.supported_options().replacement { - this.toggle_replace(action, cx); - } - }); - register_action(workspace, |this, _: &ActivateRegexMode, cx| { + let handle = cx.view().downgrade(); + register_action( + editor, + handle.clone(), + |this, action: &ToggleCaseSensitive, cx| { + if this.supported_options().case { + this.toggle_case_sensitive(action, cx); + } + }, + ); + register_action( + editor, + handle.clone(), + |this, action: &ToggleWholeWord, cx| { + if this.supported_options().word { + this.toggle_whole_word(action, cx); + } + }, + ); + register_action( + editor, + handle.clone(), + |this, action: &ToggleReplace, cx| { + if this.supported_options().replacement { + this.toggle_replace(action, cx); + } + }, + ); + register_action(editor, handle.clone(), |this, _: &ActivateRegexMode, cx| { if this.supported_options().regex { this.activate_search_mode(SearchMode::Regex, cx); } }); - register_action(workspace, |this, _: &ActivateTextMode, cx| { + register_action(editor, handle.clone(), |this, _: &ActivateTextMode, cx| { this.activate_search_mode(SearchMode::Text, cx); }); - register_action(workspace, |this, action: &CycleMode, cx| { + register_action(editor, handle.clone(), |this, action: &CycleMode, cx| { if this.supported_options().regex { // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting // cycling. this.cycle_mode(action, cx) } }); - register_action(workspace, |this, action: &SelectNextMatch, cx| { - this.select_next_match(action, cx); - }); - register_action(workspace, |this, action: &SelectPrevMatch, cx| { - this.select_prev_match(action, cx); - }); - register_action(workspace, |this, action: &SelectAllMatches, cx| { - this.select_all_matches(action, cx); - }); - register_action(workspace, |this, _: &editor::Cancel, cx| { + register_action( + editor, + handle.clone(), + |this, action: &SelectNextMatch, cx| { + this.select_next_match(action, cx); + }, + ); + register_action( + editor, + handle.clone(), + |this, action: &SelectPrevMatch, cx| { + this.select_prev_match(action, cx); + }, + ); + register_action( + editor, + handle.clone(), + |this, action: &SelectAllMatches, cx| { + this.select_all_matches(action, cx); + }, + ); + register_action(editor, handle.clone(), |this, _: &editor::Cancel, cx| { if !this.dismissed { this.dismiss(&Dismiss, cx); + return; } + cx.propagate(); }); } pub fn new(cx: &mut ViewContext) -> Self { diff --git a/crates/workspace2/src/modal_layer.rs b/crates/workspace2/src/modal_layer.rs index 6b14151e0901b08d2e45fd9f82967a87ddfb1f4c..6d28a6299b9f6cbef4231eda2f2bf5d087a3e9d4 100644 --- a/crates/workspace2/src/modal_layer.rs +++ b/crates/workspace2/src/modal_layer.rs @@ -46,6 +46,7 @@ impl ModalLayer { previous_focus_handle: cx.focused(), focus_handle: cx.focus_handle(), }); + dbg!("focusing"); cx.focus_view(&new_modal); cx.notify(); } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index fbc706c560cfb626af748381a91c445b26d9d2b5..50f8611c4cc645c414ad68c030867ab4b8f07dab 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -2342,6 +2342,11 @@ impl Workspace { &self.active_pane } + pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option> { + let weak_pane = self.panes_by_item.get(&handle.item_id())?; + weak_pane.upgrade() + } + fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext) { self.follower_states.retain(|_, state| { if state.leader_id == peer_id { From 47b4d9942fb4351413a0f869938e96d9dd40cdae Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 27 Nov 2023 15:32:26 -0700 Subject: [PATCH 042/151] Fix panic on quit --- crates/gpui2/src/platform/mac/window.rs | 3 +++ crates/gpui2/src/window.rs | 7 +++++++ crates/zed2/src/zed2.rs | 17 +++++++++++------ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/crates/gpui2/src/platform/mac/window.rs b/crates/gpui2/src/platform/mac/window.rs index bb3a659a62bb998d191d28e31e796e31ca1eb3fe..5b72c10851ff555b08669d8db96e143509e8ad46 100644 --- a/crates/gpui2/src/platform/mac/window.rs +++ b/crates/gpui2/src/platform/mac/window.rs @@ -683,6 +683,9 @@ impl Drop for MacWindow { this.executor .spawn(async move { unsafe { + // todo!() this panic()s when you click the red close button + // unless should_close returns false. + // (luckliy in zed it always returns false) window.close(); } }) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 5d33f0161c687325d77db16d6dc5ec316236ff2c..20561c544368b6b9c41124194c07ada33145c968 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1517,6 +1517,13 @@ impl<'a> WindowContext<'a> { .set_input_handler(Box::new(input_handler)); } } + + pub fn on_window_should_close(&mut self, f: impl Fn(&mut WindowContext) -> bool + 'static) { + let mut this = self.to_async(); + self.window + .platform_window + .on_should_close(Box::new(move || this.update(|_, cx| f(cx)).unwrap_or(true))) + } } impl Context for WindowContext<'_> { diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index 1286594138f2e4b32310dbde972f348c3b9719d2..50998a7fb89afdfd0b052b5e6f45e0c90df081e7 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -166,12 +166,17 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { // vim::observe_keystrokes(cx); - // cx.on_window_should_close(|workspace, cx| { - // if let Some(task) = workspace.close(&Default::default(), cx) { - // task.detach_and_log_err(cx); - // } - // false - // }); + let handle = cx.view().downgrade(); + cx.on_window_should_close(move |cx| { + handle + .update(cx, |workspace, cx| { + if let Some(task) = workspace.close(&Default::default(), cx) { + task.detach_and_log_err(cx); + } + false + }) + .unwrap_or(true) + }); cx.spawn(|workspace_handle, mut cx| async move { let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone()); From 6468fe737ed1ed5fc88f73d5e3177d8b6d8ac8f2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 27 Nov 2023 15:06:20 -0800 Subject: [PATCH 043/151] Use 'any-match?' predicate in elixir embedding query --- crates/semantic_index/src/semantic_index_tests.rs | 4 ++-- crates/zed/src/languages/elixir/embedding.scm | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/semantic_index/src/semantic_index_tests.rs b/crates/semantic_index/src/semantic_index_tests.rs index 2145d1f9e0a2300adece9db33946cbc19ac88381..f4e2c5ea13bcec498d69c14cc6dbab40d775e6b7 100644 --- a/crates/semantic_index/src/semantic_index_tests.rs +++ b/crates/semantic_index/src/semantic_index_tests.rs @@ -1659,13 +1659,13 @@ fn elixir_lang() -> Arc { target: (identifier) @name) operator: "when") ]) - (#match? @name "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp)$")) @item + (#any-match? @name "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp)$")) @item ) (call target: (identifier) @name (arguments (alias) @name) - (#match? @name "^(defmodule|defprotocol)$")) @item + (#any-match? @name "^(defmodule|defprotocol)$")) @item "#, ) .unwrap(), diff --git a/crates/zed/src/languages/elixir/embedding.scm b/crates/zed/src/languages/elixir/embedding.scm index 16ad20746d4b0c8697ff126fcc5150636cb8b794..743ebe4d2fee8f6e6fadbfce3b3b94f54e19b7bb 100644 --- a/crates/zed/src/languages/elixir/embedding.scm +++ b/crates/zed/src/languages/elixir/embedding.scm @@ -18,10 +18,10 @@ target: (identifier) @name) operator: "when") ]) - (#match? @name "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp)$")) @item + (#any-match? @name "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp)$")) @item ) (call target: (identifier) @name (arguments (alias) @name) - (#match? @name "^(defmodule|defprotocol)$")) @item + (#any-match? @name "^(defmodule|defprotocol)$")) @item From 4a25fae51efa030d8ba74903ce86c8daa2c8d93e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 27 Nov 2023 16:22:01 -0700 Subject: [PATCH 044/151] TEMP --- crates/collab_ui2/src/collab_panel.rs | 62 +++++++++++++++++---------- crates/ui2/src/components/list.rs | 16 +++---- 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index c1c966972131ce3e065fb063b85a31be5d74317c..5cce89416558a988dce5754044f710bc1a3394b2 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -155,19 +155,19 @@ actions!( const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel"; -use std::sync::Arc; +use std::{iter::once, sync::Arc}; use client::{Client, Contact, UserStore}; use db::kvp::KEY_VALUE_STORE; use gpui::{ actions, div, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle, - Focusable, FocusableView, InteractiveElement, Model, ParentElement, Render, Styled, View, - ViewContext, VisualContext, WeakView, + Focusable, FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, + RenderOnce, Styled, View, ViewContext, VisualContext, WeakView, }; use project::Fs; use serde_derive::{Deserialize, Serialize}; use settings::Settings; -use ui::{h_stack, v_stack, Avatar, Button, Label}; +use ui::{h_stack, v_stack, Avatar, Button, Icon, IconButton, Label, List, ListHeader}; use util::ResultExt; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, @@ -309,7 +309,7 @@ pub struct CollabPanel { // match_candidates: Vec, // list_state: ListState, // subscriptions: Vec, - // collapsed_sections: Vec
, + collapsed_sections: Vec
, // collapsed_channels: Vec, // drag_target_channel: ChannelDragTarget, workspace: WeakView, @@ -336,16 +336,16 @@ struct SerializedCollabPanel { // Dismissed, // } -// #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)] -// enum Section { -// ActiveCall, -// Channels, -// ChannelInvites, -// ContactRequests, -// Contacts, -// Online, -// Offline, -// } +#[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)] +enum Section { + // ActiveCall, + // Channels, + // ChannelInvites, + // ContactRequests, + Contacts, + // Online, + Offline, +} // #[derive(Clone, Debug)] // enum ListEntry { @@ -603,7 +603,7 @@ impl CollabPanel { // project: workspace.project().clone(), // subscriptions: Vec::default(), // match_candidates: Vec::default(), - // collapsed_sections: vec![Section::Offline], + collapsed_sections: vec![Section::Offline], // collapsed_channels: Vec::default(), workspace: workspace.weak_handle(), client: workspace.app_state().client.clone(), @@ -3269,11 +3269,23 @@ impl CollabPanel { ))) } - fn render_signed_in(&mut self, cx: &mut ViewContext) -> Div { + fn render_signed_in(&mut self, cx: &mut ViewContext) -> List { let contacts = self.contacts(cx).unwrap_or_default(); let workspace = self.workspace.clone(); - v_stack().children(contacts.into_iter().map(|contact| { + let children = once( + ListHeader::new("Contacts") + .right_button( + IconButton::new("add-contact", Icon::Plus).on_click(cx.listener( + |this, _, cx| { + todo!(); + //this.toggle_contact_finder(cx); + }, + )), + ) + .render(cx), + ) + .chain(contacts.into_iter().map(|contact| { let id = contact.user.id; h_stack() .p_2() @@ -3298,7 +3310,9 @@ impl CollabPanel { .log_err(); } }) - })) + })); + + List::new().children(children) } } @@ -3354,10 +3368,12 @@ impl Render for CollabPanel { div() .key_context("CollabPanel") .track_focus(&self.focus_handle) - .child(if self.user_store.read(cx).current_user().is_none() { - self.render_signed_out(cx) - } else { - self.render_signed_in(cx) + .map(|el| { + if self.user_store.read(cx).current_user().is_none() { + el.child(self.render_signed_out(cx)) + } else { + el.child(self.render_signed_in(cx)) + } }) } } diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index 875ab6d97e2032ae8c6be9bb9e0445d99897d099..a674a5084c2ce6b8c7913fda97dbe11d2d40abb0 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -5,7 +5,8 @@ use smallvec::SmallVec; use std::rc::Rc; use crate::{ - disclosure_control, h_stack, v_stack, Avatar, Icon, IconElement, IconSize, Label, Toggle, + disclosure_control, h_stack, v_stack, Avatar, Icon, IconButton, IconElement, IconSize, Label, + Toggle, }; use crate::{prelude::*, GraphicSlot}; @@ -18,8 +19,7 @@ pub enum ListItemVariant { } pub enum ListHeaderMeta { - // TODO: These should be IconButtons - Tools(Vec), + Tools(Vec), // TODO: This should be a button Button(Label), Text(Label), @@ -45,11 +45,7 @@ impl RenderOnce for ListHeader { h_stack() .gap_2() .items_center() - .children(icons.into_iter().map(|i| { - IconElement::new(i) - .color(Color::Muted) - .size(IconSize::Small) - })), + .children(icons.into_iter().map(|i| i.color(Color::Muted))), ), Some(ListHeaderMeta::Button(label)) => div().child(label), Some(ListHeaderMeta::Text(label)) => div().child(label), @@ -113,6 +109,10 @@ impl ListHeader { self } + pub fn right_button(self, button: IconButton) -> Self { + self.meta(Some(ListHeaderMeta::Tools(vec![button]))) + } + pub fn meta(mut self, meta: Option) -> Self { self.meta = meta; self From f3d142b7e9a8110e1554b6bcc45519855544a0fb Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 27 Nov 2023 16:22:27 -0700 Subject: [PATCH 045/151] remove zed1 notification mess --- crates/workspace/src/workspace.rs | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 268c4f2ca0ea8fa26ecc36d04687f4527692a4f1..bea26e402ef41fea9b0acaa7d73a7459110be40a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -56,14 +56,16 @@ use std::{ }; use crate::{ - notifications::{simple_message_notification::MessageNotification, NotificationTracker}, + notifications::NotificationTracker, persistence::model::{ DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace, }, }; use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle}; use lazy_static::lazy_static; -use notifications::{simple_message_notification, NotificationHandle, NotifyResultExt}; +use notifications::{ + simple_message_notification::MessageNotification, NotificationHandle, NotifyResultExt, +}; pub use pane::*; pub use pane_group::*; use persistence::{model::SerializedItem, DB}; @@ -778,20 +780,6 @@ impl Workspace { cx.defer(|this, cx| { this.update_window_title(cx); - - this.show_notification(0, cx, |cx| { - cx.add_view(|_cx| { - simple_message_notification::MessageNotification::new(format!( - "Error: what happens if this message is very very very very very long " - )) - .with_click_message("Click here because!") - }) - }); - this.show_notification(1, cx, |cx| { - cx.add_view(|_cx| { - simple_message_notification::MessageNotification::new(format!("Nope")) - }) - }); }); Workspace { weak_self: weak_handle.clone(), From 979ff70196ddd7286dedf8d2a27ebba3b160a57f Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Mon, 27 Nov 2023 20:12:20 -0500 Subject: [PATCH 046/151] Update popover.rs --- crates/ui2/src/components/popover.rs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/crates/ui2/src/components/popover.rs b/crates/ui2/src/components/popover.rs index 3838e40bec527fb191c6a0e187c6982dcb643c59..d9269b0ac414774e9dae618d082b4e27aeff92d3 100644 --- a/crates/ui2/src/components/popover.rs +++ b/crates/ui2/src/components/popover.rs @@ -1,5 +1,5 @@ use gpui::{ - AnyElement, Div, Element, ElementId, IntoElement, ParentElement, RenderOnce, Styled, + div, AnyElement, Div, Element, ElementId, IntoElement, ParentElement, RenderOnce, Styled, WindowContext, }; use smallvec::SmallVec; @@ -44,23 +44,16 @@ impl RenderOnce for Popover { type Rendered = Div; fn render(self, cx: &mut WindowContext) -> Self::Rendered { - v_stack() - .relative() - .elevation_2(cx) - .p_1() - .children(self.children) + div() + .flex() + .gap_1() + .child(v_stack().elevation_2(cx).px_1().children(self.children)) .when_some(self.aside, |this, aside| { - // TODO: This will statically position the aside to the top right of the popover. - // We should update this to use gpui2::overlay avoid collisions with the window edges. this.child( v_stack() - .top_0() - .left_full() - .ml_1() - .absolute() .elevation_2(cx) .bg(cx.theme().colors().surface_background) - .p_1() + .px_1() .child(aside), ) }) From a760508080c9fe81ef5b8370d2142de086db7871 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 27 Nov 2023 18:46:56 -0800 Subject: [PATCH 047/151] Add uiua and nu languages --- Cargo.lock | 11 +++ Cargo.toml | 1 + crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 13 ++- crates/zed/src/languages/nu.rs | 81 +++++++++++++++++++ crates/zed/src/languages/uiua.rs | 55 +++++++++++++ crates/zed/src/languages/uiua/config.toml | 10 +++ crates/zed/src/languages/uiua/highlights.scm | 50 ++++++++++++ crates/zed/src/languages/uiua/indents.scm | 3 + crates/zed2/Cargo.toml | 1 + crates/zed2/src/languages.rs | 13 ++- crates/zed2/src/languages/nu.rs | 55 +++++++++++++ crates/zed2/src/languages/uiua.rs | 55 +++++++++++++ crates/zed2/src/languages/uiua/config.toml | 10 +++ crates/zed2/src/languages/uiua/highlights.scm | 50 ++++++++++++ crates/zed2/src/languages/uiua/indents.scm | 3 + 16 files changed, 410 insertions(+), 2 deletions(-) create mode 100644 crates/zed/src/languages/nu.rs create mode 100644 crates/zed/src/languages/uiua.rs create mode 100644 crates/zed/src/languages/uiua/config.toml create mode 100644 crates/zed/src/languages/uiua/highlights.scm create mode 100644 crates/zed/src/languages/uiua/indents.scm create mode 100644 crates/zed2/src/languages/nu.rs create mode 100644 crates/zed2/src/languages/uiua.rs create mode 100644 crates/zed2/src/languages/uiua/config.toml create mode 100644 crates/zed2/src/languages/uiua/highlights.scm create mode 100644 crates/zed2/src/languages/uiua/indents.scm diff --git a/Cargo.lock b/Cargo.lock index 4b63011f08ebced0e716dec978b01be30d1b8c77..10a945c52aa2bdf3505f43c144ad66dbf669f37e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10178,6 +10178,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-uiua" +version = "0.3.3" +source = "git+https://github.com/shnarazk/tree-sitter-uiua?rev=9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2#9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-vue" version = "0.0.1" @@ -11616,6 +11625,7 @@ dependencies = [ "tree-sitter-svelte", "tree-sitter-toml", "tree-sitter-typescript", + "tree-sitter-uiua", "tree-sitter-vue", "tree-sitter-yaml", "unindent", @@ -11738,6 +11748,7 @@ dependencies = [ "tree-sitter-svelte", "tree-sitter-toml", "tree-sitter-typescript", + "tree-sitter-uiua", "tree-sitter-vue", "tree-sitter-yaml", "unindent", diff --git a/Cargo.toml b/Cargo.toml index c5c75eacecfe6febe2da8f022a262a03d1e07559..1f6a291a2606e8d41feb18743ae9cc0da142acc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -195,6 +195,7 @@ tree-sitter-lua = "0.0.14" tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" } tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"} tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"} +tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"} [patch.crates-io] tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "3b0159d25559b603af566ade3c83d930bf466db1" } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 3603f1c7fd2b46056af99ec812f25b7922d3e6b0..297785fe9f51de2e8a40b81ebe945d8d781845cf 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -140,6 +140,7 @@ tree-sitter-lua.workspace = true tree-sitter-nix.workspace = true tree-sitter-nu.workspace = true tree-sitter-vue.workspace = true +tree-sitter-uiua.workspace = true url = "2.2" urlencoding = "2.1.2" diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 2398f81c78a8d9cf26c6282694b7353057f59ae9..5ade8cb30289d1b6114158c961d504d75c586cfb 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -17,6 +17,7 @@ mod json; #[cfg(feature = "plugin_runtime")] mod language_plugin; mod lua; +mod nu; mod php; mod python; mod ruby; @@ -24,6 +25,7 @@ mod rust; mod svelte; mod tailwind; mod typescript; +mod uiua; mod vue; mod yaml; @@ -210,12 +212,21 @@ pub fn init( language("elm", tree_sitter_elm::language(), vec![]); language("glsl", tree_sitter_glsl::language(), vec![]); language("nix", tree_sitter_nix::language(), vec![]); - language("nu", tree_sitter_nu::language(), vec![]); + language( + "nu", + tree_sitter_nu::language(), + vec![Arc::new(nu::NuLanguageServer {})], + ); language( "vue", tree_sitter_vue::language(), vec![Arc::new(vue::VueLspAdapter::new(node_runtime))], ); + language( + "uiua", + tree_sitter_uiua::language(), + vec![Arc::new(uiua::UiuaLanguageServer {})], + ); } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/zed/src/languages/nu.rs b/crates/zed/src/languages/nu.rs new file mode 100644 index 0000000000000000000000000000000000000000..16a3b0e4c0a33669306832af8a064d5d185c0089 --- /dev/null +++ b/crates/zed/src/languages/nu.rs @@ -0,0 +1,81 @@ +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use language::{CodeLabel, Language, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; +use std::{any::Any, path::PathBuf, sync::Arc}; + +pub struct NuLanguageServer; + +#[async_trait] +impl LspAdapter for NuLanguageServer { + async fn name(&self) -> LanguageServerName { + LanguageServerName("nu".into()) + } + + fn short_name(&self) -> &'static str { + "nu" + } + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + ) -> Result> { + Ok(Box::new(())) + } + + async fn fetch_server_binary( + &self, + _version: Box, + _container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Result { + Err(anyhow!( + "nu v0.87.0 or greater must be installed and available in your $PATH" + )) + } + + async fn cached_server_binary( + &self, + _: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + Some(LanguageServerBinary { + path: "nu".into(), + arguments: vec!["--lsp".into()], + }) + } + + fn can_be_reinstalled(&self) -> bool { + false + } + + async fn installation_test_binary(&self, _: PathBuf) -> Option { + None + } + + async fn label_for_completion( + &self, + completion: &lsp::CompletionItem, + language: &Arc, + ) -> Option { + return Some(CodeLabel { + runs: language + .highlight_text(&completion.label.clone().into(), 0..completion.label.len()), + text: completion.label.clone(), + filter_range: 0..completion.label.len(), + }); + } + + async fn label_for_symbol( + &self, + name: &str, + _: lsp::SymbolKind, + language: &Arc, + ) -> Option { + Some(CodeLabel { + runs: language.highlight_text(&name.into(), 0..name.len()), + text: name.to_string(), + filter_range: 0..name.len(), + }) + } +} diff --git a/crates/zed/src/languages/uiua.rs b/crates/zed/src/languages/uiua.rs new file mode 100644 index 0000000000000000000000000000000000000000..0efdfdd70d400c79cb21d04102fcc1937b84c800 --- /dev/null +++ b/crates/zed/src/languages/uiua.rs @@ -0,0 +1,55 @@ +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; +use std::{any::Any, path::PathBuf}; + +pub struct UiuaLanguageServer; + +#[async_trait] +impl LspAdapter for UiuaLanguageServer { + async fn name(&self) -> LanguageServerName { + LanguageServerName("uiua".into()) + } + + fn short_name(&self) -> &'static str { + "uiua" + } + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + ) -> Result> { + Ok(Box::new(())) + } + + async fn fetch_server_binary( + &self, + _version: Box, + _container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Result { + Err(anyhow!( + "uiua must be installed and available in your $PATH" + )) + } + + async fn cached_server_binary( + &self, + _: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + Some(LanguageServerBinary { + path: "uiua".into(), + arguments: vec!["lsp".into()], + }) + } + + fn can_be_reinstalled(&self) -> bool { + false + } + + async fn installation_test_binary(&self, _: PathBuf) -> Option { + None + } +} diff --git a/crates/zed/src/languages/uiua/config.toml b/crates/zed/src/languages/uiua/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..87c0d8a9db4e4d93651f3acc764e9662f1ae5cbf --- /dev/null +++ b/crates/zed/src/languages/uiua/config.toml @@ -0,0 +1,10 @@ +name = "Uiua" +path_suffixes = ["ua"] +line_comment = "# " +autoclose_before = ")]}\"" +brackets = [ + { start = "{", end = "}", close = true, newline = false }, + { start = "[", end = "]", close = true, newline = false }, + { start = "(", end = ")", close = true, newline = false }, + { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] }, +] diff --git a/crates/zed/src/languages/uiua/highlights.scm b/crates/zed/src/languages/uiua/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..2c37f404e6767f0ef12c0f964d764daafeb1a9d9 --- /dev/null +++ b/crates/zed/src/languages/uiua/highlights.scm @@ -0,0 +1,50 @@ +[ + (openParen) + (closeParen) + (openCurly) + (closeCurly) + (openBracket) + (closeBracket) +] @punctuation.bracket + +[ + (branchSeparator) + (underscore) +] @constructor +; ] @punctuation.delimiter + +[ (character) ] @constant.character +[ (comment) ] @comment +[ (constant) ] @constant.numeric +[ (identifier) ] @variable +[ (leftArrow) ] @keyword +[ (function) ] @function +[ (modifier1) ] @operator +[ (modifier2) ] @operator +[ (number) ] @constant.numeric +[ (placeHolder) ] @special +[ (otherConstant) ] @string.special +[ (signature) ] @type +[ (system) ] @function.builtin +[ (tripleMinus) ] @module + +; planet +[ + "id" + "identity" + "∘" + "dip" + "⊙" + "gap" + "⋅" +] @tag + +[ + (string) + (multiLineString) +] @string + +; [ +; (deprecated) +; (identifierDeprecated) +; ] @warning diff --git a/crates/zed/src/languages/uiua/indents.scm b/crates/zed/src/languages/uiua/indents.scm new file mode 100644 index 0000000000000000000000000000000000000000..add68c723c4187d2ecdb63d927b8ad662ab2d2bf --- /dev/null +++ b/crates/zed/src/languages/uiua/indents.scm @@ -0,0 +1,3 @@ +[ + (array) +] @indent diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index 3212b6182b7b64fe1d79a2f20097fffd64c603b5..d5cc2d40dc6acb2dde94d33a2a8e480ec29d2b11 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -136,6 +136,7 @@ tree-sitter-lua.workspace = true tree-sitter-nix.workspace = true tree-sitter-nu.workspace = true tree-sitter-vue.workspace = true +tree-sitter-uiua.workspace = true url = "2.2" urlencoding = "2.1.2" diff --git a/crates/zed2/src/languages.rs b/crates/zed2/src/languages.rs index 555f12dd0fdf7b8ec9c90d68da5f65944c704936..129dad8f48cfb145c022785f8aad4faf98b02dfd 100644 --- a/crates/zed2/src/languages.rs +++ b/crates/zed2/src/languages.rs @@ -18,6 +18,7 @@ mod json; #[cfg(feature = "plugin_runtime")] mod language_plugin; mod lua; +mod nu; mod php; mod python; mod ruby; @@ -25,6 +26,7 @@ mod rust; mod svelte; mod tailwind; mod typescript; +mod uiua; mod vue; mod yaml; @@ -211,12 +213,21 @@ pub fn init( language("elm", tree_sitter_elm::language(), vec![]); language("glsl", tree_sitter_glsl::language(), vec![]); language("nix", tree_sitter_nix::language(), vec![]); - language("nu", tree_sitter_nu::language(), vec![]); + language( + "nu", + tree_sitter_nu::language(), + vec![Arc::new(nu::NuLanguageServer {})], + ); language( "vue", tree_sitter_vue::language(), vec![Arc::new(vue::VueLspAdapter::new(node_runtime))], ); + language( + "uiua", + tree_sitter_uiua::language(), + vec![Arc::new(uiua::UiuaLanguageServer {})], + ); } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/zed2/src/languages/nu.rs b/crates/zed2/src/languages/nu.rs new file mode 100644 index 0000000000000000000000000000000000000000..a3631b8471d8f06dc88f0ef9fe8bbf48cb76d52d --- /dev/null +++ b/crates/zed2/src/languages/nu.rs @@ -0,0 +1,55 @@ +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; +use std::{any::Any, path::PathBuf}; + +pub struct NuLanguageServer; + +#[async_trait] +impl LspAdapter for NuLanguageServer { + async fn name(&self) -> LanguageServerName { + LanguageServerName("nu".into()) + } + + fn short_name(&self) -> &'static str { + "nu" + } + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + ) -> Result> { + Ok(Box::new(())) + } + + async fn fetch_server_binary( + &self, + _version: Box, + _container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Result { + Err(anyhow!( + "nu v0.87.0 or greater must be installed and available in your $PATH" + )) + } + + async fn cached_server_binary( + &self, + _: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + Some(LanguageServerBinary { + path: "nu".into(), + arguments: vec!["--lsp".into()], + }) + } + + fn can_be_reinstalled(&self) -> bool { + false + } + + async fn installation_test_binary(&self, _: PathBuf) -> Option { + None + } +} diff --git a/crates/zed2/src/languages/uiua.rs b/crates/zed2/src/languages/uiua.rs new file mode 100644 index 0000000000000000000000000000000000000000..0efdfdd70d400c79cb21d04102fcc1937b84c800 --- /dev/null +++ b/crates/zed2/src/languages/uiua.rs @@ -0,0 +1,55 @@ +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; +use std::{any::Any, path::PathBuf}; + +pub struct UiuaLanguageServer; + +#[async_trait] +impl LspAdapter for UiuaLanguageServer { + async fn name(&self) -> LanguageServerName { + LanguageServerName("uiua".into()) + } + + fn short_name(&self) -> &'static str { + "uiua" + } + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + ) -> Result> { + Ok(Box::new(())) + } + + async fn fetch_server_binary( + &self, + _version: Box, + _container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Result { + Err(anyhow!( + "uiua must be installed and available in your $PATH" + )) + } + + async fn cached_server_binary( + &self, + _: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + Some(LanguageServerBinary { + path: "uiua".into(), + arguments: vec!["lsp".into()], + }) + } + + fn can_be_reinstalled(&self) -> bool { + false + } + + async fn installation_test_binary(&self, _: PathBuf) -> Option { + None + } +} diff --git a/crates/zed2/src/languages/uiua/config.toml b/crates/zed2/src/languages/uiua/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..72fdc910408222e7a6c6e9aa1b48c45b0d7176d6 --- /dev/null +++ b/crates/zed2/src/languages/uiua/config.toml @@ -0,0 +1,10 @@ +name = "Uiua" +path_suffixes = ["ua"] +line_comment = "# " +autoclose_before = ")]}\"" +brackets = [ + { start = "{", end = "}", close = true, newline = false}, + { start = "[", end = "]", close = true, newline = false }, + { start = "(", end = ")", close = true, newline = false }, + { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] }, +] diff --git a/crates/zed2/src/languages/uiua/highlights.scm b/crates/zed2/src/languages/uiua/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..2c37f404e6767f0ef12c0f964d764daafeb1a9d9 --- /dev/null +++ b/crates/zed2/src/languages/uiua/highlights.scm @@ -0,0 +1,50 @@ +[ + (openParen) + (closeParen) + (openCurly) + (closeCurly) + (openBracket) + (closeBracket) +] @punctuation.bracket + +[ + (branchSeparator) + (underscore) +] @constructor +; ] @punctuation.delimiter + +[ (character) ] @constant.character +[ (comment) ] @comment +[ (constant) ] @constant.numeric +[ (identifier) ] @variable +[ (leftArrow) ] @keyword +[ (function) ] @function +[ (modifier1) ] @operator +[ (modifier2) ] @operator +[ (number) ] @constant.numeric +[ (placeHolder) ] @special +[ (otherConstant) ] @string.special +[ (signature) ] @type +[ (system) ] @function.builtin +[ (tripleMinus) ] @module + +; planet +[ + "id" + "identity" + "∘" + "dip" + "⊙" + "gap" + "⋅" +] @tag + +[ + (string) + (multiLineString) +] @string + +; [ +; (deprecated) +; (identifierDeprecated) +; ] @warning diff --git a/crates/zed2/src/languages/uiua/indents.scm b/crates/zed2/src/languages/uiua/indents.scm new file mode 100644 index 0000000000000000000000000000000000000000..add68c723c4187d2ecdb63d927b8ad662ab2d2bf --- /dev/null +++ b/crates/zed2/src/languages/uiua/indents.scm @@ -0,0 +1,3 @@ +[ + (array) +] @indent From 26121713b39549713d36ec58db864e61b54dbb67 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 27 Nov 2023 19:57:55 -0700 Subject: [PATCH 048/151] Show channels and users in the sidebar --- Cargo.lock | 1 + crates/collab_ui2/src/collab_panel.rs | 2606 +++++++++++++------------ crates/zed2/Cargo.toml | 2 +- crates/zed2/src/main.rs | 4 +- 4 files changed, 1315 insertions(+), 1298 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90cc0460ff22193eac28119ec9f8ff9e2dbb752c..7d8fb758bfc729d1e92ea52b9a76d78f7a840521 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11651,6 +11651,7 @@ dependencies = [ "auto_update2", "backtrace", "call2", + "channel2", "chrono", "cli", "client2", diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 5cce89416558a988dce5754044f710bc1a3394b2..0df51b02a2b7bea5246522b414d6550ba8f21743 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -1,3 +1,4 @@ +#![allow(unused)] // mod channel_modal; // mod contact_finder; @@ -155,20 +156,27 @@ actions!( const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel"; -use std::{iter::once, sync::Arc}; +use std::{iter::once, mem, sync::Arc}; -use client::{Client, Contact, UserStore}; +use call::ActiveCall; +use channel::{Channel, ChannelId, ChannelStore}; +use client::{Client, Contact, User, UserStore}; use db::kvp::KEY_VALUE_STORE; +use editor::Editor; +use feature_flags::{ChannelsAlpha, FeatureFlagAppExt}; +use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ actions, div, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, - RenderOnce, Styled, View, ViewContext, VisualContext, WeakView, + RenderOnce, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, }; use project::Fs; use serde_derive::{Deserialize, Serialize}; use settings::Settings; -use ui::{h_stack, v_stack, Avatar, Button, Icon, IconButton, Label, List, ListHeader}; -use util::ResultExt; +use ui::{ + h_stack, v_stack, Avatar, Button, Icon, IconButton, Label, List, ListHeader, ListItem, Tooltip, +}; +use util::{maybe, ResultExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, notifications::NotifyResultExt, @@ -269,26 +277,26 @@ pub fn init(cx: &mut AppContext) { // ); } -// #[derive(Debug)] -// pub enum ChannelEditingState { -// Create { -// location: Option, -// pending_name: Option, -// }, -// Rename { -// location: ChannelId, -// pending_name: Option, -// }, -// } +#[derive(Debug)] +pub enum ChannelEditingState { + Create { + location: Option, + pending_name: Option, + }, + Rename { + location: ChannelId, + pending_name: Option, + }, +} -// impl ChannelEditingState { -// fn pending_name(&self) -> Option<&str> { -// match self { -// ChannelEditingState::Create { pending_name, .. } => pending_name.as_deref(), -// ChannelEditingState::Rename { pending_name, .. } => pending_name.as_deref(), -// } -// } -// } +impl ChannelEditingState { + fn pending_name(&self) -> Option<&str> { + match self { + ChannelEditingState::Create { pending_name, .. } => pending_name.as_deref(), + ChannelEditingState::Rename { pending_name, .. } => pending_name.as_deref(), + } + } +} pub struct CollabPanel { width: Option, @@ -297,20 +305,20 @@ pub struct CollabPanel { // channel_clipboard: Option, // pending_serialization: Task>, // context_menu: ViewHandle, - // filter_editor: ViewHandle, + filter_editor: View, // channel_name_editor: ViewHandle, - // channel_editing_state: Option, - // entries: Vec, + channel_editing_state: Option, + entries: Vec, // selection: Option, + channel_store: Model, user_store: Model, client: Arc, - // channel_store: ModelHandle, // project: ModelHandle, - // match_candidates: Vec, + match_candidates: Vec, // list_state: ListState, - // subscriptions: Vec, + subscriptions: Vec, collapsed_sections: Vec
, - // collapsed_channels: Vec, + collapsed_channels: Vec, // drag_target_channel: ChannelDragTarget, workspace: WeakView, // context_menu_on_selected: bool, @@ -338,56 +346,56 @@ struct SerializedCollabPanel { #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)] enum Section { - // ActiveCall, - // Channels, - // ChannelInvites, - // ContactRequests, + ActiveCall, + Channels, + ChannelInvites, + ContactRequests, Contacts, - // Online, + Online, Offline, } -// #[derive(Clone, Debug)] -// enum ListEntry { -// Header(Section), -// CallParticipant { -// user: Arc, -// peer_id: Option, -// is_pending: bool, -// }, -// ParticipantProject { -// project_id: u64, -// worktree_root_names: Vec, -// host_user_id: u64, -// is_last: bool, -// }, -// ParticipantScreen { -// peer_id: Option, -// is_last: bool, -// }, -// IncomingRequest(Arc), -// OutgoingRequest(Arc), -// ChannelInvite(Arc), -// Channel { -// channel: Arc, -// depth: usize, -// has_children: bool, -// }, -// ChannelNotes { -// channel_id: ChannelId, -// }, -// ChannelChat { -// channel_id: ChannelId, -// }, -// ChannelEditor { -// depth: usize, -// }, -// Contact { -// contact: Arc, -// calling: bool, -// }, -// ContactPlaceholder, -// } +#[derive(Clone, Debug)] +enum ListEntry { + Header(Section), + // CallParticipant { + // user: Arc, + // peer_id: Option, + // is_pending: bool, + // }, + // ParticipantProject { + // project_id: u64, + // worktree_root_names: Vec, + // host_user_id: u64, + // is_last: bool, + // }, + // ParticipantScreen { + // peer_id: Option, + // is_last: bool, + // }, + IncomingRequest(Arc), + OutgoingRequest(Arc), + // ChannelInvite(Arc), + Channel { + channel: Arc, + depth: usize, + has_children: bool, + }, + // ChannelNotes { + // channel_id: ChannelId, + // }, + // ChannelChat { + // channel_id: ChannelId, + // }, + ChannelEditor { + depth: usize, + }, + Contact { + contact: Arc, + calling: bool, + }, + ContactPlaceholder, +} // impl Entity for CollabPanel { // type Event = Event; @@ -398,16 +406,11 @@ impl CollabPanel { cx.build_view(|cx| { // let view_id = cx.view_id(); - // let filter_editor = cx.add_view(|cx| { - // let mut editor = Editor::single_line( - // Some(Arc::new(|theme| { - // theme.collab_panel.user_query_editor.clone() - // })), - // cx, - // ); - // editor.set_placeholder_text("Filter channels, contacts", cx); - // editor - // }); + let filter_editor = cx.build_view(|cx| { + let mut editor = Editor::single_line(cx); + editor.set_placeholder_text("Filter channels, contacts", cx); + editor + }); // cx.subscribe(&filter_editor, |this, _, event, cx| { // if let editor::Event::BufferEdited = event { @@ -586,7 +589,7 @@ impl CollabPanel { // } // }); - let this = Self { + let mut this = Self { width: None, focus_handle: cx.focus_handle(), // channel_clipboard: None, @@ -594,17 +597,17 @@ impl CollabPanel { // pending_serialization: Task::ready(None), // context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)), // channel_name_editor, - // filter_editor, - // entries: Vec::default(), - // channel_editing_state: None, + filter_editor, + entries: Vec::default(), + channel_editing_state: None, // selection: None, + channel_store: ChannelStore::global(cx), user_store: workspace.user_store().clone(), - // channel_store: ChannelStore::global(cx), // project: workspace.project().clone(), - // subscriptions: Vec::default(), - // match_candidates: Vec::default(), + subscriptions: Vec::default(), + match_candidates: Vec::default(), collapsed_sections: vec![Section::Offline], - // collapsed_channels: Vec::default(), + collapsed_channels: Vec::default(), workspace: workspace.weak_handle(), client: workspace.app_state().client.clone(), // context_menu_on_selected: true, @@ -612,7 +615,7 @@ impl CollabPanel { // list_state, }; - // this.update_entries(false, cx); + this.update_entries(false, cx); // // Update the dock position when the setting changes. // let mut old_dock_position = this.position(cx); @@ -629,10 +632,10 @@ impl CollabPanel { // ); // let active_call = ActiveCall::global(cx); - // this.subscriptions - // .push(cx.observe(&this.user_store, |this, _, cx| { - // this.update_entries(true, cx) - // })); + this.subscriptions + .push(cx.observe(&this.user_store, |this, _, cx| { + this.update_entries(true, cx) + })); // this.subscriptions // .push(cx.observe(&this.channel_store, |this, _, cx| { // this.update_entries(true, cx) @@ -721,449 +724,449 @@ impl CollabPanel { // ); // } - // 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); - // let query = self.filter_editor.read(cx).text(cx); - // let executor = cx.background().clone(); - - // let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned()); - // let old_entries = mem::take(&mut self.entries); - // let mut scroll_to_top = false; - - // if let Some(room) = ActiveCall::global(cx).read(cx).room() { - // self.entries.push(ListEntry::Header(Section::ActiveCall)); - // if !old_entries - // .iter() - // .any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall))) - // { - // scroll_to_top = true; - // } - - // if !self.collapsed_sections.contains(&Section::ActiveCall) { - // let room = room.read(cx); - - // if let Some(channel_id) = room.channel_id() { - // self.entries.push(ListEntry::ChannelNotes { channel_id }); - // self.entries.push(ListEntry::ChannelChat { channel_id }) - // } - - // // Populate the active user. - // if let Some(user) = user_store.current_user() { - // self.match_candidates.clear(); - // self.match_candidates.push(StringMatchCandidate { - // id: 0, - // string: user.github_login.clone(), - // char_bag: user.github_login.chars().collect(), - // }); - // let matches = executor.block(match_strings( - // &self.match_candidates, - // &query, - // true, - // usize::MAX, - // &Default::default(), - // executor.clone(), - // )); - // if !matches.is_empty() { - // let user_id = user.id; - // self.entries.push(ListEntry::CallParticipant { - // user, - // peer_id: None, - // is_pending: false, - // }); - // let mut projects = room.local_participant().projects.iter().peekable(); - // while let Some(project) = projects.next() { - // self.entries.push(ListEntry::ParticipantProject { - // project_id: project.id, - // worktree_root_names: project.worktree_root_names.clone(), - // host_user_id: user_id, - // is_last: projects.peek().is_none() && !room.is_screen_sharing(), - // }); - // } - // if room.is_screen_sharing() { - // self.entries.push(ListEntry::ParticipantScreen { - // peer_id: None, - // is_last: true, - // }); - // } - // } - // } - - // // Populate remote participants. - // self.match_candidates.clear(); - // self.match_candidates - // .extend(room.remote_participants().iter().map(|(_, participant)| { - // StringMatchCandidate { - // id: participant.user.id as usize, - // string: participant.user.github_login.clone(), - // char_bag: participant.user.github_login.chars().collect(), - // } - // })); - // let matches = executor.block(match_strings( - // &self.match_candidates, - // &query, - // true, - // usize::MAX, - // &Default::default(), - // executor.clone(), - // )); - // for mat in matches { - // let user_id = mat.candidate_id as u64; - // let participant = &room.remote_participants()[&user_id]; - // self.entries.push(ListEntry::CallParticipant { - // user: participant.user.clone(), - // peer_id: Some(participant.peer_id), - // is_pending: false, - // }); - // let mut projects = participant.projects.iter().peekable(); - // while let Some(project) = projects.next() { - // self.entries.push(ListEntry::ParticipantProject { - // project_id: project.id, - // worktree_root_names: project.worktree_root_names.clone(), - // host_user_id: participant.user.id, - // is_last: projects.peek().is_none() - // && participant.video_tracks.is_empty(), - // }); - // } - // if !participant.video_tracks.is_empty() { - // self.entries.push(ListEntry::ParticipantScreen { - // peer_id: Some(participant.peer_id), - // is_last: true, - // }); - // } - // } - - // // Populate pending participants. - // self.match_candidates.clear(); - // self.match_candidates - // .extend(room.pending_participants().iter().enumerate().map( - // |(id, participant)| StringMatchCandidate { - // id, - // string: participant.github_login.clone(), - // char_bag: participant.github_login.chars().collect(), - // }, - // )); - // let matches = executor.block(match_strings( - // &self.match_candidates, - // &query, - // true, - // usize::MAX, - // &Default::default(), - // executor.clone(), - // )); - // self.entries - // .extend(matches.iter().map(|mat| ListEntry::CallParticipant { - // user: room.pending_participants()[mat.candidate_id].clone(), - // peer_id: None, - // is_pending: true, - // })); - // } - // } - - // let mut request_entries = Vec::new(); - - // if cx.has_flag::() { - // self.entries.push(ListEntry::Header(Section::Channels)); - - // if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { - // self.match_candidates.clear(); - // self.match_candidates - // .extend(channel_store.ordered_channels().enumerate().map( - // |(ix, (_, channel))| StringMatchCandidate { - // id: ix, - // string: channel.name.clone(), - // char_bag: channel.name.chars().collect(), - // }, - // )); - // let matches = executor.block(match_strings( - // &self.match_candidates, - // &query, - // true, - // usize::MAX, - // &Default::default(), - // executor.clone(), - // )); - // if let Some(state) = &self.channel_editing_state { - // if matches!(state, ChannelEditingState::Create { location: None, .. }) { - // self.entries.push(ListEntry::ChannelEditor { depth: 0 }); - // } - // } - // let mut collapse_depth = None; - // for mat in matches { - // let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); - // let depth = channel.parent_path.len(); - - // if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { - // collapse_depth = Some(depth); - // } else if let Some(collapsed_depth) = collapse_depth { - // if depth > collapsed_depth { - // continue; - // } - // if self.is_channel_collapsed(channel.id) { - // collapse_depth = Some(depth); - // } else { - // collapse_depth = None; - // } - // } - - // let has_children = channel_store - // .channel_at_index(mat.candidate_id + 1) - // .map_or(false, |next_channel| { - // next_channel.parent_path.ends_with(&[channel.id]) - // }); - - // match &self.channel_editing_state { - // Some(ChannelEditingState::Create { - // location: parent_id, - // .. - // }) if *parent_id == Some(channel.id) => { - // self.entries.push(ListEntry::Channel { - // channel: channel.clone(), - // depth, - // has_children: false, - // }); - // self.entries - // .push(ListEntry::ChannelEditor { depth: depth + 1 }); - // } - // Some(ChannelEditingState::Rename { - // location: parent_id, - // .. - // }) if parent_id == &channel.id => { - // self.entries.push(ListEntry::ChannelEditor { depth }); - // } - // _ => { - // self.entries.push(ListEntry::Channel { - // channel: channel.clone(), - // depth, - // has_children, - // }); - // } - // } - // } - // } - - // let channel_invites = channel_store.channel_invitations(); - // if !channel_invites.is_empty() { - // self.match_candidates.clear(); - // self.match_candidates - // .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { - // StringMatchCandidate { - // id: ix, - // string: channel.name.clone(), - // char_bag: channel.name.chars().collect(), - // } - // })); - // let matches = executor.block(match_strings( - // &self.match_candidates, - // &query, - // true, - // usize::MAX, - // &Default::default(), - // executor.clone(), - // )); - // request_entries.extend(matches.iter().map(|mat| { - // ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone()) - // })); - - // if !request_entries.is_empty() { - // self.entries - // .push(ListEntry::Header(Section::ChannelInvites)); - // if !self.collapsed_sections.contains(&Section::ChannelInvites) { - // self.entries.append(&mut request_entries); - // } - // } - // } - // } - - // self.entries.push(ListEntry::Header(Section::Contacts)); - - // request_entries.clear(); - // let incoming = user_store.incoming_contact_requests(); - // if !incoming.is_empty() { - // self.match_candidates.clear(); - // self.match_candidates - // .extend( - // incoming - // .iter() - // .enumerate() - // .map(|(ix, user)| StringMatchCandidate { - // id: ix, - // string: user.github_login.clone(), - // char_bag: user.github_login.chars().collect(), - // }), - // ); - // let matches = executor.block(match_strings( - // &self.match_candidates, - // &query, - // true, - // usize::MAX, - // &Default::default(), - // executor.clone(), - // )); - // request_entries.extend( - // matches - // .iter() - // .map(|mat| ListEntry::IncomingRequest(incoming[mat.candidate_id].clone())), - // ); - // } - - // let outgoing = user_store.outgoing_contact_requests(); - // if !outgoing.is_empty() { - // self.match_candidates.clear(); - // self.match_candidates - // .extend( - // outgoing - // .iter() - // .enumerate() - // .map(|(ix, user)| StringMatchCandidate { - // id: ix, - // string: user.github_login.clone(), - // char_bag: user.github_login.chars().collect(), - // }), - // ); - // let matches = executor.block(match_strings( - // &self.match_candidates, - // &query, - // true, - // usize::MAX, - // &Default::default(), - // executor.clone(), - // )); - // request_entries.extend( - // matches - // .iter() - // .map(|mat| ListEntry::OutgoingRequest(outgoing[mat.candidate_id].clone())), - // ); - // } - - // if !request_entries.is_empty() { - // self.entries - // .push(ListEntry::Header(Section::ContactRequests)); - // if !self.collapsed_sections.contains(&Section::ContactRequests) { - // self.entries.append(&mut request_entries); - // } - // } - - // let contacts = user_store.contacts(); - // if !contacts.is_empty() { - // self.match_candidates.clear(); - // self.match_candidates - // .extend( - // contacts - // .iter() - // .enumerate() - // .map(|(ix, contact)| StringMatchCandidate { - // id: ix, - // string: contact.user.github_login.clone(), - // char_bag: contact.user.github_login.chars().collect(), - // }), - // ); - - // let matches = executor.block(match_strings( - // &self.match_candidates, - // &query, - // true, - // usize::MAX, - // &Default::default(), - // executor.clone(), - // )); - - // let (online_contacts, offline_contacts) = matches - // .iter() - // .partition::, _>(|mat| contacts[mat.candidate_id].online); - - // for (matches, section) in [ - // (online_contacts, Section::Online), - // (offline_contacts, Section::Offline), - // ] { - // if !matches.is_empty() { - // self.entries.push(ListEntry::Header(section)); - // if !self.collapsed_sections.contains(§ion) { - // let active_call = &ActiveCall::global(cx).read(cx); - // for mat in matches { - // let contact = &contacts[mat.candidate_id]; - // self.entries.push(ListEntry::Contact { - // contact: contact.clone(), - // calling: active_call.pending_invites().contains(&contact.user.id), - // }); - // } - // } - // } - // } - // } - - // if incoming.is_empty() && outgoing.is_empty() && contacts.is_empty() { - // self.entries.push(ListEntry::ContactPlaceholder); - // } - - // if select_same_item { - // if let Some(prev_selected_entry) = prev_selected_entry { - // self.selection.take(); - // for (ix, entry) in self.entries.iter().enumerate() { - // if *entry == prev_selected_entry { - // self.selection = Some(ix); - // break; - // } - // } - // } - // } else { - // self.selection = self.selection.and_then(|prev_selection| { - // if self.entries.is_empty() { - // None - // } else { - // Some(prev_selection.min(self.entries.len() - 1)) - // } - // }); - // } - - // let old_scroll_top = self.list_state.logical_scroll_top(); + 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); + let query = self.filter_editor.read(cx).text(cx); + let executor = cx.background_executor().clone(); + + // let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned()); + let _old_entries = mem::take(&mut self.entries); + // let mut scroll_to_top = false; + + // if let Some(room) = ActiveCall::global(cx).read(cx).room() { + // self.entries.push(ListEntry::Header(Section::ActiveCall)); + // if !old_entries + // .iter() + // .any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall))) + // { + // scroll_to_top = true; + // } + + // if !self.collapsed_sections.contains(&Section::ActiveCall) { + // let room = room.read(cx); + + // if let Some(channel_id) = room.channel_id() { + // self.entries.push(ListEntry::ChannelNotes { channel_id }); + // self.entries.push(ListEntry::ChannelChat { channel_id }) + // } + + // // Populate the active user. + // if let Some(user) = user_store.current_user() { + // self.match_candidates.clear(); + // self.match_candidates.push(StringMatchCandidate { + // id: 0, + // string: user.github_login.clone(), + // char_bag: user.github_login.chars().collect(), + // }); + // let matches = executor.block(match_strings( + // &self.match_candidates, + // &query, + // true, + // usize::MAX, + // &Default::default(), + // executor.clone(), + // )); + // if !matches.is_empty() { + // let user_id = user.id; + // self.entries.push(ListEntry::CallParticipant { + // user, + // peer_id: None, + // is_pending: false, + // }); + // let mut projects = room.local_participant().projects.iter().peekable(); + // while let Some(project) = projects.next() { + // self.entries.push(ListEntry::ParticipantProject { + // project_id: project.id, + // worktree_root_names: project.worktree_root_names.clone(), + // host_user_id: user_id, + // is_last: projects.peek().is_none() && !room.is_screen_sharing(), + // }); + // } + // if room.is_screen_sharing() { + // self.entries.push(ListEntry::ParticipantScreen { + // peer_id: None, + // is_last: true, + // }); + // } + // } + // } + + // // Populate remote participants. + // self.match_candidates.clear(); + // self.match_candidates + // .extend(room.remote_participants().iter().map(|(_, participant)| { + // StringMatchCandidate { + // id: participant.user.id as usize, + // string: participant.user.github_login.clone(), + // char_bag: participant.user.github_login.chars().collect(), + // } + // })); + // let matches = executor.block(match_strings( + // &self.match_candidates, + // &query, + // true, + // usize::MAX, + // &Default::default(), + // executor.clone(), + // )); + // for mat in matches { + // let user_id = mat.candidate_id as u64; + // let participant = &room.remote_participants()[&user_id]; + // self.entries.push(ListEntry::CallParticipant { + // user: participant.user.clone(), + // peer_id: Some(participant.peer_id), + // is_pending: false, + // }); + // let mut projects = participant.projects.iter().peekable(); + // while let Some(project) = projects.next() { + // self.entries.push(ListEntry::ParticipantProject { + // project_id: project.id, + // worktree_root_names: project.worktree_root_names.clone(), + // host_user_id: participant.user.id, + // is_last: projects.peek().is_none() + // && participant.video_tracks.is_empty(), + // }); + // } + // if !participant.video_tracks.is_empty() { + // self.entries.push(ListEntry::ParticipantScreen { + // peer_id: Some(participant.peer_id), + // is_last: true, + // }); + // } + // } + + // // Populate pending participants. + // self.match_candidates.clear(); + // self.match_candidates + // .extend(room.pending_participants().iter().enumerate().map( + // |(id, participant)| StringMatchCandidate { + // id, + // string: participant.github_login.clone(), + // char_bag: participant.github_login.chars().collect(), + // }, + // )); + // let matches = executor.block(match_strings( + // &self.match_candidates, + // &query, + // true, + // usize::MAX, + // &Default::default(), + // executor.clone(), + // )); + // self.entries + // .extend(matches.iter().map(|mat| ListEntry::CallParticipant { + // user: room.pending_participants()[mat.candidate_id].clone(), + // peer_id: None, + // is_pending: true, + // })); + // } + // } + + let mut request_entries = Vec::new(); + + if cx.has_flag::() { + self.entries.push(ListEntry::Header(Section::Channels)); + + if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { + self.match_candidates.clear(); + self.match_candidates + .extend(channel_store.ordered_channels().enumerate().map( + |(ix, (_, channel))| StringMatchCandidate { + id: ix, + string: channel.name.clone(), + char_bag: channel.name.chars().collect(), + }, + )); + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + if let Some(state) = &self.channel_editing_state { + if matches!(state, ChannelEditingState::Create { location: None, .. }) { + self.entries.push(ListEntry::ChannelEditor { depth: 0 }); + } + } + let mut collapse_depth = None; + for mat in matches { + let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); + let depth = channel.parent_path.len(); + + if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { + collapse_depth = Some(depth); + } else if let Some(collapsed_depth) = collapse_depth { + if depth > collapsed_depth { + continue; + } + if self.is_channel_collapsed(channel.id) { + collapse_depth = Some(depth); + } else { + collapse_depth = None; + } + } - // self.list_state.reset(self.entries.len()); + let has_children = channel_store + .channel_at_index(mat.candidate_id + 1) + .map_or(false, |next_channel| { + next_channel.parent_path.ends_with(&[channel.id]) + }); + + match &self.channel_editing_state { + Some(ChannelEditingState::Create { + location: parent_id, + .. + }) if *parent_id == Some(channel.id) => { + self.entries.push(ListEntry::Channel { + channel: channel.clone(), + depth, + has_children: false, + }); + self.entries + .push(ListEntry::ChannelEditor { depth: depth + 1 }); + } + Some(ChannelEditingState::Rename { + location: parent_id, + .. + }) if parent_id == &channel.id => { + self.entries.push(ListEntry::ChannelEditor { depth }); + } + _ => { + self.entries.push(ListEntry::Channel { + channel: channel.clone(), + depth, + has_children, + }); + } + } + } + } - // if scroll_to_top { - // self.list_state.scroll_to(ListOffset::default()); - // } else { - // // Attempt to maintain the same scroll position. - // if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) { - // let new_scroll_top = self - // .entries - // .iter() - // .position(|entry| entry == old_top_entry) - // .map(|item_ix| ListOffset { - // item_ix, - // offset_in_item: old_scroll_top.offset_in_item, - // }) - // .or_else(|| { - // let entry_after_old_top = old_entries.get(old_scroll_top.item_ix + 1)?; - // let item_ix = self - // .entries - // .iter() - // .position(|entry| entry == entry_after_old_top)?; - // Some(ListOffset { - // item_ix, - // offset_in_item: 0., - // }) - // }) - // .or_else(|| { - // let entry_before_old_top = - // old_entries.get(old_scroll_top.item_ix.saturating_sub(1))?; - // let item_ix = self - // .entries - // .iter() - // .position(|entry| entry == entry_before_old_top)?; - // Some(ListOffset { - // item_ix, - // offset_in_item: 0., - // }) - // }); + // let channel_invites = channel_store.channel_invitations(); + // if !channel_invites.is_empty() { + // self.match_candidates.clear(); + // self.match_candidates + // .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { + // StringMatchCandidate { + // id: ix, + // string: channel.name.clone(), + // char_bag: channel.name.chars().collect(), + // } + // })); + // let matches = executor.block(match_strings( + // &self.match_candidates, + // &query, + // true, + // usize::MAX, + // &Default::default(), + // executor.clone(), + // )); + // request_entries.extend(matches.iter().map(|mat| { + // ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone()) + // })); - // self.list_state - // .scroll_to(new_scroll_top.unwrap_or(old_scroll_top)); - // } - // } + // if !request_entries.is_empty() { + // self.entries + // .push(ListEntry::Header(Section::ChannelInvites)); + // if !self.collapsed_sections.contains(&Section::ChannelInvites) { + // self.entries.append(&mut request_entries); + // } + // } + // } + } + + self.entries.push(ListEntry::Header(Section::Contacts)); + + request_entries.clear(); + let incoming = user_store.incoming_contact_requests(); + if !incoming.is_empty() { + self.match_candidates.clear(); + self.match_candidates + .extend( + incoming + .iter() + .enumerate() + .map(|(ix, user)| StringMatchCandidate { + id: ix, + string: user.github_login.clone(), + char_bag: user.github_login.chars().collect(), + }), + ); + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + request_entries.extend( + matches + .iter() + .map(|mat| ListEntry::IncomingRequest(incoming[mat.candidate_id].clone())), + ); + } + + let outgoing = user_store.outgoing_contact_requests(); + if !outgoing.is_empty() { + self.match_candidates.clear(); + self.match_candidates + .extend( + outgoing + .iter() + .enumerate() + .map(|(ix, user)| StringMatchCandidate { + id: ix, + string: user.github_login.clone(), + char_bag: user.github_login.chars().collect(), + }), + ); + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + request_entries.extend( + matches + .iter() + .map(|mat| ListEntry::OutgoingRequest(outgoing[mat.candidate_id].clone())), + ); + } + + if !request_entries.is_empty() { + self.entries + .push(ListEntry::Header(Section::ContactRequests)); + if !self.collapsed_sections.contains(&Section::ContactRequests) { + self.entries.append(&mut request_entries); + } + } + + let contacts = user_store.contacts(); + if !contacts.is_empty() { + self.match_candidates.clear(); + self.match_candidates + .extend( + contacts + .iter() + .enumerate() + .map(|(ix, contact)| StringMatchCandidate { + id: ix, + string: contact.user.github_login.clone(), + char_bag: contact.user.github_login.chars().collect(), + }), + ); + + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + + let (online_contacts, offline_contacts) = matches + .iter() + .partition::, _>(|mat| contacts[mat.candidate_id].online); + + for (matches, section) in [ + (online_contacts, Section::Online), + (offline_contacts, Section::Offline), + ] { + if !matches.is_empty() { + self.entries.push(ListEntry::Header(section)); + if !self.collapsed_sections.contains(§ion) { + let active_call = &ActiveCall::global(cx).read(cx); + for mat in matches { + let contact = &contacts[mat.candidate_id]; + self.entries.push(ListEntry::Contact { + contact: contact.clone(), + calling: active_call.pending_invites().contains(&contact.user.id), + }); + } + } + } + } + } + + if incoming.is_empty() && outgoing.is_empty() && contacts.is_empty() { + self.entries.push(ListEntry::ContactPlaceholder); + } + + // if select_same_item { + // if let Some(prev_selected_entry) = prev_selected_entry { + // self.selection.take(); + // for (ix, entry) in self.entries.iter().enumerate() { + // if *entry == prev_selected_entry { + // self.selection = Some(ix); + // break; + // } + // } + // } + // } else { + // self.selection = self.selection.and_then(|prev_selection| { + // if self.entries.is_empty() { + // None + // } else { + // Some(prev_selection.min(self.entries.len() - 1)) + // } + // }); + // } + + // let old_scroll_top = self.list_state.logical_scroll_top(); + + // self.list_state.reset(self.entries.len()); + + // if scroll_to_top { + // self.list_state.scroll_to(ListOffset::default()); + // } else { + // // Attempt to maintain the same scroll position. + // if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) { + // let new_scroll_top = self + // .entries + // .iter() + // .position(|entry| entry == old_top_entry) + // .map(|item_ix| ListOffset { + // item_ix, + // offset_in_item: old_scroll_top.offset_in_item, + // }) + // .or_else(|| { + // let entry_after_old_top = old_entries.get(old_scroll_top.item_ix + 1)?; + // let item_ix = self + // .entries + // .iter() + // .position(|entry| entry == entry_after_old_top)?; + // Some(ListOffset { + // item_ix, + // offset_in_item: 0., + // }) + // }) + // .or_else(|| { + // let entry_before_old_top = + // old_entries.get(old_scroll_top.item_ix.saturating_sub(1))?; + // let item_ix = self + // .entries + // .iter() + // .position(|entry| entry == entry_before_old_top)?; + // Some(ListOffset { + // item_ix, + // offset_in_item: 0., + // }) + // }); + + // self.list_state + // .scroll_to(new_scroll_top.unwrap_or(old_scroll_top)); + // } + // } - // cx.notify(); - // } + cx.notify(); + } // fn render_call_participant( // user: &User, @@ -1461,389 +1464,6 @@ impl CollabPanel { // } // } - // fn render_header( - // &self, - // section: Section, - // theme: &theme::Theme, - // is_selected: bool, - // is_collapsed: bool, - // cx: &mut ViewContext, - // ) -> AnyElement { - // enum Header {} - // enum LeaveCallContactList {} - // enum AddChannel {} - - // let tooltip_style = &theme.tooltip; - // let mut channel_link = None; - // let mut channel_tooltip_text = None; - // let mut channel_icon = None; - // let mut is_dragged_over = false; - - // let text = match section { - // Section::ActiveCall => { - // let channel_name = maybe!({ - // let channel_id = ActiveCall::global(cx).read(cx).channel_id(cx)?; - - // let channel = self.channel_store.read(cx).channel_for_id(channel_id)?; - - // channel_link = Some(channel.link()); - // (channel_icon, channel_tooltip_text) = match channel.visibility { - // proto::ChannelVisibility::Public => { - // (Some("icons/public.svg"), Some("Copy public channel link.")) - // } - // proto::ChannelVisibility::Members => { - // (Some("icons/hash.svg"), Some("Copy private channel link.")) - // } - // }; - - // Some(channel.name.as_str()) - // }); - - // if let Some(name) = channel_name { - // Cow::Owned(format!("{}", name)) - // } else { - // Cow::Borrowed("Current Call") - // } - // } - // Section::ContactRequests => Cow::Borrowed("Requests"), - // Section::Contacts => Cow::Borrowed("Contacts"), - // Section::Channels => Cow::Borrowed("Channels"), - // Section::ChannelInvites => Cow::Borrowed("Invites"), - // Section::Online => Cow::Borrowed("Online"), - // Section::Offline => Cow::Borrowed("Offline"), - // }; - - // enum AddContact {} - // let button = match section { - // Section::ActiveCall => channel_link.map(|channel_link| { - // let channel_link_copy = channel_link.clone(); - // MouseEventHandler::new::(0, cx, |state, _| { - // render_icon_button( - // theme - // .collab_panel - // .leave_call_button - // .style_for(is_selected, state), - // "icons/link.svg", - // ) - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .on_click(MouseButton::Left, move |_, _, cx| { - // let item = ClipboardItem::new(channel_link_copy.clone()); - // cx.write_to_clipboard(item) - // }) - // .with_tooltip::( - // 0, - // channel_tooltip_text.unwrap(), - // None, - // tooltip_style.clone(), - // cx, - // ) - // }), - // Section::Contacts => Some( - // MouseEventHandler::new::(0, cx, |state, _| { - // render_icon_button( - // theme - // .collab_panel - // .add_contact_button - // .style_for(is_selected, state), - // "icons/plus.svg", - // ) - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .on_click(MouseButton::Left, |_, this, cx| { - // this.toggle_contact_finder(cx); - // }) - // .with_tooltip::( - // 0, - // "Search for new contact", - // None, - // tooltip_style.clone(), - // cx, - // ), - // ), - // Section::Channels => { - // if cx - // .global::>() - // .currently_dragged::(cx.window()) - // .is_some() - // && self.drag_target_channel == ChannelDragTarget::Root - // { - // is_dragged_over = true; - // } - - // Some( - // MouseEventHandler::new::(0, cx, |state, _| { - // render_icon_button( - // theme - // .collab_panel - // .add_contact_button - // .style_for(is_selected, state), - // "icons/plus.svg", - // ) - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .on_click(MouseButton::Left, |_, this, cx| this.new_root_channel(cx)) - // .with_tooltip::( - // 0, - // "Create a channel", - // None, - // tooltip_style.clone(), - // cx, - // ), - // ) - // } - // _ => None, - // }; - - // let can_collapse = match section { - // Section::ActiveCall | Section::Channels | Section::Contacts => false, - // Section::ChannelInvites - // | Section::ContactRequests - // | Section::Online - // | Section::Offline => true, - // }; - // let icon_size = (&theme.collab_panel).section_icon_size; - // let mut result = MouseEventHandler::new::(section as usize, cx, |state, _| { - // let header_style = if can_collapse { - // theme - // .collab_panel - // .subheader_row - // .in_state(is_selected) - // .style_for(state) - // } else { - // &theme.collab_panel.header_row - // }; - - // Flex::row() - // .with_children(if can_collapse { - // Some( - // Svg::new(if is_collapsed { - // "icons/chevron_right.svg" - // } else { - // "icons/chevron_down.svg" - // }) - // .with_color(header_style.text.color) - // .constrained() - // .with_max_width(icon_size) - // .with_max_height(icon_size) - // .aligned() - // .constrained() - // .with_width(icon_size) - // .contained() - // .with_margin_right( - // theme.collab_panel.contact_username.container.margin.left, - // ), - // ) - // } else if let Some(channel_icon) = channel_icon { - // Some( - // Svg::new(channel_icon) - // .with_color(header_style.text.color) - // .constrained() - // .with_max_width(icon_size) - // .with_max_height(icon_size) - // .aligned() - // .constrained() - // .with_width(icon_size) - // .contained() - // .with_margin_right( - // theme.collab_panel.contact_username.container.margin.left, - // ), - // ) - // } else { - // None - // }) - // .with_child( - // Label::new(text, header_style.text.clone()) - // .aligned() - // .left() - // .flex(1., true), - // ) - // .with_children(button.map(|button| button.aligned().right())) - // .constrained() - // .with_height(theme.collab_panel.row_height) - // .contained() - // .with_style(if is_dragged_over { - // theme.collab_panel.dragged_over_header - // } else { - // header_style.container - // }) - // }); - - // result = result - // .on_move(move |_, this, cx| { - // if cx - // .global::>() - // .currently_dragged::(cx.window()) - // .is_some() - // { - // this.drag_target_channel = ChannelDragTarget::Root; - // cx.notify() - // } - // }) - // .on_up(MouseButton::Left, move |_, this, cx| { - // if let Some((_, dragged_channel)) = cx - // .global::>() - // .currently_dragged::(cx.window()) - // { - // this.channel_store - // .update(cx, |channel_store, cx| { - // channel_store.move_channel(dragged_channel.id, None, cx) - // }) - // .detach_and_log_err(cx) - // } - // }); - - // if can_collapse { - // result = result - // .with_cursor_style(CursorStyle::PointingHand) - // .on_click(MouseButton::Left, move |_, this, cx| { - // if can_collapse { - // this.toggle_section_expanded(section, cx); - // } - // }) - // } - - // result.into_any() - // } - - // fn render_contact( - // contact: &Contact, - // calling: bool, - // project: &ModelHandle, - // theme: &theme::Theme, - // is_selected: bool, - // cx: &mut ViewContext, - // ) -> AnyElement { - // enum ContactTooltip {} - - // let collab_theme = &theme.collab_panel; - // let online = contact.online; - // let busy = contact.busy || calling; - // let user_id = contact.user.id; - // let github_login = contact.user.github_login.clone(); - // let initial_project = project.clone(); - - // let event_handler = - // MouseEventHandler::new::(contact.user.id as usize, cx, |state, cx| { - // Flex::row() - // .with_children(contact.user.avatar.clone().map(|avatar| { - // let status_badge = if contact.online { - // Some( - // Empty::new() - // .collapsed() - // .contained() - // .with_style(if busy { - // collab_theme.contact_status_busy - // } else { - // collab_theme.contact_status_free - // }) - // .aligned(), - // ) - // } else { - // None - // }; - // Stack::new() - // .with_child( - // Image::from_data(avatar) - // .with_style(collab_theme.contact_avatar) - // .aligned() - // .left(), - // ) - // .with_children(status_badge) - // })) - // .with_child( - // Label::new( - // contact.user.github_login.clone(), - // collab_theme.contact_username.text.clone(), - // ) - // .contained() - // .with_style(collab_theme.contact_username.container) - // .aligned() - // .left() - // .flex(1., true), - // ) - // .with_children(if state.hovered() { - // Some( - // MouseEventHandler::new::( - // contact.user.id as usize, - // cx, - // |mouse_state, _| { - // let button_style = - // collab_theme.contact_button.style_for(mouse_state); - // render_icon_button(button_style, "icons/x.svg") - // .aligned() - // .flex_float() - // }, - // ) - // .with_padding(Padding::uniform(2.)) - // .with_cursor_style(CursorStyle::PointingHand) - // .on_click(MouseButton::Left, move |_, this, cx| { - // this.remove_contact(user_id, &github_login, cx); - // }) - // .flex_float(), - // ) - // } else { - // None - // }) - // .with_children(if calling { - // Some( - // Label::new("Calling", collab_theme.calling_indicator.text.clone()) - // .contained() - // .with_style(collab_theme.calling_indicator.container) - // .aligned(), - // ) - // } else { - // None - // }) - // .constrained() - // .with_height(collab_theme.row_height) - // .contained() - // .with_style( - // *collab_theme - // .contact_row - // .in_state(is_selected) - // .style_for(state), - // ) - // }); - - // if online && !busy { - // let room = ActiveCall::global(cx).read(cx).room(); - // let label = if room.is_some() { - // format!("Invite {} to join call", contact.user.github_login) - // } else { - // format!("Call {}", contact.user.github_login) - // }; - - // event_handler - // .on_click(MouseButton::Left, move |_, this, cx| { - // this.call(user_id, Some(initial_project.clone()), cx); - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .with_tooltip::( - // contact.user.id as usize, - // label, - // None, - // theme.tooltip.clone(), - // cx, - // ) - // .into_any() - // } else { - // event_handler - // .with_tooltip::( - // contact.user.id as usize, - // format!( - // "{} is {}", - // contact.user.github_login, - // if busy { "on a call" } else { "offline" } - // ), - // None, - // theme.tooltip.clone(), - // cx, - // ) - // .into_any() - // } - // } - // fn render_contact_placeholder( // &self, // theme: &theme::CollabPanel, @@ -1939,335 +1559,6 @@ impl CollabPanel { // .into_any() // } - // fn render_channel( - // &self, - // channel: &Channel, - // depth: usize, - // theme: &theme::Theme, - // is_selected: bool, - // has_children: bool, - // ix: usize, - // cx: &mut ViewContext, - // ) -> AnyElement { - // let channel_id = channel.id; - // let collab_theme = &theme.collab_panel; - // let is_public = self - // .channel_store - // .read(cx) - // .channel_for_id(channel_id) - // .map(|channel| channel.visibility) - // == Some(proto::ChannelVisibility::Public); - // let other_selected = self.selected_channel().map(|channel| channel.id) == Some(channel.id); - // let disclosed = - // has_children.then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok()); - - // let is_active = maybe!({ - // let call_channel = ActiveCall::global(cx) - // .read(cx) - // .room()? - // .read(cx) - // .channel_id()?; - // Some(call_channel == channel_id) - // }) - // .unwrap_or(false); - - // const FACEPILE_LIMIT: usize = 3; - - // enum ChannelCall {} - // enum ChannelNote {} - // enum NotesTooltip {} - // enum ChatTooltip {} - // enum ChannelTooltip {} - - // let mut is_dragged_over = false; - // if cx - // .global::>() - // .currently_dragged::(cx.window()) - // .is_some() - // && self.drag_target_channel == ChannelDragTarget::Channel(channel_id) - // { - // is_dragged_over = true; - // } - - // let has_messages_notification = channel.unseen_message_id.is_some(); - - // MouseEventHandler::new::(ix, cx, |state, cx| { - // let row_hovered = state.hovered(); - - // let mut select_state = |interactive: &Interactive| { - // if state.clicked() == Some(MouseButton::Left) && interactive.clicked.is_some() { - // interactive.clicked.as_ref().unwrap().clone() - // } else if state.hovered() || other_selected { - // interactive - // .hovered - // .as_ref() - // .unwrap_or(&interactive.default) - // .clone() - // } else { - // interactive.default.clone() - // } - // }; - - // Flex::::row() - // .with_child( - // Svg::new(if is_public { - // "icons/public.svg" - // } else { - // "icons/hash.svg" - // }) - // .with_color(collab_theme.channel_hash.color) - // .constrained() - // .with_width(collab_theme.channel_hash.width) - // .aligned() - // .left(), - // ) - // .with_child({ - // let style = collab_theme.channel_name.inactive_state(); - // Flex::row() - // .with_child( - // Label::new(channel.name.clone(), style.text.clone()) - // .contained() - // .with_style(style.container) - // .aligned() - // .left() - // .with_tooltip::( - // ix, - // "Join channel", - // None, - // theme.tooltip.clone(), - // cx, - // ), - // ) - // .with_children({ - // let participants = - // self.channel_store.read(cx).channel_participants(channel_id); - - // if !participants.is_empty() { - // let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT); - - // let result = FacePile::new(collab_theme.face_overlap) - // .with_children( - // participants - // .iter() - // .filter_map(|user| { - // Some( - // Image::from_data(user.avatar.clone()?) - // .with_style(collab_theme.channel_avatar), - // ) - // }) - // .take(FACEPILE_LIMIT), - // ) - // .with_children((extra_count > 0).then(|| { - // Label::new( - // format!("+{}", extra_count), - // collab_theme.extra_participant_label.text.clone(), - // ) - // .contained() - // .with_style(collab_theme.extra_participant_label.container) - // })); - - // Some(result) - // } else { - // None - // } - // }) - // .with_spacing(8.) - // .align_children_center() - // .flex(1., true) - // }) - // .with_child( - // MouseEventHandler::new::(ix, cx, move |mouse_state, _| { - // let container_style = collab_theme - // .disclosure - // .button - // .style_for(mouse_state) - // .container; - - // if channel.unseen_message_id.is_some() { - // Svg::new("icons/conversations.svg") - // .with_color(collab_theme.channel_note_active_color) - // .constrained() - // .with_width(collab_theme.channel_hash.width) - // .contained() - // .with_style(container_style) - // .with_uniform_padding(4.) - // .into_any() - // } else if row_hovered { - // Svg::new("icons/conversations.svg") - // .with_color(collab_theme.channel_hash.color) - // .constrained() - // .with_width(collab_theme.channel_hash.width) - // .contained() - // .with_style(container_style) - // .with_uniform_padding(4.) - // .into_any() - // } else { - // Empty::new().into_any() - // } - // }) - // .on_click(MouseButton::Left, move |_, this, cx| { - // this.join_channel_chat(&JoinChannelChat { channel_id }, cx); - // }) - // .with_tooltip::( - // ix, - // "Open channel chat", - // None, - // theme.tooltip.clone(), - // cx, - // ) - // .contained() - // .with_margin_right(4.), - // ) - // .with_child( - // MouseEventHandler::new::(ix, cx, move |mouse_state, cx| { - // let container_style = collab_theme - // .disclosure - // .button - // .style_for(mouse_state) - // .container; - // if row_hovered || channel.unseen_note_version.is_some() { - // Svg::new("icons/file.svg") - // .with_color(if channel.unseen_note_version.is_some() { - // collab_theme.channel_note_active_color - // } else { - // collab_theme.channel_hash.color - // }) - // .constrained() - // .with_width(collab_theme.channel_hash.width) - // .contained() - // .with_style(container_style) - // .with_uniform_padding(4.) - // .with_margin_right(collab_theme.channel_hash.container.margin.left) - // .with_tooltip::( - // ix as usize, - // "Open channel notes", - // None, - // theme.tooltip.clone(), - // cx, - // ) - // .into_any() - // } else if has_messages_notification { - // Empty::new() - // .constrained() - // .with_width(collab_theme.channel_hash.width) - // .contained() - // .with_uniform_padding(4.) - // .with_margin_right(collab_theme.channel_hash.container.margin.left) - // .into_any() - // } else { - // Empty::new().into_any() - // } - // }) - // .on_click(MouseButton::Left, move |_, this, cx| { - // this.open_channel_notes(&OpenChannelNotes { channel_id }, cx); - // }), - // ) - // .align_children_center() - // .styleable_component() - // .disclosable( - // disclosed, - // Box::new(ToggleCollapse { - // location: channel.id.clone(), - // }), - // ) - // .with_id(ix) - // .with_style(collab_theme.disclosure.clone()) - // .element() - // .constrained() - // .with_height(collab_theme.row_height) - // .contained() - // .with_style(select_state( - // collab_theme - // .channel_row - // .in_state(is_selected || is_active || is_dragged_over), - // )) - // .with_padding_left( - // collab_theme.channel_row.default_style().padding.left - // + collab_theme.channel_indent * depth as f32, - // ) - // }) - // .on_click(MouseButton::Left, move |_, this, cx| { - // if this.drag_target_channel == ChannelDragTarget::None { - // if is_active { - // this.open_channel_notes(&OpenChannelNotes { channel_id }, cx) - // } else { - // this.join_channel(channel_id, cx) - // } - // } - // }) - // .on_click(MouseButton::Right, { - // let channel = channel.clone(); - // move |e, this, cx| { - // this.deploy_channel_context_menu(Some(e.position), &channel, ix, cx); - // } - // }) - // .on_up(MouseButton::Left, move |_, this, cx| { - // if let Some((_, dragged_channel)) = cx - // .global::>() - // .currently_dragged::(cx.window()) - // { - // this.channel_store - // .update(cx, |channel_store, cx| { - // channel_store.move_channel(dragged_channel.id, Some(channel_id), cx) - // }) - // .detach_and_log_err(cx) - // } - // }) - // .on_move({ - // let channel = channel.clone(); - // move |_, this, cx| { - // if let Some((_, dragged_channel)) = cx - // .global::>() - // .currently_dragged::(cx.window()) - // { - // if channel.id != dragged_channel.id { - // this.drag_target_channel = ChannelDragTarget::Channel(channel.id); - // } - // cx.notify() - // } - // } - // }) - // .as_draggable::<_, Channel>( - // channel.clone(), - // move |_, channel, cx: &mut ViewContext| { - // let theme = &theme::current(cx).collab_panel; - - // Flex::::row() - // .with_child( - // Svg::new("icons/hash.svg") - // .with_color(theme.channel_hash.color) - // .constrained() - // .with_width(theme.channel_hash.width) - // .aligned() - // .left(), - // ) - // .with_child( - // Label::new(channel.name.clone(), theme.channel_name.text.clone()) - // .contained() - // .with_style(theme.channel_name.container) - // .aligned() - // .left(), - // ) - // .align_children_center() - // .contained() - // .with_background_color( - // theme - // .container - // .background_color - // .unwrap_or(gpui::color::Color::transparent_black()), - // ) - // .contained() - // .with_padding_left( - // theme.channel_row.default_style().padding.left - // + theme.channel_indent * depth as f32, - // ) - // .into_any() - // }, - // ) - // .with_cursor_style(CursorStyle::PointingHand) - // .into_any() - // } - // fn render_channel_notes( // &self, // channel_id: ChannelId, @@ -2954,9 +2245,9 @@ impl CollabPanel { // cx.focus_self(); // } - // fn is_channel_collapsed(&self, channel_id: ChannelId) -> bool { - // self.collapsed_channels.binary_search(&channel_id).is_ok() - // } + fn is_channel_collapsed(&self, channel_id: ChannelId) -> bool { + self.collapsed_channels.binary_search(&channel_id).is_ok() + } // fn leave_call(cx: &mut ViewContext) { // ActiveCall::global(cx) @@ -3270,49 +2561,774 @@ impl CollabPanel { } fn render_signed_in(&mut self, cx: &mut ViewContext) -> List { - let contacts = self.contacts(cx).unwrap_or_default(); - let workspace = self.workspace.clone(); - - let children = once( - ListHeader::new("Contacts") - .right_button( - IconButton::new("add-contact", Icon::Plus).on_click(cx.listener( - |this, _, cx| { - todo!(); - //this.toggle_contact_finder(cx); - }, - )), - ) - .render(cx), - ) - .chain(contacts.into_iter().map(|contact| { - let id = contact.user.id; - h_stack() - .p_2() - .gap_2() - .children( - contact - .user - .avatar - .as_ref() - .map(|avatar| Avatar::data(avatar.clone())), + let is_selected = false; // todo!() this.selection == Some(ix); + + List::new().children(self.entries.clone().into_iter().map(|entry| { + match entry { + ListEntry::Header(section) => { + let is_collapsed = self.collapsed_sections.contains(§ion); + 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, cx) + .into_any_element(), + ListEntry::ChannelEditor { depth } => todo!(), + } + })) + } + + fn render_header( + &mut self, + section: Section, + is_selected: bool, + is_collapsed: bool, + cx: &ViewContext, + ) -> impl IntoElement { + // let mut channel_link = None; + // let mut channel_tooltip_text = None; + // let mut channel_icon = None; + // let mut is_dragged_over = false; + + let text = match section { + Section::ActiveCall => { + // let channel_name = maybe!({ + // let channel_id = ActiveCall::global(cx).read(cx).channel_id(cx)?; + + // let channel = self.channel_store.read(cx).channel_for_id(channel_id)?; + + // channel_link = Some(channel.link()); + // (channel_icon, channel_tooltip_text) = match channel.visibility { + // proto::ChannelVisibility::Public => { + // (Some("icons/public.svg"), Some("Copy public channel link.")) + // } + // proto::ChannelVisibility::Members => { + // (Some("icons/hash.svg"), Some("Copy private channel link.")) + // } + // }; + + // Some(channel.name.as_str()) + // }); + + // if let Some(name) = channel_name { + // SharedString::from(format!("{}", name)) + // } else { + // SharedString::from("Current Call") + // } + todo!() + } + Section::ContactRequests => SharedString::from("Requests"), + Section::Contacts => SharedString::from("Contacts"), + Section::Channels => SharedString::from("Channels"), + Section::ChannelInvites => SharedString::from("Invites"), + Section::Online => SharedString::from("Online"), + Section::Offline => SharedString::from("Offline"), + }; + + let button = match section { + Section::ActiveCall => + // channel_link.map(|channel_link| { + // let channel_link_copy = channel_link.clone(); + // MouseEventHandler::new::(0, cx, |state, _| { + // render_icon_button( + // theme + // .collab_panel + // .leave_call_button + // .style_for(is_selected, state), + // "icons/link.svg", + // ) + // }) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_click(MouseButton::Left, move |_, _, cx| { + // let item = ClipboardItem::new(channel_link_copy.clone()); + // cx.write_to_clipboard(item) + // }) + // .with_tooltip::( + // 0, + // channel_tooltip_text.unwrap(), + // None, + // tooltip_style.clone(), + // cx, + // ) + // }), + { + todo!() + } + Section::Contacts => Some( + IconButton::new("add-contact", Icon::Plus) + .on_click(cx.listener(|this, _, cx| { + todo!() + // this.toggle_contact_finder(cx) + })) + .tooltip(|cx| Tooltip::text("Search for new contact", cx)), + ), + Section::Channels => { + // todo!() + // if cx + // .global::>() + // .currently_dragged::(cx.window()) + // .is_some() + // && self.drag_target_channel == ChannelDragTarget::Root + // { + // is_dragged_over = true; + // } + + Some( + IconButton::new("add-channel", Icon::Plus) + .on_click(cx.listener(|this, _, cx| { + todo!() + // this.new_root_channel(cx) + })) + .tooltip(|cx| Tooltip::text("Create a channel", cx)), ) - .child(Label::new(contact.user.github_login.clone())) - .on_mouse_down(gpui::MouseButton::Left, { - let workspace = workspace.clone(); - move |_, cx| { - workspace - .update(cx, |this, cx| { - this.call_state() - .invite(id, None, cx) - .detach_and_log_err(cx) - }) - .log_err(); - } - }) - })); + } + _ => None, + }; + + let can_collapse = match section { + Section::ActiveCall | Section::Channels | Section::Contacts => false, + Section::ChannelInvites + | Section::ContactRequests + | Section::Online + | Section::Offline => true, + }; + + let mut header = ListHeader::new(text); + if let Some(button) = button { + header = header.right_button(button) + } + // todo!() is selected + if can_collapse { + // todo!() on click to toggle + header = header.toggle(ui::Toggle::Toggled(is_collapsed)); + } + + header + } + fn render_contact( + &mut self, + contact: &Contact, + calling: bool, + is_selected: bool, + cx: &mut ViewContext, + ) -> impl IntoElement { + enum ContactTooltip {} + + let online = contact.online; + let busy = contact.busy || calling; + let user_id = contact.user.id; + let github_login = SharedString::from(contact.user.github_login.clone()); + + let item = ListItem::new(github_login.clone()) + .child(Label::new(github_login.clone())) + .on_click(cx.listener(|this, _, cx| { + todo!(); + })); + + // let event_handler = + // MouseEventHandler::new::(contact.user.id as usize, cx, |state, cx| { + // Flex::row() + // .with_children(contact.user.avatar.clone().map(|avatar| { + // let status_badge = if contact.online { + // Some( + // Empty::new() + // .collapsed() + // .contained() + // .with_style(if busy { + // collab_theme.contact_status_busy + // } else { + // collab_theme.contact_status_free + // }) + // .aligned(), + // ) + // } else { + // None + // }; + // Stack::new() + // .with_child( + // Image::from_data(avatar) + // .with_style(collab_theme.contact_avatar) + // .aligned() + // .left(), + // ) + // .with_children(status_badge) + // })) + // .with_child( + // Label::new( + // contact.user.github_login.clone(), + // collab_theme.contact_username.text.clone(), + // ) + // .contained() + // .with_style(collab_theme.contact_username.container) + // .aligned() + // .left() + // .flex(1., true), + // ) + // .with_children(if state.hovered() { + // Some( + // MouseEventHandler::new::( + // contact.user.id as usize, + // cx, + // |mouse_state, _| { + // let button_style = + // collab_theme.contact_button.style_for(mouse_state); + // render_icon_button(button_style, "icons/x.svg") + // .aligned() + // .flex_float() + // }, + // ) + // .with_padding(Padding::uniform(2.)) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_click(MouseButton::Left, move |_, this, cx| { + // this.remove_contact(user_id, &github_login, cx); + // }) + // .flex_float(), + // ) + // } else { + // None + // }) + // .with_children(if calling { + // Some( + // Label::new("Calling", collab_theme.calling_indicator.text.clone()) + // .contained() + // .with_style(collab_theme.calling_indicator.container) + // .aligned(), + // ) + // } else { + // None + // }) + // .constrained() + // .with_height(collab_theme.row_height) + // .contained() + // .with_style( + // *collab_theme + // .contact_row + // .in_state(is_selected) + // .style_for(state), + // ) + // }); + + // if online && !busy { + // let room = ActiveCall::global(cx).read(cx).room(); + // let label = if room.is_some() { + // format!("Invite {} to join call", contact.user.github_login) + // } else { + // format!("Call {}", contact.user.github_login) + // }; + + // event_handler + // .on_click(MouseButton::Left, move |_, this, cx| { + // this.call(user_id, Some(initial_project.clone()), cx); + // }) + // .with_cursor_style(CursorStyle::PointingHand) + // .with_tooltip::( + // contact.user.id as usize, + // label, + // None, + // theme.tooltip.clone(), + // cx, + // ) + // .into_any() + // } else { + // event_handler + // .with_tooltip::( + // contact.user.id as usize, + // format!( + // "{} is {}", + // contact.user.github_login, + // if busy { "on a call" } else { "offline" } + // ), + // None, + // theme.tooltip.clone(), + // cx, + // ) + // .into_any() + // }; + + item + } + + fn render_contact_request( + &mut self, + user: Arc, + is_incoming: bool, + is_selected: bool, + cx: &mut ViewContext, + ) -> impl IntoElement { + let github_login = SharedString::from(user.github_login.clone()); + + let mut row = ListItem::new(github_login.clone()).child(Label::new(github_login.clone())); + + // .with_children(user.avatar.clone().map(|avatar| { + // Image::from_data(avatar) + // .with_style(theme.contact_avatar) + // .aligned() + // .left() + // })) + // .with_child( + // Label::new( + // user.github_login.clone(), + // theme.contact_username.text.clone(), + // ) + // .contained() + // .with_style(theme.contact_username.container) + // .aligned() + // .left() + // .flex(1., true), + // ); + + // let user_id = user.id; + // let github_login = user.github_login.clone(); + // let is_contact_request_pending = user_store.read(cx).is_contact_request_pending(&user); + // let button_spacing = theme.contact_button_spacing; + + // if is_incoming { + // row.add_child( + // MouseEventHandler::new::(user.id as usize, cx, |mouse_state, _| { + // let button_style = if is_contact_request_pending { + // &theme.disabled_button + // } else { + // theme.contact_button.style_for(mouse_state) + // }; + // render_icon_button(button_style, "icons/x.svg").aligned() + // }) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_click(MouseButton::Left, move |_, this, cx| { + // this.respond_to_contact_request(user_id, false, cx); + // }) + // .contained() + // .with_margin_right(button_spacing), + // ); + + // row.add_child( + // MouseEventHandler::new::(user.id as usize, cx, |mouse_state, _| { + // let button_style = if is_contact_request_pending { + // &theme.disabled_button + // } else { + // theme.contact_button.style_for(mouse_state) + // }; + // render_icon_button(button_style, "icons/check.svg") + // .aligned() + // .flex_float() + // }) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_click(MouseButton::Left, move |_, this, cx| { + // this.respond_to_contact_request(user_id, true, cx); + // }), + // ); + // } else { + // row.add_child( + // MouseEventHandler::new::(user.id as usize, cx, |mouse_state, _| { + // let button_style = if is_contact_request_pending { + // &theme.disabled_button + // } else { + // theme.contact_button.style_for(mouse_state) + // }; + // render_icon_button(button_style, "icons/x.svg") + // .aligned() + // .flex_float() + // }) + // .with_padding(Padding::uniform(2.)) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_click(MouseButton::Left, move |_, this, cx| { + // this.remove_contact(user_id, &github_login, cx); + // }) + // .flex_float(), + // ); + // } + + // row.constrained() + // .with_height(theme.row_height) + // .contained() + // .with_style( + // *theme + // .contact_row + // .in_state(is_selected) + // .style_for(&mut Default::default()), + // ) + // .into_any() + row + } + + fn render_contact_placeholder( + &self, + is_selected: bool, + cx: &mut ViewContext, + ) -> impl IntoElement { + ListItem::new("contact-placeholder") + .child(Label::new("Add a Contact")) + .on_click(cx.listener(|this, _, cx| todo!())) + // enum AddContacts {} + // MouseEventHandler::new::(0, cx, |state, _| { + // let style = theme.list_empty_state.style_for(is_selected, state); + // Flex::row() + // .with_child( + // Svg::new("icons/plus.svg") + // .with_color(theme.list_empty_icon.color) + // .constrained() + // .with_width(theme.list_empty_icon.width) + // .aligned() + // .left(), + // ) + // .with_child( + // Label::new("Add a contact", style.text.clone()) + // .contained() + // .with_style(theme.list_empty_label_container), + // ) + // .align_children_center() + // .contained() + // .with_style(style.container) + // .into_any() + // }) + // .on_click(MouseButton::Left, |_, this, cx| { + // this.toggle_contact_finder(cx); + // }) + // .into_any() + } - List::new().children(children) + fn render_channel( + &self, + channel: &Channel, + depth: usize, + has_children: bool, + is_selected: bool, + cx: &mut ViewContext, + ) -> impl IntoElement { + let channel_id = channel.id; + ListItem::new(channel_id as usize).child(Label::new(channel.name.clone())) + // let channel_id = channel.id; + // let collab_theme = &theme.collab_panel; + // let is_public = self + // .channel_store + // .read(cx) + // .channel_for_id(channel_id) + // .map(|channel| channel.visibility) + // == Some(proto::ChannelVisibility::Public); + // let other_selected = self.selected_channel().map(|channel| channel.id) == Some(channel.id); + // let disclosed = + // has_children.then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok()); + + // let is_active = maybe!({ + // let call_channel = ActiveCall::global(cx) + // .read(cx) + // .room()? + // .read(cx) + // .channel_id()?; + // Some(call_channel == channel_id) + // }) + // .unwrap_or(false); + + // const FACEPILE_LIMIT: usize = 3; + + // enum ChannelCall {} + // enum ChannelNote {} + // enum NotesTooltip {} + // enum ChatTooltip {} + // enum ChannelTooltip {} + + // let mut is_dragged_over = false; + // if cx + // .global::>() + // .currently_dragged::(cx.window()) + // .is_some() + // && self.drag_target_channel == ChannelDragTarget::Channel(channel_id) + // { + // is_dragged_over = true; + // } + + // let has_messages_notification = channel.unseen_message_id.is_some(); + + // MouseEventHandler::new::(ix, cx, |state, cx| { + // let row_hovered = state.hovered(); + + // let mut select_state = |interactive: &Interactive| { + // if state.clicked() == Some(MouseButton::Left) && interactive.clicked.is_some() { + // interactive.clicked.as_ref().unwrap().clone() + // } else if state.hovered() || other_selected { + // interactive + // .hovered + // .as_ref() + // .unwrap_or(&interactive.default) + // .clone() + // } else { + // interactive.default.clone() + // } + // }; + + // Flex::::row() + // .with_child( + // Svg::new(if is_public { + // "icons/public.svg" + // } else { + // "icons/hash.svg" + // }) + // .with_color(collab_theme.channel_hash.color) + // .constrained() + // .with_width(collab_theme.channel_hash.width) + // .aligned() + // .left(), + // ) + // .with_child({ + // let style = collab_theme.channel_name.inactive_state(); + // Flex::row() + // .with_child( + // Label::new(channel.name.clone(), style.text.clone()) + // .contained() + // .with_style(style.container) + // .aligned() + // .left() + // .with_tooltip::( + // ix, + // "Join channel", + // None, + // theme.tooltip.clone(), + // cx, + // ), + // ) + // .with_children({ + // let participants = + // self.channel_store.read(cx).channel_participants(channel_id); + + // if !participants.is_empty() { + // let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT); + + // let result = FacePile::new(collab_theme.face_overlap) + // .with_children( + // participants + // .iter() + // .filter_map(|user| { + // Some( + // Image::from_data(user.avatar.clone()?) + // .with_style(collab_theme.channel_avatar), + // ) + // }) + // .take(FACEPILE_LIMIT), + // ) + // .with_children((extra_count > 0).then(|| { + // Label::new( + // format!("+{}", extra_count), + // collab_theme.extra_participant_label.text.clone(), + // ) + // .contained() + // .with_style(collab_theme.extra_participant_label.container) + // })); + + // Some(result) + // } else { + // None + // } + // }) + // .with_spacing(8.) + // .align_children_center() + // .flex(1., true) + // }) + // .with_child( + // MouseEventHandler::new::(ix, cx, move |mouse_state, _| { + // let container_style = collab_theme + // .disclosure + // .button + // .style_for(mouse_state) + // .container; + + // if channel.unseen_message_id.is_some() { + // Svg::new("icons/conversations.svg") + // .with_color(collab_theme.channel_note_active_color) + // .constrained() + // .with_width(collab_theme.channel_hash.width) + // .contained() + // .with_style(container_style) + // .with_uniform_padding(4.) + // .into_any() + // } else if row_hovered { + // Svg::new("icons/conversations.svg") + // .with_color(collab_theme.channel_hash.color) + // .constrained() + // .with_width(collab_theme.channel_hash.width) + // .contained() + // .with_style(container_style) + // .with_uniform_padding(4.) + // .into_any() + // } else { + // Empty::new().into_any() + // } + // }) + // .on_click(MouseButton::Left, move |_, this, cx| { + // this.join_channel_chat(&JoinChannelChat { channel_id }, cx); + // }) + // .with_tooltip::( + // ix, + // "Open channel chat", + // None, + // theme.tooltip.clone(), + // cx, + // ) + // .contained() + // .with_margin_right(4.), + // ) + // .with_child( + // MouseEventHandler::new::(ix, cx, move |mouse_state, cx| { + // let container_style = collab_theme + // .disclosure + // .button + // .style_for(mouse_state) + // .container; + // if row_hovered || channel.unseen_note_version.is_some() { + // Svg::new("icons/file.svg") + // .with_color(if channel.unseen_note_version.is_some() { + // collab_theme.channel_note_active_color + // } else { + // collab_theme.channel_hash.color + // }) + // .constrained() + // .with_width(collab_theme.channel_hash.width) + // .contained() + // .with_style(container_style) + // .with_uniform_padding(4.) + // .with_margin_right(collab_theme.channel_hash.container.margin.left) + // .with_tooltip::( + // ix as usize, + // "Open channel notes", + // None, + // theme.tooltip.clone(), + // cx, + // ) + // .into_any() + // } else if has_messages_notification { + // Empty::new() + // .constrained() + // .with_width(collab_theme.channel_hash.width) + // .contained() + // .with_uniform_padding(4.) + // .with_margin_right(collab_theme.channel_hash.container.margin.left) + // .into_any() + // } else { + // Empty::new().into_any() + // } + // }) + // .on_click(MouseButton::Left, move |_, this, cx| { + // this.open_channel_notes(&OpenChannelNotes { channel_id }, cx); + // }), + // ) + // .align_children_center() + // .styleable_component() + // .disclosable( + // disclosed, + // Box::new(ToggleCollapse { + // location: channel.id.clone(), + // }), + // ) + // .with_id(ix) + // .with_style(collab_theme.disclosure.clone()) + // .element() + // .constrained() + // .with_height(collab_theme.row_height) + // .contained() + // .with_style(select_state( + // collab_theme + // .channel_row + // .in_state(is_selected || is_active || is_dragged_over), + // )) + // .with_padding_left( + // collab_theme.channel_row.default_style().padding.left + // + collab_theme.channel_indent * depth as f32, + // ) + // }) + // .on_click(MouseButton::Left, move |_, this, cx| { + // if this.drag_target_channel == ChannelDragTarget::None { + // if is_active { + // this.open_channel_notes(&OpenChannelNotes { channel_id }, cx) + // } else { + // this.join_channel(channel_id, cx) + // } + // } + // }) + // .on_click(MouseButton::Right, { + // let channel = channel.clone(); + // move |e, this, cx| { + // this.deploy_channel_context_menu(Some(e.position), &channel, ix, cx); + // } + // }) + // .on_up(MouseButton::Left, move |_, this, cx| { + // if let Some((_, dragged_channel)) = cx + // .global::>() + // .currently_dragged::(cx.window()) + // { + // this.channel_store + // .update(cx, |channel_store, cx| { + // channel_store.move_channel(dragged_channel.id, Some(channel_id), cx) + // }) + // .detach_and_log_err(cx) + // } + // }) + // .on_move({ + // let channel = channel.clone(); + // move |_, this, cx| { + // if let Some((_, dragged_channel)) = cx + // .global::>() + // .currently_dragged::(cx.window()) + // { + // if channel.id != dragged_channel.id { + // this.drag_target_channel = ChannelDragTarget::Channel(channel.id); + // } + // cx.notify() + // } + // } + // }) + // .as_draggable::<_, Channel>( + // channel.clone(), + // move |_, channel, cx: &mut ViewContext| { + // let theme = &theme::current(cx).collab_panel; + + // Flex::::row() + // .with_child( + // Svg::new("icons/hash.svg") + // .with_color(theme.channel_hash.color) + // .constrained() + // .with_width(theme.channel_hash.width) + // .aligned() + // .left(), + // ) + // .with_child( + // Label::new(channel.name.clone(), theme.channel_name.text.clone()) + // .contained() + // .with_style(theme.channel_name.container) + // .aligned() + // .left(), + // ) + // .align_children_center() + // .contained() + // .with_background_color( + // theme + // .container + // .background_color + // .unwrap_or(gpui::color::Color::transparent_black()), + // ) + // .contained() + // .with_padding_left( + // theme.channel_row.default_style().padding.left + // + theme.channel_indent * depth as f32, + // ) + // .into_any() + // }, + // ) + // .with_cursor_style(CursorStyle::PointingHand) + // .into_any() } } diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index 3212b6182b7b64fe1d79a2f20097fffd64c603b5..9deec31d2132179194e6a56341ffc46f1b172963 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -21,7 +21,7 @@ audio = { package = "audio2", path = "../audio2" } auto_update = { package = "auto_update2", path = "../auto_update2" } # breadcrumbs = { path = "../breadcrumbs" } call = { package = "call2", path = "../call2" } -# channel = { path = "../channel" } +channel = { package = "channel2", path = "../channel2" } cli = { path = "../cli" } collab_ui = { package = "collab_ui2", path = "../collab_ui2" } collections = { path = "../collections" } diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index c9ed26436ab8e07529c2365d8bb91ec989eed51a..7b51c894fefe9f1cee1a9d741ec883f77e956791 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -189,7 +189,7 @@ fn main() { let app_state = Arc::new(AppState { languages, client: client.clone(), - user_store, + user_store: user_store.clone(), fs, build_window_options, call_factory: call::Call::new, @@ -210,7 +210,7 @@ fn main() { // outline::init(cx); // project_symbols::init(cx); project_panel::init(Assets, cx); - // channel::init(&client, user_store.clone(), cx); + channel::init(&client, user_store.clone(), cx); // diagnostics::init(cx); search::init(cx); // semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx); From 17b5f9294ccca7de6e6d9758110ce7bdf8c2ecd6 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 27 Nov 2023 23:11:06 -0700 Subject: [PATCH 049/151] Fix hover state when element is occluded --- crates/gpui2/src/elements/div.rs | 29 ++++++++++++++++++------- crates/gpui2/src/window.rs | 37 ++++++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 406f2ea31179d265202b7bd381c3b8b782b813c9..6bf24f750b3a2f6c0e1ea51d0bd1a3599b6eca33 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -755,6 +755,14 @@ impl Interactivity { ) { let style = self.compute_style(Some(bounds), element_state, cx); + if style + .background + .as_ref() + .is_some_and(|fill| fill.color().is_some_and(|color| !color.is_transparent())) + { + cx.with_z_index(style.z_index.unwrap_or(0), |cx| cx.add_opaque_layer(bounds)) + } + if let Some(mouse_cursor) = style.mouse_cursor { let hovered = bounds.contains_point(&cx.mouse_position()); if hovered { @@ -1098,19 +1106,21 @@ impl Interactivity { } } } - // if self.hover_style.is_some() { - if bounds.contains_point(&mouse_position) { - // eprintln!("div hovered {bounds:?} {mouse_position:?}"); - style.refine(&self.hover_style); - } else { - // eprintln!("div NOT hovered {bounds:?} {mouse_position:?}"); + if self.hover_style.is_some() { + if bounds + .intersect(&cx.content_mask().bounds) + .contains_point(&mouse_position) + && cx.was_top_layer(&mouse_position, cx.stacking_order()) + { + style.refine(&self.hover_style); + } } - // } if let Some(drag) = cx.active_drag.take() { for (state_type, group_drag_style) in &self.group_drag_over_styles { if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) { if *state_type == drag.view.entity_type() + // todo!() needs to handle cx.content_mask() and cx.is_top() && group_bounds.contains_point(&mouse_position) { style.refine(&group_drag_style.style); @@ -1120,7 +1130,10 @@ impl Interactivity { for (state_type, drag_over_style) in &self.drag_over_styles { if *state_type == drag.view.entity_type() - && bounds.contains_point(&mouse_position) + && bounds + .intersect(&cx.content_mask().bounds) + .contains_point(&mouse_position) + && cx.was_top_layer(&mouse_position, cx.stacking_order()) { style.refine(drag_over_style); } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 20561c544368b6b9c41124194c07ada33145c968..0d444d762ecd80c7846aa71c062dcfeb755e5a7d 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -12,7 +12,7 @@ use crate::{ VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; -use collections::HashMap; +use collections::{BTreeMap, HashMap}; use derive_more::{Deref, DerefMut}; use futures::{ channel::{mpsc, oneshot}, @@ -39,8 +39,8 @@ use util::ResultExt; /// 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(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Default)] -pub(crate) struct StackingOrder(pub(crate) SmallVec<[u32; 16]>); +#[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Default, Debug)] +pub struct StackingOrder(pub(crate) SmallVec<[u32; 16]>); /// Represents the two different phases when dispatching events. #[derive(Default, Copy, Clone, Debug, Eq, PartialEq)] @@ -243,7 +243,8 @@ pub(crate) struct Frame { pub(crate) dispatch_tree: DispatchTree, pub(crate) focus_listeners: Vec, pub(crate) scene_builder: SceneBuilder, - z_index_stack: StackingOrder, + pub(crate) depth_map: BTreeMap>, + pub(crate) z_index_stack: StackingOrder, content_mask_stack: Vec>, element_offset_stack: Vec>, } @@ -257,6 +258,7 @@ impl Frame { focus_listeners: Vec::new(), scene_builder: SceneBuilder::default(), z_index_stack: StackingOrder::default(), + depth_map: Default::default(), content_mask_stack: Vec::new(), element_offset_stack: Vec::new(), } @@ -806,6 +808,32 @@ impl<'a> WindowContext<'a> { result } + /// 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.current_frame.z_index_stack.clone(); + self.window + .current_frame + .depth_map + .insert(stacking_order, bounds); + } + + /// Returns true if the top-most opaque layer painted over this point was part of the + /// same layer as the given stacking order. + pub fn was_top_layer(&self, point: &Point, level: &StackingOrder) -> bool { + for (stack, bounds) in self.window.previous_frame.depth_map.iter() { + if bounds.contains_point(point) { + return level.starts_with(stack) || stack.starts_with(level); + } + } + + false + } + + /// Called during painting to get the current stacking order. + pub fn stacking_order(&self) -> &StackingOrder { + &self.window.current_frame.z_index_stack + } + /// Paint one or more drop shadows into the scene for the current frame at the current z-index. pub fn paint_shadows( &mut self, @@ -1153,6 +1181,7 @@ impl<'a> WindowContext<'a> { frame.mouse_listeners.values_mut().for_each(Vec::clear); frame.focus_listeners.clear(); frame.dispatch_tree.clear(); + frame.depth_map.clear(); } /// Dispatch a mouse or keyboard event on the window. From ca0dcf741f7467ee31e0b6f25713557fa799f5f5 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 28 Nov 2023 00:07:26 -0700 Subject: [PATCH 050/151] Use layers to correct mouse event handling too --- crates/gpui2/src/elements/div.rs | 102 +++++++++++++++++++-------- crates/picker2/src/picker2.rs | 1 - crates/workspace2/src/modal_layer.rs | 5 -- 3 files changed, 72 insertions(+), 36 deletions(-) diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 6bf24f750b3a2f6c0e1ea51d0bd1a3599b6eca33..6185cd644364794eec85f6218c62c7e3643f5cb8 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -3,7 +3,8 @@ use crate::{ BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle, IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent, - SharedString, Size, Style, StyleRefinement, Styled, Task, View, Visibility, WindowContext, + SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility, + WindowContext, }; use collections::HashMap; use refineable::Refineable; @@ -84,7 +85,7 @@ pub trait InteractiveElement: Sized + Element { move |event, bounds, phase, cx| { if phase == DispatchPhase::Bubble && event.button == button - && bounds.contains_point(&event.position) + && bounds.visibly_contains(&event.position, cx) { (listener)(event, cx) } @@ -99,7 +100,7 @@ pub trait InteractiveElement: Sized + Element { ) -> Self { self.interactivity().mouse_down_listeners.push(Box::new( move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { + if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { (listener)(event, cx) } }, @@ -117,7 +118,7 @@ pub trait InteractiveElement: Sized + Element { .push(Box::new(move |event, bounds, phase, cx| { if phase == DispatchPhase::Bubble && event.button == button - && bounds.contains_point(&event.position) + && bounds.visibly_contains(&event.position, cx) { (listener)(event, cx) } @@ -132,7 +133,7 @@ pub trait InteractiveElement: Sized + Element { self.interactivity() .mouse_up_listeners .push(Box::new(move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { + if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { (listener)(event, cx) } })); @@ -145,7 +146,8 @@ pub trait InteractiveElement: Sized + Element { ) -> Self { self.interactivity().mouse_down_listeners.push(Box::new( move |event, bounds, phase, cx| { - if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) { + if phase == DispatchPhase::Capture && !bounds.visibly_contains(&event.position, cx) + { (listener)(event, cx) } }, @@ -163,7 +165,7 @@ pub trait InteractiveElement: Sized + Element { .push(Box::new(move |event, bounds, phase, cx| { if phase == DispatchPhase::Capture && event.button == button - && !bounds.contains_point(&event.position) + && !bounds.visibly_contains(&event.position, cx) { (listener)(event, cx); } @@ -177,7 +179,7 @@ pub trait InteractiveElement: Sized + Element { ) -> Self { self.interactivity().mouse_move_listeners.push(Box::new( move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { + if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { (listener)(event, cx); } }, @@ -191,7 +193,7 @@ pub trait InteractiveElement: Sized + Element { ) -> Self { self.interactivity().scroll_wheel_listeners.push(Box::new( move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { + if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { (listener)(event, cx); } }, @@ -526,15 +528,15 @@ pub type FocusListeners = SmallVec<[FocusListener; 2]>; pub type FocusListener = Box; pub type MouseDownListener = - Box, DispatchPhase, &mut WindowContext) + 'static>; + Box; pub type MouseUpListener = - Box, DispatchPhase, &mut WindowContext) + 'static>; + Box; pub type MouseMoveListener = - Box, DispatchPhase, &mut WindowContext) + 'static>; + Box; pub type ScrollWheelListener = - Box, DispatchPhase, &mut WindowContext) + 'static>; + Box; pub type ClickListener = Box; @@ -719,6 +721,18 @@ pub struct Interactivity { pub tooltip_builder: Option, } +#[derive(Clone)] +pub struct InteractiveBounds { + bounds: Bounds, + stacking_order: StackingOrder, +} + +impl InteractiveBounds { + fn visibly_contains(&self, point: &Point, cx: &WindowContext) -> bool { + self.bounds.contains_point(point) && cx.was_top_layer(&point, &self.stacking_order) + } +} + impl Interactivity { pub fn layout( &mut self, @@ -763,34 +777,44 @@ impl Interactivity { cx.with_z_index(style.z_index.unwrap_or(0), |cx| cx.add_opaque_layer(bounds)) } + let interactive_bounds = Rc::new(InteractiveBounds { + bounds: bounds.intersect(&cx.content_mask().bounds), + stacking_order: cx.stacking_order().clone(), + }); + if let Some(mouse_cursor) = style.mouse_cursor { - let hovered = bounds.contains_point(&cx.mouse_position()); + let mouse_position = &cx.mouse_position(); + let hovered = interactive_bounds.visibly_contains(mouse_position, cx); if hovered { cx.set_cursor_style(mouse_cursor); } } 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, &bounds, phase, cx); + listener(event, &*interactive_bounds, phase, cx); }) } 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, &bounds, phase, cx); + listener(event, &*interactive_bounds, phase, 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, &bounds, phase, cx); + listener(event, &*interactive_bounds, phase, 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, &bounds, phase, cx); + listener(event, &*interactive_bounds, phase, cx); }) } @@ -800,6 +824,7 @@ impl Interactivity { .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx)); if let Some(group_bounds) = hover_group_bounds { + // todo!() needs cx.was_top_layer let hovered = group_bounds.contains_point(&cx.mouse_position()); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { if phase == DispatchPhase::Capture { @@ -813,10 +838,11 @@ impl Interactivity { if self.hover_style.is_some() || (cx.active_drag.is_some() && !self.drag_over_styles.is_empty()) { - let hovered = bounds.contains_point(&cx.mouse_position()); + let interactive_bounds = interactive_bounds.clone(); + let hovered = interactive_bounds.visibly_contains(&cx.mouse_position(), cx); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { if phase == DispatchPhase::Capture { - if bounds.contains_point(&event.position) != hovered { + if interactive_bounds.visibly_contains(&event.position, cx) != hovered { cx.notify(); } } @@ -825,8 +851,11 @@ impl Interactivity { if cx.active_drag.is_some() { let drop_listeners = mem::take(&mut self.drop_listeners); + let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { + if phase == DispatchPhase::Bubble + && interactive_bounds.visibly_contains(&event.position, &cx) + { if let Some(drag_state_type) = cx.active_drag.as_ref().map(|drag| drag.view.entity_type()) { @@ -855,6 +884,7 @@ impl Interactivity { if let Some(mouse_down) = mouse_down { if let Some(drag_listener) = drag_listener { let active_state = element_state.clicked_state.clone(); + let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { if cx.active_drag.is_some() { @@ -862,7 +892,7 @@ impl Interactivity { cx.notify(); } } else if phase == DispatchPhase::Bubble - && bounds.contains_point(&event.position) + && interactive_bounds.visibly_contains(&event.position, cx) && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD { *active_state.borrow_mut() = ElementClickedState::default(); @@ -875,8 +905,11 @@ impl Interactivity { }); } + let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { + if phase == DispatchPhase::Bubble + && interactive_bounds.visibly_contains(&event.position, cx) + { let mouse_click = ClickEvent { down: mouse_down.clone(), up: event.clone(), @@ -889,8 +922,11 @@ impl Interactivity { cx.notify(); }); } else { + let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { + if phase == DispatchPhase::Bubble + && interactive_bounds.visibly_contains(&event.position, cx) + { *pending_mouse_down.borrow_mut() = Some(event.clone()); cx.notify(); } @@ -901,13 +937,14 @@ impl Interactivity { if let Some(hover_listener) = self.hover_listener.take() { let was_hovered = element_state.hover_state.clone(); let has_mouse_down = element_state.pending_mouse_down.clone(); + let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { if phase != DispatchPhase::Bubble { return; } - let is_hovered = - bounds.contains_point(&event.position) && has_mouse_down.borrow().is_none(); + let is_hovered = interactive_bounds.visibly_contains(&event.position, cx) + && has_mouse_down.borrow().is_none(); let mut was_hovered = was_hovered.borrow_mut(); if is_hovered != was_hovered.clone() { @@ -922,14 +959,15 @@ impl Interactivity { if let Some(tooltip_builder) = self.tooltip_builder.take() { let active_tooltip = element_state.active_tooltip.clone(); let pending_mouse_down = element_state.pending_mouse_down.clone(); + let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { if phase != DispatchPhase::Bubble { return; } - let is_hovered = - bounds.contains_point(&event.position) && pending_mouse_down.borrow().is_none(); + 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; @@ -987,11 +1025,12 @@ impl Interactivity { .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 { let group = active_group_bounds .map_or(false, |bounds| bounds.contains_point(&down.position)); - let element = bounds.contains_point(&down.position); + let element = interactive_bounds.visibly_contains(&down.position, cx); if group || element { *active_state.borrow_mut() = ElementClickedState { group, element }; cx.notify(); @@ -1008,9 +1047,12 @@ impl Interactivity { .clone(); let line_height = cx.line_height(); let scroll_max = (content_size - bounds.size).max(&Size::default()); + let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { + 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); diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs index dc6b77c7c7b21db00cc3cb1b6ae23588e07d6c36..70a8df21e138e85c6d9cfbc3528fba22667f1405 100644 --- a/crates/picker2/src/picker2.rs +++ b/crates/picker2/src/picker2.rs @@ -114,7 +114,6 @@ impl Picker { } fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { - dbg!("canceling!"); self.delegate.dismissed(cx); } diff --git a/crates/workspace2/src/modal_layer.rs b/crates/workspace2/src/modal_layer.rs index 6d28a6299b9f6cbef4231eda2f2bf5d087a3e9d4..a9b6189fdc313394061f21abb1449d6664f3daed 100644 --- a/crates/workspace2/src/modal_layer.rs +++ b/crates/workspace2/src/modal_layer.rs @@ -46,7 +46,6 @@ impl ModalLayer { previous_focus_handle: cx.focused(), focus_handle: cx.focus_handle(), }); - dbg!("focusing"); cx.focus_view(&new_modal); cx.notify(); } @@ -96,10 +95,6 @@ impl Render for ModalLayer { .track_focus(&active_modal.focus_handle) .child( h_stack() - // needed to prevent mouse events leaking to the - // UI below. // todo! for gpui3. - .on_any_mouse_down(|_, cx| cx.stop_propagation()) - .on_any_mouse_up(|_, cx| cx.stop_propagation()) .on_mouse_down_out(cx.listener(|this, _, cx| { this.hide_modal(cx); })) From 6c37393dd163ccfd501fcd12678d9e297cfc8959 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:16:51 +0100 Subject: [PATCH 051/151] Add contact finder, change ui::List's on_click handler signature --- crates/collab_ui2/src/collab_panel.rs | 68 +++-- .../src/collab_panel/contact_finder.rs | 264 ++++++++---------- crates/ui2/src/components/avatar.rs | 6 + crates/ui2/src/components/context_menu.rs | 7 +- crates/ui2/src/components/list.rs | 46 +-- crates/ui2/src/components/slot.rs | 4 +- 6 files changed, 191 insertions(+), 204 deletions(-) diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 0df51b02a2b7bea5246522b414d6550ba8f21743..2d8cd2cd3c9969aebf4301d111a5daffa3fdfb08 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -1,6 +1,6 @@ #![allow(unused)] // mod channel_modal; -// mod contact_finder; +mod contact_finder; // use crate::{ // channel_view::{self, ChannelView}, @@ -16,7 +16,7 @@ // proto::{self, PeerId}, // Client, Contact, User, UserStore, // }; -// use contact_finder::ContactFinder; +use contact_finder::ContactFinder; // use context_menu::{ContextMenu, ContextMenuItem}; // use db::kvp::KEY_VALUE_STORE; // use drag_and_drop::{DragAndDrop, Draggable}; @@ -166,7 +166,7 @@ use editor::Editor; use feature_flags::{ChannelsAlpha, FeatureFlagAppExt}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ - actions, div, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle, + actions, div, img, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, RenderOnce, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, }; @@ -2255,19 +2255,17 @@ impl CollabPanel { // .detach_and_log_err(cx); // } - // fn toggle_contact_finder(&mut self, cx: &mut ViewContext) { - // if let Some(workspace) = self.workspace.upgrade(cx) { - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_modal(cx, |_, cx| { - // cx.add_view(|cx| { - // let mut finder = ContactFinder::new(self.user_store.clone(), cx); - // finder.set_query(self.filter_editor.read(cx).text(cx), cx); - // finder - // }) - // }); - // }); - // } - // } + fn toggle_contact_finder(&mut self, cx: &mut ViewContext) { + if let Some(workspace) = self.workspace.upgrade() { + workspace.update(cx, |workspace, cx| { + workspace.toggle_modal(cx, |cx| { + let mut finder = ContactFinder::new(self.user_store.clone(), cx); + finder.set_query(self.filter_editor.read(cx).text(cx), cx); + finder + }); + }); + } + } // fn new_root_channel(&mut self, cx: &mut ViewContext) { // self.channel_editing_state = Some(ChannelEditingState::Create { @@ -2672,10 +2670,7 @@ impl CollabPanel { } Section::Contacts => Some( IconButton::new("add-contact", Icon::Plus) - .on_click(cx.listener(|this, _, cx| { - todo!() - // this.toggle_contact_finder(cx) - })) + .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx))) .tooltip(|cx| Tooltip::text("Search for new contact", cx)), ), Section::Channels => { @@ -2734,13 +2729,20 @@ impl CollabPanel { let busy = contact.busy || calling; let user_id = contact.user.id; let github_login = SharedString::from(contact.user.github_login.clone()); - - let item = ListItem::new(github_login.clone()) - .child(Label::new(github_login.clone())) - .on_click(cx.listener(|this, _, cx| { - todo!(); - })); - + let mut item = ListItem::new(github_login.clone()) + .on_click(cx.listener(move |this, _, cx| { + this.workspace + .update(cx, |this, cx| { + this.call_state() + .invite(user_id, None, cx) + .detach_and_log_err(cx) + }) + .log_err(); + })) + .child(Label::new(github_login.clone())); + if let Some(avatar) = contact.user.avatar.clone() { + //item = item.left_avatar(avatar); + } // let event_handler = // MouseEventHandler::new::(contact.user.id as usize, cx, |state, cx| { // Flex::row() @@ -2873,8 +2875,14 @@ impl CollabPanel { ) -> impl IntoElement { let github_login = SharedString::from(user.github_login.clone()); - let mut row = ListItem::new(github_login.clone()).child(Label::new(github_login.clone())); - + let mut item = ListItem::new(github_login.clone()) + .child(Label::new(github_login.clone())) + .on_click(cx.listener(|this, _, cx| { + todo!(); + })); + if let Some(avatar) = user.avatar.clone() { + item = item.left_avatar(avatar); + } // .with_children(user.avatar.clone().map(|avatar| { // Image::from_data(avatar) // .with_style(theme.contact_avatar) @@ -2963,7 +2971,7 @@ impl CollabPanel { // .style_for(&mut Default::default()), // ) // .into_any() - row + item } fn render_contact_placeholder( diff --git a/crates/collab_ui2/src/collab_panel/contact_finder.rs b/crates/collab_ui2/src/collab_panel/contact_finder.rs index d0c12a7f90a430a70615f6c4b91ca555619081fe..80872db729c6cff103046ca4e1fe8f7282a59b4a 100644 --- a/crates/collab_ui2/src/collab_panel/contact_finder.rs +++ b/crates/collab_ui2/src/collab_panel/contact_finder.rs @@ -1,37 +1,34 @@ use client::{ContactRequestStatus, User, UserStore}; use gpui::{ - elements::*, AppContext, Entity, ModelHandle, MouseState, Task, View, ViewContext, ViewHandle, + div, img, svg, AnyElement, AppContext, DismissEvent, Div, Entity, EventEmitter, FocusHandle, + FocusableView, Img, IntoElement, Model, ParentElement as _, Render, Styled, Task, View, + ViewContext, VisualContext, WeakView, }; -use picker::{Picker, PickerDelegate, PickerEvent}; +use picker::{Picker, PickerDelegate}; use std::sync::Arc; -use util::TryFutureExt; -use workspace::Modal; +use theme::ActiveTheme as _; +use ui::{h_stack, v_stack, Label}; +use util::{ResultExt as _, TryFutureExt}; pub fn init(cx: &mut AppContext) { - Picker::::init(cx); - cx.add_action(ContactFinder::dismiss) + //Picker::::init(cx); + //cx.add_action(ContactFinder::dismiss) } pub struct ContactFinder { - picker: ViewHandle>, + picker: View>, has_focus: bool, } impl ContactFinder { - pub fn new(user_store: ModelHandle, cx: &mut ViewContext) -> Self { - let picker = cx.add_view(|cx| { - Picker::new( - ContactFinderDelegate { - user_store, - potential_contacts: Arc::from([]), - selected_index: 0, - }, - cx, - ) - .with_theme(|theme| theme.collab_panel.tabbed_modal.picker.clone()) - }); - - cx.subscribe(&picker, |_, _, e, cx| cx.emit(*e)).detach(); + pub fn new(user_store: Model, cx: &mut ViewContext) -> Self { + let delegate = ContactFinderDelegate { + parent: cx.view().downgrade(), + user_store, + potential_contacts: Arc::from([]), + selected_index: 0, + }; + let picker = cx.build_view(|cx| Picker::new(delegate, cx)); Self { picker, @@ -41,105 +38,72 @@ impl ContactFinder { pub fn set_query(&mut self, query: String, cx: &mut ViewContext) { self.picker.update(cx, |picker, cx| { - picker.set_query(query, cx); + // todo!() + // picker.set_query(query, cx); }); } - - fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { - cx.emit(PickerEvent::Dismiss); - } -} - -impl Entity for ContactFinder { - type Event = PickerEvent; } -impl View for ContactFinder { - fn ui_name() -> &'static str { - "ContactFinder" - } - - fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let full_theme = &theme::current(cx); - let theme = &full_theme.collab_panel.tabbed_modal; - - fn render_mode_button( - text: &'static str, - theme: &theme::TabbedModal, - _cx: &mut ViewContext, - ) -> AnyElement { - let contained_text = &theme.tab_button.active_state().default; - Label::new(text, contained_text.text.clone()) - .contained() - .with_style(contained_text.container.clone()) - .into_any() +impl Render for ContactFinder { + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + fn render_mode_button(text: &'static str) -> AnyElement { + Label::new(text).into_any_element() } - Flex::column() - .with_child( - Flex::column() - .with_child( - Label::new("Contacts", theme.title.text.clone()) - .contained() - .with_style(theme.title.container.clone()), - ) - .with_child(Flex::row().with_children([render_mode_button( - "Invite new contacts", - &theme, - cx, - )])) - .expanded() - .contained() - .with_style(theme.header), - ) - .with_child( - ChildView::new(&self.picker, cx) - .contained() - .with_style(theme.body), + v_stack() + .child( + v_stack() + .child(Label::new("Contacts")) + .child(h_stack().children([render_mode_button("Invite new contacts")])) + .bg(cx.theme().colors().element_background), ) - .constrained() - .with_max_height(theme.max_height) - .with_max_width(theme.max_width) - .contained() - .with_style(theme.modal) - .into_any() + .child(self.picker.clone()) + .w_96() } - fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { - self.has_focus = true; - if cx.is_self_focused() { - cx.focus(&self.picker) - } - } + // fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { + // self.has_focus = true; + // if cx.is_self_focused() { + // cx.focus(&self.picker) + // } + // } - fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext) { - self.has_focus = false; - } + // fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext) { + // self.has_focus = false; + // } + + type Element = Div; } -impl Modal for ContactFinder { - fn has_focus(&self) -> bool { - self.has_focus - } +// impl Modal for ContactFinder { +// fn has_focus(&self) -> bool { +// self.has_focus +// } - fn dismiss_on_event(event: &Self::Event) -> bool { - match event { - PickerEvent::Dismiss => true, - } - } -} +// fn dismiss_on_event(event: &Self::Event) -> bool { +// match event { +// PickerEvent::Dismiss => true, +// } +// } +// } pub struct ContactFinderDelegate { + parent: WeakView, potential_contacts: Arc<[Arc]>, - user_store: ModelHandle, + user_store: Model, selected_index: usize, } -impl PickerDelegate for ContactFinderDelegate { - fn placeholder_text(&self) -> Arc { - "Search collaborator by username...".into() +impl EventEmitter for ContactFinder {} + +impl FocusableView for ContactFinder { + fn focus_handle(&self, cx: &AppContext) -> FocusHandle { + self.picker.focus_handle(cx) } +} +impl PickerDelegate for ContactFinderDelegate { + type ListItem = Div; fn match_count(&self) -> usize { self.potential_contacts.len() } @@ -152,6 +116,10 @@ impl PickerDelegate for ContactFinderDelegate { self.selected_index = ix; } + fn placeholder_text(&self) -> Arc { + "Search collaborator by username...".into() + } + fn update_matches(&mut self, query: String, cx: &mut ViewContext>) -> Task<()> { let search_users = self .user_store @@ -161,7 +129,7 @@ impl PickerDelegate for ContactFinderDelegate { async { let potential_contacts = search_users.await?; picker.update(&mut cx, |picker, cx| { - picker.delegate_mut().potential_contacts = potential_contacts.into(); + picker.delegate.potential_contacts = potential_contacts.into(); cx.notify(); })?; anyhow::Ok(()) @@ -191,19 +159,18 @@ impl PickerDelegate for ContactFinderDelegate { } fn dismissed(&mut self, cx: &mut ViewContext>) { - cx.emit(PickerEvent::Dismiss); + //cx.emit(PickerEvent::Dismiss); + self.parent + .update(cx, |_, cx| cx.emit(DismissEvent::Dismiss)) + .log_err(); } fn render_match( &self, ix: usize, - mouse_state: &mut MouseState, selected: bool, - cx: &gpui::AppContext, - ) -> AnyElement> { - let full_theme = &theme::current(cx); - let theme = &full_theme.collab_panel.contact_finder; - let tabbed_modal = &full_theme.collab_panel.tabbed_modal; + cx: &mut ViewContext>, + ) -> Self::ListItem { let user = &self.potential_contacts[ix]; let request_status = self.user_store.read(cx).contact_request_status(user); @@ -214,48 +181,45 @@ impl PickerDelegate for ContactFinderDelegate { ContactRequestStatus::RequestSent => Some("icons/x.svg"), ContactRequestStatus::RequestAccepted => None, }; - let button_style = if self.user_store.read(cx).is_contact_request_pending(user) { - &theme.disabled_contact_button - } else { - &theme.contact_button - }; - let style = tabbed_modal - .picker - .item - .in_state(selected) - .style_for(mouse_state); - Flex::row() - .with_children(user.avatar.clone().map(|avatar| { - Image::from_data(avatar) - .with_style(theme.contact_avatar) - .aligned() - .left() - })) - .with_child( - Label::new(user.github_login.clone(), style.label.clone()) - .contained() - .with_style(theme.contact_username) - .aligned() - .left(), - ) - .with_children(icon_path.map(|icon_path| { - Svg::new(icon_path) - .with_color(button_style.color) - .constrained() - .with_width(button_style.icon_width) - .aligned() - .contained() - .with_style(button_style.container) - .constrained() - .with_width(button_style.button_width) - .with_height(button_style.button_width) - .aligned() - .flex_float() - })) - .contained() - .with_style(style.container) - .constrained() - .with_height(tabbed_modal.row_height) - .into_any() + dbg!(icon_path); + div() + .flex_1() + .justify_between() + .children(user.avatar.clone().map(|avatar| img().data(avatar))) + .child(Label::new(user.github_login.clone())) + .children(icon_path.map(|icon_path| svg().path(icon_path))) + // Flex::row() + // .with_children(user.avatar.clone().map(|avatar| { + // Image::from_data(avatar) + // .with_style(theme.contact_avatar) + // .aligned() + // .left() + // })) + // .with_child( + // Label::new(user.github_login.clone(), style.label.clone()) + // .contained() + // .with_style(theme.contact_username) + // .aligned() + // .left(), + // ) + // .with_children(icon_path.map(|icon_path| { + // Svg::new(icon_path) + // .with_color(button_style.color) + // .constrained() + // .with_width(button_style.icon_width) + // .aligned() + // .contained() + // .with_style(button_style.container) + // .constrained() + // .with_width(button_style.button_width) + // .with_height(button_style.button_width) + // .aligned() + // .flex_float() + // })) + // .contained() + // .with_style(style.container) + // .constrained() + // .with_height(tabbed_modal.row_height) + // .into_any() } } diff --git a/crates/ui2/src/components/avatar.rs b/crates/ui2/src/components/avatar.rs index d358b221da9287f488bd68dd95bcff659191b5a5..57aa17ebbaa3c43c559d14803e3ed74a3a2fdc19 100644 --- a/crates/ui2/src/components/avatar.rs +++ b/crates/ui2/src/components/avatar.rs @@ -49,6 +49,12 @@ impl Avatar { } } + pub fn source(src: ImageSource) -> Self { + Self { + src, + shape: Shape::Circle, + } + } pub fn shape(mut self, shape: Shape) -> Self { self.shape = shape; self diff --git a/crates/ui2/src/components/context_menu.rs b/crates/ui2/src/components/context_menu.rs index a92c08d82fae0164d0488e350f273df86f7ae48f..c9048418382ff8034a6013e27362f7f036186688 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -12,7 +12,10 @@ use gpui::{ pub enum ContextMenuItem { Separator, Header(SharedString), - Entry(SharedString, Rc), + Entry( + SharedString, + Rc, + ), } pub struct ContextMenu { @@ -58,7 +61,7 @@ impl ContextMenu { pub fn entry( mut self, label: impl Into, - on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static, + on_click: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, ) -> Self { self.items .push(ContextMenuItem::Entry(label.into(), Rc::new(on_click))); diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index a674a5084c2ce6b8c7913fda97dbe11d2d40abb0..712e5d4c7b065e81084ba898e5f0a71b25836d38 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -1,5 +1,6 @@ use gpui::{ - div, px, AnyElement, ClickEvent, Div, IntoElement, Stateful, StatefulInteractiveElement, + div, px, AnyElement, ClickEvent, Div, ImageSource, IntoElement, MouseButton, MouseDownEvent, + Stateful, StatefulInteractiveElement, }; use smallvec::SmallVec; use std::rc::Rc; @@ -250,7 +251,7 @@ pub struct ListItem { size: ListEntrySize, toggle: Toggle, variant: ListItemVariant, - on_click: Option>, + on_click: Option>, children: SmallVec<[AnyElement; 2]>, } @@ -270,7 +271,10 @@ impl ListItem { } } - pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self { + pub fn on_click( + mut self, + handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, + ) -> Self { self.on_click = Some(Rc::new(handler)); self } @@ -300,7 +304,7 @@ impl ListItem { self } - pub fn left_avatar(mut self, left_avatar: impl Into) -> Self { + pub fn left_avatar(mut self, left_avatar: impl Into) -> Self { self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into())); self } @@ -323,7 +327,7 @@ impl RenderOnce for ListItem { .color(Color::Muted), ), ), - Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::uri(src))), + Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::source(src))), Some(GraphicSlot::PublicActor(src)) => Some(h_stack().child(Avatar::uri(src))), None => None, }; @@ -335,25 +339,18 @@ impl RenderOnce for ListItem { div() .id(self.id) .relative() - .hover(|mut style| { - style.background = Some(cx.theme().colors().editor_background.into()); - style - }) - .on_click({ - let on_click = self.on_click.clone(); - move |event, cx| { - if let Some(on_click) = &on_click { - (on_click)(event, cx) - } - } - }) + .bg(cx.theme().colors().editor_background.clone()) + // .hover(|mut style| { + // style.background = Some(cx.theme().colors().editor_background.into()); + // style + // }) // TODO: Add focus state // .when(self.state == InteractionState::Focused, |this| { // this.border() // .border_color(cx.theme().colors().border_focused) // }) - .hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) - .active(|style| style.bg(cx.theme().colors().ghost_element_active)) + //.hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) + //.active(|style| style.bg(cx.theme().colors().ghost_element_active)) .child( sized_item .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) @@ -377,7 +374,16 @@ impl RenderOnce for ListItem { .relative() .child(disclosure_control(self.toggle)) .children(left_content) - .children(self.children), + .children(self.children) + .on_mouse_down(MouseButton::Left, { + let on_click = self.on_click.clone(); + move |event, cx| { + dbg!("Clicking!"); + if let Some(on_click) = &on_click { + (on_click)(event, cx) + } + } + }), ) } } diff --git a/crates/ui2/src/components/slot.rs b/crates/ui2/src/components/slot.rs index a672694dc5781c6a474211d621a71ee640e7dcaa..7c896bf85b23d604a41aec259de125f06c059514 100644 --- a/crates/ui2/src/components/slot.rs +++ b/crates/ui2/src/components/slot.rs @@ -1,4 +1,4 @@ -use gpui::SharedString; +use gpui::{ImageSource, SharedString}; use crate::Icon; @@ -9,6 +9,6 @@ use crate::Icon; /// Can be filled with a [] pub enum GraphicSlot { Icon(Icon), - Avatar(SharedString), + Avatar(ImageSource), PublicActor(SharedString), } From 9fb3cb6a697c96c09afae83fb570fc0d97057baa Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:37:53 +0100 Subject: [PATCH 052/151] fixup! Add contact finder, change ui::List's on_click handler signature --- crates/ui2/src/components/context_menu.rs | 6 +++--- crates/ui2/src/components/list.rs | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/ui2/src/components/context_menu.rs b/crates/ui2/src/components/context_menu.rs index c9048418382ff8034a6013e27362f7f036186688..fda446a78b645de6f7237f3d74052fd72ab7a929 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -4,9 +4,9 @@ use std::rc::Rc; use crate::{prelude::*, v_stack, Label, List}; use crate::{ListItem, ListSeparator, ListSubHeader}; use gpui::{ - overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, ClickEvent, DismissEvent, - DispatchPhase, Div, EventEmitter, FocusHandle, FocusableView, IntoElement, LayoutId, - ManagedView, MouseButton, MouseDownEvent, Pixels, Point, Render, View, VisualContext, + overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, DismissEvent, DispatchPhase, + Div, EventEmitter, FocusHandle, FocusableView, IntoElement, LayoutId, ManagedView, MouseButton, + MouseDownEvent, Pixels, Point, Render, View, VisualContext, }; pub enum ContextMenuItem { diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index 712e5d4c7b065e81084ba898e5f0a71b25836d38..ecdd32d168f2c4df4ba4d8f986bcd2409e191f8b 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -1,6 +1,5 @@ use gpui::{ - div, px, AnyElement, ClickEvent, Div, ImageSource, IntoElement, MouseButton, MouseDownEvent, - Stateful, StatefulInteractiveElement, + div, px, AnyElement, Div, ImageSource, IntoElement, MouseButton, MouseDownEvent, Stateful, }; use smallvec::SmallVec; use std::rc::Rc; @@ -378,7 +377,6 @@ impl RenderOnce for ListItem { .on_mouse_down(MouseButton::Left, { let on_click = self.on_click.clone(); move |event, cx| { - dbg!("Clicking!"); if let Some(on_click) = &on_click { (on_click)(event, cx) } From 63bd4ac999c3b3abf1f201e23b101294d32a35e0 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 28 Nov 2023 10:33:13 -0500 Subject: [PATCH 053/151] Allow `render_match` to return an `Option` to represent no matches --- .../command_palette2/src/command_palette.rs | 42 ++++++++++--------- crates/file_finder2/src/file_finder.rs | 30 ++++++------- crates/picker2/src/picker2.rs | 4 +- crates/storybook2/src/stories/picker.rs | 28 +++++++------ 4 files changed, 55 insertions(+), 49 deletions(-) diff --git a/crates/command_palette2/src/command_palette.rs b/crates/command_palette2/src/command_palette.rs index 07b819d3a10c054ea20cddac787d28e792d3b187..73dca81b9327c25ec4b2755c50516c40b9f93362 100644 --- a/crates/command_palette2/src/command_palette.rs +++ b/crates/command_palette2/src/command_palette.rs @@ -294,32 +294,34 @@ impl PickerDelegate for CommandPaletteDelegate { ix: usize, selected: bool, cx: &mut ViewContext>, - ) -> Self::ListItem { + ) -> Option { let colors = cx.theme().colors(); let Some(r#match) = self.matches.get(ix) else { - return div(); + return None; }; let Some(command) = self.commands.get(r#match.candidate_id) else { - return div(); + return None; }; - div() - .px_1() - .text_color(colors.text) - .text_ui() - .bg(colors.ghost_element_background) - .rounded_md() - .when(selected, |this| this.bg(colors.ghost_element_selected)) - .hover(|this| this.bg(colors.ghost_element_hover)) - .child( - h_stack() - .justify_between() - .child(HighlightedLabel::new( - command.name.clone(), - r#match.positions.clone(), - )) - .children(KeyBinding::for_action(&*command.action, cx)), - ) + Some( + div() + .px_1() + .text_color(colors.text) + .text_ui() + .bg(colors.ghost_element_background) + .rounded_md() + .when(selected, |this| this.bg(colors.ghost_element_selected)) + .hover(|this| this.bg(colors.ghost_element_hover)) + .child( + h_stack() + .justify_between() + .child(HighlightedLabel::new( + command.name.clone(), + r#match.positions.clone(), + )) + .children(KeyBinding::for_action(&*command.action, cx)), + ), + ) } } diff --git a/crates/file_finder2/src/file_finder.rs b/crates/file_finder2/src/file_finder.rs index ea578fbb0ebb5703f3f31ed142f5589a0345c814..c7d8e9eae0c1ed3d3c721af8e5a4dfb706637ddb 100644 --- a/crates/file_finder2/src/file_finder.rs +++ b/crates/file_finder2/src/file_finder.rs @@ -711,7 +711,7 @@ impl PickerDelegate for FileFinderDelegate { ix: usize, selected: bool, cx: &mut ViewContext>, - ) -> Self::ListItem { + ) -> Option { let path_match = self .matches .get(ix) @@ -722,19 +722,21 @@ impl PickerDelegate for FileFinderDelegate { let (file_name, file_name_positions, full_path, full_path_positions) = self.labels_for_match(path_match, cx, ix); - div() - .px_1() - .text_color(colors.text) - .text_ui() - .bg(colors.ghost_element_background) - .rounded_md() - .when(selected, |this| this.bg(colors.ghost_element_selected)) - .hover(|this| this.bg(colors.ghost_element_hover)) - .child( - v_stack() - .child(HighlightedLabel::new(file_name, file_name_positions)) - .child(HighlightedLabel::new(full_path, full_path_positions)), - ) + Some( + div() + .px_1() + .text_color(colors.text) + .text_ui() + .bg(colors.ghost_element_background) + .rounded_md() + .when(selected, |this| this.bg(colors.ghost_element_selected)) + .hover(|this| this.bg(colors.ghost_element_hover)) + .child( + v_stack() + .child(HighlightedLabel::new(file_name, file_name_positions)) + .child(HighlightedLabel::new(full_path, full_path_positions)), + ), + ) } } diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs index dc6b77c7c7b21db00cc3cb1b6ae23588e07d6c36..ea31419201748ade52647504026d3d23e5ac27ea 100644 --- a/crates/picker2/src/picker2.rs +++ b/crates/picker2/src/picker2.rs @@ -32,7 +32,7 @@ pub trait PickerDelegate: Sized + 'static { ix: usize, selected: bool, cx: &mut ViewContext>, - ) -> Self::ListItem; + ) -> Option; } impl FocusableView for Picker { @@ -230,7 +230,7 @@ impl Render for Picker { ) }), ) - .child(picker.delegate.render_match( + .children(picker.delegate.render_match( ix, ix == selected_index, cx, diff --git a/crates/storybook2/src/stories/picker.rs b/crates/storybook2/src/stories/picker.rs index ae6a26161bd3a587c54aa7231336d40428dbbe69..f66fe7a93af28ddf5ba5274594fc03597299d915 100644 --- a/crates/storybook2/src/stories/picker.rs +++ b/crates/storybook2/src/stories/picker.rs @@ -51,25 +51,27 @@ impl PickerDelegate for Delegate { ix: usize, selected: bool, cx: &mut gpui::ViewContext>, - ) -> Self::ListItem { + ) -> Option { let colors = cx.theme().colors(); let Some(candidate_ix) = self.matches.get(ix) else { - return div(); + return None; }; // TASK: Make StringMatchCandidate::string a SharedString let candidate = SharedString::from(self.candidates[*candidate_ix].string.clone()); - div() - .text_color(colors.text) - .when(selected, |s| { - s.border_l_10().border_color(colors.terminal_ansi_yellow) - }) - .hover(|style| { - style - .bg(colors.element_active) - .text_color(colors.text_accent) - }) - .child(candidate) + Some( + div() + .text_color(colors.text) + .when(selected, |s| { + s.border_l_10().border_color(colors.terminal_ansi_yellow) + }) + .hover(|style| { + style + .bg(colors.element_active) + .text_color(colors.text_accent) + }) + .child(candidate), + ) } fn selected_index(&self) -> usize { From 1ee109cec723ae8e8816eae8961503fb052db0b3 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 28 Nov 2023 10:44:57 -0500 Subject: [PATCH 054/151] Use `ListItem` when rendering picker matches --- .../command_palette2/src/command_palette.rs | 37 +++++++------------ crates/file_finder2/src/file_finder.rs | 30 +++++---------- crates/ui2/src/components/list.rs | 30 ++++++--------- 3 files changed, 33 insertions(+), 64 deletions(-) diff --git a/crates/command_palette2/src/command_palette.rs b/crates/command_palette2/src/command_palette.rs index 73dca81b9327c25ec4b2755c50516c40b9f93362..5f3fb4a98595afc3b6930370bd552f0912af6344 100644 --- a/crates/command_palette2/src/command_palette.rs +++ b/crates/command_palette2/src/command_palette.rs @@ -1,17 +1,15 @@ use collections::{CommandPaletteFilter, HashMap}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - actions, div, prelude::*, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, - FocusableView, Keystroke, ParentElement, Render, Styled, View, ViewContext, VisualContext, - WeakView, + actions, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, + Keystroke, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView, }; use picker::{Picker, PickerDelegate}; use std::{ cmp::{self, Reverse}, sync::Arc, }; -use theme::ActiveTheme; -use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, StyledExt}; +use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, ListItem}; use util::{ channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL}, ResultExt, @@ -141,7 +139,7 @@ impl CommandPaletteDelegate { } impl PickerDelegate for CommandPaletteDelegate { - type ListItem = Div; + type ListItem = ListItem; fn placeholder_text(&self) -> Arc { "Execute a command...".into() @@ -295,7 +293,6 @@ impl PickerDelegate for CommandPaletteDelegate { selected: bool, cx: &mut ViewContext>, ) -> Option { - let colors = cx.theme().colors(); let Some(r#match) = self.matches.get(ix) else { return None; }; @@ -304,23 +301,15 @@ impl PickerDelegate for CommandPaletteDelegate { }; Some( - div() - .px_1() - .text_color(colors.text) - .text_ui() - .bg(colors.ghost_element_background) - .rounded_md() - .when(selected, |this| this.bg(colors.ghost_element_selected)) - .hover(|this| this.bg(colors.ghost_element_hover)) - .child( - h_stack() - .justify_between() - .child(HighlightedLabel::new( - command.name.clone(), - r#match.positions.clone(), - )) - .children(KeyBinding::for_action(&*command.action, cx)), - ), + ListItem::new(ix).selected(selected).child( + h_stack() + .justify_between() + .child(HighlightedLabel::new( + command.name.clone(), + r#match.positions.clone(), + )) + .children(KeyBinding::for_action(&*command.action, cx)), + ), ) } } diff --git a/crates/file_finder2/src/file_finder.rs b/crates/file_finder2/src/file_finder.rs index c7d8e9eae0c1ed3d3c721af8e5a4dfb706637ddb..b54d28a400750b4caffbec99d163f561cf7f7784 100644 --- a/crates/file_finder2/src/file_finder.rs +++ b/crates/file_finder2/src/file_finder.rs @@ -2,9 +2,8 @@ use collections::HashMap; use editor::{scroll::autoscroll::Autoscroll, Bias, Editor}; use fuzzy::{CharBag, PathMatch, PathMatchCandidate}; use gpui::{ - actions, div, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, - InteractiveElement, IntoElement, Model, ParentElement, Render, Styled, Task, View, ViewContext, - VisualContext, WeakView, + actions, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Model, + ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView, }; use picker::{Picker, PickerDelegate}; use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId}; @@ -16,8 +15,7 @@ use std::{ }, }; use text::Point; -use theme::ActiveTheme; -use ui::{v_stack, HighlightedLabel, StyledExt}; +use ui::{v_stack, HighlightedLabel, ListItem}; use util::{paths::PathLikeWithPosition, post_inc, ResultExt}; use workspace::Workspace; @@ -530,7 +528,7 @@ impl FileFinderDelegate { } impl PickerDelegate for FileFinderDelegate { - type ListItem = Div; + type ListItem = ListItem; fn placeholder_text(&self) -> Arc { "Search project files...".into() @@ -716,26 +714,16 @@ impl PickerDelegate for FileFinderDelegate { .matches .get(ix) .expect("Invalid matches state: no element for index {ix}"); - let theme = cx.theme(); - let colors = theme.colors(); let (file_name, file_name_positions, full_path, full_path_positions) = self.labels_for_match(path_match, cx, ix); Some( - div() - .px_1() - .text_color(colors.text) - .text_ui() - .bg(colors.ghost_element_background) - .rounded_md() - .when(selected, |this| this.bg(colors.ghost_element_selected)) - .hover(|this| this.bg(colors.ghost_element_hover)) - .child( - v_stack() - .child(HighlightedLabel::new(file_name, file_name_positions)) - .child(HighlightedLabel::new(full_path, full_path_positions)), - ), + ListItem::new(ix).selected(selected).child( + v_stack() + .child(HighlightedLabel::new(file_name, file_name_positions)) + .child(HighlightedLabel::new(full_path, full_path_positions)), + ), ) } } diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index 875ab6d97e2032ae8c6be9bb9e0445d99897d099..642903f09b39ddbbe0ca0c71ec9b43b8bd9f93da 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -231,23 +231,16 @@ impl RenderOnce for ListSubHeader { } } -#[derive(Default, PartialEq, Copy, Clone)] -pub enum ListEntrySize { - #[default] - Small, - Medium, -} - #[derive(IntoElement)] pub struct ListItem { id: ElementId, disabled: bool, + selected: bool, // TODO: Reintroduce this // disclosure_control_style: DisclosureControlVisibility, indent_level: u32, left_slot: Option, overflow: OverflowStyle, - size: ListEntrySize, toggle: Toggle, variant: ListItemVariant, on_click: Option>, @@ -259,10 +252,10 @@ impl ListItem { Self { id: id.into(), disabled: false, + selected: false, indent_level: 0, left_slot: None, overflow: OverflowStyle::Hidden, - size: ListEntrySize::default(), toggle: Toggle::NotToggleable, variant: ListItemVariant::default(), on_click: Default::default(), @@ -290,6 +283,11 @@ impl ListItem { self } + pub fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } + pub fn left_content(mut self, left_content: GraphicSlot) -> Self { self.left_slot = Some(left_content); self @@ -304,11 +302,6 @@ impl ListItem { self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into())); self } - - pub fn size(mut self, size: ListEntrySize) -> Self { - self.size = size; - self - } } impl RenderOnce for ListItem { @@ -328,10 +321,6 @@ impl RenderOnce for ListItem { None => None, }; - let sized_item = match self.size { - ListEntrySize::Small => div().h_6(), - ListEntrySize::Medium => div().h_7(), - }; div() .id(self.id) .relative() @@ -354,8 +343,11 @@ impl RenderOnce for ListItem { // }) .hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) .active(|style| style.bg(cx.theme().colors().ghost_element_active)) + .when(self.selected, |this| { + this.bg(cx.theme().colors().ghost_element_selected) + }) .child( - sized_item + div() .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) // .ml(rems(0.75 * self.indent_level as f32)) .children((0..self.indent_level).map(|_| { From 600b564bbfb31d12240921dcab62fd7be16212ad Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 28 Nov 2023 16:40:43 +0100 Subject: [PATCH 055/151] Introduce surface rendering Co-Authored-By: Julia --- crates/call2/src/shared_screen.rs | 18 +-- crates/gpui2/build.rs | 2 + crates/gpui2/src/elements/img.rs | 77 ++++++---- .../gpui2/src/platform/mac/metal_renderer.rs | 138 +++++++++++++++++- crates/gpui2/src/platform/mac/shaders.metal | 52 +++++++ crates/gpui2/src/scene.rs | 66 +++++++++ crates/gpui2/src/window.rs | 22 ++- 7 files changed, 332 insertions(+), 43 deletions(-) diff --git a/crates/call2/src/shared_screen.rs b/crates/call2/src/shared_screen.rs index 7b7cd7d9df33045759ae9bda2f16e016f959c9d6..14b997af3b4f7221ff6a2acded9af516d7773310 100644 --- a/crates/call2/src/shared_screen.rs +++ b/crates/call2/src/shared_screen.rs @@ -3,8 +3,8 @@ use anyhow::Result; use client::{proto::PeerId, User}; use futures::StreamExt; use gpui::{ - div, AppContext, Div, Element, EventEmitter, FocusHandle, FocusableView, ParentElement, Render, - SharedString, Task, View, ViewContext, VisualContext, WindowContext, + div, img, AppContext, Div, Element, EventEmitter, FocusHandle, FocusableView, ParentElement, + Render, SharedString, Styled, Task, View, ViewContext, VisualContext, WindowContext, }; use std::sync::{Arc, Weak}; use workspace::{item::Item, ItemNavHistory, WorkspaceId}; @@ -68,15 +68,11 @@ impl Render for SharedScreen { type Element = Div; fn render(&mut self, _: &mut ViewContext) -> Self::Element { let frame = self.frame.clone(); - let frame_id = self.current_frame_id; - self.current_frame_id = self.current_frame_id.wrapping_add(1); - div().children(frame.map(|_| { - ui::Label::new(frame_id.to_string()).color(ui::Color::Error) - // img().data(Arc::new(ImageData::new(image::ImageBuffer::new( - // frame.width() as u32, - // frame.height() as u32, - // )))) - })) + // let frame_id = self.current_frame_id; + // self.current_frame_id = self.current_frame_id.wrapping_add(1); + div() + .size_full() + .children(frame.map(|frame| img().size_full().surface(frame.image()))) } } // impl View for SharedScreen { diff --git a/crates/gpui2/build.rs b/crates/gpui2/build.rs index 6e8a0868b969c7a85627fd974f0f1bde793eb587..24e493cb812d6e7478b266cc621eea7cbc77b051 100644 --- a/crates/gpui2/build.rs +++ b/crates/gpui2/build.rs @@ -65,6 +65,8 @@ fn generate_shader_bindings() -> PathBuf { "MonochromeSprite".into(), "PolychromeSprite".into(), "PathSprite".into(), + "SurfaceInputIndex".into(), + "SurfaceBounds".into(), ]); config.no_includes = true; config.enumeration.prefix_with_name = true; diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index 2aece17b4707f4c9fa473e7d7388e9ffdc8093f8..1c1fbd0961fb0a86a1c407d803245d11ce1c72d6 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -5,6 +5,7 @@ use crate::{ IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext, }; use futures::FutureExt; +use media::core_video::CVImageBuffer; use util::ResultExt; #[derive(Clone, Debug)] @@ -12,6 +13,7 @@ pub enum ImageSource { /// Image content will be loaded from provided URI at render time. Uri(SharedString), Data(Arc), + Surface(CVImageBuffer), } impl From for ImageSource { @@ -26,6 +28,12 @@ impl From> for ImageSource { } } +impl From for ImageSource { + fn from(value: CVImageBuffer) -> Self { + Self::Surface(value) + } +} + pub struct Img { interactivity: Interactivity, source: Option, @@ -45,11 +53,17 @@ impl Img { self.source = Some(ImageSource::from(uri.into())); self } + pub fn data(mut self, data: Arc) -> Self { self.source = Some(ImageSource::from(data)); self } + pub fn surface(mut self, data: CVImageBuffer) -> Self { + self.source = Some(ImageSource::from(data)); + self + } + pub fn source(mut self, source: impl Into) -> Self { self.source = Some(source.into()); self @@ -85,36 +99,41 @@ impl Element for Img { element_state, cx, |style, _scroll_offset, cx| { - let corner_radii = style.corner_radii; - - if let Some(source) = self.source { - let image = match source { - ImageSource::Uri(uri) => { - let image_future = cx.image_cache.get(uri.clone()); - if let Some(data) = image_future - .clone() - .now_or_never() - .and_then(|result| result.ok()) - { - data - } else { - cx.spawn(|mut cx| async move { - if image_future.await.ok().is_some() { - cx.on_next_frame(|cx| cx.notify()); - } - }) - .detach(); - return; + let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size()); + cx.with_z_index(1, |cx| { + if let Some(source) = self.source { + match source { + ImageSource::Uri(uri) => { + let image_future = cx.image_cache.get(uri.clone()); + if let Some(data) = image_future + .clone() + .now_or_never() + .and_then(|result| result.ok()) + { + cx.paint_image(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.notify()); + } + }) + .detach(); + } + } + + ImageSource::Data(image) => { + cx.paint_image(bounds, corner_radii, image, self.grayscale) + .log_err(); + } + + ImageSource::Surface(surface) => { + // TODO: Add support for corner_radii and grayscale. + cx.paint_surface(bounds, surface); } - } - ImageSource::Data(image) => image, - }; - let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size()); - cx.with_z_index(1, |cx| { - cx.paint_image(bounds, corner_radii, image, self.grayscale) - .log_err() - }); - } + }; + } + }); }, ) } diff --git a/crates/gpui2/src/platform/mac/metal_renderer.rs b/crates/gpui2/src/platform/mac/metal_renderer.rs index 0631c75de5e222e43f780f29fb9844f5e96c099c..19afb503324907a0b84bcac7722fc71ead05451d 100644 --- a/crates/gpui2/src/platform/mac/metal_renderer.rs +++ b/crates/gpui2/src/platform/mac/metal_renderer.rs @@ -1,7 +1,7 @@ use crate::{ point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels, Hsla, MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, - Quad, ScaledPixels, Scene, Shadow, Size, Underline, + Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline, }; use cocoa::{ base::{NO, YES}, @@ -9,6 +9,9 @@ use cocoa::{ quartzcore::AutoresizingMask, }; use collections::HashMap; +use core_foundation::base::TCFType; +use foreign_types::ForeignType; +use media::core_video::CVMetalTextureCache; use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange}; use objc::{self, msg_send, sel, sel_impl}; use smallvec::SmallVec; @@ -27,9 +30,11 @@ pub(crate) struct MetalRenderer { underlines_pipeline_state: metal::RenderPipelineState, monochrome_sprites_pipeline_state: metal::RenderPipelineState, polychrome_sprites_pipeline_state: metal::RenderPipelineState, + surfaces_pipeline_state: metal::RenderPipelineState, unit_vertices: metal::Buffer, instances: metal::Buffer, sprite_atlas: Arc, + core_video_texture_cache: CVMetalTextureCache, } impl MetalRenderer { @@ -143,6 +148,14 @@ impl MetalRenderer { "polychrome_sprite_fragment", MTLPixelFormat::BGRA8Unorm, ); + let surfaces_pipeline_state = build_pipeline_state( + &device, + &library, + "surfaces", + "surface_vertex", + "surface_fragment", + MTLPixelFormat::BGRA8Unorm, + ); let command_queue = device.new_command_queue(); let sprite_atlas = Arc::new(MetalAtlas::new(device.clone())); @@ -157,9 +170,11 @@ impl MetalRenderer { underlines_pipeline_state, monochrome_sprites_pipeline_state, polychrome_sprites_pipeline_state, + surfaces_pipeline_state, unit_vertices, instances, sprite_atlas, + core_video_texture_cache: CVMetalTextureCache::new(device.as_ptr()).unwrap(), } } @@ -268,6 +283,14 @@ impl MetalRenderer { command_encoder, ); } + PrimitiveBatch::Surfaces(surfaces) => { + self.draw_surfaces( + surfaces, + &mut instance_offset, + viewport_size, + command_encoder, + ); + } } } @@ -793,6 +816,102 @@ impl MetalRenderer { ); *offset = next_offset; } + + fn draw_surfaces( + &mut self, + surfaces: &[Surface], + offset: &mut usize, + viewport_size: Size, + command_encoder: &metal::RenderCommandEncoderRef, + ) { + command_encoder.set_render_pipeline_state(&self.surfaces_pipeline_state); + command_encoder.set_vertex_buffer( + SurfaceInputIndex::Vertices as u64, + Some(&self.unit_vertices), + 0, + ); + command_encoder.set_vertex_bytes( + SurfaceInputIndex::ViewportSize as u64, + mem::size_of_val(&viewport_size) as u64, + &viewport_size as *const Size as *const _, + ); + + for surface in surfaces { + let texture_size = size( + DevicePixels::from(surface.image_buffer.width() as i32), + DevicePixels::from(surface.image_buffer.height() as i32), + ); + + assert_eq!( + surface.image_buffer.pixel_format_type(), + media::core_video::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange + ); + + let y_texture = self + .core_video_texture_cache + .create_texture_from_image( + surface.image_buffer.as_concrete_TypeRef(), + ptr::null(), + MTLPixelFormat::R8Unorm, + surface.image_buffer.plane_width(0), + surface.image_buffer.plane_height(0), + 0, + ) + .unwrap(); + let cb_cr_texture = self + .core_video_texture_cache + .create_texture_from_image( + surface.image_buffer.as_concrete_TypeRef(), + ptr::null(), + MTLPixelFormat::RG8Unorm, + surface.image_buffer.plane_width(1), + surface.image_buffer.plane_height(1), + 1, + ) + .unwrap(); + + align_offset(offset); + let next_offset = *offset + mem::size_of::(); + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); + + command_encoder.set_vertex_buffer( + SurfaceInputIndex::Surfaces as u64, + Some(&self.instances), + *offset as u64, + ); + command_encoder.set_vertex_bytes( + SurfaceInputIndex::TextureSize as u64, + mem::size_of_val(&texture_size) as u64, + &texture_size as *const Size as *const _, + ); + command_encoder.set_fragment_texture( + SurfaceInputIndex::YTexture as u64, + Some(y_texture.as_texture_ref()), + ); + command_encoder.set_fragment_texture( + SurfaceInputIndex::CbCrTexture as u64, + Some(cb_cr_texture.as_texture_ref()), + ); + + unsafe { + let buffer_contents = + (self.instances.contents() as *mut u8).add(*offset) as *mut SurfaceBounds; + ptr::write( + buffer_contents, + SurfaceBounds { + bounds: surface.bounds, + content_mask: surface.content_mask.clone(), + }, + ); + } + + command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6); + *offset = next_offset; + } + } } fn build_pipeline_state( @@ -898,6 +1017,16 @@ enum SpriteInputIndex { AtlasTexture = 4, } +#[repr(C)] +enum SurfaceInputIndex { + Vertices = 0, + Surfaces = 1, + ViewportSize = 2, + TextureSize = 3, + YTexture = 4, + CbCrTexture = 5, +} + #[repr(C)] enum PathRasterizationInputIndex { Vertices = 0, @@ -911,3 +1040,10 @@ pub struct PathSprite { pub color: Hsla, pub tile: AtlasTile, } + +#[derive(Clone, Debug, Eq, PartialEq)] +#[repr(C)] +pub struct SurfaceBounds { + pub bounds: Bounds, + pub content_mask: ContentMask, +} diff --git a/crates/gpui2/src/platform/mac/shaders.metal b/crates/gpui2/src/platform/mac/shaders.metal index 4def1c33b85a430d051376322dca47de93b9e70c..aba01b9d5b059da1c1df55c0a01120d8be10775b 100644 --- a/crates/gpui2/src/platform/mac/shaders.metal +++ b/crates/gpui2/src/platform/mac/shaders.metal @@ -469,6 +469,58 @@ fragment float4 path_sprite_fragment( return color; } +struct SurfaceVertexOutput { + float4 position [[position]]; + float2 texture_position; + float clip_distance [[clip_distance]][4]; +}; + +struct SurfaceFragmentInput { + float4 position [[position]]; + float2 texture_position; +}; + +vertex SurfaceVertexOutput surface_vertex( + uint unit_vertex_id [[vertex_id]], uint surface_id [[instance_id]], + constant float2 *unit_vertices [[buffer(SurfaceInputIndex_Vertices)]], + constant SurfaceBounds *surfaces [[buffer(SurfaceInputIndex_Surfaces)]], + constant Size_DevicePixels *viewport_size + [[buffer(SurfaceInputIndex_ViewportSize)]], + constant Size_DevicePixels *texture_size + [[buffer(SurfaceInputIndex_TextureSize)]]) { + float2 unit_vertex = unit_vertices[unit_vertex_id]; + SurfaceBounds surface = surfaces[surface_id]; + float4 device_position = + to_device_position(unit_vertex, surface.bounds, viewport_size); + float4 clip_distance = distance_from_clip_rect(unit_vertex, surface.bounds, + surface.content_mask.bounds); + // We are going to copy the whole texture, so the texture position corresponds + // to the current vertex of the unit triangle. + float2 texture_position = unit_vertex; + return SurfaceVertexOutput{ + device_position, + texture_position, + {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}}; +} + +fragment float4 surface_fragment(SurfaceFragmentInput input [[stage_in]], + texture2d y_texture + [[texture(SurfaceInputIndex_YTexture)]], + texture2d cb_cr_texture + [[texture(SurfaceInputIndex_CbCrTexture)]]) { + constexpr sampler texture_sampler(mag_filter::linear, min_filter::linear); + const float4x4 ycbcrToRGBTransform = + float4x4(float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f), + float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f), + float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f), + float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f)); + float4 ycbcr = float4( + y_texture.sample(texture_sampler, input.texture_position).r, + cb_cr_texture.sample(texture_sampler, input.texture_position).rg, 1.0); + + return ycbcrToRGBTransform * ycbcr; +} + float4 hsla_to_rgba(Hsla hsla) { float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range float s = hsla.s; diff --git a/crates/gpui2/src/scene.rs b/crates/gpui2/src/scene.rs index 87e89adfa0e15d18512d2a1a748ea5a212a48489..549260560236ffb43179caf6cb8fce5340c10b7a 100644 --- a/crates/gpui2/src/scene.rs +++ b/crates/gpui2/src/scene.rs @@ -25,6 +25,7 @@ pub(crate) struct SceneBuilder { underlines: Vec, monochrome_sprites: Vec, polychrome_sprites: Vec, + surfaces: Vec, } impl Default for SceneBuilder { @@ -38,6 +39,7 @@ impl Default for SceneBuilder { underlines: Vec::new(), monochrome_sprites: Vec::new(), polychrome_sprites: Vec::new(), + surfaces: Vec::new(), } } } @@ -120,6 +122,7 @@ impl SceneBuilder { (PrimitiveKind::PolychromeSprite, ix) => { self.polychrome_sprites[ix].order = draw_order as DrawOrder } + (PrimitiveKind::Surface, ix) => self.surfaces[ix].order = draw_order as DrawOrder, } } @@ -129,6 +132,7 @@ impl SceneBuilder { self.underlines.sort_unstable(); self.monochrome_sprites.sort_unstable(); self.polychrome_sprites.sort_unstable(); + self.surfaces.sort_unstable(); Scene { shadows: mem::take(&mut self.shadows), @@ -137,6 +141,7 @@ impl SceneBuilder { underlines: mem::take(&mut self.underlines), monochrome_sprites: mem::take(&mut self.monochrome_sprites), polychrome_sprites: mem::take(&mut self.polychrome_sprites), + surfaces: mem::take(&mut self.surfaces), } } @@ -185,6 +190,10 @@ impl SceneBuilder { sprite.order = layer_id; self.polychrome_sprites.push(sprite); } + Primitive::Surface(mut surface) => { + surface.order = layer_id; + self.surfaces.push(surface); + } } } } @@ -196,6 +205,7 @@ pub(crate) struct Scene { pub underlines: Vec, pub monochrome_sprites: Vec, pub polychrome_sprites: Vec, + pub surfaces: Vec, } impl Scene { @@ -224,6 +234,9 @@ impl Scene { polychrome_sprites: &self.polychrome_sprites, polychrome_sprites_start: 0, polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(), + surfaces: &self.surfaces, + surfaces_start: 0, + surfaces_iter: self.surfaces.iter().peekable(), } } } @@ -247,6 +260,9 @@ struct BatchIterator<'a> { polychrome_sprites: &'a [PolychromeSprite], polychrome_sprites_start: usize, polychrome_sprites_iter: Peekable>, + surfaces: &'a [Surface], + surfaces_start: usize, + surfaces_iter: Peekable>, } impl<'a> Iterator for BatchIterator<'a> { @@ -272,6 +288,10 @@ impl<'a> Iterator for BatchIterator<'a> { self.polychrome_sprites_iter.peek().map(|s| s.order), PrimitiveKind::PolychromeSprite, ), + ( + self.surfaces_iter.peek().map(|s| s.order), + PrimitiveKind::Surface, + ), ]; orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind)); @@ -378,6 +398,21 @@ impl<'a> Iterator for BatchIterator<'a> { sprites: &self.polychrome_sprites[sprites_start..sprites_end], }) } + PrimitiveKind::Surface => { + let surfaces_start = self.surfaces_start; + let mut surfaces_end = surfaces_start; + while self + .surfaces_iter + .next_if(|surface| surface.order <= max_order) + .is_some() + { + surfaces_end += 1; + } + self.surfaces_start = surfaces_end; + Some(PrimitiveBatch::Surfaces( + &self.surfaces[surfaces_start..surfaces_end], + )) + } } } } @@ -391,6 +426,7 @@ pub enum PrimitiveKind { Underline, MonochromeSprite, PolychromeSprite, + Surface, } pub enum Primitive { @@ -400,6 +436,7 @@ pub enum Primitive { Underline(Underline), MonochromeSprite(MonochromeSprite), PolychromeSprite(PolychromeSprite), + Surface(Surface), } impl Primitive { @@ -411,6 +448,7 @@ impl Primitive { Primitive::Underline(underline) => &underline.bounds, Primitive::MonochromeSprite(sprite) => &sprite.bounds, Primitive::PolychromeSprite(sprite) => &sprite.bounds, + Primitive::Surface(surface) => &surface.bounds, } } @@ -422,6 +460,7 @@ impl Primitive { Primitive::Underline(underline) => &underline.content_mask, Primitive::MonochromeSprite(sprite) => &sprite.content_mask, Primitive::PolychromeSprite(sprite) => &sprite.content_mask, + Primitive::Surface(surface) => &surface.content_mask, } } } @@ -440,6 +479,7 @@ pub(crate) enum PrimitiveBatch<'a> { texture_id: AtlasTextureId, sprites: &'a [PolychromeSprite], }, + Surfaces(&'a [Surface]), } #[derive(Default, Debug, Clone, Eq, PartialEq)] @@ -593,6 +633,32 @@ impl From for Primitive { } } +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Surface { + pub order: u32, + pub bounds: Bounds, + pub content_mask: ContentMask, + pub image_buffer: media::core_video::CVImageBuffer, +} + +impl Ord for Surface { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.order.cmp(&other.order) + } +} + +impl PartialOrd for Surface { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl From for Primitive { + fn from(surface: Surface) -> Self { + Primitive::Surface(surface) + } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub(crate) struct PathId(pub(crate) usize); diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 20561c544368b6b9c41124194c07ada33145c968..1f98860f09c40eed3a427225dc9aa22fb7494986 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -8,8 +8,8 @@ use crate::{ MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, - Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, - VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline, + UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; use collections::HashMap; @@ -18,6 +18,7 @@ use futures::{ channel::{mpsc, oneshot}, StreamExt, }; +use media::core_video::CVImageBuffer; use parking_lot::RwLock; use slotmap::SlotMap; use smallvec::SmallVec; @@ -1090,6 +1091,23 @@ impl<'a> WindowContext<'a> { Ok(()) } + /// Paint a surface into the scene for the current frame at the current z-index. + pub fn paint_surface(&mut self, bounds: Bounds, image_buffer: CVImageBuffer) { + let scale_factor = self.scale_factor(); + let bounds = bounds.scale(scale_factor); + let content_mask = self.content_mask().scale(scale_factor); + let window = &mut *self.window; + window.current_frame.scene_builder.insert( + &window.current_frame.z_index_stack, + Surface { + order: 0, + bounds, + content_mask, + image_buffer, + }, + ); + } + /// Draw pixels to the display for this window based on the contents of its scene. pub(crate) fn draw(&mut self) { let root_view = self.window.root_view.take().unwrap(); From ecb3bd7f5943d2bd00e9dfd3f64843399f2749d9 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 28 Nov 2023 10:52:17 -0500 Subject: [PATCH 056/151] Use `ListItem`s in `Picker` story --- crates/storybook2/src/stories/picker.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/crates/storybook2/src/stories/picker.rs b/crates/storybook2/src/stories/picker.rs index f66fe7a93af28ddf5ba5274594fc03597299d915..8bcfb8923dbaef403ec65916821ae2341367f136 100644 --- a/crates/storybook2/src/stories/picker.rs +++ b/crates/storybook2/src/stories/picker.rs @@ -5,6 +5,7 @@ use gpui::{ use picker::{Picker, PickerDelegate}; use std::sync::Arc; use theme2::ActiveTheme; +use ui::{Label, ListItem}; pub struct PickerStory { picker: View>, @@ -36,7 +37,7 @@ impl Delegate { } impl PickerDelegate for Delegate { - type ListItem = Div; + type ListItem = ListItem; fn match_count(&self) -> usize { self.candidates.len() @@ -52,7 +53,6 @@ impl PickerDelegate for Delegate { selected: bool, cx: &mut gpui::ViewContext>, ) -> Option { - let colors = cx.theme().colors(); let Some(candidate_ix) = self.matches.get(ix) else { return None; }; @@ -60,17 +60,9 @@ impl PickerDelegate for Delegate { let candidate = SharedString::from(self.candidates[*candidate_ix].string.clone()); Some( - div() - .text_color(colors.text) - .when(selected, |s| { - s.border_l_10().border_color(colors.terminal_ansi_yellow) - }) - .hover(|style| { - style - .bg(colors.element_active) - .text_color(colors.text_accent) - }) - .child(candidate), + ListItem::new(ix) + .selected(selected) + .child(Label::new(candidate)), ) } From 3ac545088ac41e7b27ed855de84ba9cfafdceaf9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 28 Nov 2023 17:32:27 +0100 Subject: [PATCH 057/151] WIP: preserve aspect ratio of images --- crates/call2/src/shared_screen.rs | 2 +- crates/gpui2/src/elements/img.rs | 134 ++++++++++++++++------------ crates/gpui2/src/geometry.rs | 6 ++ crates/ui2/src/components/avatar.rs | 5 +- 4 files changed, 88 insertions(+), 59 deletions(-) diff --git a/crates/call2/src/shared_screen.rs b/crates/call2/src/shared_screen.rs index 14b997af3b4f7221ff6a2acded9af516d7773310..58d8445f8f0ef242ba563eeca76d2c3da5d78030 100644 --- a/crates/call2/src/shared_screen.rs +++ b/crates/call2/src/shared_screen.rs @@ -72,7 +72,7 @@ impl Render for SharedScreen { // self.current_frame_id = self.current_frame_id.wrapping_add(1); div() .size_full() - .children(frame.map(|frame| img().size_full().surface(frame.image()))) + .children(frame.map(|frame| img(frame.image()).w_full())) } } // impl View for SharedScreen { diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index 1c1fbd0961fb0a86a1c407d803245d11ce1c72d6..87a7cda07fe11d9f9da0fee9c33a76551b3b8347 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use crate::{ - Bounds, Element, ImageData, InteractiveElement, InteractiveElementState, Interactivity, - IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext, + size, Bounds, Element, ImageData, InteractiveElement, InteractiveElementState, Interactivity, + IntoElement, LayoutId, Pixels, SharedString, Size, StyleRefinement, Styled, WindowContext, }; use futures::FutureExt; use media::core_video::CVImageBuffer; @@ -36,38 +36,19 @@ impl From for ImageSource { pub struct Img { interactivity: Interactivity, - source: Option, + source: ImageSource, grayscale: bool, } -pub fn img() -> Img { +pub fn img(source: impl Into) -> Img { Img { interactivity: Interactivity::default(), - source: None, + source: source.into(), grayscale: false, } } impl Img { - pub fn uri(mut self, uri: impl Into) -> Self { - self.source = Some(ImageSource::from(uri.into())); - self - } - - pub fn data(mut self, data: Arc) -> Self { - self.source = Some(ImageSource::from(data)); - self - } - - pub fn surface(mut self, data: CVImageBuffer) -> Self { - self.source = Some(ImageSource::from(data)); - self - } - - pub fn source(mut self, source: impl Into) -> Self { - self.source = Some(source.into()); - self - } pub fn grayscale(mut self, grayscale: bool) -> Self { self.grayscale = grayscale; self @@ -83,7 +64,52 @@ impl Element for Img { cx: &mut WindowContext, ) -> (LayoutId, Self::State) { self.interactivity.layout(element_state, cx, |style, cx| { - cx.request_layout(&style, None) + let image_size = match &self.source { + ImageSource::Uri(uri) => { + let image_future = cx.image_cache.get(uri.clone()); + if let Some(data) = image_future + .clone() + .now_or_never() + .and_then(|result| result.ok()) + { + data.size().map(|pixels| Pixels::from(u32::from(pixels))) + } else { + Size::default() + } + } + + ImageSource::Data(data) => { + data.size().map(|pixels| Pixels::from(u32::from(pixels))) + } + + ImageSource::Surface(surface) => { + size(surface.width().into(), surface.height().into()) + } + }; + dbg!(image_size); + + cx.request_measured_layout( + style, + cx.rem_size(), + move |known_dimensions, available_space| match dbg!( + known_dimensions.width, + known_dimensions.height, + ) { + (None, None) => image_size, + + (None, Some(height)) => { + let aspect_ratio = height / image_size.height; + size(image_size.width * aspect_ratio, height) + } + + (Some(width), None) => { + let aspect_ratio = width / image_size.width; + size(width, image_size.height * aspect_ratio) + } + + (Some(width), Some(height)) => size(width, height), + }, + ) }) } @@ -101,38 +127,36 @@ impl Element for Img { |style, _scroll_offset, cx| { let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size()); cx.with_z_index(1, |cx| { - if let Some(source) = self.source { - match source { - ImageSource::Uri(uri) => { - let image_future = cx.image_cache.get(uri.clone()); - if let Some(data) = image_future - .clone() - .now_or_never() - .and_then(|result| result.ok()) - { - cx.paint_image(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.notify()); - } - }) - .detach(); - } - } - - ImageSource::Data(image) => { - cx.paint_image(bounds, corner_radii, image, self.grayscale) + match self.source { + ImageSource::Uri(uri) => { + let image_future = cx.image_cache.get(uri.clone()); + if let Some(data) = image_future + .clone() + .now_or_never() + .and_then(|result| result.ok()) + { + cx.paint_image(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.notify()); + } + }) + .detach(); } - - ImageSource::Surface(surface) => { - // TODO: Add support for corner_radii and grayscale. - cx.paint_surface(bounds, surface); - } - }; - } + } + + ImageSource::Data(image) => { + cx.paint_image(bounds, corner_radii, image, self.grayscale) + .log_err(); + } + + ImageSource::Surface(surface) => { + // TODO: Add support for corner_radii and grayscale. + cx.paint_surface(bounds, surface); + } + }; }); }, ) diff --git a/crates/gpui2/src/geometry.rs b/crates/gpui2/src/geometry.rs index e1f039e309466bcd07e867551b2e082b27c6a186..5ade68663c6ea4a379663044027bd0dbef9061ae 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -905,6 +905,12 @@ impl From for usize { } } +impl From for Pixels { + fn from(pixels: usize) -> Self { + Pixels(pixels as f32) + } +} + #[derive( Add, AddAssign, Clone, Copy, Default, Div, Eq, Hash, Ord, PartialEq, PartialOrd, Sub, SubAssign, )] diff --git a/crates/ui2/src/components/avatar.rs b/crates/ui2/src/components/avatar.rs index d358b221da9287f488bd68dd95bcff659191b5a5..f74a7d02eadfa5a076ce07c219983f4e60853551 100644 --- a/crates/ui2/src/components/avatar.rs +++ b/crates/ui2/src/components/avatar.rs @@ -20,7 +20,7 @@ impl RenderOnce for Avatar { type Rendered = Img; fn render(self, _: &mut WindowContext) -> Self::Rendered { - let mut img = img(); + let mut img = img(self.src); if self.shape == Shape::Circle { img = img.rounded_full(); @@ -28,8 +28,7 @@ impl RenderOnce for Avatar { img = img.rounded_md(); } - img.source(self.src.clone()) - .size_4() + img.size_4() // todo!(Pull the avatar fallback background from the theme.) .bg(gpui::red()) } From 5451db9c96acefe842654e23b33230855cd0eb48 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 28 Nov 2023 18:39:03 +0200 Subject: [PATCH 058/151] Use proper npm arguments and clean its inherited env vars --- crates/node_runtime/src/node_runtime.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index 2621c58120af8ae3e711637af7a941dcc24af150..ecabdeb71817af4120cd3f9118c66adc85bd91b3 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -73,6 +73,7 @@ impl RealNodeRuntime { let npm_file = node_dir.join("bin/npm"); let result = Command::new(&node_binary) + .env_clear() .arg(npm_file) .arg("--version") .stdin(Stdio::null()) @@ -149,6 +150,7 @@ impl NodeRuntime for RealNodeRuntime { } let mut command = Command::new(node_binary); + command.env_clear(); command.env("PATH", env_path); command.arg(npm_file).arg(subcommand); command.args(["--cache".into(), installation_path.join("cache")]); @@ -200,11 +202,11 @@ impl NodeRuntime for RealNodeRuntime { &[ name, "--json", - "-fetch-retry-mintimeout", + "--fetch-retry-mintimeout", "2000", - "-fetch-retry-maxtimeout", + "--fetch-retry-maxtimeout", "5000", - "-fetch-timeout", + "--fetch-timeout", "5000", ], ) @@ -229,11 +231,11 @@ impl NodeRuntime for RealNodeRuntime { let mut arguments: Vec<_> = packages.iter().map(|p| p.as_str()).collect(); arguments.extend_from_slice(&[ - "-fetch-retry-mintimeout", + "--fetch-retry-mintimeout", "2000", - "-fetch-retry-maxtimeout", + "--fetch-retry-maxtimeout", "5000", - "-fetch-timeout", + "--fetch-timeout", "5000", ]); From a761e6ca0ea187061dee36e77ce61ac740c4f862 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 28 Nov 2023 09:59:58 -0700 Subject: [PATCH 059/151] More mouse occlusion work --- crates/editor2/src/element.rs | 73 +++++++++++++++++++++++------ crates/editor2/src/hover_popover.rs | 3 -- crates/gpui2/src/elements/div.rs | 6 +-- crates/gpui2/src/window.rs | 12 ++--- crates/workspace2/src/workspace2.rs | 11 ++--- 5 files changed, 73 insertions(+), 32 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 6a6afd5461b6abb1b2944635216b5fd8ff5270a2..74ca292ecf8b712588ab0f1713c2a93d9619d92d 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -22,10 +22,11 @@ use collections::{BTreeMap, HashMap}; use gpui::{ div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId, - ElementInputHandler, Entity, EntityId, Hsla, InteractiveElement, IntoElement, LineLayout, - MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, RenderOnce, - ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled, - TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine, + ElementInputHandler, Entity, EntityId, Hsla, InteractiveBounds, InteractiveElement, + IntoElement, LineLayout, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, + ParentElement, Pixels, RenderOnce, ScrollWheelEvent, ShapedLine, SharedString, Size, + StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View, + ViewContext, WeakView, WindowContext, WrappedLine, }; use itertools::Itertools; use language::language_settings::ShowWhitespaceSetting; @@ -316,6 +317,7 @@ impl EditorElement { position_map: &PositionMap, text_bounds: Bounds, gutter_bounds: Bounds, + stacking_order: &StackingOrder, cx: &mut ViewContext, ) -> bool { let mut click_count = event.click_count; @@ -326,6 +328,9 @@ impl EditorElement { } else if !text_bounds.contains_point(&event.position) { return false; } + if !cx.was_top_layer(&event.position, stacking_order) { + return false; + } let point_for_position = position_map.point_for_position(text_bounds, event.position); let position = point_for_position.previous_valid; @@ -384,6 +389,7 @@ impl EditorElement { event: &MouseUpEvent, position_map: &PositionMap, text_bounds: Bounds, + stacking_order: &StackingOrder, cx: &mut ViewContext, ) -> bool { let end_selection = editor.has_pending_selection(); @@ -396,6 +402,7 @@ impl EditorElement { if !pending_nonempty_selections && event.modifiers.command && text_bounds.contains_point(&event.position) + && cx.was_top_layer(&event.position, stacking_order) { let point = position_map.point_for_position(text_bounds, event.position); let could_be_inlay = point.as_valid().is_none(); @@ -418,6 +425,7 @@ impl EditorElement { position_map: &PositionMap, text_bounds: Bounds, gutter_bounds: Bounds, + stacking_order: &StackingOrder, cx: &mut ViewContext, ) -> bool { let modifiers = event.modifiers; @@ -457,10 +465,12 @@ impl EditorElement { let text_hovered = text_bounds.contains_point(&event.position); let gutter_hovered = gutter_bounds.contains_point(&event.position); + let was_top = cx.was_top_layer(&event.position, stacking_order); + editor.set_gutter_hovered(gutter_hovered, cx); // Don't trigger hover popover if mouse is hovering over context menu - if text_hovered { + if text_hovered && was_top { let point_for_position = position_map.point_for_position(text_bounds, event.position); match point_for_position.as_valid() { @@ -490,7 +500,7 @@ impl EditorElement { } else { update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx); hover_at(editor, None, cx); - gutter_hovered + gutter_hovered && was_top } } @@ -498,10 +508,10 @@ impl EditorElement { editor: &mut Editor, event: &ScrollWheelEvent, position_map: &PositionMap, - bounds: Bounds, + bounds: &InteractiveBounds, cx: &mut ViewContext, ) -> bool { - if !bounds.contains_point(&event.position) { + if !bounds.visibly_contains(&event.position, cx) { return false; } @@ -2282,10 +2292,15 @@ impl EditorElement { cx: &mut WindowContext, ) { let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO); + let interactive_bounds = InteractiveBounds { + bounds: bounds.intersect(&cx.content_mask().bounds), + stacking_order: cx.stacking_order().clone(), + }; cx.on_mouse_event({ let position_map = layout.position_map.clone(); let editor = self.editor.clone(); + let interactive_bounds = interactive_bounds.clone(); move |event: &ScrollWheelEvent, phase, cx| { if phase != DispatchPhase::Bubble { @@ -2293,7 +2308,7 @@ impl EditorElement { } let should_cancel = editor.update(cx, |editor, cx| { - Self::scroll(editor, event, &position_map, bounds, cx) + Self::scroll(editor, event, &position_map, &interactive_bounds, cx) }); if should_cancel { cx.stop_propagation(); @@ -2304,6 +2319,7 @@ impl EditorElement { 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: &MouseDownEvent, phase, cx| { if phase != DispatchPhase::Bubble { @@ -2311,7 +2327,15 @@ impl EditorElement { } let should_cancel = editor.update(cx, |editor, cx| { - Self::mouse_down(editor, event, &position_map, text_bounds, gutter_bounds, cx) + Self::mouse_down( + editor, + event, + &position_map, + text_bounds, + gutter_bounds, + &stacking_order, + cx, + ) }); if should_cancel { @@ -2323,9 +2347,18 @@ impl EditorElement { 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| { let should_cancel = editor.update(cx, |editor, cx| { - Self::mouse_up(editor, event, &position_map, text_bounds, cx) + Self::mouse_up( + editor, + event, + &position_map, + text_bounds, + &stacking_order, + cx, + ) }); if should_cancel { @@ -2351,13 +2384,23 @@ impl EditorElement { 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 phase != DispatchPhase::Bubble { return; } let stop_propogating = editor.update(cx, |editor, cx| { - Self::mouse_moved(editor, event, &position_map, text_bounds, gutter_bounds, cx) + Self::mouse_moved( + editor, + event, + &position_map, + text_bounds, + gutter_bounds, + &stacking_order, + cx, + ) }); if stop_propogating { @@ -2617,9 +2660,11 @@ impl Element for EditorElement { // We call with_z_index to establish a new stacking context. cx.with_z_index(0, |cx| { cx.with_content_mask(Some(ContentMask { bounds }), |cx| { - // Paint mouse listeners first, so any elements we paint on top of the editor + // Paint mouse listeners at z-index 0 so any elements we paint on top of the editor // take precedence. - self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx); + cx.with_z_index(0, |cx| { + self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx); + }); let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx); cx.handle_input(&focus_handle, input_handler); diff --git a/crates/editor2/src/hover_popover.rs b/crates/editor2/src/hover_popover.rs index 37c7df650b126c859c28339a09077a96f4844bf2..f80168ed250d3f3bdcbedb7ad1d61e9e173f15ef 100644 --- a/crates/editor2/src/hover_popover.rs +++ b/crates/editor2/src/hover_popover.rs @@ -483,9 +483,6 @@ impl InfoPopover { // Prevent a mouse move on the popover from being propagated to the editor, // because that would dismiss the popover. .on_mouse_move(|_, cx| cx.stop_propagation()) - // Prevent a mouse down on the popover from being propagated to the editor, - // because that would move the cursor. - .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation()) .child(crate::render_parsed_markdown( "content", &self.parsed_content, diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 6185cd644364794eec85f6218c62c7e3643f5cb8..635e8b634f0f6982fffa2d35ca46da5bfb736e90 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -723,12 +723,12 @@ pub struct Interactivity { #[derive(Clone)] pub struct InteractiveBounds { - bounds: Bounds, - stacking_order: StackingOrder, + pub bounds: Bounds, + pub stacking_order: StackingOrder, } impl InteractiveBounds { - fn visibly_contains(&self, point: &Point, cx: &WindowContext) -> bool { + pub fn visibly_contains(&self, point: &Point, cx: &WindowContext) -> bool { self.bounds.contains_point(point) && cx.was_top_layer(&point, &self.stacking_order) } } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 0d444d762ecd80c7846aa71c062dcfeb755e5a7d..6f342f70654653808d6b9797c898682ba4c532e8 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -12,7 +12,7 @@ use crate::{ VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; -use collections::{BTreeMap, HashMap}; +use collections::HashMap; use derive_more::{Deref, DerefMut}; use futures::{ channel::{mpsc, oneshot}, @@ -243,7 +243,7 @@ pub(crate) struct Frame { pub(crate) dispatch_tree: DispatchTree, pub(crate) focus_listeners: Vec, pub(crate) scene_builder: SceneBuilder, - pub(crate) depth_map: BTreeMap>, + pub(crate) depth_map: Vec<(StackingOrder, Bounds)>, pub(crate) z_index_stack: StackingOrder, content_mask_stack: Vec>, element_offset_stack: Vec>, @@ -811,10 +811,10 @@ impl<'a> WindowContext<'a> { /// 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.current_frame.z_index_stack.clone(); - self.window - .current_frame - .depth_map - .insert(stacking_order, bounds); + let depth_map = &mut self.window.current_frame.depth_map; + match depth_map.binary_search_by(|(level, _)| stacking_order.cmp(&level)) { + Ok(i) | Err(i) => depth_map.insert(i, (stacking_order, bounds)), + } } /// Returns true if the top-most opaque layer painted over this point was part of the diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 50f8611c4cc645c414ad68c030867ab4b8f07dab..6fa722f7757615ad01acdddfc8d527f457f94411 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -2640,12 +2640,11 @@ impl Workspace { .flex_col() .justify_end() .gap_2() - .children(self.notifications.iter().map(|(_, _, notification)| { - div() - .on_any_mouse_down(|_, cx| cx.stop_propagation()) - .on_any_mouse_up(|_, cx| cx.stop_propagation()) - .child(notification.to_any()) - })), + .children( + self.notifications + .iter() + .map(|(_, _, notification)| notification.to_any()), + ), ) } } From 94118987206bbfb5da747034faaa5839dff0e51a Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 28 Nov 2023 13:11:43 -0500 Subject: [PATCH 060/151] Use `ListItem`s in the project panel (#3421) This PR reworks the project panel to render its items using the `ListItem` component. There are a few hacks in here in order to get click handlers working for the `ListItem`, but we'll want to get these fixed in GPUI. Release Notes: - N/A --- crates/project_panel2/src/project_panel.rs | 66 +++----- crates/ui2/src/components/list.rs | 142 +++++++----------- .../ui2/src/components/stories/list_item.rs | 8 + 3 files changed, 84 insertions(+), 132 deletions(-) diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index b0272098702f7006649850c93f57148e557e6115..d4c63e75bf68057bffa8f616f47fa8488e28bedf 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -10,9 +10,8 @@ use anyhow::{anyhow, Result}; use gpui::{ actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext, ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement, - IntoElement, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, - Render, Stateful, StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View, - ViewContext, VisualContext as _, WeakView, WindowContext, + Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, Stateful, Styled, + Task, UniformListScrollHandle, View, ViewContext, VisualContext as _, WeakView, WindowContext, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{ @@ -30,7 +29,7 @@ use std::{ sync::Arc, }; use theme::ActiveTheme as _; -use ui::{h_stack, v_stack, IconElement, Label}; +use ui::{v_stack, IconElement, Label, ListItem}; use unicase::UniCase; use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ @@ -1335,13 +1334,19 @@ impl ProjectPanel { } } - fn render_entry_visual_element( - details: &EntryDetails, - editor: Option<&View>, - padding: Pixels, + fn render_entry( + &self, + entry_id: ProjectEntryId, + details: EntryDetails, + // dragged_entry_destination: &mut Option>, cx: &mut ViewContext, - ) -> Div { + ) -> ListItem { + let kind = details.kind; + let settings = ProjectPanelSettings::get_global(cx); let show_editor = details.is_editing && !details.is_processing; + let is_selected = self + .selection + .map_or(false, |selection| selection.entry_id == entry_id); let theme = cx.theme(); let filename_text_color = details @@ -1354,14 +1359,17 @@ impl ProjectPanel { }) .unwrap_or(theme.status().info); - h_stack() + ListItem::new(entry_id.to_proto() as usize) + .indent_level(details.depth) + .indent_step_size(px(settings.indent_size)) + .selected(is_selected) .child(if let Some(icon) = &details.icon { div().child(IconElement::from_path(icon.to_string())) } else { div() }) .child( - if let (Some(editor), true) = (editor, show_editor) { + if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) { div().w_full().child(editor.clone()) } else { div() @@ -1370,33 +1378,6 @@ impl ProjectPanel { } .ml_1(), ) - .pl(padding) - } - - fn render_entry( - &self, - entry_id: ProjectEntryId, - details: EntryDetails, - // dragged_entry_destination: &mut Option>, - cx: &mut ViewContext, - ) -> Stateful
{ - let kind = details.kind; - let settings = ProjectPanelSettings::get_global(cx); - const INDENT_SIZE: Pixels = px(16.0); - let padding = INDENT_SIZE + details.depth as f32 * px(settings.indent_size); - let show_editor = details.is_editing && !details.is_processing; - let is_selected = self - .selection - .map_or(false, |selection| selection.entry_id == entry_id); - - Self::render_entry_visual_element(&details, Some(&self.filename_editor), padding, cx) - .id(entry_id.to_proto() as usize) - .w_full() - .cursor_pointer() - .when(is_selected, |this| { - this.bg(cx.theme().colors().element_selected) - }) - .hover(|style| style.bg(cx.theme().colors().element_hover)) .on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| { if !show_editor { if kind.is_dir() { @@ -1410,12 +1391,9 @@ impl ProjectPanel { } } })) - .on_mouse_down( - MouseButton::Right, - cx.listener(move |this, event: &MouseDownEvent, cx| { - this.deploy_context_menu(event.position, entry_id, cx); - }), - ) + .on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| { + this.deploy_context_menu(event.position, entry_id, cx); + })) // .on_drop::(|this, event, cx| { // this.move_entry( // *dragged_entry, diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index 642903f09b39ddbbe0ca0c71ec9b43b8bd9f93da..6683c9a0cff0f6640f1f837b36fbe03ad76fa176 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -1,8 +1,10 @@ +use std::rc::Rc; + use gpui::{ - div, px, AnyElement, ClickEvent, Div, IntoElement, Stateful, StatefulInteractiveElement, + div, px, AnyElement, ClickEvent, Div, IntoElement, MouseButton, MouseDownEvent, Pixels, + Stateful, StatefulInteractiveElement, }; use smallvec::SmallVec; -use std::rc::Rc; use crate::{ disclosure_control, h_stack, v_stack, Avatar, Icon, IconElement, IconSize, Label, Toggle, @@ -117,66 +119,6 @@ impl ListHeader { self.meta = meta; self } - - // before_ship!("delete") - // fn render(self, cx: &mut WindowContext) -> impl Element { - // let disclosure_control = disclosure_control(self.toggle); - - // let meta = match self.meta { - // Some(ListHeaderMeta::Tools(icons)) => div().child( - // h_stack() - // .gap_2() - // .items_center() - // .children(icons.into_iter().map(|i| { - // IconElement::new(i) - // .color(TextColor::Muted) - // .size(IconSize::Small) - // })), - // ), - // Some(ListHeaderMeta::Button(label)) => div().child(label), - // Some(ListHeaderMeta::Text(label)) => div().child(label), - // None => div(), - // }; - - // h_stack() - // .w_full() - // .bg(cx.theme().colors().surface_background) - // // TODO: Add focus state - // // .when(self.state == InteractionState::Focused, |this| { - // // this.border() - // // .border_color(cx.theme().colors().border_focused) - // // }) - // .relative() - // .child( - // div() - // .h_5() - // .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) - // .flex() - // .flex_1() - // .items_center() - // .justify_between() - // .w_full() - // .gap_1() - // .child( - // h_stack() - // .gap_1() - // .child( - // div() - // .flex() - // .gap_1() - // .items_center() - // .children(self.left_icon.map(|i| { - // IconElement::new(i) - // .color(TextColor::Muted) - // .size(IconSize::Small) - // })) - // .child(Label::new(self.label.clone()).color(TextColor::Muted)), - // ) - // .child(disclosure_control), - // ) - // .child(meta), - // ) - // } } #[derive(IntoElement, Clone)] @@ -238,12 +180,14 @@ pub struct ListItem { selected: bool, // TODO: Reintroduce this // disclosure_control_style: DisclosureControlVisibility, - indent_level: u32, + indent_level: usize, + indent_step_size: Pixels, left_slot: Option, overflow: OverflowStyle, toggle: Toggle, variant: ListItemVariant, on_click: Option>, + on_secondary_mouse_down: Option>, children: SmallVec<[AnyElement; 2]>, } @@ -254,11 +198,13 @@ impl ListItem { disabled: false, selected: false, indent_level: 0, + indent_step_size: px(12.), left_slot: None, overflow: OverflowStyle::Hidden, toggle: Toggle::NotToggleable, variant: ListItemVariant::default(), - on_click: Default::default(), + on_click: None, + on_secondary_mouse_down: None, children: SmallVec::new(), } } @@ -268,16 +214,29 @@ impl ListItem { self } + pub fn on_secondary_mouse_down( + mut self, + handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, + ) -> Self { + self.on_secondary_mouse_down = Some(Rc::new(handler)); + self + } + pub fn variant(mut self, variant: ListItemVariant) -> Self { self.variant = variant; self } - pub fn indent_level(mut self, indent_level: u32) -> Self { + pub fn indent_level(mut self, indent_level: usize) -> Self { self.indent_level = indent_level; self } + pub fn indent_step_size(mut self, indent_step_size: Pixels) -> Self { + self.indent_step_size = indent_step_size; + self + } + pub fn toggle(mut self, toggle: Toggle) -> Self { self.toggle = toggle; self @@ -328,14 +287,6 @@ impl RenderOnce for ListItem { style.background = Some(cx.theme().colors().editor_background.into()); style }) - .on_click({ - let on_click = self.on_click.clone(); - move |event, cx| { - if let Some(on_click) = &on_click { - (on_click)(event, cx) - } - } - }) // TODO: Add focus state // .when(self.state == InteractionState::Focused, |this| { // this.border() @@ -346,30 +297,45 @@ impl RenderOnce for ListItem { .when(self.selected, |this| { this.bg(cx.theme().colors().ghost_element_selected) }) + .when_some(self.on_click.clone(), |this, on_click| { + this.on_click(move |event, cx| { + // HACK: GPUI currently fires `on_click` with any mouse button, + // but we only care about the left button. + if event.down.button == MouseButton::Left { + (on_click)(event, cx) + } + }) + }) + .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| { + this.on_mouse_down(MouseButton::Right, move |event, cx| { + (on_mouse_down)(event, cx) + }) + }) .child( div() .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) - // .ml(rems(0.75 * self.indent_level as f32)) - .children((0..self.indent_level).map(|_| { - div() - .w(px(4.)) - .h_full() - .flex() - .justify_center() - .group_hover("", |style| style.bg(cx.theme().colors().border_focused)) - .child( - h_stack() - .child(div().w_px().h_full()) - .child(div().w_px().h_full().bg(cx.theme().colors().border)), - ) - })) + .ml(self.indent_level as f32 * self.indent_step_size) .flex() .gap_1() .items_center() .relative() .child(disclosure_control(self.toggle)) .children(left_content) - .children(self.children), + .children(self.children) + // HACK: We need to attach the `on_click` handler to the child element in order to have the click + // event actually fire. + // Once this is fixed in GPUI we can remove this and rely on the `on_click` handler set above on the + // outer `div`. + .id("on_click_hack") + .when_some(self.on_click, |this, on_click| { + this.on_click(move |event, cx| { + // HACK: GPUI currently fires `on_click` with any mouse button, + // but we only care about the left button. + if event.down.button == MouseButton::Left { + (on_click)(event, cx) + } + }) + }), ) } } diff --git a/crates/ui2/src/components/stories/list_item.rs b/crates/ui2/src/components/stories/list_item.rs index ee1b4d0be6d274f79191c42542a01ee8c7b77931..ec0da7f07e8d8784aacf42fdb551b39521952c73 100644 --- a/crates/ui2/src/components/stories/list_item.rs +++ b/crates/ui2/src/components/stories/list_item.rs @@ -22,5 +22,13 @@ impl Render for ListItemStory { println!("Clicked!"); }), ) + .child(Story::label("With `on_secondary_mouse_down`")) + .child( + ListItem::new("with_on_secondary_mouse_down").on_secondary_mouse_down( + |_event, _cx| { + println!("Right mouse down!"); + }, + ), + ) } } From 874fde09ab85ade8fa66091e7d52f3bd1882e67a Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 28 Nov 2023 14:27:19 -0500 Subject: [PATCH 061/151] Add inset variant to `ListItem` (#3422) This PR adds an inset variant to the `ListItem` component. We're now using this inset variant for the `ListItem`s we render in pickers. Release Notes: - N/A --- .../command_palette2/src/command_palette.rs | 2 +- crates/file_finder2/src/file_finder.rs | 2 +- crates/storybook2/src/stories/picker.rs | 1 + crates/ui2/src/components/list.rs | 104 +++++++++--------- 4 files changed, 54 insertions(+), 55 deletions(-) diff --git a/crates/command_palette2/src/command_palette.rs b/crates/command_palette2/src/command_palette.rs index 5f3fb4a98595afc3b6930370bd552f0912af6344..b0d18c593d8d7b5e48ef5977c2cb06c26d6f0074 100644 --- a/crates/command_palette2/src/command_palette.rs +++ b/crates/command_palette2/src/command_palette.rs @@ -301,7 +301,7 @@ impl PickerDelegate for CommandPaletteDelegate { }; Some( - ListItem::new(ix).selected(selected).child( + ListItem::new(ix).inset(true).selected(selected).child( h_stack() .justify_between() .child(HighlightedLabel::new( diff --git a/crates/file_finder2/src/file_finder.rs b/crates/file_finder2/src/file_finder.rs index b54d28a400750b4caffbec99d163f561cf7f7784..2f7b26dfb55c9a548eea0ab4fbc88a5544f29921 100644 --- a/crates/file_finder2/src/file_finder.rs +++ b/crates/file_finder2/src/file_finder.rs @@ -719,7 +719,7 @@ impl PickerDelegate for FileFinderDelegate { self.labels_for_match(path_match, cx, ix); Some( - ListItem::new(ix).selected(selected).child( + ListItem::new(ix).inset(true).selected(selected).child( v_stack() .child(HighlightedLabel::new(file_name, file_name_positions)) .child(HighlightedLabel::new(full_path, full_path_positions)), diff --git a/crates/storybook2/src/stories/picker.rs b/crates/storybook2/src/stories/picker.rs index 8bcfb8923dbaef403ec65916821ae2341367f136..80818946f6dc5234f177366a435d3e50dd4223b3 100644 --- a/crates/storybook2/src/stories/picker.rs +++ b/crates/storybook2/src/stories/picker.rs @@ -61,6 +61,7 @@ impl PickerDelegate for Delegate { Some( ListItem::new(ix) + .inset(true) .selected(selected) .child(Label::new(candidate)), ) diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index d17c77ea445659ff747f861192d75a2ff14311e0..ad04408e13ecec02c871d76b1bd0253259924c40 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -12,14 +12,6 @@ use crate::{ }; use crate::{prelude::*, GraphicSlot}; -#[derive(Clone, Copy, Default, Debug, PartialEq)] -pub enum ListItemVariant { - /// The list item extends to the far left and right of the list. - FullWidth, - #[default] - Inset, -} - pub enum ListHeaderMeta { Tools(Vec), // TODO: This should be a button @@ -32,8 +24,39 @@ pub struct ListHeader { label: SharedString, left_icon: Option, meta: Option, - variant: ListItemVariant, toggle: Toggle, + inset: bool, +} + +impl ListHeader { + pub fn new(label: impl Into) -> Self { + Self { + label: label.into(), + left_icon: None, + meta: None, + inset: false, + toggle: Toggle::NotToggleable, + } + } + + pub fn toggle(mut self, toggle: Toggle) -> Self { + self.toggle = toggle; + self + } + + pub fn left_icon(mut self, left_icon: Option) -> Self { + self.left_icon = left_icon; + self + } + + pub fn right_button(self, button: IconButton) -> Self { + self.meta(Some(ListHeaderMeta::Tools(vec![button]))) + } + + pub fn meta(mut self, meta: Option) -> Self { + self.meta = meta; + self + } } impl RenderOnce for ListHeader { @@ -61,7 +84,7 @@ impl RenderOnce for ListHeader { .child( div() .h_5() - .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) + .when(self.inset, |this| this.px_2()) .flex() .flex_1() .items_center() @@ -90,42 +113,11 @@ impl RenderOnce for ListHeader { } } -impl ListHeader { - pub fn new(label: impl Into) -> Self { - Self { - label: label.into(), - left_icon: None, - meta: None, - variant: ListItemVariant::default(), - toggle: Toggle::NotToggleable, - } - } - - pub fn toggle(mut self, toggle: Toggle) -> Self { - self.toggle = toggle; - self - } - - pub fn left_icon(mut self, left_icon: Option) -> Self { - self.left_icon = left_icon; - self - } - - pub fn right_button(self, button: IconButton) -> Self { - self.meta(Some(ListHeaderMeta::Tools(vec![button]))) - } - - pub fn meta(mut self, meta: Option) -> Self { - self.meta = meta; - self - } -} - #[derive(IntoElement, Clone)] pub struct ListSubHeader { label: SharedString, left_icon: Option, - variant: ListItemVariant, + inset: bool, } impl ListSubHeader { @@ -133,7 +125,7 @@ impl ListSubHeader { Self { label: label.into(), left_icon: None, - variant: ListItemVariant::default(), + inset: false, } } @@ -150,7 +142,7 @@ impl RenderOnce for ListSubHeader { h_stack().flex_1().w_full().relative().py_1().child( div() .h_6() - .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) + .when(self.inset, |this| this.px_2()) .flex() .flex_1() .w_full() @@ -185,7 +177,7 @@ pub struct ListItem { left_slot: Option, overflow: OverflowStyle, toggle: Toggle, - variant: ListItemVariant, + inset: bool, on_click: Option>, on_secondary_mouse_down: Option>, children: SmallVec<[AnyElement; 2]>, @@ -202,7 +194,7 @@ impl ListItem { left_slot: None, overflow: OverflowStyle::Hidden, toggle: Toggle::NotToggleable, - variant: ListItemVariant::default(), + inset: false, on_click: None, on_secondary_mouse_down: None, children: SmallVec::new(), @@ -222,8 +214,8 @@ impl ListItem { self } - pub fn variant(mut self, variant: ListItemVariant) -> Self { - self.variant = variant; + pub fn inset(mut self, inset: bool) -> Self { + self.inset = inset; self } @@ -283,20 +275,26 @@ impl RenderOnce for ListItem { div() .id(self.id) .relative() - .hover(|mut style| { - style.background = Some(cx.theme().colors().editor_background.into()); - style - }) // TODO: Add focus state // .when(self.state == InteractionState::Focused, |this| { // this.border() // .border_color(cx.theme().colors().border_focused) // }) + .when(self.inset, |this| this.rounded_md()) .hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) .active(|style| style.bg(cx.theme().colors().ghost_element_active)) .when(self.selected, |this| { this.bg(cx.theme().colors().ghost_element_selected) }) + .when_some(self.on_click.clone(), |this, on_click| { + this.on_click(move |event, cx| { + // HACK: GPUI currently fires `on_click` with any mouse button, + // but we only care about the left button. + if event.down.button == MouseButton::Left { + (on_click)(event, cx) + } + }) + }) .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| { this.on_mouse_down(MouseButton::Right, move |event, cx| { (on_mouse_down)(event, cx) @@ -304,7 +302,7 @@ impl RenderOnce for ListItem { }) .child( div() - .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) + .when(self.inset, |this| this.px_2()) .ml(self.indent_level as f32 * self.indent_step_size) .flex() .gap_1() From a9cb6589dd2a7764db3648e5897fb77cf1c684a5 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 28 Nov 2023 14:40:26 -0500 Subject: [PATCH 062/151] Update keybinding.rs --- crates/ui2/src/components/keybinding.rs | 90 ++++++++++++++++++++----- 1 file changed, 74 insertions(+), 16 deletions(-) diff --git a/crates/ui2/src/components/keybinding.rs b/crates/ui2/src/components/keybinding.rs index 1da0425ad376aab0cb8db3a0c4c27052a3cda8b5..15e365e5264dc0d9eac57971f05929b36f6c2a42 100644 --- a/crates/ui2/src/components/keybinding.rs +++ b/crates/ui2/src/components/keybinding.rs @@ -1,5 +1,5 @@ -use crate::prelude::*; -use gpui::{Action, Div, IntoElement}; +use crate::{h_stack, prelude::*, Icon, IconElement, IconSize}; +use gpui::{relative, rems, Action, Div, IntoElement, Keystroke}; #[derive(IntoElement, Clone)] pub struct KeyBinding { @@ -14,19 +14,35 @@ impl RenderOnce for KeyBinding { type Rendered = Div; fn render(self, cx: &mut WindowContext) -> Self::Rendered { - div() - .flex() + h_stack() + .flex_none() .gap_2() .children(self.key_binding.keystrokes().iter().map(|keystroke| { - div() - .flex() - .gap_1() + let key_icon = Self::icon_for_key(&keystroke); + + h_stack() + .flex_none() + .gap_0p5() + .bg(cx.theme().colors().element_background) + .p_0p5() + .rounded_sm() .when(keystroke.modifiers.function, |el| el.child(Key::new("fn"))) - .when(keystroke.modifiers.control, |el| el.child(Key::new("^"))) - .when(keystroke.modifiers.alt, |el| el.child(Key::new("⌥"))) - .when(keystroke.modifiers.command, |el| el.child(Key::new("⌘"))) - .when(keystroke.modifiers.shift, |el| el.child(Key::new("⇧"))) - .child(Key::new(keystroke.key.clone())) + .when(keystroke.modifiers.control, |el| { + el.child(KeyIcon::new(Icon::Control)) + }) + .when(keystroke.modifiers.alt, |el| { + el.child(KeyIcon::new(Icon::Option)) + }) + .when(keystroke.modifiers.command, |el| { + el.child(KeyIcon::new(Icon::Command)) + }) + .when(keystroke.modifiers.shift, |el| { + el.child(KeyIcon::new(Icon::Shift)) + }) + // .when_some(key_icon, |el, icon| el.child(KeyIcon::new(icon))) + .when(key_icon.is_none(), |el| { + el.child(Key::new(keystroke.key.to_uppercase().clone())) + }) })) } } @@ -39,6 +55,22 @@ impl KeyBinding { Some(Self::new(key_binding)) } + fn icon_for_key(keystroke: &Keystroke) -> Option { + let mut icon: Option = None; + + if keystroke.key == "left".to_string() { + icon = Some(Icon::ArrowLeft); + } else if keystroke.key == "right".to_string() { + icon = Some(Icon::ArrowRight); + } else if keystroke.key == "up".to_string() { + icon = Some(Icon::ArrowUp); + } else if keystroke.key == "down".to_string() { + icon = Some(Icon::ArrowDown); + } + + icon + } + pub fn new(key_binding: gpui::KeyBinding) -> Self { Self { key_binding } } @@ -53,13 +85,18 @@ impl RenderOnce for Key { type Rendered = Div; fn render(self, cx: &mut WindowContext) -> Self::Rendered { + let single_char = self.key.len() == 1; + div() - .px_2() .py_0() - .rounded_md() - .text_ui_sm() + .when(single_char, |el| { + el.w(rems(14. / 16.)).flex().flex_none().justify_center() + }) + .when(!single_char, |el| el.px_0p5()) + .h(rems(14. / 16.)) + .text_ui() + .line_height(relative(1.)) .text_color(cx.theme().colors().text) - .bg(cx.theme().colors().element_background) .child(self.key.clone()) } } @@ -69,3 +106,24 @@ impl Key { Self { key: key.into() } } } + +#[derive(IntoElement)] +pub struct KeyIcon { + icon: Icon, +} + +impl RenderOnce for KeyIcon { + type Rendered = Div; + + fn render(self, cx: &mut WindowContext) -> Self::Rendered { + div() + .w(rems(14. / 16.)) + .child(IconElement::new(self.icon).size(IconSize::Small)) + } +} + +impl KeyIcon { + pub fn new(icon: Icon) -> Self { + Self { icon } + } +} From 070674a4fd6a248471d8e0f759f2bc78e24181c6 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 28 Nov 2023 14:44:19 -0500 Subject: [PATCH 063/151] ui2: Unsuppress and fix warnings (#3423) This PR unsupresses the warnings in `ui2` and summarily fixes them. Release Notes: - N/A --- crates/ui2/src/components/context_menu.rs | 2 +- crates/ui2/src/components/divider.rs | 13 ----- crates/ui2/src/components/icon.rs | 13 ----- crates/ui2/src/components/icon_button.rs | 6 +- crates/ui2/src/components/keybinding.rs | 2 +- crates/ui2/src/components/label.rs | 8 +-- crates/ui2/src/components/list.rs | 8 +-- crates/ui2/src/components/stories/avatar.rs | 2 +- crates/ui2/src/components/stories/button.rs | 4 +- .../src/components/stories/context_menu.rs | 6 +- crates/ui2/src/components/stories/icon.rs | 2 +- crates/ui2/src/components/stories/input.rs | 2 +- .../ui2/src/components/stories/keybinding.rs | 2 +- crates/ui2/src/components/stories/label.rs | 2 +- .../ui2/src/components/stories/list_item.rs | 2 +- crates/ui2/src/components/tooltip.rs | 2 +- crates/ui2/src/prelude.rs | 6 -- crates/ui2/src/ui2.rs | 2 - crates/ui2/src/utils/format_distance.rs | 55 +++++++++---------- 19 files changed, 46 insertions(+), 93 deletions(-) diff --git a/crates/ui2/src/components/context_menu.rs b/crates/ui2/src/components/context_menu.rs index a92c08d82fae0164d0488e350f273df86f7ae48f..8cd519f62916dd351f2056100acb8d396801f8c0 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -264,7 +264,7 @@ impl Element for MenuHandle { let new_menu = (builder)(cx); let menu2 = menu.clone(); - cx.subscribe(&new_menu, move |modal, e, cx| match e { + cx.subscribe(&new_menu, move |_modal, e, cx| match e { &DismissEvent::Dismiss => { *menu2.borrow_mut() = None; cx.notify(); diff --git a/crates/ui2/src/components/divider.rs b/crates/ui2/src/components/divider.rs index b203a0feda4dfac5546c62e387de8502fb558140..cb48ce00ae24f031807d8522510aa18bc586d638 100644 --- a/crates/ui2/src/components/divider.rs +++ b/crates/ui2/src/components/divider.rs @@ -49,17 +49,4 @@ impl Divider { self.inset = true; self } - - fn render(self, cx: &mut WindowContext) -> impl Element { - div() - .map(|this| match self.direction { - DividerDirection::Horizontal => { - this.h_px().w_full().when(self.inset, |this| this.mx_1p5()) - } - DividerDirection::Vertical => { - this.w_px().h_full().when(self.inset, |this| this.my_1p5()) - } - }) - .bg(cx.theme().colors().border_variant) - } } diff --git a/crates/ui2/src/components/icon.rs b/crates/ui2/src/components/icon.rs index 575b4fdb2877b91da18ad104b0314a4a696efe70..8190871767d19f65b8cac52648d9a4c8c6fda865 100644 --- a/crates/ui2/src/components/icon.rs +++ b/crates/ui2/src/components/icon.rs @@ -189,17 +189,4 @@ impl IconElement { self.size = size; self } - - fn render(self, cx: &mut WindowContext) -> impl Element { - let svg_size = match self.size { - IconSize::Small => rems(0.75), - IconSize::Medium => rems(0.9375), - }; - - svg() - .size(svg_size) - .flex_none() - .path(self.path) - .text_color(self.color.color(cx)) - } } diff --git a/crates/ui2/src/components/icon_button.rs b/crates/ui2/src/components/icon_button.rs index 1ab35384c9d4bd8d8659079f4debfa8c6d54427c..e23882f5f778934cfc4de37e280908ea11315ceb 100644 --- a/crates/ui2/src/components/icon_button.rs +++ b/crates/ui2/src/components/icon_button.rs @@ -23,15 +23,13 @@ impl RenderOnce for IconButton { _ => self.color, }; - let (mut bg_color, bg_hover_color, bg_active_color) = match self.variant { + let (mut bg_color, bg_active_color) = match self.variant { ButtonVariant::Filled => ( cx.theme().colors().element_background, - cx.theme().colors().element_hover, cx.theme().colors().element_active, ), ButtonVariant::Ghost => ( cx.theme().colors().ghost_element_background, - cx.theme().colors().ghost_element_hover, cx.theme().colors().ghost_element_active, ), }; @@ -124,6 +122,6 @@ impl IconButton { } pub fn action(self, action: Box) -> Self { - self.on_click(move |this, cx| cx.dispatch_action(action.boxed_clone())) + self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone())) } } diff --git a/crates/ui2/src/components/keybinding.rs b/crates/ui2/src/components/keybinding.rs index 1da0425ad376aab0cb8db3a0c4c27052a3cda8b5..78300ebe54c25e5cb37a05d41acfc472ba68bf26 100644 --- a/crates/ui2/src/components/keybinding.rs +++ b/crates/ui2/src/components/keybinding.rs @@ -13,7 +13,7 @@ pub struct KeyBinding { impl RenderOnce for KeyBinding { type Rendered = Div; - fn render(self, cx: &mut WindowContext) -> Self::Rendered { + fn render(self, _cx: &mut WindowContext) -> Self::Rendered { div() .flex() .gap_2() diff --git a/crates/ui2/src/components/label.rs b/crates/ui2/src/components/label.rs index 627a95d9534d2a4aadda019d6be59b2977653ed3..562131a96975ad2f8c5986bac2bfdd723c20032d 100644 --- a/crates/ui2/src/components/label.rs +++ b/crates/ui2/src/components/label.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use crate::styled_ext::StyledExt; -use gpui::{relative, Div, Hsla, IntoElement, StyledText, TextRun, WindowContext}; +use gpui::{relative, Div, IntoElement, StyledText, TextRun, WindowContext}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum LabelSize { @@ -182,9 +182,3 @@ impl HighlightedLabel { self } } - -/// A run of text that receives the same style. -struct Run { - pub text: String, - pub color: Hsla, -} diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index ad04408e13ecec02c871d76b1bd0253259924c40..749de951d9a3055050e18198956eb71c75684cfe 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -138,7 +138,7 @@ impl ListSubHeader { impl RenderOnce for ListSubHeader { type Rendered = Div; - fn render(self, cx: &mut WindowContext) -> Self::Rendered { + fn render(self, _cx: &mut WindowContext) -> Self::Rendered { h_stack().flex_1().w_full().relative().py_1().child( div() .h_6() @@ -168,14 +168,12 @@ impl RenderOnce for ListSubHeader { #[derive(IntoElement)] pub struct ListItem { id: ElementId, - disabled: bool, selected: bool, // TODO: Reintroduce this // disclosure_control_style: DisclosureControlVisibility, indent_level: usize, indent_step_size: Pixels, left_slot: Option, - overflow: OverflowStyle, toggle: Toggle, inset: bool, on_click: Option>, @@ -187,12 +185,10 @@ impl ListItem { pub fn new(id: impl Into) -> Self { Self { id: id.into(), - disabled: false, selected: false, indent_level: 0, indent_step_size: px(12.), left_slot: None, - overflow: OverflowStyle::Hidden, toggle: Toggle::NotToggleable, inset: false, on_click: None, @@ -365,7 +361,7 @@ pub struct List { impl RenderOnce for List { type Rendered = Div; - fn render(self, cx: &mut WindowContext) -> Self::Rendered { + fn render(self, _cx: &mut WindowContext) -> Self::Rendered { let list_content = match (self.children.is_empty(), self.toggle) { (false, _) => div().children(self.children), (true, Toggle::Toggled(false)) => div(), diff --git a/crates/ui2/src/components/stories/avatar.rs b/crates/ui2/src/components/stories/avatar.rs index 505ede4ecc98d1248533f806b8fb47fc932a0028..3e830b8b79df059c4257cfad73691dbda938c6a3 100644 --- a/crates/ui2/src/components/stories/avatar.rs +++ b/crates/ui2/src/components/stories/avatar.rs @@ -9,7 +9,7 @@ pub struct AvatarStory; impl Render for AvatarStory { type Element = Div; - fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { Story::container() .child(Story::title_for::()) .child(Story::label("Default")) diff --git a/crates/ui2/src/components/stories/button.rs b/crates/ui2/src/components/stories/button.rs index 6a23060c7813034434285e0e6af036a40e8a21b5..7339f95ae2e04afe8bd9aa705e93b219a8ba7869 100644 --- a/crates/ui2/src/components/stories/button.rs +++ b/crates/ui2/src/components/stories/button.rs @@ -10,7 +10,7 @@ pub struct ButtonStory; impl Render for ButtonStory { type Element = Div; - fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { let states = InteractionState::iter(); Story::container() @@ -139,7 +139,7 @@ impl Render for ButtonStory { .child( Button::new("Label") .variant(ButtonVariant::Ghost) - .on_click(|_, cx| println!("Button clicked.")), + .on_click(|_, _cx| println!("Button clicked.")), ) } } diff --git a/crates/ui2/src/components/stories/context_menu.rs b/crates/ui2/src/components/stories/context_menu.rs index 98faea70aabdaec14a78757ce4a6a00919932654..9a8b7efbe666bb95317c98299b4ad72d0a1d23cd 100644 --- a/crates/ui2/src/components/stories/context_menu.rs +++ b/crates/ui2/src/components/stories/context_menu.rs @@ -10,11 +10,11 @@ fn build_menu(cx: &mut WindowContext, header: impl Into) -> View) -> Self::Element { + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { Story::container() .on_action(|_: &PrintCurrentDate, _| { println!("printing unix time!"); diff --git a/crates/ui2/src/components/stories/icon.rs b/crates/ui2/src/components/stories/icon.rs index bd3cafd5314d17b06d3b96e0a300cc774c489fd8..ee7dfe532cdf3619bee41c47d2fb94ec95a26b21 100644 --- a/crates/ui2/src/components/stories/icon.rs +++ b/crates/ui2/src/components/stories/icon.rs @@ -10,7 +10,7 @@ pub struct IconStory; impl Render for IconStory { type Element = Div; - fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { let icons = Icon::iter(); Story::container() diff --git a/crates/ui2/src/components/stories/input.rs b/crates/ui2/src/components/stories/input.rs index f8eb553e7db6a50415983b9acff78b208be74286..d041543616072e943cb61f2e4e37f5e494e9b66a 100644 --- a/crates/ui2/src/components/stories/input.rs +++ b/crates/ui2/src/components/stories/input.rs @@ -9,7 +9,7 @@ pub struct InputStory; impl Render for InputStory { type Element = Div; - fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { Story::container() .child(Story::title_for::()) .child(Story::label("Default")) diff --git a/crates/ui2/src/components/stories/keybinding.rs b/crates/ui2/src/components/stories/keybinding.rs index a1aba23b5930f7e64730bd4c25ed90a0e34308fc..b1b3401f17f5e30f5de28c12c6d72aef53c6a25d 100644 --- a/crates/ui2/src/components/stories/keybinding.rs +++ b/crates/ui2/src/components/stories/keybinding.rs @@ -16,7 +16,7 @@ pub fn binding(key: &str) -> gpui::KeyBinding { impl Render for KeybindingStory { type Element = Div; - fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { let all_modifier_permutations = ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2); Story::container() diff --git a/crates/ui2/src/components/stories/label.rs b/crates/ui2/src/components/stories/label.rs index f19f643ad635c28090ab05cd09f73ff80ae2dd04..2417bee6e1cd77646ceddd2c4febe4c6e45b7e19 100644 --- a/crates/ui2/src/components/stories/label.rs +++ b/crates/ui2/src/components/stories/label.rs @@ -9,7 +9,7 @@ pub struct LabelStory; impl Render for LabelStory { type Element = Div; - fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { Story::container() .child(Story::title_for::