feat: show facepill on tabpanel
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -5629,7 +5629,6 @@ dependencies = [
|
|||||||
"gpui",
|
"gpui",
|
||||||
"image",
|
"image",
|
||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
"nostr-sdk",
|
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"paste",
|
"paste",
|
||||||
"regex",
|
"regex",
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ impl RenderOnce for Message {
|
|||||||
.border_l_2()
|
.border_l_2()
|
||||||
.border_color(cx.theme().background)
|
.border_color(cx.theme().background)
|
||||||
.hover(|this| {
|
.hover(|this| {
|
||||||
this.bg(cx.theme().accent.step(cx, ColorScaleStep::TWO))
|
this.bg(cx.theme().accent.step(cx, ColorScaleStep::ONE))
|
||||||
.border_color(cx.theme().accent.step(cx, ColorScaleStep::NINE))
|
.border_color(cx.theme().accent.step(cx, ColorScaleStep::NINE))
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
|
|||||||
@@ -386,8 +386,15 @@ impl Panel for ChatPanel {
|
|||||||
self.id.clone()
|
self.id.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn panel_metadata(&self) -> Option<Metadata> {
|
fn panel_facepile(&self, cx: &WindowContext) -> Option<Vec<String>> {
|
||||||
None
|
Some(
|
||||||
|
self.room
|
||||||
|
.read(cx)
|
||||||
|
.members
|
||||||
|
.iter()
|
||||||
|
.map(|member| member.avatar())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self, _cx: &WindowContext) -> AnyElement {
|
fn title(&self, _cx: &WindowContext) -> AnyElement {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ smallvec.workspace = true
|
|||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
nostr-sdk.workspace = true
|
|
||||||
|
|
||||||
paste = "1"
|
paste = "1"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,6 @@ use gpui::{
|
|||||||
AnyElement, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, Global, Hsla,
|
AnyElement, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, Global, Hsla,
|
||||||
IntoElement, SharedString, View, WeakView, WindowContext,
|
IntoElement, SharedString, View, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use nostr_sdk::prelude::Metadata;
|
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
pub enum PanelEvent {
|
pub enum PanelEvent {
|
||||||
@@ -38,8 +37,8 @@ pub trait Panel: EventEmitter<PanelEvent> + FocusableView {
|
|||||||
/// Once you have defined a panel id, this must not be changed.
|
/// Once you have defined a panel id, this must not be changed.
|
||||||
fn panel_id(&self) -> SharedString;
|
fn panel_id(&self) -> SharedString;
|
||||||
|
|
||||||
/// The optional metadata of the panel
|
/// The optional facepile of the panel
|
||||||
fn panel_metadata(&self) -> Option<Metadata> {
|
fn panel_facepile(&self, _cx: &WindowContext) -> Option<Vec<String>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +75,7 @@ pub trait Panel: EventEmitter<PanelEvent> + FocusableView {
|
|||||||
|
|
||||||
pub trait PanelView: 'static + Send + Sync {
|
pub trait PanelView: 'static + Send + Sync {
|
||||||
fn panel_id(&self, cx: &WindowContext) -> SharedString;
|
fn panel_id(&self, cx: &WindowContext) -> SharedString;
|
||||||
fn panel_metadata(&self, cx: &WindowContext) -> Option<Metadata>;
|
fn panel_facepile(&self, cx: &WindowContext) -> Option<Vec<String>>;
|
||||||
fn title(&self, _cx: &WindowContext) -> AnyElement;
|
fn title(&self, _cx: &WindowContext) -> AnyElement;
|
||||||
fn closeable(&self, cx: &WindowContext) -> bool;
|
fn closeable(&self, cx: &WindowContext) -> bool;
|
||||||
fn zoomable(&self, cx: &WindowContext) -> bool;
|
fn zoomable(&self, cx: &WindowContext) -> bool;
|
||||||
@@ -92,8 +91,8 @@ impl<T: Panel> PanelView for View<T> {
|
|||||||
self.read(cx).panel_id()
|
self.read(cx).panel_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn panel_metadata(&self, cx: &WindowContext) -> Option<Metadata> {
|
fn panel_facepile(&self, cx: &WindowContext) -> Option<Vec<String>> {
|
||||||
self.read(cx).panel_metadata()
|
self.read(cx).panel_facepile(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self, cx: &WindowContext) -> AnyElement {
|
fn title(&self, cx: &WindowContext) -> AnyElement {
|
||||||
|
|||||||
@@ -516,26 +516,25 @@ impl TabPanel {
|
|||||||
.gap_1()
|
.gap_1()
|
||||||
.text_ellipsis()
|
.text_ellipsis()
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.child(div().when_some(
|
.when_some(panel.panel_facepile(cx), |this, facepill| {
|
||||||
panel.panel_metadata(cx),
|
this.child(
|
||||||
|this, metadata| {
|
div()
|
||||||
if let Some(picture) = metadata.picture {
|
.flex()
|
||||||
this.flex_shrink_0().child(
|
.flex_row_reverse()
|
||||||
img(format!(
|
.items_center()
|
||||||
"https://wsrv.nl/?url={}&w=100&h=100&n=-1",
|
.justify_start()
|
||||||
picture
|
.children(facepill.into_iter().enumerate().rev().map(
|
||||||
))
|
|(ix, face)| {
|
||||||
.size_4()
|
div().when(ix > 0, |div| div.ml_neg_1()).child(
|
||||||
.rounded_full()
|
img(face)
|
||||||
.object_fit(ObjectFit::Cover),
|
.size_4()
|
||||||
)
|
.rounded_full()
|
||||||
} else {
|
.object_fit(ObjectFit::Cover),
|
||||||
this.flex_shrink_0().child(
|
)
|
||||||
img("brand/avatar.png").size_4().rounded_full(),
|
},
|
||||||
)
|
)),
|
||||||
}
|
)
|
||||||
},
|
})
|
||||||
))
|
|
||||||
.child(panel.title(cx)),
|
.child(panel.title(cx)),
|
||||||
)
|
)
|
||||||
.when(state.draggable, |this| {
|
.when(state.draggable, |this| {
|
||||||
@@ -595,7 +594,7 @@ impl TabPanel {
|
|||||||
active = false;
|
active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tab::new(("tab", ix), panel.title(cx), panel.panel_metadata(cx))
|
Tab::new(("tab", ix), panel.title(cx), panel.panel_facepile(cx))
|
||||||
.py_2()
|
.py_2()
|
||||||
.selected(active)
|
.selected(active)
|
||||||
.disabled(disabled)
|
.disabled(disabled)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use gpui::{ModelContext, Timer};
|
use gpui::{ModelContext, Timer};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
static INTERVAL: Duration = Duration::from_millis(500);
|
static INTERVAL: Duration = Duration::from_millis(500);
|
||||||
static PAUSE_DELAY: Duration = Duration::from_millis(300);
|
static PAUSE_DELAY: Duration = Duration::from_millis(300);
|
||||||
@@ -72,6 +71,7 @@ impl BlinkCursor {
|
|||||||
|
|
||||||
// delay 500ms to start the blinking
|
// delay 500ms to start the blinking
|
||||||
let epoch = self.next_epoch();
|
let epoch = self.next_epoch();
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
Timer::after(PAUSE_DELAY).await;
|
Timer::after(PAUSE_DELAY).await;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use crate::theme::ActiveTheme;
|
|||||||
use crate::Selectable;
|
use crate::Selectable;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use nostr_sdk::prelude::*;
|
|
||||||
|
|
||||||
pub mod tab_bar;
|
pub mod tab_bar;
|
||||||
|
|
||||||
@@ -12,7 +11,7 @@ pub struct Tab {
|
|||||||
id: ElementId,
|
id: ElementId,
|
||||||
base: Stateful<Div>,
|
base: Stateful<Div>,
|
||||||
label: AnyElement,
|
label: AnyElement,
|
||||||
metadata: Option<Metadata>,
|
facepill: Option<Vec<String>>,
|
||||||
prefix: Option<AnyElement>,
|
prefix: Option<AnyElement>,
|
||||||
suffix: Option<AnyElement>,
|
suffix: Option<AnyElement>,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
@@ -23,7 +22,7 @@ impl Tab {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
id: impl Into<ElementId>,
|
id: impl Into<ElementId>,
|
||||||
label: impl IntoElement,
|
label: impl IntoElement,
|
||||||
metadata: Option<Metadata>,
|
facepill: Option<Vec<String>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let id: ElementId = id.into();
|
let id: ElementId = id.into();
|
||||||
|
|
||||||
@@ -31,11 +30,11 @@ impl Tab {
|
|||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
base: div().id(id),
|
base: div().id(id),
|
||||||
label: label.into_any_element(),
|
label: label.into_any_element(),
|
||||||
metadata,
|
|
||||||
disabled: false,
|
disabled: false,
|
||||||
selected: false,
|
selected: false,
|
||||||
prefix: None,
|
prefix: None,
|
||||||
suffix: None,
|
suffix: None,
|
||||||
|
facepill,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,19 +142,25 @@ impl RenderOnce for Tab {
|
|||||||
.gap_1()
|
.gap_1()
|
||||||
.text_ellipsis()
|
.text_ellipsis()
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.child(div().when_some(self.metadata, |this, metadata| {
|
.when_some(self.facepill, |this, facepill| {
|
||||||
if let Some(picture) = metadata.picture {
|
this.child(
|
||||||
this.flex_shrink_0().child(
|
div()
|
||||||
img(format!("https://wsrv.nl/?url={}&w=100&h=100&n=-1", picture))
|
.flex()
|
||||||
.size_4()
|
.flex_row_reverse()
|
||||||
.rounded_full()
|
.items_center()
|
||||||
.object_fit(ObjectFit::Cover),
|
.justify_start()
|
||||||
)
|
.children(facepill.into_iter().enumerate().rev().map(
|
||||||
} else {
|
|(ix, face)| {
|
||||||
this.flex_shrink_0()
|
div().when(ix > 0, |div| div.ml_neg_1()).child(
|
||||||
.child(img("brand/avatar.png").size_4().rounded_full())
|
img(face)
|
||||||
}
|
.size_4()
|
||||||
}))
|
.rounded_full()
|
||||||
|
.object_fit(ObjectFit::Cover),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
})
|
||||||
.child(self.label),
|
.child(self.label),
|
||||||
)
|
)
|
||||||
.when_some(self.suffix, |this, suffix| this.child(suffix))
|
.when_some(self.suffix, |this, suffix| this.child(suffix))
|
||||||
|
|||||||
Reference in New Issue
Block a user