@@ -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())
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()),
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
|
||||
Reference in New Issue
Block a user