refactor sidebar (wip)
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<circle cx="12" cy="12" r="9.25" stroke="currentColor" stroke-width="1.5"/><ellipse cx="12" cy="12" rx="3.5" ry="9.25" stroke="currentColor" stroke-width="1.5"/><path d="M3.5 9.25H20.5" stroke="currentColor" stroke-width="1.5"/><path d="M3.5 14.75H20.5" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 377 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M2.75 5.75V17.25C2.75 18.3546 3.64543 19.25 4.75 19.25H19.25C20.3546 19.25 21.25 18.3546 21.25 17.25V8.75C21.25 7.64543 20.3546 6.75 19.25 6.75H13.0704C12.4017 6.75 11.7772 6.4158 11.4063 5.8594L10.5937 4.6406C10.2228 4.0842 9.59834 3.75 8.92963 3.75H4.75C3.64543 3.75 2.75 4.64543 2.75 5.75Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 473 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M21.75 12C21.75 6.84375 17.9583 3.75 12 3.75C6.04167 3.75 2.25 6.84375 2.25 12C2.25 13.3368 3.17054 15.6055 3.3145 15.9522C3.32742 15.9833 3.34021 16.0117 3.3518 16.0433C3.45089 16.3136 3.85722 17.7527 2.25 19.8828C4.41667 20.914 6.71766 19.2188 6.71766 19.2188C8.30963 20.0597 10.2038 20.25 12 20.25C17.9583 20.25 21.75 17.1562 21.75 12Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 520 B |
@@ -33,12 +33,14 @@ pub enum IconName {
|
|||||||
Close,
|
Close,
|
||||||
CloseCircle,
|
CloseCircle,
|
||||||
CloseCircleFill,
|
CloseCircleFill,
|
||||||
|
Compass,
|
||||||
Copy,
|
Copy,
|
||||||
Device,
|
Device,
|
||||||
Door,
|
Door,
|
||||||
Ellipsis,
|
Ellipsis,
|
||||||
Emoji,
|
Emoji,
|
||||||
Eye,
|
Eye,
|
||||||
|
Folder,
|
||||||
Input,
|
Input,
|
||||||
Info,
|
Info,
|
||||||
Invite,
|
Invite,
|
||||||
@@ -47,6 +49,7 @@ pub enum IconName {
|
|||||||
Link,
|
Link,
|
||||||
Loader,
|
Loader,
|
||||||
Lock,
|
Lock,
|
||||||
|
Message,
|
||||||
Moon,
|
Moon,
|
||||||
Plus,
|
Plus,
|
||||||
PlusCircle,
|
PlusCircle,
|
||||||
@@ -106,12 +109,14 @@ impl IconNamed for IconName {
|
|||||||
Self::Close => "icons/close.svg",
|
Self::Close => "icons/close.svg",
|
||||||
Self::CloseCircle => "icons/close-circle.svg",
|
Self::CloseCircle => "icons/close-circle.svg",
|
||||||
Self::CloseCircleFill => "icons/close-circle-fill.svg",
|
Self::CloseCircleFill => "icons/close-circle-fill.svg",
|
||||||
|
Self::Compass => "icons/compass.svg",
|
||||||
Self::Copy => "icons/copy.svg",
|
Self::Copy => "icons/copy.svg",
|
||||||
Self::Device => "icons/device.svg",
|
Self::Device => "icons/device.svg",
|
||||||
Self::Door => "icons/door.svg",
|
Self::Door => "icons/door.svg",
|
||||||
Self::Ellipsis => "icons/ellipsis.svg",
|
Self::Ellipsis => "icons/ellipsis.svg",
|
||||||
Self::Emoji => "icons/emoji.svg",
|
Self::Emoji => "icons/emoji.svg",
|
||||||
Self::Eye => "icons/eye.svg",
|
Self::Eye => "icons/eye.svg",
|
||||||
|
Self::Folder => "icons/folder.svg",
|
||||||
Self::Input => "icons/input.svg",
|
Self::Input => "icons/input.svg",
|
||||||
Self::Info => "icons/info.svg",
|
Self::Info => "icons/info.svg",
|
||||||
Self::Invite => "icons/invite.svg",
|
Self::Invite => "icons/invite.svg",
|
||||||
@@ -120,6 +125,7 @@ impl IconNamed for IconName {
|
|||||||
Self::Link => "icons/link.svg",
|
Self::Link => "icons/link.svg",
|
||||||
Self::Loader => "icons/loader.svg",
|
Self::Loader => "icons/loader.svg",
|
||||||
Self::Lock => "icons/lock.svg",
|
Self::Lock => "icons/lock.svg",
|
||||||
|
Self::Message => "icons/message.svg",
|
||||||
Self::Moon => "icons/moon.svg",
|
Self::Moon => "icons/moon.svg",
|
||||||
Self::Plus => "icons/plus.svg",
|
Self::Plus => "icons/plus.svg",
|
||||||
Self::PlusCircle => "icons/plus-circle.svg",
|
Self::PlusCircle => "icons/plus-circle.svg",
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ use ui::{Icon, IconName, Root, Sizable, WindowExtension, h_flex, v_flex};
|
|||||||
use crate::dialogs::import::ImportIdentity;
|
use crate::dialogs::import::ImportIdentity;
|
||||||
use crate::dialogs::restore::RestoreEncryption;
|
use crate::dialogs::restore::RestoreEncryption;
|
||||||
use crate::dialogs::settings;
|
use crate::dialogs::settings;
|
||||||
use crate::panels::{backup, contact_list, greeter, messaging_relays, profile, relay_list};
|
use crate::panels::{
|
||||||
|
backup, browse, contact_list, greeter, inbox, messaging_relays, profile, relay_list, search,
|
||||||
|
};
|
||||||
use crate::sidebar::Sidebar;
|
use crate::sidebar::Sidebar;
|
||||||
|
|
||||||
mod dialogs;
|
mod dialogs;
|
||||||
@@ -57,6 +59,9 @@ enum Command {
|
|||||||
ShowSettings,
|
ShowSettings,
|
||||||
ShowBackup,
|
ShowBackup,
|
||||||
ShowContactList,
|
ShowContactList,
|
||||||
|
ShowInbox,
|
||||||
|
ShowBrowse,
|
||||||
|
ShowSearch,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Workspace {
|
pub struct Workspace {
|
||||||
@@ -296,6 +301,15 @@ impl Workspace {
|
|||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Command::ShowInbox => {
|
||||||
|
self.add_panel_to_dock(inbox::init(window, cx), DockPlacement::Center, window, cx);
|
||||||
|
}
|
||||||
|
Command::ShowBrowse => {
|
||||||
|
self.add_panel_to_dock(browse::init(window, cx), DockPlacement::Center, window, cx);
|
||||||
|
}
|
||||||
|
Command::ShowSearch => {
|
||||||
|
self.add_panel_to_dock(search::init(window, cx), DockPlacement::Center, window, cx);
|
||||||
|
}
|
||||||
Command::ShowBackup => {
|
Command::ShowBackup => {
|
||||||
self.add_panel_to_dock(backup::init(window, cx), DockPlacement::Left, window, cx);
|
self.add_panel_to_dock(backup::init(window, cx), DockPlacement::Left, window, cx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
use gpui::{
|
||||||
|
AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
|
IntoElement, ParentElement, Render, SharedString, Styled, Window,
|
||||||
|
};
|
||||||
|
use theme::ActiveTheme;
|
||||||
|
use ui::dock::{Panel, PanelEvent};
|
||||||
|
use ui::{Icon, IconName, Sizable, h_flex};
|
||||||
|
|
||||||
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<BrowsePanel> {
|
||||||
|
cx.new(|cx| BrowsePanel::new(window, cx))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BrowsePanel {
|
||||||
|
name: SharedString,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BrowsePanel {
|
||||||
|
fn new(_window: &mut Window, cx: &mut App) -> Self {
|
||||||
|
Self {
|
||||||
|
name: "Browse".into(),
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Panel for BrowsePanel {
|
||||||
|
fn panel_id(&self) -> SharedString {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self, cx: &App) -> AnyElement {
|
||||||
|
h_flex()
|
||||||
|
.gap_1p5()
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::Compass)
|
||||||
|
.small()
|
||||||
|
.text_color(cx.theme().icon_muted),
|
||||||
|
)
|
||||||
|
.child(self.name.clone())
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<PanelEvent> for BrowsePanel {}
|
||||||
|
|
||||||
|
impl Focusable for BrowsePanel {
|
||||||
|
fn focus_handle(&self, _: &App) -> FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for BrowsePanel {
|
||||||
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
h_flex()
|
||||||
|
.size_full()
|
||||||
|
.justify_center()
|
||||||
|
.text_sm()
|
||||||
|
.text_color(cx.theme().text_muted)
|
||||||
|
.child(self.name.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
use gpui::{
|
||||||
|
AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
|
IntoElement, ParentElement, Render, SharedString, Styled, Window,
|
||||||
|
};
|
||||||
|
use theme::ActiveTheme;
|
||||||
|
use ui::dock::{Panel, PanelEvent};
|
||||||
|
use ui::{Icon, IconName, Sizable, h_flex};
|
||||||
|
|
||||||
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<InboxPanel> {
|
||||||
|
cx.new(|cx| InboxPanel::new(window, cx))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InboxPanel {
|
||||||
|
name: SharedString,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InboxPanel {
|
||||||
|
fn new(_window: &mut Window, cx: &mut App) -> Self {
|
||||||
|
Self {
|
||||||
|
name: "Inbox".into(),
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Panel for InboxPanel {
|
||||||
|
fn panel_id(&self) -> SharedString {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self, cx: &App) -> AnyElement {
|
||||||
|
h_flex()
|
||||||
|
.gap_1p5()
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::Inbox)
|
||||||
|
.small()
|
||||||
|
.text_color(cx.theme().icon_muted),
|
||||||
|
)
|
||||||
|
.child(self.name.clone())
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<PanelEvent> for InboxPanel {}
|
||||||
|
|
||||||
|
impl Focusable for InboxPanel {
|
||||||
|
fn focus_handle(&self, _: &App) -> FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for InboxPanel {
|
||||||
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
h_flex()
|
||||||
|
.size_full()
|
||||||
|
.justify_center()
|
||||||
|
.text_sm()
|
||||||
|
.text_color(cx.theme().text_muted)
|
||||||
|
.child(self.name.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
pub mod backup;
|
pub mod backup;
|
||||||
|
pub mod browse;
|
||||||
pub mod contact_list;
|
pub mod contact_list;
|
||||||
pub mod greeter;
|
pub mod greeter;
|
||||||
|
pub mod inbox;
|
||||||
pub mod messaging_relays;
|
pub mod messaging_relays;
|
||||||
pub mod profile;
|
pub mod profile;
|
||||||
pub mod relay_list;
|
pub mod relay_list;
|
||||||
|
pub mod search;
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
use gpui::{
|
||||||
|
AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
|
IntoElement, ParentElement, Render, SharedString, Styled, Window,
|
||||||
|
};
|
||||||
|
use theme::ActiveTheme;
|
||||||
|
use ui::dock::{Panel, PanelEvent};
|
||||||
|
use ui::{Icon, IconName, Sizable, h_flex};
|
||||||
|
|
||||||
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<SearchPanel> {
|
||||||
|
cx.new(|cx| SearchPanel::new(window, cx))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SearchPanel {
|
||||||
|
name: SharedString,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SearchPanel {
|
||||||
|
fn new(_window: &mut Window, cx: &mut App) -> Self {
|
||||||
|
Self {
|
||||||
|
name: "Search".into(),
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Panel for SearchPanel {
|
||||||
|
fn panel_id(&self) -> SharedString {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self, cx: &App) -> AnyElement {
|
||||||
|
h_flex()
|
||||||
|
.gap_1p5()
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::Search)
|
||||||
|
.small()
|
||||||
|
.text_color(cx.theme().icon_muted),
|
||||||
|
)
|
||||||
|
.child(self.name.clone())
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<PanelEvent> for SearchPanel {}
|
||||||
|
|
||||||
|
impl Focusable for SearchPanel {
|
||||||
|
fn focus_handle(&self, _: &App) -> FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for SearchPanel {
|
||||||
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
h_flex()
|
||||||
|
.size_full()
|
||||||
|
.justify_center()
|
||||||
|
.text_sm()
|
||||||
|
.text_color(cx.theme().text_muted)
|
||||||
|
.child(self.name.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,8 @@ use std::rc::Rc;
|
|||||||
use chat::RoomKind;
|
use chat::RoomKind;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, ClickEvent, InteractiveElement, IntoElement, ParentElement as _, RenderOnce, SharedString,
|
AnyElement, App, ClickEvent, InteractiveElement, IntoElement, ParentElement as _, RenderOnce,
|
||||||
StatefulInteractiveElement, Styled, Window, div,
|
SharedString, StatefulInteractiveElement, Styled, Window, div, px,
|
||||||
};
|
};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
@@ -24,9 +24,11 @@ pub struct RoomEntry {
|
|||||||
avatar: Option<SharedString>,
|
avatar: Option<SharedString>,
|
||||||
created_at: Option<SharedString>,
|
created_at: Option<SharedString>,
|
||||||
kind: Option<RoomKind>,
|
kind: Option<RoomKind>,
|
||||||
|
depth: u8,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
handler: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
handler: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||||
|
trailing: Option<AnyElement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RoomEntry {
|
impl RoomEntry {
|
||||||
@@ -38,8 +40,10 @@ impl RoomEntry {
|
|||||||
avatar: None,
|
avatar: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
kind: None,
|
kind: None,
|
||||||
|
depth: 0,
|
||||||
handler: None,
|
handler: None,
|
||||||
selected: false,
|
selected: false,
|
||||||
|
trailing: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +72,16 @@ impl RoomEntry {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn depth(mut self, depth: u8) -> Self {
|
||||||
|
self.depth = depth;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trailing(mut self, trailing: impl IntoElement) -> Self {
|
||||||
|
self.trailing = Some(trailing.into_any_element());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn on_click(
|
pub fn on_click(
|
||||||
mut self,
|
mut self,
|
||||||
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||||
@@ -98,9 +112,10 @@ impl RenderOnce for RoomEntry {
|
|||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(self.ix)
|
.id(self.ix)
|
||||||
.h_9()
|
.h_8()
|
||||||
.w_full()
|
.w_full()
|
||||||
.px_1p5()
|
.pl(px(6. + self.depth as f32 * 14.))
|
||||||
|
.pr_1p5()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.rounded(cx.theme().radius)
|
.rounded(cx.theme().radius)
|
||||||
@@ -143,6 +158,7 @@ impl RenderOnce for RoomEntry {
|
|||||||
.when_some(self.created_at, |this, created_at| this.child(created_at)),
|
.when_some(self.created_at, |this, created_at| this.child(created_at)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.when_some(self.trailing, |this, trailing| this.child(trailing))
|
||||||
.hover(|this| this.bg(cx.theme().elevated_surface_background))
|
.hover(|this| this.bg(cx.theme().elevated_surface_background))
|
||||||
.when_some(self.handler, |this, handler| {
|
.when_some(self.handler, |this, handler| {
|
||||||
this.on_click(move |event, window, cx| {
|
this.on_click(move |event, window, cx| {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ use ui::{
|
|||||||
use crate::Command;
|
use crate::Command;
|
||||||
|
|
||||||
mod entry;
|
mod entry;
|
||||||
|
mod tree;
|
||||||
|
|
||||||
const INPUT_PLACEHOLDER: &str = "Find or start a conversation";
|
const INPUT_PLACEHOLDER: &str = "Find or start a conversation";
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,230 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use chat::Room;
|
||||||
|
use gpui::prelude::FluentBuilder;
|
||||||
|
use gpui::{
|
||||||
|
App, ClickEvent, ElementId, Entity, InteractiveElement, IntoElement, ParentElement, RenderOnce,
|
||||||
|
SharedString, StatefulInteractiveElement, Styled, Window, div, px,
|
||||||
|
};
|
||||||
|
use theme::ActiveTheme;
|
||||||
|
use ui::{Icon, IconName, Sizable, StyledExt, h_flex};
|
||||||
|
|
||||||
|
/// Collapsible tree sections; declaration order is render order.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum TreeSection {
|
||||||
|
Pins,
|
||||||
|
Requests,
|
||||||
|
Community,
|
||||||
|
Messages,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One rendered tree row, in flattened order.
|
||||||
|
pub enum SidebarRow {
|
||||||
|
Section {
|
||||||
|
section: TreeSection,
|
||||||
|
count: usize,
|
||||||
|
},
|
||||||
|
Room {
|
||||||
|
room: Entity<Room>,
|
||||||
|
depth: u8,
|
||||||
|
pinned: bool,
|
||||||
|
},
|
||||||
|
Community {
|
||||||
|
entry: &'static CommunityEntry,
|
||||||
|
depth: u8,
|
||||||
|
},
|
||||||
|
Hint {
|
||||||
|
text: SharedString,
|
||||||
|
depth: u8,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A community shown under the Community section.
|
||||||
|
pub struct CommunityEntry {
|
||||||
|
pub name: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Communities to show until the Concord backend is wired up.
|
||||||
|
pub fn dummy_communities() -> &'static [CommunityEntry] {
|
||||||
|
// TODO(concord): replace with ConcordRegistry communities, see docs/concord-usage.md.
|
||||||
|
&[
|
||||||
|
CommunityEntry {
|
||||||
|
name: "Coop Contributors",
|
||||||
|
},
|
||||||
|
CommunityEntry {
|
||||||
|
name: "Nostr Design",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Presentation differences between the rows [`TreeRow`] draws.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum TreeRowKind {
|
||||||
|
Section,
|
||||||
|
Community,
|
||||||
|
Hint,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Folder/file row. One element for section headers, community rows and hints.
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct TreeRow {
|
||||||
|
id: ElementId,
|
||||||
|
kind: TreeRowKind,
|
||||||
|
depth: u8,
|
||||||
|
caret: Option<IconName>,
|
||||||
|
icon: Option<IconName>,
|
||||||
|
avatar: Option<SharedString>,
|
||||||
|
label: SharedString,
|
||||||
|
count: Option<usize>,
|
||||||
|
dot: bool,
|
||||||
|
selected: bool,
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TreeRow {
|
||||||
|
pub fn new(
|
||||||
|
id: impl Into<ElementId>,
|
||||||
|
kind: TreeRowKind,
|
||||||
|
label: impl Into<SharedString>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
id: id.into(),
|
||||||
|
kind,
|
||||||
|
depth: 0,
|
||||||
|
caret: None,
|
||||||
|
icon: None,
|
||||||
|
avatar: None,
|
||||||
|
label: label.into(),
|
||||||
|
count: None,
|
||||||
|
dot: false,
|
||||||
|
selected: false,
|
||||||
|
on_click: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn depth(mut self, depth: u8) -> Self {
|
||||||
|
self.depth = depth;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn caret(mut self, caret: IconName) -> Self {
|
||||||
|
self.caret = Some(caret);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon(mut self, icon: IconName) -> Self {
|
||||||
|
self.icon = Some(icon);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn avatar(mut self, name: impl Into<SharedString>) -> Self {
|
||||||
|
self.avatar = Some(name.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn count(mut self, count: usize) -> Self {
|
||||||
|
self.count = Some(count);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dot(mut self) -> Self {
|
||||||
|
self.dot = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selected(mut self, selected: bool) -> Self {
|
||||||
|
self.selected = selected;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_click(
|
||||||
|
mut self,
|
||||||
|
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||||
|
) -> Self {
|
||||||
|
self.on_click = Some(Rc::new(handler));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for TreeRow {
|
||||||
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let indent = px(6. + self.depth as f32 * 14.);
|
||||||
|
let avatar_initial = self
|
||||||
|
.avatar
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|name| name.chars().next())
|
||||||
|
.map(|letter| SharedString::from(letter.to_uppercase().to_string()));
|
||||||
|
let is_section = self.kind == TreeRowKind::Section;
|
||||||
|
let is_community = self.kind == TreeRowKind::Community;
|
||||||
|
let is_hint = self.kind == TreeRowKind::Hint;
|
||||||
|
|
||||||
|
h_flex()
|
||||||
|
.id(self.id)
|
||||||
|
.h_8()
|
||||||
|
.w_full()
|
||||||
|
.pl(indent)
|
||||||
|
.pr_1p5()
|
||||||
|
.gap_2()
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
|
.when(is_section, |this| {
|
||||||
|
this.text_xs()
|
||||||
|
.font_semibold()
|
||||||
|
.text_color(cx.theme().text_muted)
|
||||||
|
})
|
||||||
|
.when(is_community, |this| this.text_sm())
|
||||||
|
.when(is_hint, |this| {
|
||||||
|
this.text_xs()
|
||||||
|
.font_normal()
|
||||||
|
.text_color(cx.theme().text_placeholder)
|
||||||
|
})
|
||||||
|
.when(self.selected, |this| {
|
||||||
|
this.bg(cx.theme().ghost_element_selected)
|
||||||
|
})
|
||||||
|
.when_some(self.caret, |this, caret| {
|
||||||
|
this.child(Icon::new(caret).xsmall().text_color(cx.theme().icon_muted))
|
||||||
|
})
|
||||||
|
.when_some(self.icon, |this, icon| {
|
||||||
|
this.child(Icon::new(icon).small().text_color(cx.theme().icon_muted))
|
||||||
|
})
|
||||||
|
.when_some(avatar_initial, |this, initial| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.flex_shrink_0()
|
||||||
|
.size_5()
|
||||||
|
.rounded_full()
|
||||||
|
.bg(cx.theme().element_background)
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_center()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(cx.theme().text)
|
||||||
|
.child(initial),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.child(div().flex_1().truncate().child(self.label))
|
||||||
|
.when_some(self.count, |this, count| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.flex_shrink_0()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(cx.theme().text_placeholder)
|
||||||
|
.child(count.to_string()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when(self.dot, |this| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.flex_shrink_0()
|
||||||
|
.size_1()
|
||||||
|
.rounded_full()
|
||||||
|
.bg(cx.theme().cursor),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when_some(self.on_click, |this, handler| {
|
||||||
|
this.cursor_pointer()
|
||||||
|
.hover(|this| this.bg(cx.theme().ghost_element_hover))
|
||||||
|
.on_click(move |event, window, cx| handler(event, window, cx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# Sidebar tree redesign
|
# Sidebar tree redesign
|
||||||
|
|
||||||
Status: proposed, not implemented.
|
Status: steps 1-4 implemented (icons, tree primitives, `RoomEntry` extensions,
|
||||||
|
panel shells); step 5 (search relocation + sidebar render rewrite) not started.
|
||||||
|
|
||||||
Scope: `crates/workspace/src/sidebar` (`mod.rs`, `entry.rs`, new `tree.rs`),
|
Scope: `crates/workspace/src/sidebar` (`mod.rs`, `entry.rs`, new `tree.rs`),
|
||||||
new panel shells in `crates/workspace/src/panels/`, and the `Command` wiring in
|
new panel shells in `crates/workspace/src/panels/`, and the `Command` wiring in
|
||||||
@@ -302,16 +303,16 @@ because removing the search fields breaks the old render and rewriting the
|
|||||||
render orphans the search code. Helpers added in earlier steps may warn as
|
render orphans the search code. Helpers added in earlier steps may warn as
|
||||||
unused until step 5 consumes them. Run the checks in §15 after each step.
|
unused until step 5 consumes them. Run the checks in §15 after each step.
|
||||||
|
|
||||||
- [ ] **Step 1 — icons.** Add `assets/icons/folder.svg`, `compass.svg`,
|
- [x] **Step 1 — icons.** Add `assets/icons/folder.svg`, `compass.svg`,
|
||||||
`message.svg` (24x24 viewBox, `stroke="currentColor"`, `stroke-width="1.5"`,
|
`message.svg` (24x24 viewBox, `stroke="currentColor"`, `stroke-width="1.5"`,
|
||||||
matching existing files); add `Folder`, `Compass`, `Message` variants to
|
matching existing files); add `Folder`, `Compass`, `Message` variants to
|
||||||
`IconName` and its `path()` match in `crates/ui/src/icon.rs`.
|
`IconName` and its `path()` match in `crates/ui/src/icon.rs`.
|
||||||
- [ ] **Step 2 — tree primitives.** Add `crates/workspace/src/sidebar/tree.rs`
|
- [x] **Step 2 — tree primitives.** Add `crates/workspace/src/sidebar/tree.rs`
|
||||||
with `TreeSection`, `SidebarRow`, `CommunityEntry`, `dummy_communities()`,
|
with `TreeSection`, `SidebarRow`, `CommunityEntry`, `dummy_communities()`,
|
||||||
and the `TreeRow` element; declare `mod tree;` in `sidebar/mod.rs`.
|
and the `TreeRow` element; declare `mod tree;` in `sidebar/mod.rs`.
|
||||||
- [ ] **Step 3 — `RoomEntry`.** Add `.depth(u8)` and `.trailing(AnyElement)`;
|
- [x] **Step 3 — `RoomEntry`.** Add `.depth(u8)` and `.trailing(AnyElement)`;
|
||||||
change `h_9` to `h_8`.
|
change `h_9` to `h_8`.
|
||||||
- [ ] **Step 4 — panel openers.** Add `Command::{ShowInbox, ShowBrowse,
|
- [x] **Step 4 — panel openers.** Add `Command::{ShowInbox, ShowBrowse,
|
||||||
ShowSearch}` and `panels/{inbox,browse,search}.rs` shells (`init`, `Panel`,
|
ShowSearch}` and `panels/{inbox,browse,search}.rs` shells (`init`, `Panel`,
|
||||||
`Focusable`, `EventEmitter<PanelEvent>`, empty `Render`, following
|
`Focusable`, `EventEmitter<PanelEvent>`, empty `Render`, following
|
||||||
`greeter.rs`); register them in `panels/mod.rs`; handle the commands in
|
`greeter.rs`); register them in `panels/mod.rs`; handle the commands in
|
||||||
|
|||||||
Reference in New Issue
Block a user