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:
99
crates/ui/src/avatar.rs
Normal file
99
crates/ui/src/avatar.rs
Normal 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()
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.)),
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user