chore: minor ui components improvements (#140)

* improve ui

* .

* .
This commit is contained in:
reya
2025-09-04 07:30:03 +07:00
committed by GitHub
parent b11b0e0115
commit 70e235dcc2
14 changed files with 86 additions and 75 deletions

View File

@@ -1231,7 +1231,7 @@ impl ChatSpace {
this.child(
h_flex()
.id("downloading")
.px_4()
.px_2()
.h_6()
.gap_1()
.text_xs()
@@ -1434,7 +1434,7 @@ impl Render for ChatSpace {
// Only render titlebar child elements if user is logged in
if registry.identity.is_some() {
let profile = Registry::read_global(cx).identity(cx);
let profile = registry.identity(cx);
let left_side = self
.render_titlebar_left_side(window, cx)
@@ -1457,9 +1457,7 @@ impl Render for ChatSpace {
.relative()
.size_full()
.child(
div()
.flex()
.flex_col()
v_flex()
.size_full()
// Title Bar
.child(self.title_bar.clone())

View File

@@ -79,6 +79,9 @@ fn main() {
// Open a window with default options
cx.open_window(opts, |window, cx| {
// Bring the app to the foreground
cx.activate(true);
// Automatically sync theme with system appearance
window
.observe_window_appearance(|window, cx| {
@@ -88,7 +91,6 @@ fn main() {
// Root Entity
cx.new(|cx| {
cx.activate(true);
// Initialize the tokio runtime
gpui_tokio::init(cx);

View File

@@ -6,7 +6,7 @@ use gpui::prelude::FluentBuilder;
use gpui::{
div, img, list, px, red, relative, rems, svg, white, Action, AnyElement, App, AppContext,
ClipboardItem, Context, Element, Entity, EventEmitter, Flatten, FocusHandle, Focusable,
InteractiveElement, IntoElement, ListAlignment, ListState, MouseButton, ObjectFit,
InteractiveElement, IntoElement, ListAlignment, ListOffset, ListState, MouseButton, ObjectFit,
ParentElement, PathPromptOptions, Render, RetainAllImageCache, SharedString,
StatefulInteractiveElement, Styled, StyledImage, Subscription, Task, Window,
};
@@ -139,7 +139,7 @@ impl Chat {
match signal {
RoomSignal::NewMessage((gift_wrap_id, event)) => {
if !this.is_sent_by_coop(gift_wrap_id) {
this.insert_message(event, cx);
this.insert_message(event, false, cx);
}
}
RoomSignal::Refresh => {
@@ -258,11 +258,7 @@ impl Chat {
cx.defer_in(window, |this, window, cx| {
// Optimistically update message list
this.insert_message(temp_message, cx);
// Scroll to reveal the new message
this.list_state
.scroll_to_reveal_item(this.messages.len() + 1);
this.insert_message(temp_message, true, cx);
// Remove all replies
this.remove_all_replies(cx);
@@ -341,7 +337,7 @@ impl Chat {
}
/// Convert and insert a nostr event into the chat panel
fn insert_message<E>(&mut self, event: E, _cx: &mut Context<Self>)
fn insert_message<E>(&mut self, event: E, scroll: bool, cx: &mut Context<Self>)
where
E: Into<RenderedMessage>,
{
@@ -350,13 +346,21 @@ impl Chat {
// Extend the messages list with the new events
if self.messages.insert(Message::user(event)) {
self.list_state.splice(old_len..old_len, 1);
if scroll {
self.list_state.scroll_to(ListOffset {
item_ix: self.list_state.item_count(),
offset_in_item: px(0.0),
});
cx.notify();
}
}
}
/// Convert and insert a vector of nostr events into the chat panel
fn insert_messages(&mut self, events: Vec<Event>, cx: &mut Context<Self>) {
for event in events.into_iter() {
self.insert_message(event, cx);
self.insert_message(event, false, cx);
}
cx.notify();
}

View File

@@ -656,6 +656,7 @@ impl Render for Sidebar {
})
})
.small()
.cta()
.bold()
.secondary()
.rounded(ButtonRounded::Full)
@@ -676,6 +677,7 @@ impl Render for Sidebar {
})
})
.small()
.cta()
.bold()
.secondary()
.rounded(ButtonRounded::Full)

View File

@@ -1,8 +1,8 @@
use gpui::prelude::FluentBuilder as _;
use gpui::{
div, relative, AnyElement, App, ClickEvent, Div, ElementId, Hsla, InteractiveElement,
IntoElement, MouseButton, ParentElement, RenderOnce, SharedString,
StatefulInteractiveElement as _, Styled, Window,
IntoElement, MouseButton, ParentElement, RenderOnce, SharedString, Stateful,
StatefulInteractiveElement as _, StyleRefinement, Styled, Window,
};
use theme::ActiveTheme;
@@ -121,8 +121,8 @@ type OnClick = Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>
/// A Button element.
#[derive(IntoElement)]
pub struct Button {
pub base: Div,
id: ElementId,
base: Stateful<Div>,
style: StyleRefinement,
icon: Option<Icon>,
label: Option<SharedString>,
@@ -156,8 +156,8 @@ impl From<Button> for AnyElement {
impl Button {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
base: div().flex_shrink_0(),
id: id.into(),
base: div().id(id.into()).flex_shrink_0(),
style: StyleRefinement::default(),
icon: None,
label: None,
disabled: false,
@@ -255,14 +255,14 @@ impl Disableable for Button {
}
impl Selectable for Button {
fn element_id(&self) -> &ElementId {
&self.id
}
fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
fn is_selected(&self) -> bool {
self.selected
}
}
impl Sizable for Button {
@@ -280,8 +280,8 @@ impl ButtonVariants for Button {
}
impl Styled for Button {
fn style(&mut self) -> &mut gpui::StyleRefinement {
self.base.style()
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
@@ -308,11 +308,11 @@ impl RenderOnce for Button {
};
self.base
.id(self.id)
.flex_shrink_0()
.flex()
.items_center()
.justify_center()
.cursor_pointer()
.cursor_default()
.overflow_hidden()
.map(|this| match self.rounded {
ButtonRounded::Normal => this.rounded(cx.theme().radius),
@@ -359,6 +359,8 @@ impl RenderOnce for Button {
Size::XSmall => {
if self.icon.is_some() {
this.h_6().pl_2().pr_2p5()
} else if self.cta {
this.h_6().px_4()
} else {
this.h_6().px_2()
}
@@ -366,6 +368,8 @@ impl RenderOnce for Button {
Size::Small => {
if self.icon.is_some() {
this.h_7().pl_2().pr_2p5()
} else if self.cta {
this.h_7().px_4()
} else {
this.h_7().px_2()
}
@@ -388,10 +392,6 @@ 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)
.hover(|this| {
@@ -403,6 +403,10 @@ impl RenderOnce for Button {
this.bg(active_style.bg).text_color(active_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, |this| {
let disabled_style = style.disabled(window, cx);
this.cursor_not_allowed()
@@ -410,6 +414,7 @@ impl RenderOnce for Button {
.text_color(disabled_style.fg)
.shadow_none()
})
.refine_style(&self.style)
.child({
h_flex()
.id("label")

View File

@@ -54,13 +54,13 @@ impl Disableable for Checkbox {
}
impl Selectable for Checkbox {
fn element_id(&self) -> &ElementId {
&self.id
}
fn selected(self, selected: bool) -> Self {
self.checked(selected)
}
fn is_selected(&self) -> bool {
self.checked
}
}
impl RenderOnce for Checkbox {

View File

@@ -20,7 +20,7 @@ impl Indicator {
pub fn new() -> Self {
Self {
size: Size::Small,
speed: Duration::from_secs_f64(0.8),
speed: Duration::from_secs(1),
icon: Icon::new(IconName::Loader),
color: None,
}
@@ -52,17 +52,15 @@ impl Sizable for Indicator {
impl RenderOnce for Indicator {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
div()
.child(
self.icon
.with_size(self.size)
.when_some(self.color, |this, color| this.text_color(color))
.with_animation(
"circle",
Animation::new(self.speed).repeat().with_easing(ease_in_out),
|this, delta| this.transform(Transformation::rotate(percentage(delta))),
),
)
.into_element()
div().child(
self.icon
.with_size(self.size)
.when_some(self.color, |this, color| this.text_color(color))
.with_animation(
"circle",
Animation::new(self.speed).repeat().with_easing(ease_in_out),
|this, delta| this.transform(Transformation::rotate(percentage(delta))),
),
)
}
}

View File

@@ -15,7 +15,6 @@ type Suffix = Option<Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>>
#[derive(IntoElement)]
pub struct ListItem {
id: ElementId,
base: Stateful<Div>,
disabled: bool,
selected: bool,
@@ -30,8 +29,8 @@ pub struct ListItem {
impl ListItem {
pub fn new(id: impl Into<ElementId>) -> Self {
let id: ElementId = id.into();
Self {
id: id.clone(),
base: h_flex().id(id).gap_x_1().py_1().px_2().text_base(),
disabled: false,
selected: false,
@@ -104,14 +103,14 @@ impl Disableable for ListItem {
}
impl Selectable for ListItem {
fn element_id(&self) -> &ElementId {
&self.id
}
fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
fn is_selected(&self) -> bool {
self.selected
}
}
impl Styled for ListItem {

View File

@@ -299,7 +299,7 @@ impl Render for Notification {
.border_1()
.border_color(cx.theme().border)
.bg(cx.theme().surface_background)
.rounded(cx.theme().radius)
.rounded(cx.theme().radius * 1.6)
.shadow_md()
.p_2()
.gap_3()

View File

@@ -44,7 +44,7 @@ pub fn init(cx: &mut App) {
]);
}
pub trait PopupMenuExt: Styled + Selectable + IntoElement + 'static {
pub trait PopupMenuExt: Styled + Selectable + InteractiveElement + IntoElement + 'static {
/// Create a popup menu with the given items, anchored to the TopLeft corner
fn popup_menu(
self,
@@ -60,9 +60,9 @@ pub trait PopupMenuExt: Styled + Selectable + IntoElement + 'static {
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
) -> Popover<PopupMenu> {
let style = self.style().clone();
let element_id = self.element_id();
let id = self.interactivity().element_id.clone();
Popover::new(SharedString::from(format!("popup-menu:{element_id:?}")))
Popover::new(SharedString::from(format!("popup-menu:{id:?}")))
.no_style()
.trigger(self)
.trigger_style(style)

View File

@@ -1,8 +1,6 @@
use std::fmt::{self, Display, Formatter};
use gpui::{
div, px, App, Axis, Div, Element, ElementId, Pixels, Refineable, StyleRefinement, Styled,
};
use gpui::{div, px, App, Axis, Div, Element, Pixels, Refineable, StyleRefinement, Styled};
use serde::{Deserialize, Serialize};
use theme::ActiveTheme;
@@ -105,9 +103,16 @@ impl From<Pixels> for Size {
/// A trait for defining element that can be selected.
pub trait Selectable: Sized {
fn element_id(&self) -> &ElementId;
/// Set the selected state of the element.
fn selected(self, selected: bool) -> Self;
/// Returns true if the element is selected.
fn is_selected(&self) -> bool;
/// Set is the element mouse right clicked, default do nothing.
fn secondary_selected(self, _: bool) -> Self {
self
}
}
/// A trait for defining element that can be disabled.

View File

@@ -11,7 +11,6 @@ pub mod tab_bar;
#[derive(IntoElement)]
pub struct Tab {
id: ElementId,
base: Stateful<Div>,
label: AnyElement,
prefix: Option<AnyElement>,
@@ -25,7 +24,6 @@ impl Tab {
let id: ElementId = id.into();
Self {
id: id.clone(),
base: div().id(id),
label: label.into_any_element(),
disabled: false,
@@ -55,14 +53,14 @@ impl Tab {
}
impl Selectable for Tab {
fn element_id(&self) -> &ElementId {
&self.id
}
fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
fn is_selected(&self) -> bool {
self.selected
}
}
impl InteractiveElement for Tab {

View File

@@ -1,5 +1,5 @@
use gpui::{
div, relative, App, AppContext, Context, Entity, IntoElement, ParentElement, Render,
deferred, div, relative, App, AppContext, Context, Entity, IntoElement, ParentElement, Render,
SharedString, Styled, Window,
};
use theme::ActiveTheme;
@@ -16,7 +16,7 @@ impl Tooltip {
impl Render for Tooltip {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div().child(
div().child(deferred(
div()
.font_family(".SystemUIFont")
.m_3()
@@ -30,6 +30,6 @@ impl Render for Tooltip {
.text_color(cx.theme().text_muted)
.line_height(relative(1.25))
.child(self.text.clone()),
)
))
}
}

View File

@@ -380,6 +380,6 @@ sidebar:
loading:
label:
en: "Downloading messages"
en: "Getting messages. This may take a while..."
tooltip:
en: "This may take a while. Please be patient."
en: "The progress runs in the background. It doesn't affect your experience."