diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index ed042dbf4e..0e99497cef 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -7,7 +7,7 @@ use call::ActiveCall; use client::{ proto::PeerId, Channel, ChannelEvent, ChannelId, ChannelStore, Client, Contact, User, UserStore, }; -use contact_finder::build_contact_finder; + use context_menu::{ContextMenu, ContextMenuItem}; use db::kvp::KEY_VALUE_STORE; use editor::{Cancel, Editor}; @@ -46,6 +46,8 @@ use workspace::{ use crate::face_pile::FacePile; use channel_modal::ChannelModal; +use self::contact_finder::ContactFinder; + #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] struct RemoveChannel { channel_id: u64, @@ -1945,7 +1947,7 @@ impl CollabPanel { workspace.update(cx, |workspace, cx| { workspace.toggle_modal(cx, |_, cx| { cx.add_view(|cx| { - let finder = build_contact_finder(self.user_store.clone(), cx); + let mut finder = ContactFinder::new(self.user_store.clone(), cx); finder.set_query(self.filter_editor.read(cx).text(cx), cx); finder }) diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index f72eafe7da..12c923594f 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -66,7 +66,7 @@ impl ChannelModal { }, cx, ) - .with_theme(|theme| theme.collab_panel.channel_modal.picker.clone()) + .with_theme(|theme| theme.collab_panel.tabbed_modal.picker.clone()) }); cx.subscribe(&picker, |_, _, e, cx| cx.emit(*e)).detach(); @@ -143,7 +143,7 @@ impl View for ChannelModal { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = &theme::current(cx).collab_panel.channel_modal; + let theme = &theme::current(cx).collab_panel.tabbed_modal; let mode = self.picker.read(cx).delegate().mode; let Some(channel) = self @@ -160,12 +160,12 @@ impl View for ChannelModal { mode: Mode, text: &'static str, current_mode: Mode, - theme: &theme::ChannelModal, + theme: &theme::TabbedModal, cx: &mut ViewContext, ) -> AnyElement { let active = mode == current_mode; MouseEventHandler::::new(0, cx, move |state, _| { - let contained_text = theme.mode_button.style_for(active, state); + let contained_text = theme.tab_button.style_for(active, state); Label::new(text, contained_text.text.clone()) .contained() .with_style(contained_text.container.clone()) @@ -367,11 +367,17 @@ impl PickerDelegate for ChannelModalDelegate { selected: bool, cx: &gpui::AppContext, ) -> AnyElement> { - let theme = &theme::current(cx).collab_panel.channel_modal; + let full_theme = &theme::current(cx); + let theme = &full_theme.collab_panel.channel_modal; + let tabbed_modal = &full_theme.collab_panel.tabbed_modal; let (user, admin) = self.user_at_index(ix).unwrap(); let request_status = self.member_status(user.id, cx); - let style = theme.picker.item.in_state(selected).style_for(mouse_state); + let style = tabbed_modal + .picker + .item + .in_state(selected) + .style_for(mouse_state); let in_manage = matches!(self.mode, Mode::ManageMembers); @@ -448,7 +454,7 @@ impl PickerDelegate for ChannelModalDelegate { .contained() .with_style(style.container) .constrained() - .with_height(theme.row_height) + .with_height(tabbed_modal.row_height) .into_any(); if selected { diff --git a/crates/collab_ui/src/collab_panel/contact_finder.rs b/crates/collab_ui/src/collab_panel/contact_finder.rs index 41fff2af43..4cc7034f49 100644 --- a/crates/collab_ui/src/collab_panel/contact_finder.rs +++ b/crates/collab_ui/src/collab_panel/contact_finder.rs @@ -1,28 +1,127 @@ use client::{ContactRequestStatus, User, UserStore}; -use gpui::{elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext}; +use gpui::{ + elements::*, AppContext, Entity, ModelHandle, MouseState, Task, View, ViewContext, ViewHandle, +}; use picker::{Picker, PickerDelegate, PickerEvent}; use std::sync::Arc; use util::TryFutureExt; +use workspace::Modal; pub fn init(cx: &mut AppContext) { Picker::::init(cx); } -pub type ContactFinder = Picker; +pub struct ContactFinder { + picker: ViewHandle>, + has_focus: bool, +} -pub fn build_contact_finder( - user_store: ModelHandle, - cx: &mut ViewContext, -) -> ContactFinder { - Picker::new( - ContactFinderDelegate { - user_store, - potential_contacts: Arc::from([]), - selected_index: 0, - }, - cx, - ) - .with_theme(|theme| theme.picker.clone()) +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(); + + Self { + picker, + has_focus: false, + } + } + + pub fn set_query(&mut self, query: String, cx: &mut ViewContext) { + self.picker.update(cx, |picker, cx| { + picker.set_query(query, cx); + }); + } +} + +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() + } + + 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), + ) + .constrained() + .with_max_height(theme.max_height) + .with_max_width(theme.max_width) + .contained() + .with_style(theme.modal) + .into_any() + } + + 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; + } +} + +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, + } + } } pub struct ContactFinderDelegate { @@ -97,7 +196,9 @@ impl PickerDelegate for ContactFinderDelegate { selected: bool, cx: &gpui::AppContext, ) -> AnyElement> { - let theme = &theme::current(cx).contact_finder; + let full_theme = &theme::current(cx); + let theme = &full_theme.collab_panel.contact_finder; + let tabbed_modal = &full_theme.collab_panel.tabbed_modal; let user = &self.potential_contacts[ix]; let request_status = self.user_store.read(cx).contact_request_status(user); @@ -113,7 +214,11 @@ impl PickerDelegate for ContactFinderDelegate { } else { &theme.contact_button }; - let style = theme.picker.item.in_state(selected).style_for(mouse_state); + 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) @@ -145,7 +250,7 @@ impl PickerDelegate for ContactFinderDelegate { .contained() .with_style(style.container) .constrained() - .with_height(theme.row_height) + .with_height(tabbed_modal.row_height) .into_any() } } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index cd31e312d4..1e11fbbf82 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -48,7 +48,6 @@ pub struct Theme { pub collab_panel: CollabPanel, pub project_panel: ProjectPanel, pub command_palette: CommandPalette, - pub contact_finder: ContactFinder, pub picker: Picker, pub editor: Editor, pub search: Search, @@ -224,6 +223,8 @@ pub struct CollabPanel { pub log_in_button: Interactive, pub channel_editor: ContainerStyle, pub channel_hash: Icon, + pub tabbed_modal: TabbedModal, + pub contact_finder: ContactFinder, pub channel_modal: ChannelModal, pub user_query_editor: FieldEditor, pub user_query_editor_height: f32, @@ -251,13 +252,20 @@ pub struct CollabPanel { } #[derive(Deserialize, Default, JsonSchema)] -pub struct ChannelModal { +pub struct TabbedModal { + pub tab_button: Toggleable>, + pub modal: ContainerStyle, + pub header: ContainerStyle, + pub body: ContainerStyle, + pub title: ContainedText, + pub picker: Picker, pub max_height: f32, pub max_width: f32, - pub title: ContainedText, - pub mode_button: Toggleable>, - pub picker: Picker, pub row_height: f32, +} + +#[derive(Deserialize, Default, JsonSchema)] +pub struct ChannelModal { pub contact_avatar: ImageStyle, pub contact_username: ContainerStyle, pub remove_member_button: ContainedText, @@ -265,9 +273,6 @@ pub struct ChannelModal { pub member_icon: Icon, pub invitee_icon: Icon, pub member_tag: ContainedText, - pub modal: ContainerStyle, - pub header: ContainerStyle, - pub body: ContainerStyle, } #[derive(Deserialize, Default, JsonSchema)] @@ -286,8 +291,6 @@ pub struct TreeBranch { #[derive(Deserialize, Default, JsonSchema)] pub struct ContactFinder { - pub picker: Picker, - pub row_height: f32, pub contact_avatar: ImageStyle, pub contact_username: ContainerStyle, pub contact_button: IconButton, diff --git a/crates/vcs_menu/src/lib.rs b/crates/vcs_menu/src/lib.rs index 384b622469..8be8ad2bde 100644 --- a/crates/vcs_menu/src/lib.rs +++ b/crates/vcs_menu/src/lib.rs @@ -256,7 +256,7 @@ impl PickerDelegate for BranchListDelegate { .contained() .with_style(style.container) .constrained() - .with_height(theme.contact_finder.row_height) + .with_height(theme.collab_panel.tabbed_modal.row_height) .into_any() } fn render_header( diff --git a/styles/src/style_tree/app.ts b/styles/src/style_tree/app.ts index be6d4d42bf..ee5e19e111 100644 --- a/styles/src/style_tree/app.ts +++ b/styles/src/style_tree/app.ts @@ -46,7 +46,6 @@ export default function app(): any { project_diagnostics: project_diagnostics(), project_panel: project_panel(), collab_panel: collab_panel(), - contact_finder: contact_finder(), toolbar_dropdown_menu: toolbar_dropdown_menu(), search: search(), shared_screen: shared_screen(), diff --git a/styles/src/style_tree/channel_modal.ts b/styles/src/style_tree/channel_modal.ts deleted file mode 100644 index b0621743fd..0000000000 --- a/styles/src/style_tree/channel_modal.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { useTheme } from "../theme" -import { background, border, foreground, text } from "./components" -import picker from "./picker" -import { input } from "../component/input" -import { toggleable_text_button } from "../component/text_button" - -export default function channel_modal(): any { - const theme = useTheme() - - const side_margin = 6 - const contact_button = { - background: background(theme.middle, "variant"), - color: foreground(theme.middle, "variant"), - icon_width: 8, - button_width: 16, - corner_radius: 8, - } - - const picker_style = picker() - delete picker_style.shadow - delete picker_style.border - - const picker_input = input() - - return { - header: { - background: background(theme.middle, "accent"), - border: border(theme.middle, { "bottom": true, "top": false, left: false, right: false }), - corner_radii: { - top_right: 12, - top_left: 12, - } - }, - body: { - background: background(theme.middle), - corner_radii: { - bottom_right: 12, - bottom_left: 12, - } - }, - modal: { - background: background(theme.middle), - shadow: theme.modal_shadow, - corner_radius: 12, - padding: { - bottom: 0, - left: 0, - right: 0, - top: 0, - }, - - }, - // This is used for the icons that are rendered to the right of channel Members in both UIs - member_icon: { - background: background(theme.middle), - padding: { - bottom: 4, - left: 4, - right: 4, - top: 4, - }, - width: 5, - color: foreground(theme.middle, "accent"), - }, - // This is used for the icons that are rendered to the right of channel invites in both UIs - invitee_icon: { - background: background(theme.middle), - padding: { - bottom: 4, - left: 4, - right: 4, - top: 4, - }, - width: 5, - color: foreground(theme.middle, "accent"), - }, - remove_member_button: { - ...text(theme.middle, "sans", { size: "xs" }), - background: background(theme.middle), - padding: { - left: 7, - right: 7 - } - }, - cancel_invite_button: { - ...text(theme.middle, "sans", { size: "xs" }), - background: background(theme.middle), - }, - member_tag: { - ...text(theme.middle, "sans", { size: "xs" }), - border: border(theme.middle, "active"), - background: background(theme.middle), - margin: { - left: 8, - }, - padding: { - left: 4, - right: 4, - } - }, - max_height: 400, - max_width: 540, - title: { - ...text(theme.middle, "sans", "on", { size: "lg" }), - padding: { - left: 6, - } - }, - mode_button: toggleable_text_button(theme, { - variant: "ghost", - layer: theme.middle, - active_color: "accent", - margin: { - top: 8, - bottom: 8, - right: 4 - } - }), - picker: { - empty_container: {}, - item: { - ...picker_style.item, - margin: { left: side_margin, right: side_margin }, - }, - no_matches: picker_style.no_matches, - input_editor: picker_input, - empty_input_editor: picker_input, - header: picker_style.header, - footer: picker_style.footer, - }, - row_height: 28, - contact_avatar: { - corner_radius: 10, - width: 18, - }, - contact_username: { - padding: { - left: 8, - }, - }, - contact_button: { - ...contact_button, - hover: { - background: background(theme.middle, "variant", "hovered"), - }, - }, - disabled_contact_button: { - ...contact_button, - background: background(theme.middle, "disabled"), - color: foreground(theme.middle, "disabled"), - }, - } -} diff --git a/styles/src/style_tree/collab_modals.ts b/styles/src/style_tree/collab_modals.ts new file mode 100644 index 0000000000..95690b5d85 --- /dev/null +++ b/styles/src/style_tree/collab_modals.ts @@ -0,0 +1,159 @@ +import { useTheme } from "../theme" +import { background, border, foreground, text } from "./components" +import picker from "./picker" +import { input } from "../component/input" +import { toggleable_text_button } from "../component/text_button" +import contact_finder from "./contact_finder" + +export default function channel_modal(): any { + const theme = useTheme() + + const side_margin = 6 + const contact_button = { + background: background(theme.middle, "variant"), + color: foreground(theme.middle, "variant"), + icon_width: 8, + button_width: 16, + corner_radius: 8, + } + + const picker_style = picker() + delete picker_style.shadow + delete picker_style.border + + const picker_input = input() + + return { + contact_finder: contact_finder(), + tabbed_modal: { + tab_button: toggleable_text_button(theme, { + variant: "ghost", + layer: theme.middle, + active_color: "accent", + margin: { + top: 8, + bottom: 8, + right: 4 + } + }), + row_height: 28, + header: { + background: background(theme.middle, "accent"), + border: border(theme.middle, { "bottom": true, "top": false, left: false, right: false }), + corner_radii: { + top_right: 12, + top_left: 12, + } + }, + body: { + background: background(theme.middle), + corner_radii: { + bottom_right: 12, + bottom_left: 12, + } + }, + modal: { + background: background(theme.middle), + shadow: theme.modal_shadow, + corner_radius: 12, + padding: { + bottom: 0, + left: 0, + right: 0, + top: 0, + }, + + }, + max_height: 400, + max_width: 540, + title: { + ...text(theme.middle, "sans", "on", { size: "lg" }), + padding: { + left: 6, + } + }, + picker: { + empty_container: {}, + item: { + ...picker_style.item, + margin: { left: side_margin, right: side_margin }, + }, + no_matches: picker_style.no_matches, + input_editor: picker_input, + empty_input_editor: picker_input, + header: picker_style.header, + footer: picker_style.footer, + }, + }, + channel_modal: { + // This is used for the icons that are rendered to the right of channel Members in both UIs + member_icon: { + background: background(theme.middle), + padding: { + bottom: 4, + left: 4, + right: 4, + top: 4, + }, + width: 5, + color: foreground(theme.middle, "accent"), + }, + // This is used for the icons that are rendered to the right of channel invites in both UIs + invitee_icon: { + background: background(theme.middle), + padding: { + bottom: 4, + left: 4, + right: 4, + top: 4, + }, + width: 5, + color: foreground(theme.middle, "accent"), + }, + remove_member_button: { + ...text(theme.middle, "sans", { size: "xs" }), + background: background(theme.middle), + padding: { + left: 7, + right: 7 + } + }, + cancel_invite_button: { + ...text(theme.middle, "sans", { size: "xs" }), + background: background(theme.middle), + }, + member_tag: { + ...text(theme.middle, "sans", { size: "xs" }), + border: border(theme.middle, "active"), + background: background(theme.middle), + margin: { + left: 8, + }, + padding: { + left: 4, + right: 4, + } + }, + contact_avatar: { + corner_radius: 10, + width: 18, + }, + contact_username: { + padding: { + left: 8, + }, + }, + contact_button: { + ...contact_button, + hover: { + background: background(theme.middle, "variant", "hovered"), + }, + }, + disabled_contact_button: { + ...contact_button, + background: background(theme.middle, "disabled"), + color: foreground(theme.middle, "disabled"), + }, + } + } +} diff --git a/styles/src/style_tree/collab_panel.ts b/styles/src/style_tree/collab_panel.ts index 3df2dd13d2..06170901e9 100644 --- a/styles/src/style_tree/collab_panel.ts +++ b/styles/src/style_tree/collab_panel.ts @@ -7,9 +7,7 @@ import { } from "./components" import { interactive, toggleable } from "../element" import { useTheme } from "../theme" -import channel_modal from "./channel_modal" -import { icon_button, toggleable_icon_button } from "../component/icon_button" - +import collab_modals from "./collab_modals" export default function contacts_panel(): any { const theme = useTheme() @@ -109,7 +107,7 @@ export default function contacts_panel(): any { return { - channel_modal: channel_modal(), + ...collab_modals(), log_in_button: interactive({ base: { background: background(theme.middle), diff --git a/styles/src/style_tree/contact_finder.ts b/styles/src/style_tree/contact_finder.ts index aa88a9f26a..04f95cc367 100644 --- a/styles/src/style_tree/contact_finder.ts +++ b/styles/src/style_tree/contact_finder.ts @@ -1,11 +1,11 @@ -import picker from "./picker" +// import picker from "./picker" import { background, border, foreground, text } from "./components" import { useTheme } from "../theme" export default function contact_finder(): any { const theme = useTheme() - const side_margin = 6 + // const side_margin = 6 const contact_button = { background: background(theme.middle, "variant"), color: foreground(theme.middle, "variant"), @@ -14,42 +14,42 @@ export default function contact_finder(): any { corner_radius: 8, } - const picker_style = picker() - const picker_input = { - background: background(theme.middle, "on"), - corner_radius: 6, - text: text(theme.middle, "mono"), - placeholder_text: text(theme.middle, "mono", "on", "disabled", { - size: "xs", - }), - selection: theme.players[0], - border: border(theme.middle), - padding: { - bottom: 4, - left: 8, - right: 8, - top: 4, - }, - margin: { - left: side_margin, - right: side_margin, - }, - } + // const picker_style = picker() + // const picker_input = { + // background: background(theme.middle, "on"), + // corner_radius: 6, + // text: text(theme.middle, "mono"), + // placeholder_text: text(theme.middle, "mono", "on", "disabled", { + // size: "xs", + // }), + // selection: theme.players[0], + // border: border(theme.middle), + // padding: { + // bottom: 4, + // left: 8, + // right: 8, + // top: 4, + // }, + // margin: { + // left: side_margin, + // right: side_margin, + // }, + // } return { - picker: { - empty_container: {}, - item: { - ...picker_style.item, - margin: { left: side_margin, right: side_margin }, - }, - no_matches: picker_style.no_matches, - input_editor: picker_input, - empty_input_editor: picker_input, - header: picker_style.header, - footer: picker_style.footer, - }, - row_height: 28, + // picker: { + // empty_container: {}, + // item: { + // ...picker_style.item, + // margin: { left: side_margin, right: side_margin }, + // }, + // no_matches: picker_style.no_matches, + // input_editor: picker_input, + // empty_input_editor: picker_input, + // header: picker_style.header, + // footer: picker_style.footer, + // }, + // row_height: 28, contact_avatar: { corner_radius: 10, width: 18,