This commit is contained in:
2026-09-18 15:35:42 +07:00
parent e75b1b9f10
commit 492e50746f
3 changed files with 65 additions and 12 deletions
+4
View File
@@ -16,6 +16,9 @@ use ui::{Icon, IconName, Selectable, Sizable, StyledExt, WindowExtension, h_flex
use crate::dialogs::screening; use crate::dialogs::screening;
/// Group name callers can target from a `trailing` element to react to row hover.
pub const ROOM_ENTRY_GROUP: &str = "room-entry";
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct RoomEntry { pub struct RoomEntry {
ix: usize, ix: usize,
@@ -112,6 +115,7 @@ impl RenderOnce for RoomEntry {
h_flex() h_flex()
.id(self.ix) .id(self.ix)
.group(ROOM_ENTRY_GROUP)
.h_8() .h_8()
.w_full() .w_full()
.pl(px(6. + self.depth as f32 * 14.)) .pl(px(6. + self.depth as f32 * 14.))
+44 -1
View File
@@ -30,6 +30,7 @@ use crate::Command;
mod entry; mod entry;
mod tree; mod tree;
use entry::ROOM_ENTRY_GROUP;
pub(crate) use entry::RoomEntry; pub(crate) use entry::RoomEntry;
use tree::{SidebarRow, TreeRow, TreeRowKind, TreeSection, dummy_communities}; use tree::{SidebarRow, TreeRow, TreeRowKind, TreeSection, dummy_communities};
@@ -241,8 +242,10 @@ impl Sidebar {
SidebarRow::Room { SidebarRow::Room {
room, room,
depth, depth,
pinned: _pinned, pinned,
} => { } => {
let pinned = *pinned;
let room_id = room.read(cx).id;
let public_key = room.read(cx).display_member(cx).public_key(); let public_key = room.read(cx).display_member(cx).public_key();
let name = room.read(cx).display_name(cx); let name = room.read(cx).display_name(cx);
let avatar = room.read(cx).display_image(cx); let avatar = room.read(cx).display_image(cx);
@@ -255,6 +258,45 @@ impl Sidebar {
}); });
}); });
let sidebar = cx.entity().downgrade();
let trailing =
Button::new(ElementId::NamedInteger("room-menu".into(), index as u64))
.icon(IconName::Ellipsis)
.ghost_alt()
.xsmall()
.compact()
.invisible()
.group_hover(ROOM_ENTRY_GROUP, |style| style.visible())
.dropdown_menu(move |this, _window, _cx| {
let sidebar = sidebar.clone();
if pinned {
this.item(PopupMenuItem::new("Unpin").on_click(
move |_event, _window, cx| {
if let Err(error) =
sidebar.update(cx, |sidebar, cx| {
sidebar.unpin_room(room_id, cx);
})
{
log::error!("Failed to unpin room: {error}");
}
},
))
} else {
this.item(PopupMenuItem::new("Pin").on_click(
move |_event, _window, cx| {
if let Err(error) =
sidebar.update(cx, |sidebar, cx| {
sidebar.pin_room(room_id, cx);
})
{
log::error!("Failed to pin room: {error}");
}
},
))
}
});
RoomEntry::new(index) RoomEntry::new(index)
.name(name) .name(name)
.avatar(avatar) .avatar(avatar)
@@ -262,6 +304,7 @@ impl Sidebar {
.kind(kind) .kind(kind)
.created_at(created_at) .created_at(created_at)
.depth(*depth) .depth(*depth)
.trailing(trailing)
.on_click(handler) .on_click(handler)
.into_any_element() .into_any_element()
} }
+17 -11
View File
@@ -1,10 +1,9 @@
# Sidebar tree redesign # Sidebar tree redesign
Status: steps 1-5 implemented. Search now lives in `panels/search.rs`; the Status: steps 1-6 implemented. Search lives in `panels/search.rs`; the sidebar
sidebar renders the nav rail and the flattened tree. Remaining: step 6 (pin UI), renders the nav rail, the flattened tree, and per-row pin/unpin menus. Remaining:
step 7 (community rows are already rendered from dummy data, tracked by the step 7 (confirm the placeholder community names), optional step 8 (persistence),
`TODO(concord)`), optional step 8 (persistence), step 9 (cleanup of the step-6 step 9 (remove the unused `TreeRow::selected` and run the final cleanup).
dead code).
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
@@ -264,10 +263,10 @@ Search is now a panel, not a sidebar mode:
- Pin state: `pinned_rooms: Vec<u64>` in `Sidebar`, order = pin order. - Pin state: `pinned_rooms: Vec<u64>` in `Sidebar`, order = pin order.
- UI: hover ellipsis (`IconName::Ellipsis`, `ghost_alt`, `xsmall`, `compact`) - UI: hover ellipsis (`IconName::Ellipsis`, `ghost_alt`, `xsmall`, `compact`)
on each room row, opening a `DropdownMenu` with `Pin` / `Unpin` on each room row, opening a `DropdownMenu` with `Pin` / `Unpin`
(`PopupMenuItem::new(...).on_click(...)`). Verify the trigger click does not (`PopupMenuItem::new(...).on_click(...)`). The ellipsis is a `RoomEntry`
also fire the row's `emit_room` click; if it does, `cx.stop_propagation()` trailing element, hidden by default and revealed with `group_hover` against the
in the menu trigger's `on_click`. (There is no right-click menu pattern in row's `ROOM_ENTRY_GROUP` group. (There is no right-click menu pattern in the
the codebase yet; a context menu is a follow-up.) codebase yet; a context menu is a follow-up.)
- `Pinned` folder is hidden when no pinned room resolves to a live room; - `Pinned` folder is hidden when no pinned room resolves to a live room;
otherwise expanded by default, showing pinned rooms in pin order. otherwise expanded by default, showing pinned rooms in pin order.
- A pinned room remains listed under `Messages`. - A pinned room remains listed under `Messages`.
@@ -335,8 +334,15 @@ unused until step 5 consumes them. Run the checks in §15 after each step.
`uniform_list("sidebar-tree")`. `has_search`, `find_focused`, `set_input_focus` `uniform_list("sidebar-tree")`. `has_search`, `find_focused`, `set_input_focus`
were dropped because they only existed to switch the sidebar between the room were dropped because they only existed to switch the sidebar between the room
list and the search view. list and the search view.
- [ ] **Step 6 — pin UI.** Build the per-row ellipsis dropdown, wire - [x] **Step 6 — pin UI.** Per-row ellipsis (`IconName::Ellipsis`, `ghost_alt`,
`pin_room`/`unpin_room`. `xsmall`, `compact`) passed to `RoomEntry::trailing`, revealed on row hover
through the `ROOM_ENTRY_GROUP` group name, opening a `DropdownMenu` with
Pin/Unpin; the handlers call `pin_room`/`unpin_room` through a
`WeakEntity<Sidebar>`. Click propagation: `gpui_base::Popover` registers the
trigger's `on_mouse_down` with `cx.stop_propagation()`, and GPUI only fires an
element's `on_click` when that element recorded the matching mouse-down, so the
row's `emit_room` click does not fire when the menu trigger is clicked. No extra
handling was needed.
- [ ] **Step 7 — community section.** Render dummy entries and hint; add the - [ ] **Step 7 — community section.** Render dummy entries and hint; add the
`TODO(concord)` marker. The flattening and rendering landed with step 5 `TODO(concord)` marker. The flattening and rendering landed with step 5
(`SidebarRow::Community` -> `TreeRow`, dummy data from `dummy_communities()`), (`SidebarRow::Community` -> `TreeRow`, dummy data from `dummy_communities()`),