feat: add contact-based chat creation and re-add chat screening #54
@@ -299,9 +299,9 @@ impl Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A stable seed for the room's generated avatar
|
/// A stable seed for the room's generated avatar
|
||||||
pub fn display_image_seed(&self, cx: &App) -> SharedString {
|
pub fn display_image_seed(&self, cx: &App) -> String {
|
||||||
if self.is_group() {
|
if self.is_group() {
|
||||||
SharedString::from(self.id.to_string())
|
self.id.to_string()
|
||||||
} else {
|
} else {
|
||||||
self.display_member(cx).avatar_seed()
|
self.display_member(cx).avatar_seed()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -641,10 +641,7 @@ impl CommunityPanel {
|
|||||||
.community
|
.community
|
||||||
.read_with(cx, |community, _cx| {
|
.read_with(cx, |community, _cx| {
|
||||||
let seed = community.id().to_hex();
|
let seed = community.id().to_hex();
|
||||||
let avatar = match community.icon() {
|
let avatar = Avatar::from_source(community.icon()).seed(seed).large();
|
||||||
Some(path) => Avatar::from_source(path).seed(seed).large(),
|
|
||||||
None => Avatar::new(None).seed(seed).large(),
|
|
||||||
};
|
|
||||||
|
|
||||||
(community.name(), avatar)
|
(community.name(), avatar)
|
||||||
})
|
})
|
||||||
@@ -762,10 +759,7 @@ impl Panel for CommunityPanel {
|
|||||||
self.community
|
self.community
|
||||||
.read_with(cx, |community, _cx| {
|
.read_with(cx, |community, _cx| {
|
||||||
let seed = community.id().to_hex();
|
let seed = community.id().to_hex();
|
||||||
let avatar = match community.icon() {
|
let avatar = Avatar::from_source(community.icon()).seed(seed).xsmall();
|
||||||
Some(path) => Avatar::from_source(path).seed(seed).xsmall(),
|
|
||||||
None => Avatar::new(None).seed(seed).xsmall(),
|
|
||||||
};
|
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
|||||||
@@ -113,8 +113,8 @@ impl Person {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A stable seed for this profile's generated avatar
|
/// A stable seed for this profile's generated avatar
|
||||||
pub fn avatar_seed(&self) -> SharedString {
|
pub fn avatar_seed(&self) -> String {
|
||||||
SharedString::from(self.public_key().to_hex())
|
self.public_key().to_hex()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get profile name
|
/// Get profile name
|
||||||
|
|||||||
+24
-132
@@ -10,7 +10,10 @@ use crate::{Selectable, Sizable, Size, StyledExt};
|
|||||||
|
|
||||||
/// Number of rows and columns in the generated pixel grid.
|
/// Number of rows and columns in the generated pixel grid.
|
||||||
const PIXEL_GRID: usize = 8;
|
const PIXEL_GRID: usize = 8;
|
||||||
/// Probability that a cell in the left half of the grid is filled.
|
/// Empty cells kept between the pattern and the avatar edge, so the art
|
||||||
|
/// gathers in the center instead of filling the whole avatar.
|
||||||
|
const MARGIN: usize = 1;
|
||||||
|
/// Probability that a cell in the left half of the pattern area is filled.
|
||||||
const FILL_PROBABILITY: f32 = 0.42;
|
const FILL_PROBABILITY: f32 = 0.42;
|
||||||
/// Probability that a filled cell uses the accent shade instead of the main color.
|
/// Probability that a filled cell uses the accent shade instead of the main color.
|
||||||
const ACCENT_PROBABILITY: f32 = 0.25;
|
const ACCENT_PROBABILITY: f32 = 0.25;
|
||||||
@@ -33,19 +36,6 @@ pub(super) fn avatar_size(size: Size) -> AbsoluteLength {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A deterministic, offline pixel-art avatar derived from a seed.
|
/// A deterministic, offline pixel-art avatar derived from a seed.
|
||||||
///
|
|
||||||
/// Use it for entities that have no profile picture: the same seed always
|
|
||||||
/// renders the same pattern, so identities stay recognizable without a
|
|
||||||
/// network round trip. The pattern is painted as geometry and cropped to a
|
|
||||||
/// circle, at the same sizes as [`Avatar`].
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use ui::avatar::PixelAvatar;
|
|
||||||
///
|
|
||||||
/// PixelAvatar::new("alice");
|
|
||||||
/// ```
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct PixelAvatar {
|
pub struct PixelAvatar {
|
||||||
seed: u64,
|
seed: u64,
|
||||||
@@ -87,7 +77,7 @@ impl RenderOnce for PixelAvatar {
|
|||||||
move |bounds, seed, window, cx| {
|
move |bounds, seed, window, cx| {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let main = Hsla {
|
let main = Hsla {
|
||||||
h: (theme.icon_accent.h + seed as f32 / u64::MAX as f32) % 1.,
|
h: seed as f32 / u64::MAX as f32,
|
||||||
s: 0.6,
|
s: 0.6,
|
||||||
l: if theme.is_dark() { 0.6 } else { 0.45 },
|
l: if theme.is_dark() { 0.6 } else { 0.45 },
|
||||||
a: 1.,
|
a: 1.,
|
||||||
@@ -142,8 +132,11 @@ fn pixel_pattern(seed: u64) -> [u8; PIXEL_GRID * PIXEL_GRID] {
|
|||||||
let mut pattern = [0u8; PIXEL_GRID * PIXEL_GRID];
|
let mut pattern = [0u8; PIXEL_GRID * PIXEL_GRID];
|
||||||
let mut filled = 0usize;
|
let mut filled = 0usize;
|
||||||
|
|
||||||
for row in 0..PIXEL_GRID {
|
let art_rows = PIXEL_GRID - 2 * MARGIN;
|
||||||
for col in 0..PIXEL_GRID / 2 {
|
let art_columns = PIXEL_GRID / 2 - MARGIN;
|
||||||
|
|
||||||
|
for row in MARGIN..PIXEL_GRID - MARGIN {
|
||||||
|
for col in MARGIN..PIXEL_GRID / 2 {
|
||||||
if rng.chance(FILL_PROBABILITY) {
|
if rng.chance(FILL_PROBABILITY) {
|
||||||
let accent = rng.chance(ACCENT_PROBABILITY);
|
let accent = rng.chance(ACCENT_PROBABILITY);
|
||||||
set_cell(&mut pattern, row, col, if accent { 2 } else { 1 });
|
set_cell(&mut pattern, row, col, if accent { 2 } else { 1 });
|
||||||
@@ -153,17 +146,17 @@ fn pixel_pattern(seed: u64) -> [u8; PIXEL_GRID * PIXEL_GRID] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if filled < MIN_FILLED {
|
if filled < MIN_FILLED {
|
||||||
let half = PIXEL_GRID * PIXEL_GRID / 2;
|
let total = art_rows * art_columns;
|
||||||
let start = (rng.next() % half as u64) as usize;
|
let start = (rng.next() % total as u64) as usize;
|
||||||
|
|
||||||
for offset in 0..half {
|
for offset in 0..total {
|
||||||
if filled >= MIN_FILLED {
|
if filled >= MIN_FILLED {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ix = (start + offset) % half;
|
let ix = (start + offset) % total;
|
||||||
let row = ix / (PIXEL_GRID / 2);
|
let row = MARGIN + ix / art_columns;
|
||||||
let col = ix % (PIXEL_GRID / 2);
|
let col = MARGIN + ix % art_columns;
|
||||||
|
|
||||||
if pattern[row * PIXEL_GRID + col] == 0 {
|
if pattern[row * PIXEL_GRID + col] == 0 {
|
||||||
set_cell(&mut pattern, row, col, 1);
|
set_cell(&mut pattern, row, col, 1);
|
||||||
@@ -358,24 +351,12 @@ fn generated_avatar(seed: Option<&str>, size: Pixels) -> AnyElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An element that renders a user avatar with customizable appearance options.
|
/// An element that renders a user avatar with customizable appearance options.
|
||||||
///
|
|
||||||
/// Entities without a picture still get a stable identity: the avatar falls
|
|
||||||
/// back to a [`PixelAvatar`] seeded through [`Avatar::seed`], both when there
|
|
||||||
/// is no picture and when the picture fails to load.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use ui::avatar::Avatar;
|
|
||||||
///
|
|
||||||
/// Avatar::new(None).seed("alice");
|
|
||||||
/// ```
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Avatar {
|
pub struct Avatar {
|
||||||
base: Div,
|
base: Div,
|
||||||
picture: Option<ImageSource>,
|
picture: Option<ImageSource>,
|
||||||
grayscale: bool,
|
grayscale: bool,
|
||||||
seed: Option<SharedString>,
|
seed: Option<String>,
|
||||||
style: StyleRefinement,
|
style: StyleRefinement,
|
||||||
size: Size,
|
size: Size,
|
||||||
border_color: Option<Hsla>,
|
border_color: Option<Hsla>,
|
||||||
@@ -384,16 +365,13 @@ pub struct Avatar {
|
|||||||
|
|
||||||
impl Avatar {
|
impl Avatar {
|
||||||
/// Creates an avatar for an entity whose profile picture may be missing.
|
/// Creates an avatar for an entity whose profile picture may be missing.
|
||||||
///
|
|
||||||
/// Use [`Avatar::seed`] to choose the generated
|
|
||||||
/// pixel avatar rendered when `picture` is `None`.
|
|
||||||
pub fn new(picture: Option<SharedString>) -> Self {
|
pub fn new(picture: Option<SharedString>) -> Self {
|
||||||
Self::from_picture(picture.map(ImageSource::from))
|
Self::from_source(picture)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an avatar from an already-resolved source.
|
/// Creates an avatar from an already-resolved source, which may be missing.
|
||||||
pub fn from_source(picture: impl Into<ImageSource>) -> Self {
|
pub fn from_source(picture: Option<impl Into<ImageSource>>) -> Self {
|
||||||
Self::from_picture(Some(picture.into()))
|
Self::from_picture(picture.map(Into::into))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_picture(picture: Option<ImageSource>) -> Self {
|
fn from_picture(picture: Option<ImageSource>) -> Self {
|
||||||
@@ -410,33 +388,18 @@ impl Avatar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the seed for the generated pixel avatar.
|
/// Sets the seed for the generated pixel avatar.
|
||||||
///
|
pub fn seed(mut self, seed: impl Into<String>) -> Self {
|
||||||
/// The seed should be a stable identifier of the entity the avatar
|
|
||||||
/// represents, such as a public key.
|
|
||||||
pub fn seed(mut self, seed: impl Into<SharedString>) -> Self {
|
|
||||||
self.seed = Some(seed.into());
|
self.seed = Some(seed.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies a grayscale filter to the avatar image.
|
/// Applies a grayscale filter to the avatar image.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use ui::avatar::Avatar;
|
|
||||||
///
|
|
||||||
/// Avatar::new(None).grayscale(true);
|
|
||||||
/// ```
|
|
||||||
pub fn grayscale(mut self, grayscale: bool) -> Self {
|
pub fn grayscale(mut self, grayscale: bool) -> Self {
|
||||||
self.grayscale = grayscale;
|
self.grayscale = grayscale;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the border color of the avatar.
|
/// Sets the border color of the avatar.
|
||||||
///
|
|
||||||
/// This might be used to match the border to the background color of
|
|
||||||
/// the parent element to create the illusion of cropping another
|
|
||||||
/// shape underneath (for example in face piles.)
|
|
||||||
pub fn border_color(mut self, color: impl Into<Hsla>) -> Self {
|
pub fn border_color(mut self, color: impl Into<Hsla>) -> Self {
|
||||||
self.border_color = Some(color.into());
|
self.border_color = Some(color.into());
|
||||||
self
|
self
|
||||||
@@ -480,6 +443,7 @@ impl RenderOnce for Avatar {
|
|||||||
} else {
|
} else {
|
||||||
px(0.)
|
px(0.)
|
||||||
};
|
};
|
||||||
|
|
||||||
let image_size = avatar_size(self.size).to_pixels(window.rem_size());
|
let image_size = avatar_size(self.size).to_pixels(window.rem_size());
|
||||||
let container_size = image_size + border_width * 2.;
|
let container_size = image_size + border_width * 2.;
|
||||||
|
|
||||||
@@ -487,6 +451,7 @@ impl RenderOnce for Avatar {
|
|||||||
Some(picture) => {
|
Some(picture) => {
|
||||||
let seed = self.seed;
|
let seed = self.seed;
|
||||||
let grayscale = self.grayscale;
|
let grayscale = self.grayscale;
|
||||||
|
|
||||||
img(picture)
|
img(picture)
|
||||||
.size(image_size)
|
.size(image_size)
|
||||||
.rounded_full()
|
.rounded_full()
|
||||||
@@ -510,76 +475,3 @@ impl RenderOnce for Avatar {
|
|||||||
.child(content)
|
.child(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pixel_patterns_are_symmetric_and_stable() {
|
|
||||||
for seed in 0..50 {
|
|
||||||
let pattern = pixel_pattern(seed);
|
|
||||||
let filled = pattern.iter().filter(|&&cell| cell != 0).count();
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
filled >= MIN_FILLED * 2,
|
|
||||||
"pattern too sparse for seed {seed}"
|
|
||||||
);
|
|
||||||
|
|
||||||
for row in 0..PIXEL_GRID {
|
|
||||||
for col in 0..PIXEL_GRID {
|
|
||||||
assert_eq!(
|
|
||||||
pattern[row * PIXEL_GRID + col],
|
|
||||||
pattern[row * PIXEL_GRID + (PIXEL_GRID - 1 - col)],
|
|
||||||
"asymmetric pattern for seed {seed} at ({row}, {col})"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for seed in [0, 1, 42, u64::MAX] {
|
|
||||||
assert_eq!(pixel_pattern(seed), pixel_pattern(seed));
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_ne!(pixel_pattern(42), pixel_pattern(43));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn area(polygon: &[Point<Pixels>]) -> f32 {
|
|
||||||
let mut sum: f32 = 0.;
|
|
||||||
for (&a, &b) in polygon.iter().zip(polygon.iter().cycle().skip(1)) {
|
|
||||||
sum += a.x.as_f32() * b.y.as_f32() - b.x.as_f32() * a.y.as_f32();
|
|
||||||
}
|
|
||||||
(sum / 2.).abs()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn clipping_keeps_only_the_part_inside_the_circle() {
|
|
||||||
let circle = circle_polygon(point(px(10.), px(10.)), 10.);
|
|
||||||
let square = |left: f32, top: f32| {
|
|
||||||
[
|
|
||||||
point(px(left), px(top)),
|
|
||||||
point(px(left + 4.), px(top)),
|
|
||||||
point(px(left + 4.), px(top + 4.)),
|
|
||||||
point(px(left), px(top + 4.)),
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
let inside = clip_polygon(&square(8., 8.), &circle);
|
|
||||||
assert!((area(&inside) - 16.).abs() < 0.05, "area {}", area(&inside));
|
|
||||||
|
|
||||||
assert!(clip_polygon(&square(20., 20.), &circle).is_empty());
|
|
||||||
|
|
||||||
let straddling = clip_polygon(&square(0., 0.), &circle);
|
|
||||||
for vertex in &straddling {
|
|
||||||
let delta_x = vertex.x.as_f32() - 10.;
|
|
||||||
let delta_y = vertex.y.as_f32() - 10.;
|
|
||||||
assert!(
|
|
||||||
delta_x.hypot(delta_y) <= 10. + 0.1,
|
|
||||||
"clipped vertex outside the circle"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let area = area(&straddling);
|
|
||||||
assert!(area > 0. && area < 16., "area {area}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use person::PersonRegistry;
|
use person::PersonRegistry;
|
||||||
|
use settings::AppSettings;
|
||||||
use state::NostrRegistry;
|
use state::NostrRegistry;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
@@ -21,8 +22,6 @@ use ui::input::{Input, InputEvent, InputState};
|
|||||||
use ui::nav::Nav;
|
use ui::nav::Nav;
|
||||||
use ui::{Disableable, IconName, Sizable, WindowExtension, h_flex, v_flex};
|
use ui::{Disableable, IconName, Sizable, WindowExtension, h_flex, v_flex};
|
||||||
|
|
||||||
use crate::sidebar::nav_avatar;
|
|
||||||
|
|
||||||
/// Maximum number of members a chat can have.
|
/// Maximum number of members a chat can have.
|
||||||
const MAX_MEMBERS: usize = 3;
|
const MAX_MEMBERS: usize = 3;
|
||||||
|
|
||||||
@@ -349,10 +348,14 @@ impl NewChat {
|
|||||||
Nav::new(SharedString::from(format!("contact-{}", start + offset)))
|
Nav::new(SharedString::from(format!("contact-{}", start + offset)))
|
||||||
.label(profile.name())
|
.label(profile.name())
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.when_some(
|
.when(!AppSettings::get_hide_avatar(cx), |this| {
|
||||||
nav_avatar(Some(profile.avatar_seed()), profile.avatar(), cx),
|
this.prefix(
|
||||||
|this, avatar| this.prefix(avatar),
|
Avatar::from_source(profile.avatar())
|
||||||
)
|
.seed(profile.avatar_seed())
|
||||||
|
.small()
|
||||||
|
.flex_shrink_0(),
|
||||||
|
)
|
||||||
|
})
|
||||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||||
this.toggle_member(&public_key, cx);
|
this.toggle_member(&public_key, cx);
|
||||||
}))
|
}))
|
||||||
@@ -380,9 +383,12 @@ impl NewChat {
|
|||||||
.bg(cx.theme().ghost_element_background_alt)
|
.bg(cx.theme().ghost_element_background_alt)
|
||||||
.text_size(px(10.))
|
.text_size(px(10.))
|
||||||
.hover(|this| this.bg(cx.theme().ghost_element_hover))
|
.hover(|this| this.bg(cx.theme().ghost_element_hover))
|
||||||
.when_some(profile.avatar(), |this, avatar| {
|
.child(
|
||||||
this.child(Avatar::from_source(avatar).xsmall().flex_shrink_0())
|
Avatar::from_source(profile.avatar())
|
||||||
})
|
.seed(profile.avatar_seed())
|
||||||
|
.xsmall()
|
||||||
|
.flex_shrink_0(),
|
||||||
|
)
|
||||||
.child(div().max_w_32().truncate().child(profile.name()))
|
.child(div().max_w_32().truncate().child(profile.name()))
|
||||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||||
this.toggle_member(&public_key, cx);
|
this.toggle_member(&public_key, cx);
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ use gpui::{
|
|||||||
use instant::Duration;
|
use instant::Duration;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use person::PersonRegistry;
|
use person::PersonRegistry;
|
||||||
|
use settings::AppSettings;
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
use state::{FIND_DELAY, NostrRegistry};
|
use state::{FIND_DELAY, NostrRegistry};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::dock::{DockArea, DockPlacement, Panel, PanelEvent, PanelHandle};
|
use ui::dock::{DockArea, DockPlacement, Panel, PanelEvent, PanelHandle};
|
||||||
use ui::input::{Input, InputEvent, InputState};
|
use ui::input::{Input, InputEvent, InputState};
|
||||||
@@ -23,8 +25,6 @@ use ui::nav::Nav;
|
|||||||
use ui::notification::Notification;
|
use ui::notification::Notification;
|
||||||
use ui::{Icon, IconName, Selectable, Sizable, StyledExt, WindowExtension, h_flex, v_flex};
|
use ui::{Icon, IconName, Selectable, Sizable, StyledExt, WindowExtension, h_flex, v_flex};
|
||||||
|
|
||||||
use crate::sidebar::nav_avatar;
|
|
||||||
|
|
||||||
const INPUT_PLACEHOLDER: &str = "Find or start a conversation";
|
const INPUT_PLACEHOLDER: &str = "Find or start a conversation";
|
||||||
|
|
||||||
pub fn init(dock: WeakEntity<DockArea>, window: &mut Window, cx: &mut App) -> Entity<SearchPanel> {
|
pub fn init(dock: WeakEntity<DockArea>, window: &mut Window, cx: &mut App) -> Entity<SearchPanel> {
|
||||||
@@ -337,6 +337,7 @@ impl SearchPanel {
|
|||||||
range: Range<usize>,
|
range: Range<usize>,
|
||||||
cx: &Context<Self>,
|
cx: &Context<Self>,
|
||||||
) -> Vec<impl IntoElement + use<>> {
|
) -> Vec<impl IntoElement + use<>> {
|
||||||
|
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
||||||
let persons = PersonRegistry::global(cx);
|
let persons = PersonRegistry::global(cx);
|
||||||
|
|
||||||
// Get the results
|
// Get the results
|
||||||
@@ -365,10 +366,14 @@ impl SearchPanel {
|
|||||||
.label(profile.name())
|
.label(profile.name())
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.font_medium()
|
.font_medium()
|
||||||
.when_some(
|
.when(!hide_avatar, |this| {
|
||||||
nav_avatar(Some(profile.avatar_seed()), profile.avatar(), cx),
|
this.prefix(
|
||||||
|this, avatar| this.prefix(avatar),
|
Avatar::from_source(profile.avatar())
|
||||||
)
|
.seed(profile.avatar_seed())
|
||||||
|
.small()
|
||||||
|
.flex_shrink_0(),
|
||||||
|
)
|
||||||
|
})
|
||||||
.on_click(handler)
|
.on_click(handler)
|
||||||
.selected(selected)
|
.selected(selected)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
@@ -382,6 +387,7 @@ impl SearchPanel {
|
|||||||
range: Range<usize>,
|
range: Range<usize>,
|
||||||
cx: &Context<Self>,
|
cx: &Context<Self>,
|
||||||
) -> Vec<impl IntoElement + use<>> {
|
) -> Vec<impl IntoElement + use<>> {
|
||||||
|
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
||||||
let persons = PersonRegistry::global(cx);
|
let persons = PersonRegistry::global(cx);
|
||||||
|
|
||||||
// Get the contact list
|
// Get the contact list
|
||||||
@@ -410,10 +416,14 @@ impl SearchPanel {
|
|||||||
.label(profile.name().trim())
|
.label(profile.name().trim())
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.font_medium()
|
.font_medium()
|
||||||
.when_some(
|
.when(!hide_avatar, |this| {
|
||||||
nav_avatar(Some(profile.avatar_seed()), profile.avatar(), cx),
|
this.prefix(
|
||||||
|this, avatar| this.prefix(avatar),
|
Avatar::from_source(profile.avatar())
|
||||||
)
|
.seed(profile.avatar_seed())
|
||||||
|
.small()
|
||||||
|
.flex_shrink_0(),
|
||||||
|
)
|
||||||
|
})
|
||||||
.on_click(handler)
|
.on_click(handler)
|
||||||
.selected(selected)
|
.selected(selected)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ mod tab;
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use tab::SidebarTab;
|
use tab::SidebarTab;
|
||||||
pub(crate) use utils::{nav_avatar, nav_icon, pick_banner};
|
pub(crate) use utils::{nav_icon, pick_banner};
|
||||||
|
|
||||||
pub enum SidebarRow {
|
pub enum SidebarRow {
|
||||||
Room { room: Entity<Room> },
|
Room { room: Entity<Room> },
|
||||||
@@ -833,6 +833,8 @@ impl Sidebar {
|
|||||||
rows: &[SidebarRow],
|
rows: &[SidebarRow],
|
||||||
cx: &Context<Sidebar>,
|
cx: &Context<Sidebar>,
|
||||||
) -> Vec<AnyElement> {
|
) -> Vec<AnyElement> {
|
||||||
|
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
||||||
|
|
||||||
rows.get(range.clone())
|
rows.get(range.clone())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
@@ -855,8 +857,13 @@ impl Sidebar {
|
|||||||
.label(name)
|
.label(name)
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.font_medium()
|
.font_medium()
|
||||||
.when_some(nav_avatar(Some(seed), picture, cx), |this, avatar| {
|
.when(!hide_avatar, |this| {
|
||||||
this.prefix(avatar)
|
this.prefix(
|
||||||
|
Avatar::from_source(picture)
|
||||||
|
.seed(seed)
|
||||||
|
.small()
|
||||||
|
.flex_shrink_0(),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.suffix(
|
.suffix(
|
||||||
div()
|
div()
|
||||||
@@ -904,10 +911,14 @@ impl Sidebar {
|
|||||||
.label(person.name())
|
.label(person.name())
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.font_medium()
|
.font_medium()
|
||||||
.when_some(
|
.when(!hide_avatar, |this| {
|
||||||
nav_avatar(Some(person.avatar_seed()), person.avatar(), cx),
|
this.prefix(
|
||||||
|this, avatar| this.prefix(avatar),
|
Avatar::from_source(person.avatar())
|
||||||
)
|
.seed(person.avatar_seed())
|
||||||
|
.small()
|
||||||
|
.flex_shrink_0(),
|
||||||
|
)
|
||||||
|
})
|
||||||
.selected(selected)
|
.selected(selected)
|
||||||
.on_click(move |_event, _window, cx| {
|
.on_click(move |_event, _window, cx| {
|
||||||
if let Err(error) = sidebar.update(cx, |this, cx| {
|
if let Err(error) = sidebar.update(cx, |this, cx| {
|
||||||
@@ -929,8 +940,13 @@ impl Sidebar {
|
|||||||
Nav::new(SharedString::from(format!("com-{index}")))
|
Nav::new(SharedString::from(format!("com-{index}")))
|
||||||
.label(name)
|
.label(name)
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.when_some(nav_avatar(Some(seed), picture, cx), |this, avatar| {
|
.when(!hide_avatar, |this| {
|
||||||
this.prefix(avatar)
|
this.prefix(
|
||||||
|
Avatar::from_source(picture)
|
||||||
|
.seed(seed)
|
||||||
|
.small()
|
||||||
|
.flex_shrink_0(),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.on_click(move |_event, window, cx| {
|
.on_click(move |_event, window, cx| {
|
||||||
ui::dock::add_panel_to(
|
ui::dock::add_panel_to(
|
||||||
@@ -1021,6 +1037,7 @@ impl Sidebar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn member_row(&self, public_key: &PublicKey, cx: &App) -> AnyElement {
|
fn member_row(&self, public_key: &PublicKey, cx: &App) -> AnyElement {
|
||||||
|
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
||||||
let persons = PersonRegistry::global(cx);
|
let persons = PersonRegistry::global(cx);
|
||||||
let person = persons.read(cx).get(public_key, cx);
|
let person = persons.read(cx).get(public_key, cx);
|
||||||
|
|
||||||
@@ -1028,10 +1045,14 @@ impl Sidebar {
|
|||||||
.label(person.name())
|
.label(person.name())
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.font_medium()
|
.font_medium()
|
||||||
.when_some(
|
.when(!hide_avatar, |this| {
|
||||||
nav_avatar(Some(person.avatar_seed()), person.avatar(), cx),
|
this.prefix(
|
||||||
|this, avatar| this.prefix(avatar),
|
Avatar::from_source(person.avatar())
|
||||||
)
|
.seed(person.avatar_seed())
|
||||||
|
.small()
|
||||||
|
.flex_shrink_0(),
|
||||||
|
)
|
||||||
|
})
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,9 @@
|
|||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use gpui::prelude::FluentBuilder as _;
|
use gpui::{AnyElement, App, IntoElement, ParentElement, SharedString, Styled, px};
|
||||||
use gpui::{AnyElement, App, ImageSource, IntoElement, ParentElement, SharedString, Styled, px};
|
|
||||||
use settings::AppSettings;
|
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::avatar::{Avatar, PixelAvatar};
|
|
||||||
use ui::{Icon, IconName, Sizable, h_flex};
|
use ui::{Icon, IconName, Sizable, h_flex};
|
||||||
|
|
||||||
pub(crate) fn nav_avatar(
|
|
||||||
seed: Option<impl Into<SharedString>>,
|
|
||||||
picture: Option<impl Into<ImageSource>>,
|
|
||||||
cx: &App,
|
|
||||||
) -> Option<AnyElement> {
|
|
||||||
if AppSettings::get_hide_avatar(cx) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
match (seed.map(Into::into), picture.map(Into::into)) {
|
|
||||||
(None, None) => None,
|
|
||||||
(seed, Some(picture)) => Some(
|
|
||||||
Avatar::from_source(picture)
|
|
||||||
.when_some(seed, |avatar, seed| avatar.seed(seed))
|
|
||||||
.small()
|
|
||||||
.flex_shrink_0()
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
(Some(seed), None) => Some(
|
|
||||||
PixelAvatar::new(seed)
|
|
||||||
.small()
|
|
||||||
.flex_shrink_0()
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn nav_icon(icon: IconName, cx: &App) -> AnyElement {
|
pub(crate) fn nav_icon(icon: IconName, cx: &App) -> AnyElement {
|
||||||
h_flex()
|
h_flex()
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
|
|||||||
Reference in New Issue
Block a user