chore: improve performance (#42)

* use uniform list for rooms list

* move profile cache to outside gpui context

* update comment

* refactor

* refactor

* .

* .

* add avatar component

* .

* refactor

* .
This commit is contained in:
reya
2025-05-27 07:34:22 +07:00
committed by GitHub
parent 45564c7722
commit 0f884f8142
25 changed files with 1087 additions and 1373 deletions

99
crates/ui/src/avatar.rs Normal file
View File

@@ -0,0 +1,99 @@
use gpui::{
div, img, prelude::FluentBuilder, px, rems, AbsoluteLength, App, Hsla, ImageSource, Img,
IntoElement, ParentElement, RenderOnce, Styled, StyledImage, Window,
};
use theme::ActiveTheme;
/// An element that renders a user avatar with customizable appearance options.
///
/// # Examples
///
/// ```
/// use ui::{Avatar};
///
/// Avatar::new("path/to/image.png")
/// .grayscale(true)
/// .border_color(gpui::red());
/// ```
#[derive(IntoElement)]
pub struct Avatar {
image: Img,
size: Option<AbsoluteLength>,
border_color: Option<Hsla>,
}
impl Avatar {
/// Creates a new avatar element with the specified image source.
pub fn new(src: impl Into<ImageSource>) -> Self {
Avatar {
image: img(src),
size: None,
border_color: None,
}
}
/// Applies a grayscale filter to the avatar image.
///
/// # Examples
///
/// ```
/// use ui::{Avatar, AvatarShape};
///
/// let avatar = Avatar::new("path/to/image.png").grayscale(true);
/// ```
pub fn grayscale(mut self, grayscale: bool) -> Self {
self.image = self.image.grayscale(grayscale);
self
}
/// 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 {
self.border_color = Some(color.into());
self
}
/// Size overrides the avatar size. By default they are 1rem.
pub fn size<L: Into<AbsoluteLength>>(mut self, size: impl Into<Option<L>>) -> Self {
self.size = size.into().map(Into::into);
self
}
}
impl RenderOnce for Avatar {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let border_width = if self.border_color.is_some() {
px(2.)
} else {
px(0.)
};
let image_size = self.size.unwrap_or_else(|| rems(1.).into());
let container_size = image_size.to_pixels(window.rem_size()) + border_width * 2.;
div()
.flex_shrink_0()
.size(container_size)
.rounded_full()
.overflow_hidden()
.when_some(self.border_color, |this, color| {
this.border(border_width).border_color(color)
})
.child(
self.image
.size(image_size)
.rounded_full()
.object_fit(gpui::ObjectFit::Fill)
.bg(cx.theme().ghost_element_background)
.with_fallback(move || {
img("brand/avatar.png")
.size(image_size)
.rounded_full()
.into_any_element()
}),
)
}
}

View File

@@ -30,19 +30,19 @@ pub trait ButtonVariants: Sized {
self.with_variant(ButtonVariant::Primary)
}
/// With the secondary style for the Button.
fn secondary(self) -> Self {
self.with_variant(ButtonVariant::Secondary)
}
/// With the ghost style for the Button.
fn ghost(self) -> Self {
self.with_variant(ButtonVariant::Ghost)
}
/// With the link style for the Button.
fn link(self) -> Self {
self.with_variant(ButtonVariant::Link)
}
/// With the text style for the Button, it will no padding look like a normal text.
fn text(self) -> Self {
self.with_variant(ButtonVariant::Text)
/// With the transparent style for the Button.
fn transparent(self) -> Self {
self.with_variant(ButtonVariant::Transparent)
}
/// With the custom style for the Button.
@@ -86,9 +86,9 @@ impl ButtonCustomVariant {
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ButtonVariant {
Primary,
Secondary,
Ghost,
Link,
Text,
Transparent,
Custom(ButtonCustomVariant),
}
@@ -98,20 +98,6 @@ impl Default for ButtonVariant {
}
}
impl ButtonVariant {
fn is_link(&self) -> bool {
matches!(self, Self::Link)
}
fn is_text(&self) -> bool {
matches!(self, Self::Text)
}
fn no_padding(&self) -> bool {
self.is_link() || self.is_text()
}
}
type OnClick = Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>;
/// A Button element.
@@ -295,7 +281,7 @@ impl RenderOnce for Button {
ButtonRounded::Normal => this.rounded(cx.theme().radius),
ButtonRounded::Full => this.rounded_full(),
})
.when(!style.no_padding(), |this| {
.map(|this| {
if self.label.is_none() && self.children.is_empty() {
// Icon Button
match self.size {
@@ -321,13 +307,13 @@ impl RenderOnce for Button {
}
}
})
.text_color(normal_style.fg)
.when(self.selected, |this| {
let selected_style = style.selected(window, cx);
this.bg(selected_style.bg).text_color(selected_style.fg)
})
.when(!self.disabled && !self.selected, |this| {
this.bg(normal_style.bg)
.when(normal_style.underline, |this| this.text_decoration_1())
.hover(|this| {
let hover_style = style.hovered(window, cx);
this.bg(hover_style.bg)
@@ -359,7 +345,6 @@ impl RenderOnce for Button {
.text_color(disabled_style.fg)
.shadow_none()
})
.text_color(normal_style.fg)
.child({
div()
.flex()
@@ -405,13 +390,14 @@ impl RenderOnce for Button {
struct ButtonVariantStyle {
bg: Hsla,
fg: Hsla,
underline: bool,
}
impl ButtonVariant {
fn bg_color(&self, _window: &Window, cx: &App) -> Hsla {
match self {
ButtonVariant::Primary => cx.theme().element_background,
ButtonVariant::Secondary => cx.theme().elevated_surface_background,
ButtonVariant::Transparent => gpui::transparent_black(),
ButtonVariant::Custom(colors) => colors.color,
_ => cx.theme().ghost_element_background,
}
@@ -420,90 +406,87 @@ impl ButtonVariant {
fn text_color(&self, _window: &Window, cx: &App) -> Hsla {
match self {
ButtonVariant::Primary => cx.theme().element_foreground,
ButtonVariant::Link => cx.theme().text_accent,
ButtonVariant::Secondary => cx.theme().text_muted,
ButtonVariant::Transparent => cx.theme().text_placeholder,
ButtonVariant::Ghost => cx.theme().text_muted,
ButtonVariant::Custom(colors) => colors.foreground,
_ => cx.theme().text,
}
}
fn underline(&self, _window: &Window, _cx: &App) -> bool {
matches!(self, ButtonVariant::Link)
}
fn normal(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = self.bg_color(window, cx);
let fg = self.text_color(window, cx);
let underline = self.underline(window, cx);
ButtonVariantStyle { bg, fg, underline }
ButtonVariantStyle { bg, fg }
}
fn hovered(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = match self {
ButtonVariant::Primary => cx.theme().element_hover,
ButtonVariant::Secondary => cx.theme().secondary_hover,
ButtonVariant::Ghost => cx.theme().ghost_element_hover,
ButtonVariant::Link => cx.theme().ghost_element_background,
ButtonVariant::Text => cx.theme().ghost_element_background,
ButtonVariant::Transparent => gpui::transparent_black(),
ButtonVariant::Custom(colors) => colors.hover,
};
let fg = match self {
ButtonVariant::Secondary => cx.theme().secondary_foreground,
ButtonVariant::Ghost => cx.theme().text,
ButtonVariant::Link => cx.theme().text_accent,
ButtonVariant::Transparent => cx.theme().text_placeholder,
_ => self.text_color(window, cx),
};
let underline = self.underline(window, cx);
ButtonVariantStyle { bg, fg, underline }
ButtonVariantStyle { bg, fg }
}
fn active(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = match self {
ButtonVariant::Primary => cx.theme().element_active,
ButtonVariant::Secondary => cx.theme().secondary_active,
ButtonVariant::Ghost => cx.theme().ghost_element_active,
ButtonVariant::Transparent => gpui::transparent_black(),
ButtonVariant::Custom(colors) => colors.active,
_ => cx.theme().ghost_element_background,
};
let fg = match self {
ButtonVariant::Link => cx.theme().text_accent,
ButtonVariant::Text => cx.theme().text,
ButtonVariant::Secondary => cx.theme().secondary_foreground,
ButtonVariant::Transparent => cx.theme().text_placeholder,
_ => self.text_color(window, cx),
};
let underline = self.underline(window, cx);
ButtonVariantStyle { bg, fg, underline }
ButtonVariantStyle { bg, fg }
}
fn selected(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = match self {
ButtonVariant::Primary => cx.theme().element_selected,
ButtonVariant::Secondary => cx.theme().secondary_selected,
ButtonVariant::Ghost => cx.theme().ghost_element_selected,
ButtonVariant::Transparent => gpui::transparent_black(),
ButtonVariant::Custom(colors) => colors.active,
_ => cx.theme().ghost_element_background,
};
let fg = match self {
ButtonVariant::Link => cx.theme().text_accent,
ButtonVariant::Text => cx.theme().text,
ButtonVariant::Secondary => cx.theme().secondary_foreground,
ButtonVariant::Transparent => cx.theme().text_placeholder,
_ => self.text_color(window, cx),
};
let underline = self.underline(window, cx);
ButtonVariantStyle { bg, fg, underline }
ButtonVariantStyle { bg, fg }
}
fn disabled(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
fn disabled(&self, _window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = match self {
ButtonVariant::Link | ButtonVariant::Ghost | ButtonVariant::Text => {
cx.theme().ghost_element_disabled
}
ButtonVariant::Ghost => cx.theme().ghost_element_disabled,
ButtonVariant::Secondary => cx.theme().secondary_disabled,
_ => cx.theme().element_disabled,
};
let fg = match self {
ButtonVariant::Primary => cx.theme().text_muted, // TODO: use a different color?
_ => cx.theme().text_muted,
};
let underline = self.underline(window, cx);
ButtonVariantStyle { bg, fg, underline }
ButtonVariantStyle { bg, fg }
}
}

View File

@@ -8,16 +8,9 @@ pub use window_border::{window_border, WindowBorder};
pub use crate::Disableable;
mod event;
mod focusable;
mod icon;
mod root;
mod styled;
mod title_bar;
mod window_border;
pub(crate) mod actions;
pub mod animation;
pub mod avatar;
pub mod button;
pub mod checkbox;
pub mod context_menu;
@@ -41,6 +34,14 @@ pub mod tab;
pub mod text;
pub mod tooltip;
mod event;
mod focusable;
mod icon;
mod root;
mod styled;
mod title_bar;
mod window_border;
/// Initialize the UI module.
///
/// This must be called before using any of the UI components.

View File

@@ -198,7 +198,8 @@ impl<T: Styled> StyleSized<T> for T {
fn input_h(self, size: Size) -> Self {
match size {
Size::Small => self.h_7(),
Size::XSmall => self.h_7(),
Size::Small => self.h_8(),
Size::Medium => self.h_9(),
Size::Large => self.h_12(),
_ => self.h(px(24.)),

View File

@@ -1,4 +1,4 @@
use common::profile::SharedProfile;
use common::profile::RenderProfile;
use gpui::{
AnyElement, AnyView, App, ElementId, FontWeight, HighlightStyle, InteractiveText, IntoElement,
SharedString, StyledText, UnderlineStyle, Window,
@@ -273,7 +273,7 @@ pub fn render_plain_text_mut(
if let Some(profile) = profile_match {
// Profile found - create a mention
let display_name = format!("@{}", profile.shared_name());
let display_name = format!("@{}", profile.render_name());
// Replace mention with profile name
text.replace_range(range.clone(), &display_name);