Detailed changes
@@ -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
})
@@ -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<Self>) -> AnyElement<Self> {
- 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<ChannelModal>,
) -> AnyElement<ChannelModal> {
let active = mode == current_mode;
MouseEventHandler::<T, _>::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<Picker<Self>> {
- 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 {
@@ -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::<ContactFinderDelegate>::init(cx);
}
-pub type ContactFinder = Picker<ContactFinderDelegate>;
+pub struct ContactFinder {
+ picker: ViewHandle<Picker<ContactFinderDelegate>>,
+ has_focus: bool,
+}
-pub fn build_contact_finder(
- user_store: ModelHandle<UserStore>,
- cx: &mut ViewContext<ContactFinder>,
-) -> 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<UserStore>, cx: &mut ViewContext<Self>) -> 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>) {
+ 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<Self>) -> AnyElement<Self> {
+ 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<ContactFinder>,
+ ) -> AnyElement<ContactFinder> {
+ 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>) {
+ self.has_focus = true;
+ if cx.is_self_focused() {
+ cx.focus(&self.picker)
+ }
+ }
+
+ fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
+ 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<Picker<Self>> {
- 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()
}
}
@@ -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<ContainedText>,
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 max_height: f32,
- pub max_width: f32,
+pub struct TabbedModal {
+ pub tab_button: Toggleable<Interactive<ContainedText>>,
+ pub modal: ContainerStyle,
+ pub header: ContainerStyle,
+ pub body: ContainerStyle,
pub title: ContainedText,
- pub mode_button: Toggleable<Interactive<ContainedText>>,
pub picker: Picker,
+ pub max_height: f32,
+ pub max_width: f32,
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,
@@ -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(
@@ -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(),
@@ -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"),
- },
- }
-}
@@ -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"),
+ },
+ }
+ }
+}
@@ -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),
@@ -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,