Add contact finder, change ui::List's on_click handler signature

This commit is contained in:
Piotr Osiewicz
2023-11-28 14:16:51 +01:00
parent 26121713b3
commit 6c37393dd1
6 changed files with 192 additions and 205 deletions
+38 -30
View File
@@ -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<Self>) {
// 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<Self>) {
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>) {
// 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, _>(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(
@@ -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::<ContactFinderDelegate>::init(cx);
cx.add_action(ContactFinder::dismiss)
//Picker::<ContactFinderDelegate>::init(cx);
//cx.add_action(ContactFinder::dismiss)
}
pub struct ContactFinder {
picker: ViewHandle<Picker<ContactFinderDelegate>>,
picker: View<Picker<ContactFinderDelegate>>,
has_focus: bool,
}
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();
pub fn new(user_store: Model<UserStore>, cx: &mut ViewContext<Self>) -> 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>) {
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<Self>) {
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<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()
impl Render for ContactFinder {
fn render(&mut self, cx: &mut ViewContext<Self>) -> 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),
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),
)
.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()
.child(self.picker.clone())
.w_96()
}
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_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;
}
// fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
// 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<ContactFinder>,
potential_contacts: Arc<[Arc<User>]>,
user_store: ModelHandle<UserStore>,
user_store: Model<UserStore>,
selected_index: usize,
}
impl PickerDelegate for ContactFinderDelegate {
fn placeholder_text(&self) -> Arc<str> {
"Search collaborator by username...".into()
}
impl EventEmitter<DismissEvent> 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<str> {
"Search collaborator by username...".into()
}
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> 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<Picker<Self>>) {
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<Picker<Self>> {
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<Picker<Self>>,
) -> 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()
}
}
+6
View File
@@ -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
+5 -2
View File
@@ -12,7 +12,10 @@ use gpui::{
pub enum ContextMenuItem {
Separator,
Header(SharedString),
Entry(SharedString, Rc<dyn Fn(&ClickEvent, &mut WindowContext)>),
Entry(
SharedString,
Rc<dyn Fn(&MouseDownEvent, &mut WindowContext)>,
),
}
pub struct ContextMenu {
@@ -58,7 +61,7 @@ impl ContextMenu {
pub fn entry(
mut self,
label: impl Into<SharedString>,
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)));
+26 -20
View File
@@ -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<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
on_click: Option<Rc<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
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<SharedString>) -> Self {
pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> 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)
}
}
}),
)
}
}
+2 -2
View File
@@ -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),
}