From ac35dae66ec56b18e20de5665c8b48c747ec190d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 18 Jul 2023 18:55:54 -0700 Subject: [PATCH] Add channels panel with stubbed out information co-authored-by: nate --- Cargo.lock | 26 ++ Cargo.toml | 1 + assets/settings/default.json | 6 + crates/channels/Cargo.toml | 38 ++ crates/channels/src/channels.rs | 103 +++++ crates/channels/src/channels_panel.rs | 369 ++++++++++++++++++ .../channels/src/channels_panel_settings.rs | 37 ++ crates/gpui/src/elements/flex.rs | 7 + crates/project_panel/src/project_panel.rs | 24 -- crates/theme/src/theme.rs | 80 ++++ crates/theme/src/ui.rs | 10 + crates/workspace/src/dock.rs | 28 +- crates/zed/Cargo.toml | 1 + crates/zed/src/main.rs | 1 + crates/zed/src/zed.rs | 17 +- styles/src/style_tree/app.ts | 2 + styles/src/style_tree/channels_panel.ts | 68 ++++ 17 files changed, 784 insertions(+), 34 deletions(-) create mode 100644 crates/channels/Cargo.toml create mode 100644 crates/channels/src/channels.rs create mode 100644 crates/channels/src/channels_panel.rs create mode 100644 crates/channels/src/channels_panel_settings.rs create mode 100644 styles/src/style_tree/channels_panel.ts diff --git a/Cargo.lock b/Cargo.lock index 535c20bcb9..e0a4b6a7bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1254,6 +1254,31 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "channels" +version = "0.1.0" +dependencies = [ + "anyhow", + "client", + "collections", + "context_menu", + "db", + "editor", + "futures 0.3.28", + "gpui", + "log", + "menu", + "project", + "schemars", + "serde", + "serde_derive", + "serde_json", + "settings", + "theme", + "util", + "workspace", +] + [[package]] name = "chrono" version = "0.4.26" @@ -9857,6 +9882,7 @@ dependencies = [ "backtrace", "breadcrumbs", "call", + "channels", "chrono", "cli", "client", diff --git a/Cargo.toml b/Cargo.toml index 6e79c6b657..8803d1c34b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "crates/auto_update", "crates/breadcrumbs", "crates/call", + "crates/channels", "crates/cli", "crates/client", "crates/clock", diff --git a/assets/settings/default.json b/assets/settings/default.json index 397dac0961..c40ed4e8da 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -122,6 +122,12 @@ // Amount of indentation for nested items. "indent_size": 20 }, + "channels_panel": { + // Where to dock channels panel. Can be 'left' or 'right'. + "dock": "left", + // Default width of the channels panel. + "default_width": 240 + }, "assistant": { // Where to dock the assistant. Can be 'left', 'right' or 'bottom'. "dock": "right", diff --git a/crates/channels/Cargo.toml b/crates/channels/Cargo.toml new file mode 100644 index 0000000000..7507072130 --- /dev/null +++ b/crates/channels/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "channels" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/channels.rs" +doctest = false + +[dependencies] +collections = { path = "../collections" } +context_menu = { path = "../context_menu" } +client = { path = "../client" } +db = { path = "../db" } +editor = { path = "../editor" } +gpui = { path = "../gpui" } +project = { path = "../project" } +theme = { path = "../theme" } +settings = { path = "../settings" } +workspace = { path = "../workspace" } +menu = { path = "../menu" } +util = { path = "../util" } + +log.workspace = true +anyhow.workspace = true +schemars.workspace = true +serde_json.workspace = true +serde.workspace = true +serde_derive.workspace = true +futures.workspace = true + +[dev-dependencies] +client = { path = "../client", features = ["test-support"] } +editor = { path = "../editor", features = ["test-support"] } +gpui = { path = "../gpui", features = ["test-support"] } +workspace = { path = "../workspace", features = ["test-support"] } +serde_json.workspace = true diff --git a/crates/channels/src/channels.rs b/crates/channels/src/channels.rs new file mode 100644 index 0000000000..8e55441b29 --- /dev/null +++ b/crates/channels/src/channels.rs @@ -0,0 +1,103 @@ +mod channels_panel; +mod channels_panel_settings; + +pub use channels_panel::*; +use gpui::{AppContext, Entity}; + +use std::sync::Arc; + +use client::Client; + +pub fn init(client: Arc, cx: &mut AppContext) { + let channels = cx.add_model(|cx| Channels::new(client, cx)); + cx.set_global(channels); + channels_panel::init(cx); +} + +#[derive(Debug, Clone)] +struct Channel { + id: u64, + name: String, + sub_channels: Vec, + _room: Option<()>, +} + +impl Channel { + fn new(id: u64, name: impl AsRef, members: Vec) -> Channel { + Channel { + name: name.as_ref().to_string(), + id, + sub_channels: members, + _room: None, + } + } + + fn members(&self) -> &[Channel] { + &self.sub_channels + } + + fn name(&self) -> &str { + &self.name + } +} + +struct Channels { + channels: Vec, +} + +impl Channels { + fn channels(&self) -> Vec { + self.channels.clone() + } +} + +enum ChannelEvents {} + +impl Entity for Channels { + type Event = ChannelEvents; +} + +impl Channels { + fn new(_client: Arc, _cx: &mut AppContext) -> Self { + //TODO: Subscribe to channel updates from the server + Channels { + channels: vec![Channel::new( + 0, + "Zed Industries", + vec![ + Channel::new(1, "#general", Vec::new()), + Channel::new(2, "#admiral", Vec::new()), + Channel::new(3, "#livestreaming", vec![]), + Channel::new(4, "#crdb", Vec::new()), + Channel::new(5, "#crdb-1", Vec::new()), + Channel::new(6, "#crdb-2", Vec::new()), + Channel::new(7, "#crdb-3", vec![]), + Channel::new(8, "#crdb-4", Vec::new()), + Channel::new(9, "#crdb-1", Vec::new()), + Channel::new(10, "#crdb-1", Vec::new()), + Channel::new(11, "#crdb-1", Vec::new()), + Channel::new(12, "#crdb-1", vec![]), + Channel::new(13, "#crdb-1", Vec::new()), + Channel::new(14, "#crdb-1", Vec::new()), + Channel::new(15, "#crdb-1", Vec::new()), + Channel::new(16, "#crdb-1", Vec::new()), + Channel::new(17, "#crdb", vec![]), + ], + ), + Channel::new( + 18, + "CRDB Consulting", + vec![ + Channel::new(19, "#crdb 😭", Vec::new()), + Channel::new(20, "#crdb 😌", Vec::new()), + Channel::new(21, "#crdb 🦀", vec![]), + Channel::new(22, "#crdb 😤", Vec::new()), + Channel::new(23, "#crdb 😤", Vec::new()), + Channel::new(24, "#crdb 😤", Vec::new()), + Channel::new(25, "#crdb 😤", vec![]), + Channel::new(26, "#crdb 😤", Vec::new()), + ], + )], + } + } +} diff --git a/crates/channels/src/channels_panel.rs b/crates/channels/src/channels_panel.rs new file mode 100644 index 0000000000..73697b3b72 --- /dev/null +++ b/crates/channels/src/channels_panel.rs @@ -0,0 +1,369 @@ +use std::sync::Arc; + +use crate::{ + channels_panel_settings::{ChannelsPanelDockPosition, ChannelsPanelSettings}, + Channel, Channels, +}; +use anyhow::Result; +use collections::HashMap; +use context_menu::ContextMenu; +use db::kvp::KEY_VALUE_STORE; +use gpui::{ + actions, + elements::{ChildView, Empty, Flex, Label, MouseEventHandler, ParentElement, Stack}, + serde_json, AnyElement, AppContext, AsyncAppContext, Element, Entity, ModelHandle, Task, View, + ViewContext, ViewHandle, WeakViewHandle, +}; +use project::Fs; +use serde_derive::{Deserialize, Serialize}; +use settings::SettingsStore; +use theme::ChannelTreeStyle; +use util::{ResultExt, TryFutureExt}; +use workspace::{ + dock::{DockPosition, Panel}, + Workspace, +}; + +actions!(channels, [ToggleFocus]); + +const CHANNELS_PANEL_KEY: &'static str = "ChannelsPanel"; + +pub fn init(cx: &mut AppContext) { + settings::register::(cx); +} + +pub struct ChannelsPanel { + width: Option, + fs: Arc, + has_focus: bool, + pending_serialization: Task>, + channels: ModelHandle, + context_menu: ViewHandle, + collapsed_channels: HashMap, +} + +#[derive(Serialize, Deserialize)] +struct SerializedChannelsPanel { + width: Option, + collapsed_channels: Option>, +} + +#[derive(Debug)] +pub enum Event { + DockPositionChanged, + Focus, +} + +impl Entity for ChannelsPanel { + type Event = Event; +} + +impl ChannelsPanel { + pub fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> ViewHandle { + cx.add_view(|cx| { + let view_id = cx.view_id(); + let this = Self { + width: None, + has_focus: false, + fs: workspace.app_state().fs.clone(), + pending_serialization: Task::ready(None), + channels: cx.global::>().clone(), + context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)), + collapsed_channels: HashMap::default(), + }; + + // Update the dock position when the setting changes. + let mut old_dock_position = this.position(cx); + cx.observe_global::(move |this: &mut ChannelsPanel, cx| { + let new_dock_position = this.position(cx); + if new_dock_position != old_dock_position { + old_dock_position = new_dock_position; + cx.emit(Event::DockPositionChanged); + } + }) + .detach(); + + this + }) + } + + pub fn load( + workspace: WeakViewHandle, + cx: AsyncAppContext, + ) -> Task>> { + cx.spawn(|mut cx| async move { + let serialized_panel = if let Some(panel) = cx + .background() + .spawn(async move { KEY_VALUE_STORE.read_kvp(CHANNELS_PANEL_KEY) }) + .await + .log_err() + .flatten() + { + Some(serde_json::from_str::(&panel)?) + } else { + None + }; + + workspace.update(&mut cx, |workspace, cx| { + let panel = ChannelsPanel::new(workspace, cx); + if let Some(serialized_panel) = serialized_panel { + panel.update(cx, |panel, cx| { + panel.width = serialized_panel.width; + panel.collapsed_channels = + serialized_panel.collapsed_channels.unwrap_or_default(); + cx.notify(); + }); + } + panel + }) + }) + } + + fn serialize(&mut self, cx: &mut ViewContext) { + let width = self.width; + let collapsed_channels = self.collapsed_channels.clone(); + self.pending_serialization = cx.background().spawn( + async move { + KEY_VALUE_STORE + .write_kvp( + CHANNELS_PANEL_KEY.into(), + serde_json::to_string(&SerializedChannelsPanel { + width, + collapsed_channels: Some(collapsed_channels), + })?, + ) + .await?; + anyhow::Ok(()) + } + .log_err(), + ); + } + + fn render_channel( + &mut self, + depth: usize, + channel: &Channel, + style: &ChannelTreeStyle, + root: bool, + cx: &mut ViewContext, + ) -> AnyElement { + let has_chilren = !channel.members().is_empty(); + + let sub_channel_details = has_chilren.then(|| { + let mut sub_channels = Flex::column(); + let collapsed = self + .collapsed_channels + .get(&channel.id) + .copied() + .unwrap_or_default(); + if !collapsed { + for sub_channel in channel.members() { + sub_channels = sub_channels.with_child(self.render_channel( + depth + 1, + sub_channel, + style, + false, + cx, + )); + } + } + (sub_channels, collapsed) + }); + + let channel_id = channel.id; + + enum ChannelCollapser {} + Flex::row() + .with_child( + Empty::new() + .constrained() + .with_width(depth as f32 * style.channel_indent), + ) + .with_child( + Flex::column() + .with_child( + Flex::row() + .with_child( + sub_channel_details + .as_ref() + .map(|(_, expanded)| { + MouseEventHandler::::new( + channel.id as usize, + cx, + |state, _cx| { + let icon = + style.channel_icon.style_for(!*expanded, state); + theme::ui::icon(icon) + }, + ) + .on_click( + gpui::platform::MouseButton::Left, + move |_, v, cx| { + let entry = v + .collapsed_channels + .entry(channel_id) + .or_default(); + *entry = !*entry; + v.serialize(cx); + cx.notify(); + }, + ) + .into_any() + }) + .unwrap_or_else(|| { + Empty::new() + .constrained() + .with_width(style.channel_icon.default_style().width()) + .into_any() + }), + ) + .with_child( + Label::new( + channel.name().to_string(), + if root { + style.root_name.clone() + } else { + style.channel_name.clone() + }, + ) + .into_any(), + ), + ) + .with_children(sub_channel_details.map(|(elements, _)| elements)), + ) + .into_any() + } +} + +impl View for ChannelsPanel { + fn ui_name() -> &'static str { + "ChannelsPanel" + } + + fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { + if !self.has_focus { + self.has_focus = true; + cx.emit(Event::Focus); + } + } + + fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext) { + self.has_focus = false; + } + + fn render(&mut self, cx: &mut gpui::ViewContext<'_, '_, Self>) -> gpui::AnyElement { + let theme = theme::current(cx).clone(); + + let mut channels_column = Flex::column(); + for channel in self.channels.read(cx).channels() { + channels_column = channels_column.with_child(self.render_channel( + 0, + &channel, + &theme.channels_panel.channel_tree, + true, + cx, + )); + } + + let spacing = theme.channels_panel.spacing; + + enum ChannelsPanelScrollTag {} + Stack::new() + .with_child( + // Full panel column + Flex::column() + .with_spacing(spacing) + .with_child( + // Channels section column + Flex::column() + .with_child( + Flex::row().with_child( + Label::new( + "Active Channels", + theme.editor.invalid_information_diagnostic.message.clone(), + ) + .into_any(), + ), + ) + // Channels list column + .with_child(channels_column), + ) + // TODO: Replace with spacing implementation + .with_child(Empty::new().constrained().with_height(spacing)) + .with_child( + Flex::column().with_child( + Flex::row().with_child( + Label::new( + "Contacts", + theme.editor.invalid_information_diagnostic.message.clone(), + ) + .into_any(), + ), + ), + ) + .scrollable::(0, None, cx) + .expanded(), + ) + .with_child(ChildView::new(&self.context_menu, cx)) + .into_any_named("channels panel") + .into_any() + } +} + +impl Panel for ChannelsPanel { + fn position(&self, cx: &gpui::WindowContext) -> DockPosition { + match settings::get::(cx).dock { + ChannelsPanelDockPosition::Left => DockPosition::Left, + ChannelsPanelDockPosition::Right => DockPosition::Right, + } + } + + fn position_is_valid(&self, position: DockPosition) -> bool { + matches!(position, DockPosition::Left | DockPosition::Right) + } + + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { + settings::update_settings_file::( + self.fs.clone(), + cx, + move |settings| { + let dock = match position { + DockPosition::Left | DockPosition::Bottom => ChannelsPanelDockPosition::Left, + DockPosition::Right => ChannelsPanelDockPosition::Right, + }; + settings.dock = Some(dock); + }, + ); + } + + fn size(&self, cx: &gpui::WindowContext) -> f32 { + self.width + .unwrap_or_else(|| settings::get::(cx).default_width) + } + + fn set_size(&mut self, size: f32, cx: &mut ViewContext) { + self.width = Some(size); + self.serialize(cx); + cx.notify(); + } + + fn icon_path(&self) -> &'static str { + "icons/bolt_16.svg" + } + + fn icon_tooltip(&self) -> (String, Option>) { + ("Channels Panel".to_string(), Some(Box::new(ToggleFocus))) + } + + fn should_change_position_on_event(event: &Self::Event) -> bool { + matches!(event, Event::DockPositionChanged) + } + + fn has_focus(&self, _cx: &gpui::WindowContext) -> bool { + self.has_focus + } + + fn is_focus_event(event: &Self::Event) -> bool { + matches!(event, Event::Focus) + } +} diff --git a/crates/channels/src/channels_panel_settings.rs b/crates/channels/src/channels_panel_settings.rs new file mode 100644 index 0000000000..fe3484b782 --- /dev/null +++ b/crates/channels/src/channels_panel_settings.rs @@ -0,0 +1,37 @@ +use anyhow; +use schemars::JsonSchema; +use serde_derive::{Deserialize, Serialize}; +use settings::Setting; + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ChannelsPanelDockPosition { + Left, + Right, +} + +#[derive(Deserialize, Debug)] +pub struct ChannelsPanelSettings { + pub dock: ChannelsPanelDockPosition, + pub default_width: f32, +} + +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +pub struct ChannelsPanelSettingsContent { + pub dock: Option, + pub default_width: Option, +} + +impl Setting for ChannelsPanelSettings { + const KEY: Option<&'static str> = Some("channels_panel"); + + type FileContent = ChannelsPanelSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &gpui::AppContext, + ) -> anyhow::Result { + Self::load_via_json_merge(default_value, user_values) + } +} diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index 857f3f56fc..40959c8f5c 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -22,6 +22,7 @@ pub struct Flex { children: Vec>, scroll_state: Option<(ElementStateHandle>, usize)>, child_alignment: f32, + spacing: f32, } impl Flex { @@ -31,6 +32,7 @@ impl Flex { children: Default::default(), scroll_state: None, child_alignment: -1., + spacing: 0., } } @@ -42,6 +44,11 @@ impl Flex { Self::new(Axis::Vertical) } + pub fn with_spacing(mut self, spacing: f32) -> Self { + self.spacing = spacing; + self + } + /// Render children centered relative to the cross-axis of the parent flex. /// /// If this is a flex row, children will be centered vertically. If this is a diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index e6e1cff598..67a23f8d77 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1649,22 +1649,6 @@ impl workspace::dock::Panel for ProjectPanel { cx.notify(); } - fn should_zoom_in_on_event(_: &Self::Event) -> bool { - false - } - - fn should_zoom_out_on_event(_: &Self::Event) -> bool { - false - } - - fn is_zoomed(&self, _: &WindowContext) -> bool { - false - } - - fn set_zoomed(&mut self, _: bool, _: &mut ViewContext) {} - - fn set_active(&mut self, _: bool, _: &mut ViewContext) {} - fn icon_path(&self) -> &'static str { "icons/folder_tree_16.svg" } @@ -1677,14 +1661,6 @@ impl workspace::dock::Panel for ProjectPanel { matches!(event, Event::DockPositionChanged) } - fn should_activate_on_event(_: &Self::Event) -> bool { - false - } - - fn should_close_on_event(_: &Self::Event) -> bool { - false - } - fn has_focus(&self, _: &WindowContext) -> bool { self.has_focus } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 4766f636f3..844b093a5e 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -49,6 +49,7 @@ pub struct Theme { pub copilot: Copilot, pub contact_finder: ContactFinder, pub project_panel: ProjectPanel, + pub channels_panel: ChanelsPanelStyle, pub command_palette: CommandPalette, pub picker: Picker, pub editor: Editor, @@ -880,6 +881,16 @@ impl Interactive { } } +impl Toggleable> { + pub fn style_for(&self, active: bool, state: &mut MouseState) -> &T { + self.in_state(active).style_for(state) + } + + pub fn default_style(&self) -> &T { + &self.inactive.default + } +} + impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive { fn deserialize(deserializer: D) -> Result where @@ -1045,6 +1056,75 @@ pub struct AssistantStyle { pub saved_conversation: SavedConversation, } +#[derive(Clone, Deserialize, Default, JsonSchema)] +pub struct Contained { + container: ContainerStyle, + contained: T, +} + +#[derive(Clone, Deserialize, Default, JsonSchema)] +pub struct FlexStyle { + // Between item spacing + item_spacing: f32, +} + +#[derive(Clone, Deserialize, Default, JsonSchema)] +pub struct ChannelProjectStyle { + // TODO: Implement Contained Flex + // ContainerStyle + Spacing between elements + // Negative spacing overlaps elements instead of spacing them out + pub container: Contained, + pub host: ImageStyle, + pub title: ContainedText, + pub members: Contained, + pub member: ImageStyle +} + +#[derive(Clone, Deserialize, Default, JsonSchema)] +pub struct ChanneltemStyle { + pub icon: IconStyle, + pub title: TextStyle, +} + +#[derive(Clone, Deserialize, Default, JsonSchema)] +pub struct ChannelListStyle { + pub section_title: ContainedText, + pub channel: Toggleable>, + pub project: ChannelProjectStyle +} + +#[derive(Clone, Deserialize, Default, JsonSchema)] +pub struct ContactItemStyle { + pub container: Contained, + pub avatar: IconStyle, + pub name: TextStyle, +} + +#[derive(Clone, Deserialize, Default, JsonSchema)] +pub struct ContactsListStyle { + pub section_title: ContainedText, + pub contact: ContactItemStyle, +} + + +#[derive(Clone, Deserialize, Default, JsonSchema)] +pub struct ChannelTreeStyle { + pub channel_indent: f32, + pub channel_name: TextStyle, + pub root_name: TextStyle, + pub channel_icon: Toggleable>, +} + +#[derive(Clone, Deserialize, Default, JsonSchema)] +pub struct ChanelsPanelStyle { + pub channel_tree: ChannelTreeStyle, + pub spacing: f32, + // TODO: Uncomment: + // pub container: ContainerStyle, + // pub channel_list: ChannelListStyle, + // pub contacts_list: ContactsListStyle +} + #[derive(Clone, Deserialize, Default, JsonSchema)] pub struct SavedConversation { pub container: Interactive, diff --git a/crates/theme/src/ui.rs b/crates/theme/src/ui.rs index 308ea6f2d7..76f6883f0e 100644 --- a/crates/theme/src/ui.rs +++ b/crates/theme/src/ui.rs @@ -107,6 +107,16 @@ pub struct IconStyle { pub container: ContainerStyle, } +impl IconStyle { + pub fn width(&self) -> f32 { + self.icon.dimensions.width + + self.container.padding.left + + self.container.padding.right + + self.container.margin.left + + self.container.margin.right + } +} + pub fn icon(style: &IconStyle) -> Container { svg(&style.icon).contained().with_style(style.container) } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index ebaf399e22..3b0dc81920 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -20,13 +20,27 @@ pub trait Panel: View { None } fn should_change_position_on_event(_: &Self::Event) -> bool; - fn should_zoom_in_on_event(_: &Self::Event) -> bool; - fn should_zoom_out_on_event(_: &Self::Event) -> bool; - fn is_zoomed(&self, cx: &WindowContext) -> bool; - fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext); - fn set_active(&mut self, active: bool, cx: &mut ViewContext); - fn should_activate_on_event(_: &Self::Event) -> bool; - fn should_close_on_event(_: &Self::Event) -> bool; + fn should_zoom_in_on_event(_: &Self::Event) -> bool { + false + } + fn should_zoom_out_on_event(_: &Self::Event) -> bool { + false + } + fn is_zoomed(&self, _cx: &WindowContext) -> bool { + false + } + fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext) { + + } + fn set_active(&mut self, _active: bool, _cx: &mut ViewContext) { + + } + fn should_activate_on_event(_: &Self::Event) -> bool { + false + } + fn should_close_on_event(_: &Self::Event) -> bool { + false + } fn has_focus(&self, cx: &WindowContext) -> bool; fn is_focus_event(_: &Self::Event) -> bool; } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index a5877aaccb..71d8461b01 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -21,6 +21,7 @@ activity_indicator = { path = "../activity_indicator" } auto_update = { path = "../auto_update" } breadcrumbs = { path = "../breadcrumbs" } call = { path = "../call" } +channels = { path = "../channels" } cli = { path = "../cli" } collab_ui = { path = "../collab_ui" } collections = { path = "../collections" } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index e44ab3e33a..5739052b67 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -155,6 +155,7 @@ fn main() { outline::init(cx); project_symbols::init(cx); project_panel::init(Assets, cx); + channels::init(client.clone(), cx); diagnostics::init(cx); search::init(cx); semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 4b0bf1cd4c..c1046c0995 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -9,6 +9,7 @@ use ai::AssistantPanel; use anyhow::Context; use assets::Assets; use breadcrumbs::Breadcrumbs; +use channels::ChannelsPanel; pub use client; use collab_ui::{CollabTitlebarItem, ToggleContactsMenu}; use collections::VecDeque; @@ -221,6 +222,11 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { workspace.toggle_panel_focus::(cx); }, ); + cx.add_action( + |workspace: &mut Workspace, _: &channels::ToggleFocus, cx: &mut ViewContext| { + workspace.toggle_panel_focus::(cx); + }, + ); cx.add_action( |workspace: &mut Workspace, _: &terminal_panel::ToggleFocus, @@ -339,9 +345,13 @@ pub fn initialize_workspace( let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone()); let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone()); let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone()); - let (project_panel, terminal_panel, assistant_panel) = - futures::try_join!(project_panel, terminal_panel, assistant_panel)?; - + let channels_panel = ChannelsPanel::load(workspace_handle.clone(), cx.clone()); + let (project_panel, terminal_panel, assistant_panel, channels_panel) = futures::try_join!( + project_panel, + terminal_panel, + assistant_panel, + channels_panel + )?; workspace_handle.update(&mut cx, |workspace, cx| { let project_panel_position = project_panel.position(cx); workspace.add_panel_with_extra_event_handler( @@ -359,6 +369,7 @@ pub fn initialize_workspace( ); workspace.add_panel(terminal_panel, cx); workspace.add_panel(assistant_panel, cx); + workspace.add_panel(channels_panel, cx); if !was_deserialized && workspace diff --git a/styles/src/style_tree/app.ts b/styles/src/style_tree/app.ts index ee0aa133a0..d504f8e623 100644 --- a/styles/src/style_tree/app.ts +++ b/styles/src/style_tree/app.ts @@ -24,6 +24,7 @@ import { titlebar } from "./titlebar" import editor from "./editor" import feedback from "./feedback" import { useTheme } from "../common" +import channels_panel from "./channels_panel" export default function app(): any { const theme = useTheme() @@ -46,6 +47,7 @@ export default function app(): any { editor: editor(), project_diagnostics: project_diagnostics(), project_panel: project_panel(), + channels_panel: channels_panel(), contacts_popover: contacts_popover(), contact_finder: contact_finder(), contact_list: contact_list(), diff --git a/styles/src/style_tree/channels_panel.ts b/styles/src/style_tree/channels_panel.ts new file mode 100644 index 0000000000..b46db5dc38 --- /dev/null +++ b/styles/src/style_tree/channels_panel.ts @@ -0,0 +1,68 @@ +// import { with_opacity } from "../theme/color" +import { + // Border, + // TextStyle, + // background, + // border, + foreground, + text, +} from "./components" +import { interactive, toggleable } from "../element" +// import merge from "ts-deepmerge" +import { useTheme } from "../theme" +export default function channels_panel(): any { + const theme = useTheme() + + // const { is_light } = theme + + return { + spacing: 10, + channel_tree: { + channel_indent: 10, + channel_name: text(theme.middle, "sans", "variant", { size: "md" }), + root_name: text(theme.middle, "sans", "variant", { size: "lg", weight: "bold" }), + channel_icon: (() => { + const base_icon = (asset: any, color: any) => { + return { + icon: { + color, + asset, + dimensions: { + width: 12, + height: 12, + } + }, + container: { + corner_radius: 4, + padding: { + top: 4, bottom: 4, left: 4, right: 4 + }, + margin: { + right: 4, + }, + } + } + } + + return toggleable({ + state: { + inactive: interactive({ + state: { + default: base_icon("icons/chevron_right_8.svg", foreground(theme.middle, "variant")), + hovered: base_icon("icons/chevron_right_8.svg", foreground(theme.middle, "hovered")), + clicked: base_icon("icons/chevron_right_8.svg", foreground(theme.middle, "active")), + }, + }), + active: interactive({ + state: { + default: base_icon("icons/chevron_down_8.svg", foreground(theme.highest, "variant")), + hovered: base_icon("icons/chevron_down_8.svg", foreground(theme.highest, "hovered")), + clicked: base_icon("icons/chevron_down_8.svg", foreground(theme.highest, "active")), + }, + }), + }, + }) + })(), + } + } +}