From b13638fa7632502b9e0bcc98ae90bd595165f73a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 28 Nov 2023 15:33:44 -0700 Subject: [PATCH 1/4] Remove debugging --- crates/collab_ui2/src/collab_panel.rs | 29 +++++++++++---------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 7ef2d47c819509b1e99ef9fd0628519856b66453..3bbdfd5f9224c136e807e5e3d2753cf0c7073234 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -3044,7 +3044,7 @@ impl CollabPanel { }) .unwrap_or(false); - let has_messages_notification = channel.unseen_message_id.is_some() || true; + let has_messages_notification = channel.unseen_message_id.is_some(); let has_notes_notification = channel.unseen_note_version.is_some(); const FACEPILE_LIMIT: usize = 3; @@ -3092,11 +3092,10 @@ impl CollabPanel { .child( div() .id("channel_chat") - .bg(gpui::blue()) .when(!has_messages_notification, |el| el.invisible()) .group_hover("", |style| style.visible()) .child( - IconButton::new("test_chat", Icon::MessageBubbles) + IconButton::new("channel_chat", Icon::MessageBubbles) .color(if has_messages_notification { Color::Default } else { @@ -3111,20 +3110,16 @@ impl CollabPanel { .when(!has_notes_notification, |el| el.invisible()) .group_hover("", |style| style.visible()) .child( - div().child("Notes").id("test_notes").tooltip(|cx| { - Tooltip::text("Open channel notes", cx) - }), - ), // .child( - // IconButton::new("channel_notes", Icon::File) - // .color(if has_notes_notification { - // Color::Default - // } else { - // Color::Muted - // }) - // .tooltip(|cx| { - // Tooltip::text("Open channel notes", cx) - // }), - // ), + IconButton::new("channel_notes", Icon::File) + .color(if has_notes_notification { + Color::Default + } else { + Color::Muted + }) + .tooltip(|cx| { + Tooltip::text("Open channel notes", cx) + }), + ), ), ), ) From af3fa4ec0b4cf9b321787db860175192633b0eef Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 28 Nov 2023 16:10:18 -0700 Subject: [PATCH 2/4] Basic channel joining! --- crates/call2/src/call2.rs | 51 ++++++++++++- crates/collab_ui2/src/collab_panel.rs | 106 +++++++++++++------------- 2 files changed, 102 insertions(+), 55 deletions(-) diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index 7885ef6e3f3b92fc3b6947ce3407b092ee1bb82c..3b821d4bec65c041a70906950fa5ccb9ec88cfe1 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -14,8 +14,8 @@ use client::{ use collections::HashSet; use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use gpui::{ - AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task, - View, ViewContext, VisualContext, WeakModel, WeakView, + AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, PromptLevel, + Subscription, Task, View, ViewContext, VisualContext, WeakModel, WeakView, WindowHandle, }; pub use participant::ParticipantLocation; use postage::watch; @@ -334,12 +334,55 @@ impl ActiveCall { pub fn join_channel( &mut self, channel_id: u64, + requesting_window: WindowHandle, cx: &mut ModelContext, ) -> Task>>> { if let Some(room) = self.room().cloned() { if room.read(cx).channel_id() == Some(channel_id) { - return Task::ready(Ok(Some(room))); - } else { + return cx.spawn(|_, _| async move { + todo!(); + // let future = room.update(&mut cx, |room, cx| { + // room.most_active_project(cx).map(|(host, project)| { + // room.join_project(project, host, app_state.clone(), cx) + // }) + // }) + + // if let Some(future) = future { + // future.await?; + // } + + // Ok(Some(room)) + }); + } + + let should_prompt = room.update(cx, |room, _| { + room.channel_id().is_some() + && room.is_sharing_project() + && room.remote_participants().len() > 0 + }); + if should_prompt { + return cx.spawn(|this, mut cx| async move { + let answer = requesting_window.update(&mut cx, |_, cx| { + cx.prompt( + PromptLevel::Warning, + "Leaving this call will unshare your current project.\nDo you want to switch channels?", + &["Yes, Join Channel", "Cancel"], + ) + })?; + if answer.await? == 1 { + return Ok(None); + } + + room.update(&mut cx, |room, cx| room.clear_state(cx))?; + + this.update(&mut cx, |this, cx| { + this.join_channel(channel_id, requesting_window, cx) + })? + .await + }); + } + + if room.read(cx).channel_id().is_some() { room.update(cx, |room, cx| room.clear_state(cx)); } } diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 3bbdfd5f9224c136e807e5e3d2753cf0c7073234..3c2e78423a86b09dbff5639d8595def33e0cb90c 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -90,10 +90,10 @@ use rpc::proto; // channel_id: ChannelId, // } -// #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -// pub struct OpenChannelNotes { -// pub channel_id: ChannelId, -// } +#[derive(Action, PartialEq, Debug, Clone, Serialize, Deserialize)] +pub struct OpenChannelNotes { + pub channel_id: ChannelId, +} // #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] // pub struct JoinChannelCall { @@ -167,10 +167,10 @@ use editor::Editor; use feature_flags::{ChannelsAlpha, FeatureFlagAppExt}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ - actions, div, img, prelude::*, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, - FocusHandle, Focusable, FocusableView, InteractiveElement, IntoElement, Model, ParentElement, - Render, RenderOnce, SharedString, Styled, Subscription, View, ViewContext, VisualContext, - WeakView, + actions, div, img, prelude::*, serde_json, Action, AppContext, AsyncWindowContext, Div, + EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement, IntoElement, Model, + ParentElement, Render, RenderOnce, SharedString, Styled, Subscription, View, ViewContext, + VisualContext, WeakView, }; use project::Fs; use serde_derive::{Deserialize, Serialize}; @@ -322,17 +322,17 @@ pub struct CollabPanel { subscriptions: Vec, collapsed_sections: Vec
, collapsed_channels: Vec, - // drag_target_channel: ChannelDragTarget, + drag_target_channel: ChannelDragTarget, workspace: WeakView, // context_menu_on_selected: bool, } -// #[derive(PartialEq, Eq)] -// enum ChannelDragTarget { -// None, -// Root, -// Channel(ChannelId), -// } +#[derive(PartialEq, Eq)] +enum ChannelDragTarget { + None, + Root, + Channel(ChannelId), +} #[derive(Serialize, Deserialize)] struct SerializedCollabPanel { @@ -614,7 +614,7 @@ impl CollabPanel { workspace: workspace.weak_handle(), client: workspace.app_state().client.clone(), // context_menu_on_selected: true, - // drag_target_channel: ChannelDragTarget::None, + drag_target_channel: ChannelDragTarget::None, // list_state, }; @@ -2346,11 +2346,12 @@ impl CollabPanel { // } // } - // fn open_channel_notes(&mut self, action: &OpenChannelNotes, cx: &mut ViewContext) { - // if let Some(workspace) = self.workspace.upgrade(cx) { - // ChannelView::open(action.channel_id, workspace, cx).detach(); - // } - // } + fn open_channel_notes(&mut self, action: &OpenChannelNotes, cx: &mut ViewContext) { + if let Some(workspace) = self.workspace.upgrade() { + todo!(); + // ChannelView::open(action.channel_id, workspace, cx).detach(); + } + } // fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext) { // let Some(channel) = self.selected_channel() else { @@ -2504,21 +2505,17 @@ impl CollabPanel { // .detach_and_log_err(cx); // } - // fn join_channel(&self, channel_id: u64, cx: &mut ViewContext) { - // let Some(workspace) = self.workspace.upgrade(cx) else { - // return; - // }; - // let Some(handle) = cx.window().downcast::() else { - // return; - // }; - // workspace::join_channel( - // channel_id, - // workspace.read(cx).app_state().clone(), - // Some(handle), - // cx, - // ) - // .detach_and_log_err(cx) - // } + fn join_channel(&self, channel_id: u64, cx: &mut ViewContext) { + let Some(handle) = cx.window_handle().downcast::() else { + return; + }; + let active_call = ActiveCall::global(cx); + active_call + .update(cx, |active_call, cx| { + active_call.join_channel(channel_id, handle, cx) + }) + .detach_and_log_err(cx) + } // fn join_channel_chat(&mut self, action: &JoinChannelChat, cx: &mut ViewContext) { // let channel_id = action.channel_id; @@ -2982,9 +2979,7 @@ impl CollabPanel { 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!())) + ListItem::new("contact-placeholder").child(Label::new("Add a Contact")) // enum AddContacts {} // MouseEventHandler::new::(0, cx, |state, _| { // let style = theme.list_empty_state.style_for(is_selected, state); @@ -3023,6 +3018,15 @@ impl CollabPanel { ) -> impl IntoElement { let channel_id = channel.id; + 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); let is_public = self .channel_store .read(cx) @@ -3034,16 +3038,6 @@ impl CollabPanel { .then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok()) .unwrap_or(false); - 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); - let has_messages_notification = channel.unseen_message_id.is_some(); let has_notes_notification = channel.unseen_note_version.is_some(); @@ -3052,6 +3046,7 @@ impl CollabPanel { let face_pile = if !participants.is_empty() { let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT); + let user = &participants[0]; let result = FacePile { faces: participants @@ -3059,6 +3054,7 @@ impl CollabPanel { .filter_map(|user| Some(Avatar::data(user.avatar.clone()?).into_any_element())) .take(FACEPILE_LIMIT) .chain(if extra_count > 0 { + // todo!() @nate - this label looks wrong. Some(Label::new(format!("+{}", extra_count)).into_any_element()) } else { None @@ -3081,7 +3077,7 @@ impl CollabPanel { .w_full() .justify_between() .child( - div() + h_stack() .id(channel_id as usize) .child(Label::new(channel.name.clone())) .children(face_pile.map(|face_pile| face_pile.render(cx))) @@ -3128,7 +3124,15 @@ impl CollabPanel { } else { Toggle::NotToggleable }) - .on_click(cx.listener(|this, _, cx| todo!())) + .on_click(cx.listener(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_secondary_mouse_down(cx.listener(|this, _, cx| { todo!() // open context menu })), From 4c2348eb532029b6a9fa4b702d0fbcaac53f3ba4 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 28 Nov 2023 16:20:54 -0700 Subject: [PATCH 3/4] Fix tests, notify errors --- crates/call2/src/call2.rs | 6 +-- crates/collab2/src/tests/channel_tests.rs | 37 ++++++++++++------- crates/collab2/src/tests/integration_tests.rs | 11 ++++-- crates/collab_ui2/src/collab_panel.rs | 15 +++++--- 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index 3b821d4bec65c041a70906950fa5ccb9ec88cfe1..df7dd847cf3413a410ce17b87f1c89efa2e2905e 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -334,7 +334,7 @@ impl ActiveCall { pub fn join_channel( &mut self, channel_id: u64, - requesting_window: WindowHandle, + requesting_window: Option>, cx: &mut ModelContext, ) -> Task>>> { if let Some(room) = self.room().cloned() { @@ -360,9 +360,9 @@ impl ActiveCall { && room.is_sharing_project() && room.remote_participants().len() > 0 }); - if should_prompt { + if should_prompt && requesting_window.is_some() { return cx.spawn(|this, mut cx| async move { - let answer = requesting_window.update(&mut cx, |_, cx| { + let answer = requesting_window.unwrap().update(&mut cx, |_, cx| { cx.prompt( PromptLevel::Warning, "Leaving this call will unshare your current project.\nDo you want to switch channels?", diff --git a/crates/collab2/src/tests/channel_tests.rs b/crates/collab2/src/tests/channel_tests.rs index 8ce5d99b80d3c630a81181e5f03f78d385186a10..43d18ee7d13b850b634c67a0414831a64b455d5c 100644 --- a/crates/collab2/src/tests/channel_tests.rs +++ b/crates/collab2/src/tests/channel_tests.rs @@ -364,7 +364,8 @@ async fn test_joining_channel_ancestor_member( let active_call_b = cx_b.read(ActiveCall::global); assert!(active_call_b - .update(cx_b, |active_call, cx| active_call.join_channel(sub_id, cx)) + .update(cx_b, |active_call, cx| active_call + .join_channel(sub_id, None, cx)) .await .is_ok()); } @@ -394,7 +395,9 @@ async fn test_channel_room( let active_call_b = cx_b.read(ActiveCall::global); active_call_a - .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx)) + .update(cx_a, |active_call, cx| { + active_call.join_channel(zed_id, None, cx) + }) .await .unwrap(); @@ -442,7 +445,9 @@ async fn test_channel_room( }); active_call_b - .update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx)) + .update(cx_b, |active_call, cx| { + active_call.join_channel(zed_id, None, cx) + }) .await .unwrap(); @@ -559,12 +564,16 @@ async fn test_channel_room( }); active_call_a - .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx)) + .update(cx_a, |active_call, cx| { + active_call.join_channel(zed_id, None, cx) + }) .await .unwrap(); active_call_b - .update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx)) + .update(cx_b, |active_call, cx| { + active_call.join_channel(zed_id, None, cx) + }) .await .unwrap(); @@ -608,7 +617,9 @@ async fn test_channel_jumping(executor: BackgroundExecutor, cx_a: &mut TestAppCo let active_call_a = cx_a.read(ActiveCall::global); active_call_a - .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx)) + .update(cx_a, |active_call, cx| { + active_call.join_channel(zed_id, None, cx) + }) .await .unwrap(); @@ -627,7 +638,7 @@ async fn test_channel_jumping(executor: BackgroundExecutor, cx_a: &mut TestAppCo active_call_a .update(cx_a, |active_call, cx| { - active_call.join_channel(rust_id, cx) + active_call.join_channel(rust_id, None, cx) }) .await .unwrap(); @@ -793,7 +804,7 @@ async fn test_call_from_channel( let active_call_b = cx_b.read(ActiveCall::global); active_call_a - .update(cx_a, |call, cx| call.join_channel(channel_id, cx)) + .update(cx_a, |call, cx| call.join_channel(channel_id, None, cx)) .await .unwrap(); @@ -1286,7 +1297,7 @@ async fn test_guest_access( // Non-members should not be allowed to join assert!(active_call_b - .update(cx_b, |call, cx| call.join_channel(channel_a, cx)) + .update(cx_b, |call, cx| call.join_channel(channel_a, None, cx)) .await .is_err()); @@ -1308,7 +1319,7 @@ async fn test_guest_access( // Client B joins channel A as a guest active_call_b - .update(cx_b, |call, cx| call.join_channel(channel_a, cx)) + .update(cx_b, |call, cx| call.join_channel(channel_a, None, cx)) .await .unwrap(); @@ -1341,7 +1352,7 @@ async fn test_guest_access( assert_channels_list_shape(client_b.channel_store(), cx_b, &[]); active_call_b - .update(cx_b, |call, cx| call.join_channel(channel_b, cx)) + .update(cx_b, |call, cx| call.join_channel(channel_b, None, cx)) .await .unwrap(); @@ -1372,7 +1383,7 @@ async fn test_invite_access( // should not be allowed to join assert!(active_call_b - .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx)) + .update(cx_b, |call, cx| call.join_channel(channel_b_id, None, cx)) .await .is_err()); @@ -1390,7 +1401,7 @@ async fn test_invite_access( .unwrap(); active_call_b - .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx)) + .update(cx_b, |call, cx| call.join_channel(channel_b_id, None, cx)) .await .unwrap(); diff --git a/crates/collab2/src/tests/integration_tests.rs b/crates/collab2/src/tests/integration_tests.rs index f2a39f35113df98df00f42eba2ff5fce59059358..e579c384e36a0e20beccbc812c7866bcbe6a6991 100644 --- a/crates/collab2/src/tests/integration_tests.rs +++ b/crates/collab2/src/tests/integration_tests.rs @@ -510,9 +510,10 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously( // Simultaneously join channel 1 and then channel 2 active_call_a - .update(cx_a, |call, cx| call.join_channel(channel_1, cx)) + .update(cx_a, |call, cx| call.join_channel(channel_1, None, cx)) .detach(); - let join_channel_2 = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_2, cx)); + let join_channel_2 = + active_call_a.update(cx_a, |call, cx| call.join_channel(channel_2, None, cx)); join_channel_2.await.unwrap(); @@ -538,7 +539,8 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously( call.invite(client_c.user_id().unwrap(), None, cx) }); - let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx)); + let join_channel = + active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, None, cx)); b_invite.await.unwrap(); c_invite.await.unwrap(); @@ -567,7 +569,8 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously( .unwrap(); // Simultaneously join channel 1 and call user B and user C from client A. - let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx)); + let join_channel = + active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, None, cx)); let b_invite = active_call_a.update(cx_a, |call, cx| { call.invite(client_b.user_id().unwrap(), None, cx) diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 3c2e78423a86b09dbff5639d8595def33e0cb90c..a92389e6bc1475ad6b92470dc8e4c03764d00769 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -2510,11 +2510,16 @@ impl CollabPanel { return; }; let active_call = ActiveCall::global(cx); - active_call - .update(cx, |active_call, cx| { - active_call.join_channel(channel_id, handle, cx) - }) - .detach_and_log_err(cx) + cx.spawn(|_, mut cx| async move { + active_call + .update(&mut cx, |active_call, cx| { + active_call.join_channel(channel_id, Some(handle), cx) + }) + .log_err()? + .await + .notify_async_err(&mut cx) + }) + .detach() } // fn join_channel_chat(&mut self, action: &JoinChannelChat, cx: &mut ViewContext) { From 60ce75c34a999161f63377772f6dd94d3cbf85f5 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 28 Nov 2023 16:52:12 -0700 Subject: [PATCH 4/4] Togglable channels, the greatest since sliced bread --- crates/collab_ui2/src/collab_panel.rs | 31 +++++++++++++----------- crates/ui2/src/components/disclosure.rs | 25 +++++++++++++------ crates/ui2/src/components/icon_button.rs | 15 ++++++++++-- crates/ui2/src/components/list.rs | 16 +++++++++--- 4 files changed, 61 insertions(+), 26 deletions(-) diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index a92389e6bc1475ad6b92470dc8e4c03764d00769..2a1ce758abcc39291e09aa044fe78f93307df247 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -2233,20 +2233,20 @@ impl CollabPanel { // self.toggle_channel_collapsed(action.location, cx); // } - // fn toggle_channel_collapsed<'a>(&mut self, channel_id: ChannelId, cx: &mut ViewContext) { - // match self.collapsed_channels.binary_search(&channel_id) { - // Ok(ix) => { - // self.collapsed_channels.remove(ix); - // } - // Err(ix) => { - // self.collapsed_channels.insert(ix, channel_id); - // } - // }; - // self.serialize(cx); - // self.update_entries(true, cx); - // cx.notify(); - // cx.focus_self(); - // } + fn toggle_channel_collapsed<'a>(&mut self, channel_id: ChannelId, cx: &mut ViewContext) { + match self.collapsed_channels.binary_search(&channel_id) { + Ok(ix) => { + self.collapsed_channels.remove(ix); + } + Err(ix) => { + self.collapsed_channels.insert(ix, channel_id); + } + }; + // self.serialize(cx); todo!() + self.update_entries(true, cx); + cx.notify(); + cx.focus_self(); + } fn is_channel_collapsed(&self, channel_id: ChannelId) -> bool { self.collapsed_channels.binary_search(&channel_id).is_ok() @@ -3129,6 +3129,9 @@ impl CollabPanel { } else { Toggle::NotToggleable }) + .on_toggle( + cx.listener(move |this, _, cx| this.toggle_channel_collapsed(channel_id, cx)), + ) .on_click(cx.listener(move |this, _, cx| { if this.drag_target_channel == ChannelDragTarget::None { if is_active { diff --git a/crates/ui2/src/components/disclosure.rs b/crates/ui2/src/components/disclosure.rs index 3ec8c1953e2f8edcabbf4f8a6c2eabcd0d7174d8..e0d7b1c5192611b9b2690601aaca77d6e1f89736 100644 --- a/crates/ui2/src/components/disclosure.rs +++ b/crates/ui2/src/components/disclosure.rs @@ -1,19 +1,30 @@ -use gpui::{div, Element, ParentElement}; +use std::rc::Rc; -use crate::{Color, Icon, IconElement, IconSize, Toggle}; +use gpui::{div, Element, IntoElement, MouseDownEvent, ParentElement, WindowContext}; -pub fn disclosure_control(toggle: Toggle) -> impl Element { +use crate::{Color, Icon, IconButton, IconSize, Toggle}; + +pub fn disclosure_control( + toggle: Toggle, + on_toggle: Option>, +) -> impl Element { match (toggle.is_toggleable(), toggle.is_toggled()) { (false, _) => div(), (_, true) => div().child( - IconElement::new(Icon::ChevronDown) + IconButton::new("toggle", Icon::ChevronDown) .color(Color::Muted) - .size(IconSize::Small), + .size(IconSize::Small) + .when_some(on_toggle, move |el, on_toggle| { + el.on_click(move |e, cx| on_toggle(e, cx)) + }), ), (_, false) => div().child( - IconElement::new(Icon::ChevronRight) + IconButton::new("toggle", Icon::ChevronRight) .color(Color::Muted) - .size(IconSize::Small), + .size(IconSize::Small) + .when_some(on_toggle, move |el, on_toggle| { + el.on_click(move |e, cx| on_toggle(e, cx)) + }), ), } } diff --git a/crates/ui2/src/components/icon_button.rs b/crates/ui2/src/components/icon_button.rs index b2f3f8403d6b050b6ecaa4e0638972ad3bdbe36e..104eb00ec83dc4a0b8ca9573f870628c53041b00 100644 --- a/crates/ui2/src/components/icon_button.rs +++ b/crates/ui2/src/components/icon_button.rs @@ -1,4 +1,4 @@ -use crate::{h_stack, prelude::*, Icon, IconElement}; +use crate::{h_stack, prelude::*, Icon, IconElement, IconSize}; use gpui::{prelude::*, Action, AnyView, Div, MouseButton, MouseDownEvent, Stateful}; #[derive(IntoElement)] @@ -6,6 +6,7 @@ pub struct IconButton { id: ElementId, icon: Icon, color: Color, + size: IconSize, variant: ButtonVariant, state: InteractionState, selected: bool, @@ -50,7 +51,11 @@ impl RenderOnce for IconButton { // place we use an icon button. // .hover(|style| style.bg(bg_hover_color)) .active(|style| style.bg(bg_active_color)) - .child(IconElement::new(self.icon).color(icon_color)); + .child( + IconElement::new(self.icon) + .size(self.size) + .color(icon_color), + ); if let Some(click_handler) = self.on_mouse_down { button = button.on_mouse_down(MouseButton::Left, move |event, cx| { @@ -76,6 +81,7 @@ impl IconButton { id: id.into(), icon, color: Color::default(), + size: Default::default(), variant: ButtonVariant::default(), state: InteractionState::default(), selected: false, @@ -94,6 +100,11 @@ impl IconButton { self } + pub fn size(mut self, size: IconSize) -> Self { + self.size = size; + self + } + pub fn variant(mut self, variant: ButtonVariant) -> Self { self.variant = variant; self diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index 749de951d9a3055050e18198956eb71c75684cfe..61ed483cd86eed013c46f662dd3fab4cb61f03b1 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -63,7 +63,7 @@ impl RenderOnce for ListHeader { type Rendered = Div; fn render(self, cx: &mut WindowContext) -> Self::Rendered { - let disclosure_control = disclosure_control(self.toggle); + let disclosure_control = disclosure_control(self.toggle, None); let meta = match self.meta { Some(ListHeaderMeta::Tools(icons)) => div().child( @@ -177,6 +177,7 @@ pub struct ListItem { toggle: Toggle, inset: bool, on_click: Option>, + on_toggle: Option>, on_secondary_mouse_down: Option>, children: SmallVec<[AnyElement; 2]>, } @@ -193,6 +194,7 @@ impl ListItem { inset: false, on_click: None, on_secondary_mouse_down: None, + on_toggle: None, children: SmallVec::new(), } } @@ -230,6 +232,14 @@ impl ListItem { self } + pub fn on_toggle( + mut self, + on_toggle: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, + ) -> Self { + self.on_toggle = Some(Rc::new(on_toggle)); + self + } + pub fn selected(mut self, selected: bool) -> Self { self.selected = selected; self @@ -283,7 +293,7 @@ impl RenderOnce for ListItem { this.bg(cx.theme().colors().ghost_element_selected) }) .when_some(self.on_click.clone(), |this, on_click| { - this.on_click(move |event, cx| { + this.cursor_pointer().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 { @@ -304,7 +314,7 @@ impl RenderOnce for ListItem { .gap_1() .items_center() .relative() - .child(disclosure_control(self.toggle)) + .child(disclosure_control(self.toggle, self.on_toggle)) .children(left_content) .children(self.children) // HACK: We need to attach the `on_click` handler to the child element in order to have the click