introduce typography system and motion module

This commit is contained in:
2026-10-09 19:54:08 +07:00
parent 41e991dd30
commit df411bd044
53 changed files with 1116 additions and 457 deletions
+37 -32
View File
@@ -24,7 +24,7 @@ use smallvec::{SmallVec, smallvec};
use state::{ use state::{
FileAttachment, NostrRegistry, download_and_decrypt_to_file, upload, upload_encrypted, FileAttachment, NostrRegistry, download_and_decrypt_to_file, upload, upload_encrypted,
}; };
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::dock::{Panel, PanelEvent}; use ui::dock::{Panel, PanelEvent};
@@ -903,7 +903,7 @@ impl ChatPanel {
h_flex() h_flex()
.h_10() .h_10()
.justify_center() .justify_center()
.text_sm() .text_style(TextStyle::Callout)
.bg(cx.theme().surface_raised) .bg(cx.theme().surface_raised)
.rounded(px(theme::panel_radius())) .rounded(px(theme::panel_radius()))
.child("Message isn't traced yet"), .child("Message isn't traced yet"),
@@ -918,10 +918,10 @@ impl ChatPanel {
h_flex() h_flex()
.h_7() .h_7()
.px_2() .px_2()
.gap_2() .gap(px(theme::SPACE))
.bg(cx.theme().surface_raised) .bg(cx.theme().surface_raised)
.rounded(px(theme::control_radius())) .rounded(px(theme::control_radius()))
.text_sm() .text_style(TextStyle::Callout)
.child(div().size_1p5().rounded_full().bg(gpui::green())) .child(div().size_1p5().rounded_full().bg(gpui::green()))
.child(SharedString::from(url.to_string())), .child(SharedString::from(url.to_string())),
); );
@@ -950,10 +950,10 @@ impl ChatPanel {
h_flex() h_flex()
.h_7() .h_7()
.px_2() .px_2()
.gap_2() .gap(px(theme::SPACE))
.bg(cx.theme().surface_raised) .bg(cx.theme().surface_raised)
.rounded(px(theme::control_radius())) .rounded(px(theme::control_radius()))
.text_sm() .text_style(TextStyle::Callout)
.child(div().size_1p5().rounded_full().bg(gpui::green())) .child(div().size_1p5().rounded_full().bg(gpui::green()))
.child(SharedString::from(url.to_string())), .child(SharedString::from(url.to_string())),
); );
@@ -990,7 +990,7 @@ impl ChatPanel {
.items_center() .items_center()
.justify_center() .justify_center()
.text_center() .text_center()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.line_height(relative(1.3)) .line_height(relative(1.3))
.child( .child(
@@ -1013,7 +1013,7 @@ impl ChatPanel {
h_flex() h_flex()
.w_full() .w_full()
.gap_3() .gap_3()
.text_sm() .text_style(TextStyle::Callout)
.child( .child(
h_flex() h_flex()
.flex_shrink_0() .flex_shrink_0()
@@ -1201,7 +1201,7 @@ impl ChatPanel {
.flex() .flex()
.flex_row() .flex_row()
.flex_wrap() .flex_wrap()
.gap_2() .gap(px(theme::SPACE))
.children({ .children({
let mut items = vec![]; let mut items = vec![];
@@ -1245,7 +1245,7 @@ impl ChatPanel {
.px_2() .px_2()
.border_l_2() .border_l_2()
.border_color(cx.theme().element_active) .border_color(cx.theme().element_active)
.text_sm() .text_style(TextStyle::Callout)
.child(div().font_semibold().child(author.name())) .child(div().font_semibold().child(author.name()))
.child( .child(
div() div()
@@ -1287,11 +1287,11 @@ impl ChatPanel {
.unwrap_or(false); .unwrap_or(false);
h_flex() h_flex()
.gap_2() .gap(px(theme::SPACE))
.py_0p5() .py_0p5()
.px_1() .px_1()
.rounded(px(theme::control_radius())) .rounded(px(theme::control_radius()))
.text_xs() .text_style(TextStyle::Caption)
.border_1() .border_1()
.when(has_reacted, |this| { .when(has_reacted, |this| {
this.text_color(cx.theme().text) this.text_color(cx.theme().text)
@@ -1361,12 +1361,12 @@ impl ChatPanel {
let avatar = profile.avatar(); let avatar = profile.avatar();
v_flex() v_flex()
.gap_2() .gap(px(theme::SPACE))
.w_full() .w_full()
.child( .child(
h_flex() h_flex()
.gap_2() .gap(px(theme::SPACE))
.text_sm() .text_style(TextStyle::Callout)
.child(SharedString::from("Sent to:")) .child(SharedString::from("Sent to:"))
.child( .child(
h_flex() h_flex()
@@ -1384,7 +1384,7 @@ impl ChatPanel {
.p_1() .p_1()
.h_16() .h_16()
.w_full() .w_full()
.text_sm() .text_style(TextStyle::Callout)
.rounded(px(theme::surface_radius())) .rounded(px(theme::surface_radius()))
.bg(cx.theme().warning_muted) .bg(cx.theme().warning_muted)
.text_color(cx.theme().warning) .text_color(cx.theme().warning)
@@ -1394,7 +1394,7 @@ impl ChatPanel {
.when_some(report.output().cloned(), |this, output| { .when_some(report.output().cloned(), |this, output| {
this.child( this.child(
v_flex() v_flex()
.gap_2() .gap(px(theme::SPACE))
.w_full() .w_full()
.children({ .children({
let mut items = Vec::with_capacity(output.failed.len()); let mut items = Vec::with_capacity(output.failed.len());
@@ -1409,14 +1409,14 @@ impl ChatPanel {
.bg(cx.theme().danger.opacity(0.06)) .bg(cx.theme().danger.opacity(0.06))
.child( .child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.font_semibold() .font_semibold()
.line_height(relative(1.25)) .line_height(relative(1.25))
.child(SharedString::from(url.to_string())), .child(SharedString::from(url.to_string())),
) )
.child( .child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().danger_muted.opacity(0.9)) .text_color(cx.theme().danger_muted.opacity(0.9))
.line_height(relative(1.25)) .line_height(relative(1.25))
.child(SharedString::from(msg.to_string())), .child(SharedString::from(msg.to_string())),
@@ -1439,14 +1439,14 @@ impl ChatPanel {
.bg(cx.theme().surface_card) .bg(cx.theme().surface_card)
.child( .child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.font_semibold() .font_semibold()
.line_height(relative(1.25)) .line_height(relative(1.25))
.child(SharedString::from(url.0.to_string())), .child(SharedString::from(url.0.to_string())),
) )
.child( .child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.line_height(relative(1.25)) .line_height(relative(1.25))
.child(SharedString::from("Successfully")), .child(SharedString::from("Successfully")),
), ),
@@ -1642,7 +1642,7 @@ impl ChatPanel {
.self_start() .self_start()
.items_start() .items_start()
.min_w_0() .min_w_0()
.gap_2() .gap(px(theme::SPACE))
.p_2() .p_2()
.border_1() .border_1()
.border_color(cx.theme().border_faint) .border_color(cx.theme().border_faint)
@@ -1652,11 +1652,11 @@ impl ChatPanel {
v_flex() v_flex()
.min_w_0() .min_w_0()
.overflow_hidden() .overflow_hidden()
.text_sm() .text_style(TextStyle::Callout)
.child(div().line_height(relative(1.2)).child(file.display_name())) .child(div().line_height(relative(1.2)).child(file.display_name()))
.child( .child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.child(label), .child(label),
), ),
@@ -1702,7 +1702,7 @@ impl ChatPanel {
.border_1() .border_1()
.border_color(cx.theme().border_faint) .border_color(cx.theme().border_faint)
.bg(cx.theme().surface) .bg(cx.theme().surface)
.text_xs() .text_style(TextStyle::Caption)
.text_center() .text_center()
.child("Preview not available"), .child("Preview not available"),
) )
@@ -1764,7 +1764,7 @@ impl ChatPanel {
.flex() .flex()
.items_baseline() .items_baseline()
.gap_1() .gap_1()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child("Replying to:") .child("Replying to:")
.child(div().text_color(cx.theme().accent).child(profile.name())), .child(div().text_color(cx.theme().accent).child(profile.name())),
@@ -1785,7 +1785,7 @@ impl ChatPanel {
.child( .child(
div() div()
.w_full() .w_full()
.text_sm() .text_style(TextStyle::Callout)
.text_ellipsis() .text_ellipsis()
.line_clamp(1) .line_clamp(1)
.child(text.preview()), .child(text.preview()),
@@ -1881,12 +1881,17 @@ impl Panel for ChatPanel {
h_flex() h_flex()
.gap_1() .gap_1()
.text_xs() .text_style(TextStyle::Caption)
.child(Avatar::new(picture).seed(seed).xsmall()) .child(Avatar::new(picture).seed(seed).xsmall())
.child(label) .child(label)
.into_any_element() .into_any_element()
}) })
.unwrap_or(div().text_xs().child("Unknown").into_any_element()) .unwrap_or(
div()
.text_style(TextStyle::Caption)
.child("Unknown")
.into_any_element(),
)
} }
fn toolbar_buttons(&self, _window: &Window, _cx: &App) -> Vec<Button> { fn toolbar_buttons(&self, _window: &Window, _cx: &App) -> Vec<Button> {
@@ -1933,10 +1938,10 @@ impl Render for ChatPanel {
.h_12() .h_12()
.w_full() .w_full()
.px_2() .px_2()
.gap_2() .gap(px(theme::SPACE))
.border_b_1() .border_b_1()
.border_color(cx.theme().border) .border_color(cx.theme().border)
.child(Input::new(&self.subject_input).text_sm().small()) .child(Input::new(&self.subject_input).small())
.child( .child(
Button::new("change") Button::new("change")
.icon(IconName::CheckCircle) .icon(IconName::CheckCircle)
@@ -1989,7 +1994,7 @@ impl Render for ChatPanel {
this.child( this.child(
div() div()
.px_1() .px_1()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().warning) .text_color(cx.theme().warning)
.child(WARNING), .child(WARNING),
) )
+6 -6
View File
@@ -3,7 +3,7 @@ use gpui::{
relative, relative,
}; };
use nip29::{Group, GroupMetadata}; use nip29::{Group, GroupMetadata};
use theme::{ActiveTheme, control_radius}; use theme::{ActiveTheme, TextStyle, Typeset as _, control_radius};
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::{Sizable, StyledExt, WindowExtension, h_flex, v_flex}; use ui::{Sizable, StyledExt, WindowExtension, h_flex, v_flex};
@@ -22,7 +22,7 @@ fn body(group: &WeakEntity<Group>, cx: &App) -> AnyElement {
}; };
let group = entity.read(cx); let group = entity.read(cx);
let mut body = v_flex().gap_3().text_sm().child(header(group, cx)); let mut body = v_flex().gap_3().text_style(TextStyle::Callout).child(header(group, cx));
if let Some(metadata) = group.metadata() { if let Some(metadata) = group.metadata() {
if let Some(about) = metadata.about.as_ref() { if let Some(about) = metadata.about.as_ref() {
@@ -137,14 +137,14 @@ fn header(group: &Group, cx: &App) -> AnyElement {
.min_w_0() .min_w_0()
.child( .child(
div() div()
.text_base() .text_style(TextStyle::Body)
.font_semibold() .font_semibold()
.truncate() .truncate()
.child(group.display_name()), .child(group.display_name()),
) )
.child( .child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.truncate() .truncate()
.child(SharedString::from(format!( .child(SharedString::from(format!(
@@ -162,7 +162,7 @@ fn section(label: &'static str, content: AnyElement, cx: &App) -> AnyElement {
.gap_1() .gap_1()
.child( .child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.font_medium() .font_medium()
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(label), .child(label),
@@ -186,7 +186,7 @@ fn chip(label: impl Into<SharedString>, cx: &App) -> AnyElement {
.rounded(px(control_radius())) .rounded(px(control_radius()))
.bg(cx.theme().surface_raised) .bg(cx.theme().surface_raised)
.text_color(cx.theme().text) .text_color(cx.theme().text)
.text_xs() .text_style(TextStyle::Caption)
.line_height(relative(1.)) .line_height(relative(1.))
.child(label.into()) .child(label.into())
.into_any_element() .into_any_element()
+2 -2
View File
@@ -5,7 +5,7 @@ use gpui::{
}; };
use nip29::Group; use nip29::Group;
use nostr_sdk::prelude::SecretKey; use nostr_sdk::prelude::SecretKey;
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use ui::dialog::DialogButtonProps; use ui::dialog::DialogButtonProps;
use ui::input::InputState; use ui::input::InputState;
use ui::notification::Notification; use ui::notification::Notification;
@@ -40,7 +40,7 @@ pub(crate) fn open(group: WeakEntity<Group>, window: &mut Window, cx: &mut App)
.child( .child(
v_flex().gap_3().child( v_flex().gap_3().child(
div() div()
.text_sm() .text_style(TextStyle::Callout)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child("Share the code with whoever should be able to join. The relay must accept it."), .child("Share the code with whoever should be able to join. The relay must accept it."),
) )
+11 -11
View File
@@ -16,7 +16,7 @@ use serde::Deserialize;
use settings::AppSettings; use settings::AppSettings;
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
use state::{NostrRegistry, upload}; use state::{NostrRegistry, upload};
use theme::{ActiveTheme, control_radius}; use theme::{ActiveTheme, TextStyle, Typeset as _, control_radius};
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariant, ButtonVariants}; use ui::button::{Button, ButtonVariant, ButtonVariants};
use ui::dialog::DialogButtonProps; use ui::dialog::DialogButtonProps;
@@ -168,7 +168,7 @@ pub fn confirm_leave(group: WeakEntity<Group>, window: &mut Window, cx: &mut App
) )
.child( .child(
div() div()
.text_sm() .text_style(TextStyle::Callout)
.child("You will stop receiving messages from this group."), .child("You will stop receiving messages from this group."),
) )
.on_ok(move |_event, _window, cx| { .on_ok(move |_event, _window, cx| {
@@ -567,10 +567,10 @@ impl GroupPanel {
h_flex() h_flex()
.flex_shrink_0() .flex_shrink_0()
.w_full() .w_full()
.gap_2() .gap(px(theme::SPACE))
.px_3() .px_3()
.py_2() .py_2()
.text_sm() .text_style(TextStyle::Callout)
.border_b_1() .border_b_1()
.border_color(cx.theme().border) .border_color(cx.theme().border)
.child( .child(
@@ -617,7 +617,7 @@ impl GroupPanel {
.py_0p5() .py_0p5()
.rounded(px(control_radius())) .rounded(px(control_radius()))
.bg(cx.theme().solid) .bg(cx.theme().solid)
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(pin_label(group, pin)) .child(pin_label(group, pin))
})) }))
@@ -695,10 +695,10 @@ impl GroupPanel {
.w_full() .w_full()
.justify_center() .justify_center()
.items_center() .items_center()
.gap_2() .gap(px(theme::SPACE))
.px_3() .px_3()
.py_2() .py_2()
.text_sm() .text_style(TextStyle::Callout)
.text_color(cx.theme().text_muted); .text_color(cx.theme().text_muted);
match notice { match notice {
@@ -880,7 +880,7 @@ impl GroupPanel {
.border_1() .border_1()
.border_color(cx.theme().border_faint) .border_color(cx.theme().border_faint)
.rounded(px(control_radius())) .rounded(px(control_radius()))
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(Icon::new(IconName::Link).xsmall()) .child(Icon::new(IconName::Link).xsmall())
.child( .child(
@@ -922,12 +922,12 @@ impl Panel for GroupPanel {
h_flex() h_flex()
.gap_1() .gap_1()
.text_xs() .text_style(TextStyle::Caption)
.child(Avatar::from_source(picture).seed(seed).xsmall()) .child(Avatar::from_source(picture).seed(seed).xsmall())
.child(display_name(group)) .child(display_name(group))
.into_any_element() .into_any_element()
}) })
.unwrap_or_else(|| div().text_xs().child("Unknown").into_any_element()) .unwrap_or_else(|| div().text_style(TextStyle::Caption).child("Unknown").into_any_element())
} }
fn toolbar_buttons(&self, _window: &Window, _cx: &App) -> Vec<Button> { fn toolbar_buttons(&self, _window: &Window, _cx: &App) -> Vec<Button> {
@@ -987,7 +987,7 @@ impl Render for GroupPanel {
None => h_flex() None => h_flex()
.size_full() .size_full()
.justify_center() .justify_center()
.text_sm() .text_style(TextStyle::Callout)
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.child("No messages yet") .child("No messages yet")
.into_any_element(), .into_any_element(),
+6 -6
View File
@@ -9,7 +9,7 @@ use nip29::{Activity, Group};
use nostr_sdk::prelude::{EventId, Kind}; use nostr_sdk::prelude::{EventId, Kind};
use person::PersonRegistry; use person::PersonRegistry;
use settings::AppSettings; use settings::AppSettings;
use theme::{ActiveTheme, bubble_radius}; use theme::{ActiveTheme, TextStyle, Typeset as _, bubble_radius};
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::menu::{ContextMenu, PopupMenu, PopupMenuItem}; use ui::menu::{ContextMenu, PopupMenu, PopupMenuItem};
use ui::message::MessageRow; use ui::message::MessageRow;
@@ -138,7 +138,7 @@ pub(crate) fn render_activity(ix: usize, activity: &Activity, cx: &App) -> AnyEl
h_flex() h_flex()
.id(ix) .id(ix)
.w_full() .w_full()
.gap_2() .gap(px(theme::SPACE))
.py_1() .py_1()
.px_3() .px_3()
.hover(|this| this.bg(cx.theme().element_hover)) .hover(|this| this.bg(cx.theme().element_hover))
@@ -155,7 +155,7 @@ pub(crate) fn render_activity(ix: usize, activity: &Activity, cx: &App) -> AnyEl
div() div()
.min_w_0() .min_w_0()
.truncate() .truncate()
.text_sm() .text_style(TextStyle::Callout)
.font_semibold() .font_semibold()
.child(subject.name()), .child(subject.name()),
) )
@@ -163,7 +163,7 @@ pub(crate) fn render_activity(ix: usize, activity: &Activity, cx: &App) -> AnyEl
div() div()
.min_w_0() .min_w_0()
.truncate() .truncate()
.text_sm() .text_style(TextStyle::Callout)
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.child(SharedString::from(action(activity))), .child(SharedString::from(action(activity))),
) )
@@ -171,7 +171,7 @@ pub(crate) fn render_activity(ix: usize, activity: &Activity, cx: &App) -> AnyEl
div() div()
.flex_shrink_0() .flex_shrink_0()
.ml_1() .ml_1()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.child(activity.created_at.to_ago()), .child(activity.created_at.to_ago()),
) )
@@ -225,7 +225,7 @@ fn media(media: &[SharedUri], cx: &App) -> AnyElement {
.flex() .flex()
.flex_row() .flex_row()
.flex_wrap() .flex_wrap()
.gap_2() .gap(px(theme::SPACE))
.children(media.iter().enumerate().map(|(ix, item)| { .children(media.iter().enumerate().map(|(ix, item)| {
div().id(format!("media-{ix}")).child( div().id(format!("media-{ix}")).child(
img(item.clone()) img(item.clone())
+2 -2
View File
@@ -1,4 +1,4 @@
use gpui::{App, AppContext, Entity, ParentElement, Styled, WeakEntity, Window}; use gpui::{App, AppContext, Entity, ParentElement, Styled, WeakEntity, Window, px};
use nip29::{Group, GroupMetadata}; use nip29::{Group, GroupMetadata};
use ui::dialog::DialogButtonProps; use ui::dialog::DialogButtonProps;
use ui::input::InputState; use ui::input::InputState;
@@ -73,7 +73,7 @@ pub(crate) fn open(group: WeakEntity<Group>, window: &mut Window, cx: &mut App)
.child(field("Picture", &picture, cx)) .child(field("Picture", &picture, cx))
.child( .child(
v_flex() v_flex()
.gap_2() .gap(px(theme::SPACE))
.child( .child(
Switch::new("private") Switch::new("private")
.label("Private") .label("Private")
+2 -2
View File
@@ -2,7 +2,7 @@ use anyhow::Result;
use gpui::{App, Div, Entity, ParentElement, SharedString, Styled, Task, Window}; use gpui::{App, Div, Entity, ParentElement, SharedString, Styled, Task, Window};
use nip29::{Group, Pin, Row}; use nip29::{Group, Pin, Row};
use nostr_sdk::prelude::Url; use nostr_sdk::prelude::Url;
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use ui::input::{Input, InputState}; use ui::input::{Input, InputState};
use ui::notification::Notification; use ui::notification::Notification;
use ui::{WindowExtension, v_flex}; use ui::{WindowExtension, v_flex};
@@ -52,7 +52,7 @@ pub(crate) fn opens_run(rows: &[Row], index: usize) -> bool {
pub(crate) fn field(label: &'static str, input: &Entity<InputState>, cx: &App) -> Div { pub(crate) fn field(label: &'static str, input: &Entity<InputState>, cx: &App) -> Div {
v_flex() v_flex()
.gap_1() .gap_1()
.text_sm() .text_style(TextStyle::Callout)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(label) .child(label)
.child(Input::new(input)) .child(Input::new(input))
+10 -10
View File
@@ -15,7 +15,7 @@ use person::PersonRegistry;
use settings::AppSettings; use settings::AppSettings;
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
use state::{Announcement, CLIENT_NAME, NostrRegistry, UniversalSigner}; use state::{Announcement, CLIENT_NAME, NostrRegistry, UniversalSigner};
use theme::{ActiveTheme, control_radius}; use theme::{ActiveTheme, TextStyle, Typeset as _, control_radius};
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::button::Button; use ui::button::Button;
use ui::notification::{Notification, NotificationKind}; use ui::notification::{Notification, NotificationKind};
@@ -720,25 +720,25 @@ impl DeviceRegistry {
.title("Encryption Key Request") .title("Encryption Key Request")
.content(move |_this, _window, cx| { .content(move |_this, _window, cx| {
v_flex() v_flex()
.gap_2() .gap(px(theme::SPACE))
.text_sm() .text_style(TextStyle::Callout)
.child( .child(
div() div()
.text_sm() .text_style(TextStyle::Callout)
.line_height(relative(1.25)) .line_height(relative(1.25))
.child(SharedString::from(MSG)), .child(SharedString::from(MSG)),
) )
.child( .child(
v_flex() v_flex()
.gap_2() .gap(px(theme::SPACE))
.child( .child(
v_flex() v_flex()
.gap_1() .gap_1()
.text_sm() .text_style(TextStyle::Callout)
.child( .child(
div() div()
.font_semibold() .font_semibold()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(SharedString::from("From:")), .child(SharedString::from("From:")),
) )
@@ -751,7 +751,7 @@ impl DeviceRegistry {
.bg(cx.theme().surface_card) .bg(cx.theme().surface_card)
.child( .child(
h_flex() h_flex()
.gap_2() .gap(px(theme::SPACE))
.child( .child(
Avatar::new(profile.avatar()) Avatar::new(profile.avatar())
.seed(profile.avatar_seed()) .seed(profile.avatar_seed())
@@ -764,11 +764,11 @@ impl DeviceRegistry {
.child( .child(
v_flex() v_flex()
.gap_1() .gap_1()
.text_sm() .text_style(TextStyle::Callout)
.child( .child(
div() div()
.font_semibold() .font_semibold()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(SharedString::from("Code:")), .child(SharedString::from("Code:")),
) )
+16 -10
View File
@@ -14,6 +14,7 @@ mod platform_kind;
mod scrollbar_mode; mod scrollbar_mode;
mod syntax; mod syntax;
mod theme; mod theme;
mod typography;
pub mod appearance; pub mod appearance;
@@ -38,6 +39,9 @@ pub use platform_kind::PlatformKind;
pub use scrollbar_mode::*; pub use scrollbar_mode::*;
pub use syntax::{HighlightKind, SyntaxPalette}; pub use syntax::{HighlightKind, SyntaxPalette};
pub use theme::*; pub use theme::*;
pub use typography::{
DEFAULT_BASE_TEXT_SIZE, Metrics, TextStyle, Typeset, base_text_size, set_base_text_size,
};
/// Which appearance the app is painting. Bezel names this enum `Appearance`; /// Which appearance the app is painting. Bezel names this enum `Appearance`;
/// coop's `ThemeMode` predates the port and carries the same two variants. /// coop's `ThemeMode` predates the port and carries the same two variants.
@@ -78,6 +82,7 @@ pub trait AppExt {
fn set_brand(&mut self, brand: Brand); fn set_brand(&mut self, brand: Brand);
fn set_palette(&mut self, build: PaletteBuilder); fn set_palette(&mut self, build: PaletteBuilder);
fn set_appearance_mode(&mut self, mode: AppearanceMode); fn set_appearance_mode(&mut self, mode: AppearanceMode);
fn set_base_text_size(&mut self, points: f32);
} }
impl AppExt for App { impl AppExt for App {
@@ -94,6 +99,11 @@ impl AppExt for App {
fn set_appearance_mode(&mut self, mode: AppearanceMode) { fn set_appearance_mode(&mut self, mode: AppearanceMode) {
appearance::set_mode(mode, self); appearance::set_mode(mode, self);
} }
fn set_base_text_size(&mut self, points: f32) {
typography::set_base_text_size(points);
self.refresh_windows();
}
} }
/// Re-install the palette for the current appearance. /// Re-install the palette for the current appearance.
@@ -123,12 +133,12 @@ pub fn sync_base(cx: &mut App) {
ScrollbarMode::Always => gpui_base::ScrollbarMode::Always, ScrollbarMode::Always => gpui_base::ScrollbarMode::Always,
}; };
let colors = theme.colors; let colors = theme.colors;
let font_family = theme.font_family.clone(); let font_sans = theme.font_sans.clone();
let base = gpui_base::Theme::global_mut(cx); let base = gpui_base::Theme::global_mut(cx);
base.appearance = appearance; base.appearance = appearance;
base.scrollbar = base.scrollbar.clone().with_mode(scrollbar_mode); base.scrollbar = base.scrollbar.clone().with_mode(scrollbar_mode);
base.tokens.typography.sans = font_family; base.tokens.typography.sans = font_sans;
let tokens = &mut base.tokens.colors; let tokens = &mut base.tokens.colors;
tokens.background = colors.bg; tokens.background = colors.bg;
@@ -166,10 +176,8 @@ impl ActiveTheme for App {
pub struct Theme { pub struct Theme {
pub colors: ThemeColors, pub colors: ThemeColors,
pub mode: ThemeMode, pub mode: ThemeMode,
pub font_family: SharedString, pub font_sans: SharedString,
pub font_body: SharedString,
pub font_mono: SharedString, pub font_mono: SharedString,
pub font_size: Pixels,
pub shadow: bool, pub shadow: bool,
pub scrollbar_mode: ScrollbarMode, pub scrollbar_mode: ScrollbarMode,
pub notification: NotificationSettings, pub notification: NotificationSettings,
@@ -272,16 +280,14 @@ impl Default for Theme {
let platform = PlatformKind::platform(); let platform = PlatformKind::platform();
let mode = ThemeMode::default(); let mode = ThemeMode::default();
// Define the font family based on the platform. // The system UI font, by platform.
let font_family = match platform { let font_sans = match platform {
PlatformKind::Linux => "Inter", PlatformKind::Linux => "Inter",
_ => ".SystemUIFont", _ => ".SystemUIFont",
}; };
Self { Self {
font_size: px(15.), font_sans: font_sans.into(),
font_family: font_family.into(),
font_body: font_family.into(),
font_mono: system_mono().into(), font_mono: system_mono().into(),
shadow: true, shadow: true,
scrollbar_mode: ScrollbarMode::default(), scrollbar_mode: ScrollbarMode::default(),
+131
View File
@@ -0,0 +1,131 @@
use std::sync::atomic::{AtomicU32, Ordering};
use gpui::{FontWeight, Styled, px};
/// The base the ladder starts at; bezel's measured body is 13, coop keeps 15.
pub const DEFAULT_BASE_TEXT_SIZE: f32 = 15.0;
/// The body size every painted role is scaled against, as raw `f32` bits.
static BASE: AtomicU32 = AtomicU32::new(DEFAULT_BASE_TEXT_SIZE.to_bits());
/// Point the ladder at a body size in points; the caller refreshes windows.
pub fn set_base_text_size(points: f32) {
BASE.store(points.max(1.0).to_bits(), Ordering::Relaxed);
}
/// The body size in points every painted role is scaled against.
pub fn base_text_size() -> f32 {
f32::from_bits(BASE.load(Ordering::Relaxed))
}
/// A role in the type ladder — SwiftUI's `Font.TextStyle`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TextStyle {
LargeTitle,
Title,
Title2,
Title3,
Headline,
Subheadline,
Body,
Callout,
Footnote,
Caption,
Caption2,
}
impl TextStyle {
/// The role's measured size in points.
pub const fn size(self) -> f32 {
match self {
Self::LargeTitle => 26.0,
Self::Title => 22.0,
Self::Title2 => 17.0,
Self::Title3 => 15.0,
Self::Headline | Self::Body => 13.0,
Self::Callout => 12.0,
Self::Subheadline => 11.0,
Self::Footnote | Self::Caption | Self::Caption2 => 10.0,
}
}
pub fn painted(self) -> f32 {
self.size() * base_text_size() / Self::Body.size()
}
pub const fn line_height(self) -> f32 {
match self {
Self::LargeTitle => 32.0,
Self::Title => 26.0,
Self::Title2 => 22.0,
Self::Title3 => 20.0,
Self::Headline | Self::Body => 16.0,
Self::Callout => 15.0,
Self::Subheadline => 14.0,
Self::Footnote | Self::Caption | Self::Caption2 => 13.0,
}
}
pub fn painted_line_height(self) -> f32 {
self.line_height() * base_text_size() / Self::Body.size()
}
pub const fn weight(self) -> FontWeight {
match self {
Self::Headline => FontWeight::BOLD,
Self::Caption2 => FontWeight::MEDIUM,
_ => FontWeight::NORMAL,
}
}
}
/// One role as it is set: a rung on the ladder, its leading, weight and scale.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Metrics {
pub role: TextStyle,
pub leading: f32,
pub weight: FontWeight,
pub scale: f32,
}
impl Metrics {
pub const fn new(role: TextStyle, leading: f32, weight: FontWeight) -> Self {
Self {
role,
leading,
weight,
scale: 1.0,
}
}
pub const fn scaled(self, scale: f32) -> Self {
Self { scale, ..self }
}
pub fn size(self) -> f32 {
self.role.painted() * self.scale
}
pub fn line_height(self) -> f32 {
self.size() * self.leading
}
}
impl From<TextStyle> for Metrics {
/// The ladder's own setting for a role: its measured leading and weight.
fn from(role: TextStyle) -> Self {
Self::new(role, role.line_height() / role.size(), role.weight())
}
}
/// The ladder, on anything styled.
pub trait Typeset: Styled + Sized {
/// Size, line height and weight together, from [`TextStyle`].
fn text_style(self, style: TextStyle) -> Self {
self.text_size(px(style.painted()))
.line_height(px(style.painted_line_height()))
.font_weight(style.weight())
}
}
impl<E: Styled> Typeset for E {}
-125
View File
@@ -1,125 +0,0 @@
//! Palette contrast tests, mirroring bezel's `tests/theme.rs`: each light text
//! token must land within ~0.5 of its dark counterpart's contrast ratio
//! against its own background, and the primary text must keep the ratio the
//! palette was tuned for.
use gpui::Hsla;
use theme::{Brand, ThemeColors, ThemeMode, Tint, contrast_ratio, flatten};
// The shipped palette's measured floors (AppKit-style ink, per bezel):
// text/bg is 14.4:1 dark and 15.1:1 light, muted lands near 6.3 dark and
// 4.0 light, and faint sits below 3:1 in both appearances by design.
const TEXT_RATIO: f32 = 13.9;
const MUTED_RATIO: f32 = 3.9;
const FAINT_RATIO: f32 = 1.8;
const TEXT_PARITY: f32 = 0.7;
fn ratio(fg: Hsla, bg: Hsla) -> f32 {
contrast_ratio(flatten(fg, bg), bg)
}
#[test]
fn text_keeps_its_contrast() {
for appearance in [ThemeMode::Dark, ThemeMode::Light] {
let colors = ThemeColors::for_appearance(appearance);
assert!(
ratio(colors.text, colors.bg) >= TEXT_RATIO,
"{appearance:?}: text on bg = {}",
ratio(colors.text, colors.bg)
);
assert!(
ratio(colors.text, colors.surface) >= TEXT_RATIO,
"{appearance:?}: text on surface"
);
assert!(
ratio(colors.text_muted, colors.bg) >= MUTED_RATIO,
"{appearance:?}: muted on bg = {}",
ratio(colors.text_muted, colors.bg)
);
assert!(
ratio(colors.text_faint, colors.bg) >= FAINT_RATIO,
"{appearance:?}: faint on bg = {}",
ratio(colors.text_faint, colors.bg)
);
}
}
#[test]
fn light_matches_dark_contrast() {
let dark = ThemeColors::for_appearance(ThemeMode::Dark);
let light = ThemeColors::for_appearance(ThemeMode::Light);
// Primary text keeps near-parity across appearances. Muted and faint do
// not (the light values sit further from the background by design, see
// Ink::APPKIT); their floors are asserted in `text_keeps_its_contrast`.
let pairs = [
("text/bg", dark.text, dark.bg, light.text, light.bg),
(
"text/surface",
dark.text,
dark.surface,
light.text,
light.surface,
),
];
for (name, dark_fg, dark_bg, light_fg, light_bg) in pairs {
let dark_ratio = ratio(dark_fg, dark_bg);
let light_ratio = ratio(light_fg, light_bg);
let delta = (dark_ratio - light_ratio).abs();
assert!(
delta <= TEXT_PARITY,
"{name}: light deviates by {delta} (dark {dark_ratio:.3}, light {light_ratio:.3})"
);
}
}
#[test]
fn default_brand_is_a_no_op() {
let brand = Brand::default();
assert!(brand.is_default());
for appearance in [ThemeMode::Dark, ThemeMode::Light] {
let base = ThemeColors::for_appearance(appearance);
let branded = brand.branded(appearance);
assert_eq!(
base, branded,
"{appearance:?}: default brand must not move the palette"
);
}
}
#[test]
fn brand_tint_rotates_greys_and_keeps_semantics() {
let brand = Brand {
tint: Tint::new(257.417, 0.046),
accent: Tint::new(276.935, 0.182),
..Brand::default()
};
let base = ThemeColors::for_appearance(ThemeMode::Dark);
let branded = brand.branded(ThemeMode::Dark);
assert_ne!(branded.bg, base.bg, "bg takes the tint");
assert_ne!(branded.accent, base.accent, "accent takes the accent hue");
assert_eq!(
branded.danger, base.danger,
"danger is semantic and keeps its hue"
);
assert_eq!(
branded.warning, base.warning,
"warning is semantic and keeps its hue"
);
}
#[test]
fn warning_wash_carries_its_label() {
// warning_muted is a translucent amber wash, so the label pairing is
// measured against the plate flattened onto the content plane.
for appearance in [ThemeMode::Dark, ThemeMode::Light] {
let colors = ThemeColors::for_appearance(appearance);
let plate = flatten(colors.warning_muted, colors.bg);
assert!(
ratio(colors.warning, plate) >= 3.0,
"{appearance:?}: warning on warning_muted = {}",
ratio(colors.warning, plate)
);
}
}
-14
View File
@@ -1,14 +0,0 @@
/// A cubic bezier easing function like CSS `cubic-bezier` (see https://cubic-bezier.com).
pub fn cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) -> impl Fn(f32) -> f32 {
move |t: f32| {
let one_t = 1.0 - t;
let one_t2 = one_t * one_t;
let t2 = t * t;
let t3 = t2 * t;
// The Bezier curve function for x and y, where x0 = 0, y0 = 0, x3 = 1, y3 = 1
let _x = 3.0 * x1 * one_t2 * t + 3.0 * x2 * one_t * t2 + t3;
3.0 * y1 * one_t2 * t + 3.0 * y2 * one_t * t2 + t3
}
}
+7 -4
View File
@@ -7,7 +7,10 @@ use gpui::{
Styled, Window, div, px, relative, Styled, Window, div, px, relative,
}; };
use gpui_base::Button as BaseButton; use gpui_base::Button as BaseButton;
use theme::{ActiveTheme, bubble_radius, button_radius, plate_active, plate_hover, wash}; use theme::{
ActiveTheme, TextStyle, Typeset as _, bubble_radius, button_radius, plate_active, plate_hover,
wash,
};
use crate::indicator::Indicator; use crate::indicator::Indicator;
use crate::tooltip::Tooltip; use crate::tooltip::Tooltip;
@@ -362,9 +365,9 @@ impl RenderOnce for Button {
.id("label") .id("label")
.justify_center() .justify_center()
.map(|this| match self.size { .map(|this| match self.size {
Size::XSmall => this.text_xs().gap_1(), Size::XSmall => this.text_style(TextStyle::Caption).gap_1(),
Size::Small => this.text_sm().gap_1p5(), Size::Small => this.text_style(TextStyle::Callout).gap_1p5(),
_ => this.text_sm().gap_2(), _ => this.text_style(TextStyle::Callout).gap(px(theme::SPACE)),
}) })
.when(!self.loading, |this| { .when(!self.loading, |this| {
this.when_some(self.icon, |this, icon| { this.when_some(self.icon, |this, icon| {
+20 -21
View File
@@ -2,16 +2,14 @@ use std::rc::Rc;
use gpui::prelude::FluentBuilder; use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
Animation, AnimationExt as _, AnyElement, App, ClickEvent, Div, FocusHandle, IntoElement, AnimationExt as _, AnyElement, App, ClickEvent, Div, FocusHandle, IntoElement, ParentElement,
ParentElement, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, Window, div, px, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, Window, div, px, transparent_black,
transparent_black,
}; };
use gpui_base::{DialogBackdrop, DialogPopup, DialogTitle}; use gpui_base::{DialogBackdrop, DialogPopup, DialogTitle};
use instant::Duration;
use theme::{ActiveTheme, SCRIM_ALPHA_DARK, hairline, scrim, surface_radius, surface_shadows}; use theme::{ActiveTheme, SCRIM_ALPHA_DARK, hairline, scrim, surface_radius, surface_shadows};
use crate::animation::cubic_bezier;
use crate::button::{Button, ButtonCustomVariant, ButtonVariant, ButtonVariants as _}; use crate::button::{Button, ButtonCustomVariant, ButtonVariant, ButtonVariants as _};
use crate::motion::DIALOG_IN;
use crate::scroll::ScrollableElement; use crate::scroll::ScrollableElement;
use crate::{IconName, Root, StyledExt, WindowExtension, h_flex, v_flex}; use crate::{IconName, Root, StyledExt, WindowExtension, h_flex, v_flex};
@@ -272,18 +270,17 @@ impl RenderOnce for Dialog {
padding_right = pr.to_pixels(self.width.into(), window.rem_size()); padding_right = pr.to_pixels(self.width.into(), window.rem_size());
} }
let animation = Animation::new(Duration::from_secs_f64(0.25))
.with_easing(cubic_bezier(0.32, 0.72, 0., 1.));
let backdrop = DialogBackdrop::new() let backdrop = DialogBackdrop::new()
.absolute() .absolute()
.inset_0() .inset_0()
.when(self.overlay_visible, |this| { .when(self.overlay_visible, |this| {
this.bg(scrim(SCRIM_ALPHA_DARK)) this.bg(scrim(SCRIM_ALPHA_DARK))
}) })
.with_animation("fade-in", animation.clone(), move |this, delta| { .with_animation(
this.opacity(delta) "fade-in",
}); DIALOG_IN.animation(),
move |this, delta| this.opacity(delta),
);
let card = DialogPopup::new() let card = DialogPopup::new()
.flex() .flex()
@@ -373,16 +370,18 @@ impl RenderOnce for Dialog {
.children(footer(render_ok, render_cancel, window, cx)), .children(footer(render_ok, render_cancel, window, cx)),
) )
}) })
.with_animation("slide-down", animation, move |this, delta| { .with_animation(
// Settle at the target offset; only the entrance is animated. "slide-down",
let y_offset = px(30.) * (delta - 1.); DIALOG_IN.animation(),
let mut shadow = surface_shadows(); move |this, delta| {
// The shadows fade in with the slide. let y_offset = px(2.) * (delta - 1.);
for shadow in &mut shadow { let mut shadow = surface_shadows();
shadow.color.a *= delta; for shadow in &mut shadow {
} shadow.color.a *= delta;
this.top(y_offset).shadow(shadow) }
}); this.top(y_offset).shadow(shadow)
},
);
gpui_base::Dialog::new(cx) gpui_base::Dialog::new(cx)
.layer(layer_ix, is_topmost) .layer(layer_ix, is_topmost)
+2 -2
View File
@@ -17,7 +17,7 @@ use gpui_base::dock::{
PaneRef, PanelId, TabGroupContext, TabGroupRenderer, PaneRef, PanelId, TabGroupContext, TabGroupRenderer,
}; };
use gpui_base::{HandleEdge, Placement, ResizeHandleContext}; use gpui_base::{HandleEdge, Placement, ResizeHandleContext};
use theme::{ActiveTheme, TABBAR_HEIGHT, button_radius, panel_radius}; use theme::{ActiveTheme, TABBAR_HEIGHT, TextStyle, Typeset as _, button_radius, panel_radius};
use crate::button::{Button, ButtonVariants as _}; use crate::button::{Button, ButtonVariants as _};
use crate::resizable::{resize_handle, resize_handle_appearance}; use crate::resizable::{resize_handle, resize_handle_appearance};
@@ -349,7 +349,7 @@ impl Render for DragPreview {
.overflow_hidden() .overflow_hidden()
.whitespace_nowrap() .whitespace_nowrap()
.rounded(px(button_radius())) .rounded(px(button_radius()))
.text_sm() .text_style(TextStyle::Callout)
.text_color(cx.theme().text) .text_color(cx.theme().text)
.text_ellipsis() .text_ellipsis()
.when(cx.theme().shadow, |this| this.shadow_xs()) .when(cx.theme().shadow, |this| this.shadow_xs())
+3 -4
View File
@@ -1,10 +1,10 @@
use gpui::prelude::FluentBuilder; use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
AnyElement, App, ElementId, InteractiveElement as _, IntoElement, ParentElement, RenderOnce, AnyElement, App, ElementId, InteractiveElement as _, IntoElement, ParentElement, RenderOnce,
StyleRefinement, Styled, Window, div, px, relative, StyleRefinement, Styled, Window, div, px,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use crate::{StyledExt as _, v_flex}; use crate::{StyledExt as _, v_flex};
@@ -108,8 +108,7 @@ impl RenderOnce for GroupBox {
this.child( this.child(
div() div()
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.line_height(relative(1.)) .text_style(TextStyle::Callout)
.text_sm()
.font_semibold() .font_semibold()
.child(title), .child(title),
) )
+7 -8
View File
@@ -1,10 +1,11 @@
use gpui::prelude::FluentBuilder as _; use gpui::prelude::FluentBuilder as _;
use gpui::{ use gpui::{
Animation, AnimationExt as _, App, Hsla, IntoElement, ParentElement, RenderOnce, Styled as _, App, Hsla, IntoElement, ParentElement, RenderOnce, Styled as _, Transformation, Window, div,
Transformation, Window, div, ease_in_out, percentage, percentage,
}; };
use instant::Duration; use instant::Duration;
use crate::motion::{EASE_IN_OUT, MotionSpec, Painter, pulse_delta};
use crate::{Icon, IconName, Sizable, Size}; use crate::{Icon, IconName, Sizable, Size};
#[derive(IntoElement)] #[derive(IntoElement)]
@@ -45,16 +46,14 @@ impl Sizable for Indicator {
} }
impl RenderOnce for Indicator { impl RenderOnce for Indicator {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let spec = MotionSpec::new(self.speed.as_millis().max(1) as u64, EASE_IN_OUT);
let phase = pulse_delta(&spec, Painter::from(window.current_view()), cx);
div().child( div().child(
self.icon self.icon
.with_size(self.size) .with_size(self.size)
.when_some(self.color, |this, color| this.text_color(color)) .when_some(self.color, |this, color| this.text_color(color))
.with_animation( .transform(Transformation::rotate(percentage(phase))),
"circle",
Animation::new(self.speed).repeat().with_easing(ease_in_out),
|this, delta| this.transform(Transformation::rotate(percentage(delta))),
),
) )
} }
} }
+3 -4
View File
@@ -1,8 +1,8 @@
use gpui::{ use gpui::{
Action, AsKeystroke, IntoElement, KeyContext, Keystroke, ParentElement as _, RenderOnce, Action, AsKeystroke, IntoElement, KeyContext, Keystroke, ParentElement as _, RenderOnce,
StyleRefinement, Styled, Window, div, relative, StyleRefinement, Styled, Window, div,
}; };
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use crate::StyledExt; use crate::StyledExt;
@@ -179,8 +179,7 @@ impl RenderOnce for Kbd {
.min_w_5() .min_w_5()
.text_center() .text_center()
.rounded_sm() .rounded_sm()
.line_height(relative(1.)) .text_style(TextStyle::Caption)
.text_xs()
.whitespace_normal() .whitespace_normal()
.flex_shrink_0() .flex_shrink_0()
.refine_style(&self.style) .refine_style(&self.style)
+1 -1
View File
@@ -23,9 +23,9 @@ pub mod switch;
pub mod tab; pub mod tab;
pub mod tooltip; pub mod tooltip;
mod animation;
mod icon; mod icon;
mod kbd; mod kbd;
pub mod motion;
mod popover; mod popover;
mod resizable; mod resizable;
mod root; mod root;
+1 -11
View File
@@ -63,7 +63,7 @@ impl RenderedText {
/// Render the message as a GPUI Kit text view. /// Render the message as a GPUI Kit text view.
pub fn element(&self, id: ElementId, _window: &Window, cx: &App) -> AnyElement { pub fn element(&self, id: ElementId, _window: &Window, cx: &App) -> AnyElement {
let theme = cx.theme(); let theme = cx.theme();
let code_block = StyleRefinement::default().font_family(code_font_family()); let code_block = StyleRefinement::default().font_family(theme.font_mono.clone());
let muted_heading = theme.text_muted; let muted_heading = theme.text_muted;
TextView::markdown(id, self.text.clone()) TextView::markdown(id, self.text.clone())
@@ -115,13 +115,3 @@ fn is_web_url(url: &str) -> bool {
let url = url.to_ascii_lowercase(); let url = url.to_ascii_lowercase();
url.starts_with("http://") || url.starts_with("https://") url.starts_with("http://") || url.starts_with("https://")
} }
fn code_font_family() -> &'static str {
if cfg!(target_os = "macos") {
"Menlo"
} else if cfg!(target_os = "windows") {
"Consolas"
} else {
"monospace"
}
}
+2 -2
View File
@@ -5,7 +5,7 @@ use gpui::{
Styled, Window, Styled, Window,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use crate::{Disableable, StyledExt, h_flex}; use crate::{Disableable, StyledExt, h_flex};
@@ -90,7 +90,7 @@ impl RenderOnce for MenuItemElement {
.group(&self.group_name) .group(&self.group_name)
.gap_x_1() .gap_x_1()
.p_1() .p_1()
.text_sm() .text_style(TextStyle::Callout)
.text_color(cx.theme().text) .text_color(cx.theme().text)
.relative() .relative()
.items_center() .items_center()
+6 -6
View File
@@ -8,7 +8,7 @@ use gpui::{
anchored, div, px, rems, anchored, div, px, rems,
}; };
use gpui_base::actions::{Cancel, Confirm, SelectDown, SelectLeft, SelectRight, SelectUp}; use gpui_base::actions::{Cancel, Confirm, SelectDown, SelectLeft, SelectRight, SelectUp};
use theme::{ActiveTheme, Side}; use theme::{ActiveTheme, Side, TextStyle, Typeset as _};
use crate::kbd::Kbd; use crate::kbd::Kbd;
use crate::menu::menu_item::MenuItemElement; use crate::menu::menu_item::MenuItemElement;
@@ -633,7 +633,7 @@ impl PopupMenu {
let this = MenuItemElement::new(ix, &group_name) let this = MenuItemElement::new(ix, &group_name)
.relative() .relative()
.text_sm() .text_style(TextStyle::Callout)
.py_0() .py_0()
.px(INNER_PADDING) .px(INNER_PADDING)
.rounded(options.radius) .rounded(options.radius)
@@ -663,7 +663,7 @@ impl PopupMenu {
h_flex().cursor_default().items_center().gap_x_1().child( h_flex().cursor_default().items_center().gap_x_1().child(
div() div()
.flex_1() .flex_1()
.text_sm() .text_style(TextStyle::Callout)
.font_semibold() .font_semibold()
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(label.clone()), .child(label.clone()),
@@ -686,7 +686,7 @@ impl PopupMenu {
}) })
.disabled(*disabled) .disabled(*disabled)
.h(item_height) .h(item_height)
.gap_x_2() .gap_x(px(theme::SPACE))
.children(Self::render_icon( .children(Self::render_icon(
has_left_icon, has_left_icon,
is_checked, is_checked,
@@ -718,7 +718,7 @@ impl PopupMenu {
.min_h(item_height) .min_h(item_height)
.size_full() .size_full()
.items_center() .items_center()
.gap_x_2() .gap_x(px(theme::SPACE))
.children(Self::render_icon( .children(Self::render_icon(
has_left_icon, has_left_icon,
false, false,
@@ -729,7 +729,7 @@ impl PopupMenu {
.child( .child(
h_flex() h_flex()
.flex_1() .flex_1()
.gap_2() .gap(px(theme::SPACE))
.items_center() .items_center()
.justify_between() .justify_between()
.child(label.clone()) .child(label.clone())
+6 -7
View File
@@ -5,7 +5,7 @@ use gpui::{
div, px, div, px,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use crate::{InteractiveElementExt as _, StyledExt as _, h_flex, v_flex}; use crate::{InteractiveElementExt as _, StyledExt as _, h_flex, v_flex};
@@ -190,8 +190,8 @@ impl RenderOnce for MessageRow {
.when(show_author, |this| { .when(show_author, |this| {
this.child( this.child(
h_flex() h_flex()
.gap_2() .gap(px(theme::SPACE))
.text_sm() .text_style(TextStyle::Callout)
.text_color(cx.theme().text_dim) .text_color(cx.theme().text_dim)
.when_some(author, |this, author| { .when_some(author, |this, author| {
this.child(div().font_semibold().child(author)) this.child(div().font_semibold().child(author))
@@ -280,7 +280,7 @@ impl RenderOnce for WelcomeMessage {
v_flex() v_flex()
.id(id) .id(id)
.w_full() .w_full()
.gap_2() .gap(px(theme::SPACE))
.p_3() .p_3()
.items_center() .items_center()
.justify_center() .justify_center()
@@ -295,8 +295,7 @@ impl RenderOnce for WelcomeMessage {
.when_some(title, |this, title| { .when_some(title, |this, title| {
this.child( this.child(
div() div()
.text_sm() .text_style(TextStyle::Headline)
.font_semibold()
.text_color(cx.theme().text) .text_color(cx.theme().text)
.child(title), .child(title),
) )
@@ -304,7 +303,7 @@ impl RenderOnce for WelcomeMessage {
.when_some(message, |this, message| { .when_some(message, |this, message| {
this.child( this.child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.child(message), .child(message),
) )
+668
View File
@@ -0,0 +1,668 @@
//! The motion kit, ported from bezel (`crates/motion/src/lib.rs` and its
//! `app.rs`, MIT License, Copyright (c) 2026 bezel contributors). TranslateY
//! rides a relative `top` inset, which taffy applies after layout, so
//! siblings never move.
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::Duration;
use gpui::{
Animation, AnimationElement, AnimationExt as _, App, Context, ElementId, EntityId, Global,
Hsla, IntoElement, Rgba, SharedString, Styled, Window, px,
};
use instant::Instant;
/// Redraw rate for anything driven by [`pulse_delta`].
const PULSE_FPS: f32 = 30.0;
/// How long a view stays on the tick list after its last pulse paint.
const PULSE_LEASE: Duration = Duration::from_millis(300);
/// Redraw rate for hover fades; [`HOVER_FADE`] paints in nine steps at it.
const HOVER_FPS: f32 = 60.0;
/// Floor on how long the clock sleeps between wake-ups.
const MIN_SLEEP: Duration = Duration::from_millis(1);
/// One view's claim on the clock.
struct Lease {
/// The fastest rate anything on this view has claimed.
period: Duration,
/// When this view is next owed a redraw.
due: Instant,
/// A notify is out and the render it provoked has not renewed yet.
in_flight: bool,
/// When the claim lapses if nothing renews it.
until: Instant,
}
#[derive(Default)]
struct PulseClock {
epoch: Option<Instant>,
leases: HashMap<EntityId, Lease>,
running: bool,
}
impl Global for PulseClock {}
impl PulseClock {
fn next_wake(&self) -> Option<Instant> {
self.leases
.values()
.map(|lease| lease.due.min(lease.until))
.min()
}
}
/// A component's line back to the view that paints it.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Painter(EntityId);
impl Painter {
/// The view this render belongs to; take it once where the state is built.
pub fn of<T: 'static>(cx: &Context<T>) -> Self {
Self(cx.entity_id())
}
/// Redraw this view once.
pub fn notify(self, cx: &mut App) {
cx.notify(self.0);
}
/// Claim `fps` redraws a second; renew from `render` so an unmounted
/// element drops off.
pub fn lease(self, fps: f32, until: Duration, cx: &mut App) {
lease(self.0, fps, until, cx);
}
/// Whether the clock is what asked for the render running now.
pub fn woken(self, cx: &App) -> bool {
cx.try_global::<PulseClock>()
.and_then(|clock| clock.leases.get(&self.0))
.is_some_and(|lease| lease.in_flight)
}
}
impl From<EntityId> for Painter {
fn from(entity_id: EntityId) -> Self {
Self(entity_id)
}
}
impl From<Painter> for EntityId {
fn from(painter: Painter) -> Self {
painter.0
}
}
fn lease(view: EntityId, fps: f32, until: Duration, cx: &mut App) {
if cx.pause_when_inactive() && cx.active_window().is_none() {
return;
}
let now = cx.background_executor().now();
let period = Duration::from_secs_f32(1.0 / fps.clamp(1.0, 240.0));
let clock = cx.default_global::<PulseClock>();
clock
.leases
.entry(view)
.and_modify(|lease| {
lease.period = lease.period.min(period);
lease.due = lease.due.min(now + period);
lease.in_flight = false;
lease.until = lease.until.max(now + until);
})
.or_insert(Lease {
period,
due: now + period,
in_flight: false,
until: now + until,
});
if clock.running {
return;
}
clock.running = true;
cx.spawn(async move |cx| {
loop {
let sleep = cx.update(|cx| {
let now = cx.background_executor().now();
let clock = cx.default_global::<PulseClock>();
clock
.next_wake()
.map(|wake| wake.saturating_duration_since(now).max(MIN_SLEEP))
});
let Some(sleep) = sleep else { break };
cx.background_executor().timer(sleep).await;
let parked = cx.update(|cx| {
let now = cx.background_executor().now();
tick_hover_fades();
let clock = cx.default_global::<PulseClock>();
clock.leases.retain(|_, lease| lease.until > now);
if clock.leases.is_empty() {
clock.running = false;
return true;
}
let mut owed = Vec::new();
for (view, lease) in clock.leases.iter_mut() {
if lease.due > now {
continue;
}
lease.due += lease.period;
if lease.due <= now {
lease.due = now + lease.period;
}
lease.in_flight = true;
owed.push(*view);
}
for view in owed {
cx.notify(view);
}
false
});
if parked {
break;
}
}
})
.detach();
}
/// Current phase `[0,1)` of a repeating spec; reduced motion returns a
/// static 0 and schedules nothing.
pub fn pulse_delta(spec: &MotionSpec, painter: Painter, cx: &mut App) -> f32 {
if cx.reduced_motion() {
return 0.0;
}
painter.lease(PULSE_FPS, PULSE_LEASE, cx);
let now = cx.background_executor().now();
let clock = cx.default_global::<PulseClock>();
let epoch = *clock.epoch.get_or_insert(now);
let period = spec.total().as_secs_f32();
((now - epoch).as_secs_f32() / period).fract()
}
/// A CSS `cubic-bezier(x1, y1, x2, y2)` timing function, evaluated exactly.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CubicBezier {
pub x1: f32,
pub y1: f32,
pub x2: f32,
pub y2: f32,
}
impl CubicBezier {
pub const fn new(x1: f32, y1: f32, x2: f32, y2: f32) -> Self {
Self { x1, y1, x2, y2 }
}
fn coefficients(a: f32, b: f32) -> (f32, f32, f32) {
let c = 3.0 * a;
let bb = 3.0 * (b - a) - c;
let aa = 1.0 - c - bb;
(aa, bb, c)
}
fn sample_x(&self, t: f32) -> f32 {
let (a, b, c) = Self::coefficients(self.x1, self.x2);
((a * t + b) * t + c) * t
}
fn sample_y(&self, t: f32) -> f32 {
let (a, b, c) = Self::coefficients(self.y1, self.y2);
((a * t + b) * t + c) * t
}
fn sample_x_derivative(&self, t: f32) -> f32 {
let (a, b, c) = Self::coefficients(self.x1, self.x2);
(3.0 * a * t + 2.0 * b) * t + c
}
fn solve_t_for_x(&self, x: f32) -> f32 {
let mut t = x;
for _ in 0..8 {
let err = self.sample_x(t) - x;
if err.abs() < 1e-6 {
return t;
}
let d = self.sample_x_derivative(t);
if d.abs() < 1e-6 {
break;
}
t -= err / d;
}
let (mut lo, mut hi) = (0.0_f32, 1.0_f32);
for _ in 0..32 {
let mid = (lo + hi) / 2.0;
if self.sample_x(mid) < x {
lo = mid
} else {
hi = mid
}
}
(lo + hi) / 2.0
}
/// Eased output for input progress `x ∈ [0,1]`, clamped hard — gpui's
/// animation element asserts its delta is in `[0,1]` and aborts.
pub fn eval(&self, x: f32) -> f32 {
if x <= 0.0 {
return 0.0;
}
if x >= 1.0 {
return 1.0;
}
self.sample_y(self.solve_t_for_x(x)).clamp(0.0, 1.0)
}
/// This curve as a gpui easing closure.
pub fn easing(self) -> impl Fn(f32) -> f32 + 'static {
move |x| self.eval(x)
}
}
/// The signature entrance curve — CSS `cubic-bezier(0.16, 1, 0.3, 1)`.
pub const EASE_OUT_EXPO: CubicBezier = CubicBezier::new(0.16, 1.0, 0.3, 1.0);
/// CSS `ease-out` — width/height transitions.
pub const EASE_OUT: CubicBezier = CubicBezier::new(0.0, 0.0, 0.58, 1.0);
/// CSS `ease` — quick fades, menu/dialog pops.
pub const EASE: CubicBezier = CubicBezier::new(0.25, 0.1, 0.25, 1.0);
/// Sidebar resort glide — CSS `cubic-bezier(0.22, 1, 0.36, 1)`.
pub const EASE_RESORT: CubicBezier = CubicBezier::new(0.22, 1.0, 0.36, 1.0);
/// CSS `ease-in-out`.
pub const EASE_IN_OUT: CubicBezier = CubicBezier::new(0.42, 0.0, 0.58, 1.0);
/// Tailwind's default transition curve — CSS `cubic-bezier(0.4, 0, 0.2, 1)`.
pub const EASE_TAILWIND: CubicBezier = CubicBezier::new(0.4, 0.0, 0.2, 1.0);
/// One catalog entry: duration + optional delay + curve; gpui has no native
/// delay, so `delay_ms` holds progress at 0 until it elapses.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MotionSpec {
pub duration_ms: u64,
pub delay_ms: u64,
pub curve: CubicBezier,
}
impl MotionSpec {
pub const fn new(duration_ms: u64, curve: CubicBezier) -> Self {
Self {
duration_ms,
delay_ms: 0,
curve,
}
}
pub const fn with_delay(mut self, delay_ms: u64) -> Self {
self.delay_ms = delay_ms;
self
}
/// Wall-clock span of the whole timeline (delay + duration).
pub fn total(&self) -> Duration {
Duration::from_millis(self.delay_ms + self.duration_ms)
}
/// Eased progress (0..1) for a raw timeline delta (0..1 across `total`).
pub fn progress(&self, raw_delta: f32) -> f32 {
let total = (self.delay_ms + self.duration_ms) as f32;
if total <= 0.0 || self.duration_ms == 0 {
return 1.0;
}
let t =
(raw_delta.clamp(0.0, 1.0) * total - self.delay_ms as f32) / self.duration_ms as f32;
self.curve.eval(t.clamp(0.0, 1.0))
}
/// A oneshot gpui [`Animation`] for this spec.
pub fn animation(&self) -> Animation {
let spec = *self;
Animation::new(spec.total().mul_f32(speed_scale())).with_easing(move |d| spec.progress(d))
}
}
/// Entrances: 0.5s expo-out fade + 4px rise.
pub const FADE_IN: MotionSpec = MotionSpec::new(500, EASE_OUT_EXPO);
/// Quick fade: 0.15s.
pub const FADE_QUICK: MotionSpec = MotionSpec::new(150, EASE);
/// Popover-in: 0.14s.
pub const MENU_IN: MotionSpec = MotionSpec::new(140, EASE);
/// Popover-out: 0.1s — quicker than the entrance.
pub const MENU_OUT: MotionSpec = MotionSpec::new(100, EASE);
/// Dialog-in: 0.18s.
pub const DIALOG_IN: MotionSpec = MotionSpec::new(180, EASE);
/// Boot-splash exit: 0.5s fade + 6px lift after a 0.15s hold.
pub const SPLASH_OUT: MotionSpec = MotionSpec::new(500, EASE).with_delay(150);
/// Sidebar / pane width+height transitions: 200ms ease-out.
pub const RESIZE: MotionSpec = MotionSpec::new(200, EASE_OUT);
/// Tab drag-reorder sliding transforms: 150ms.
pub const TAB_SLIDE: MotionSpec = MotionSpec::new(150, EASE_OUT);
/// Per-pane collapse: 180ms height.
pub const COLLAPSE: MotionSpec = MotionSpec::new(180, EASE_OUT);
/// A gliding move the reader caused rather than one they are dragging.
pub const LAYOUT: MotionSpec = MotionSpec::new(200, EASE_OUT);
/// Chevron rotate: 200ms.
pub const CHEVRON: MotionSpec = MotionSpec::new(200, EASE);
/// Scroll-to-row glide: 500ms ease-in-out.
pub const SCROLL_GLIDE: MotionSpec = MotionSpec::new(500, EASE_IN_OUT);
/// CSS `transition-colors` default: 150ms over [`EASE_TAILWIND`].
pub const HOVER_FADE: MotionSpec = MotionSpec::new(150, EASE_TAILWIND);
/// Toast slide/fade, coop's own: 250ms over [`EASE_TAILWIND`].
pub const NOTIFICATION_IN: MotionSpec = MotionSpec::new(250, EASE_TAILWIND);
/// Pulse loader period: 2.4s.
pub const PULSE: MotionSpec = MotionSpec::new(2400, EASE);
/// Spinner wave period: 750ms.
pub const GRADIENT_SPIN: MotionSpec = MotionSpec::new(750, EASE);
/// Standard entrance: opacity 0→1 + translateY 4→0 over [`FADE_IN`].
pub fn fade_in<E>(id: impl Into<ElementId>, element: E) -> AnimationElement<E>
where
E: Styled + IntoElement + 'static,
{
element.with_animation(id, FADE_IN.animation(), |el, t| {
el.relative().opacity(t).top(px(4.0 * (1.0 - t)))
})
}
/// Quick opacity-only fade over [`FADE_QUICK`].
pub fn fade_quick<E>(id: impl Into<ElementId>, element: E) -> AnimationElement<E>
where
E: Styled + IntoElement + 'static,
{
element.with_animation(id, FADE_QUICK.animation(), |el, t| el.opacity(t))
}
/// Popover entrance: fade + translateY −2→0 over [`MENU_IN`].
pub fn menu_in<E>(id: impl Into<ElementId>, element: E) -> AnimationElement<E>
where
E: Styled + IntoElement + 'static,
{
element.with_animation(id, MENU_IN.animation(), |el, t| {
el.relative()
.opacity(0.3 + 0.7 * t)
.top(px(-2.0 * (1.0 - t)))
})
}
/// Popover exit over [`MENU_OUT`]; the caller supplies progress because
/// `with_animation` replays from 0 on remount.
pub fn menu_out<E>(id: impl Into<ElementId>, t: f32, element: E) -> AnimationElement<E>
where
E: Styled + IntoElement + 'static,
{
element.with_animation(id, MENU_OUT.animation(), move |el, _| {
el.relative().opacity(1.0 - t).top(px(-2.0 * t))
})
}
/// Dialog entrance over [`DIALOG_IN`] (fade + 2px rise).
pub fn dialog_in<E>(id: impl Into<ElementId>, element: E) -> AnimationElement<E>
where
E: Styled + IntoElement + 'static,
{
element.with_animation(id, DIALOG_IN.animation(), |el, t| {
el.relative().opacity(t).top(px(2.0 * (1.0 - t)))
})
}
/// Boot-splash exit: hold 150ms, then fade out + lift 6px over 500ms.
pub fn splash_out<E>(id: impl Into<ElementId>, element: E) -> AnimationElement<E>
where
E: Styled + IntoElement + 'static,
{
element.with_animation(id, SPLASH_OUT.animation(), |el, t| {
el.opacity(1.0 - t).top(px(-6.0 * t))
})
}
/// Linear interpolation.
pub fn lerp(from: f32, to: f32, t: f32) -> f32 {
from + (to - from) * t
}
/// One element's hover fade progress over [`HOVER_FADE`].
#[derive(Debug, Clone, Copy)]
pub struct FadeEntry {
origin: f32,
target: f32,
started: Instant,
seen: u64,
}
impl FadeEntry {
fn value(&self, now: Instant, duration: Duration) -> f32 {
let elapsed = now.saturating_duration_since(self.started);
if duration.is_zero() || elapsed >= duration {
return self.target;
}
let raw = elapsed.as_secs_f32() / duration.as_secs_f32();
lerp(self.origin, self.target, HOVER_FADE.curve.eval(raw))
}
fn settled(&self, now: Instant, duration: Duration) -> bool {
self.origin == self.target || now.saturating_duration_since(self.started) >= duration
}
}
/// Which fade: the view that paints it plus the element key — two views
/// using the same string would trade washes.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Fade {
pub painter: Painter,
pub key: SharedString,
}
impl Fade {
pub fn new(painter: Painter, key: impl Into<SharedString>) -> Self {
Self {
painter,
key: key.into(),
}
}
}
/// Per-fade progress store.
#[derive(Default)]
pub struct HoverFades {
pub entries: HashMap<Fade, FadeEntry>,
frame: u64,
}
impl HoverFades {
pub fn duration() -> Duration {
HOVER_FADE.total().mul_f32(speed_scale())
}
/// Pointer entered (`hovered`) or left the element behind `fade`.
pub fn set_at(&mut self, fade: &Fade, hovered: bool, reduced: bool, now: Instant) {
let target = if hovered { 1.0 } else { 0.0 };
let duration = Self::duration();
let current = self
.entries
.get(fade)
.map(|e| e.value(now, duration))
.unwrap_or(0.0);
if target == 0.0 && !self.entries.contains_key(fade) {
return;
}
let origin = if reduced { target } else { current };
let seen = self.frame;
self.entries.insert(
fade.clone(),
FadeEntry {
origin,
target,
started: now,
seen,
},
);
}
/// Hover progress (0..1) for `fade` at `now`; stamps liveness.
pub fn value_at(&mut self, fade: &Fade, now: Instant) -> f32 {
let frame = self.frame;
match self.entries.get_mut(fade) {
Some(entry) => {
entry.seen = frame;
entry.value(now, Self::duration())
}
None => 0.0,
}
}
/// Advance the frame counter and prune settled or unmounted entries.
pub fn tick_at(&mut self, now: Instant) -> bool {
self.frame += 1;
let frame = self.frame;
let duration = Self::duration();
let mut active = false;
self.entries.retain(|_, entry| {
if entry.seen + 1 < frame {
return false;
}
let settled = entry.settled(now, duration);
if !settled {
active = true;
}
!(settled && entry.target == 0.0)
});
active
}
}
thread_local! {
static HOVER_FADES: RefCell<HoverFades> = RefCell::new(HoverFades::default());
}
/// Hover progress (0..1) for `fade` this frame.
pub fn hover_t(fade: &Fade) -> f32 {
HOVER_FADES.with(|fades| fades.borrow_mut().value_at(fade, Instant::now()))
}
/// Record a hover flip for `fade`; prefer [`hover_listener`].
pub fn set_hover(fade: &Fade, hovered: bool, reduced: bool) {
HOVER_FADES.with(|fades| {
fades
.borrow_mut()
.set_at(fade, hovered, reduced, Instant::now())
});
}
/// An `.on_hover` listener driving the fade.
pub fn hover_listener(fade: Fade) -> impl Fn(&bool, &mut Window, &mut App) + 'static {
move |hovered, _window, cx| {
set_hover(&fade, *hovered, cx.reduced_motion());
fade.painter.lease(HOVER_FPS, HoverFades::duration(), cx);
}
}
/// Once-per-tick bookkeeping for the fade store, driven by the clock.
fn tick_hover_fades() {
HOVER_FADES.with(|fades| {
fades.borrow_mut().tick_at(Instant::now());
});
}
/// Blend two colors by `t` the way the browser transitions them: sRGB with
/// premultiplied alpha, so a wash fading in never passes through grey.
pub fn mix(from: Hsla, to: Hsla, t: f32) -> Hsla {
let t = t.clamp(0.0, 1.0);
if t <= 0.0 {
return from;
}
if t >= 1.0 {
return to;
}
let (f, g) = (Rgba::from(from), Rgba::from(to));
let a = lerp(f.a, g.a, t);
if a <= f32::EPSILON {
return Hsla::from(Rgba { a: 0.0, ..g });
}
Hsla::from(Rgba {
r: lerp(f.r * f.a, g.r * g.a, t) / a,
g: lerp(f.g * f.a, g.g * g.a, t) / a,
b: lerp(f.b * f.a, g.b * g.a, t) / a,
a,
})
}
/// The standard hover blend: rest → hover color at this fade's progress.
pub fn hover_blend(fade: &Fade, rest: Hsla, hover: Hsla) -> Hsla {
mix(rest, hover, hover_t(fade))
}
/// Process-wide motion speed, as raw `f32` bits; an atomic mirror because the
/// timelines are read from free functions with no `cx` in scope.
static SPEED: AtomicU32 = AtomicU32::new(1.0f32.to_bits());
/// How far every timeline in the catalog is stretched. `1.0` is the designed
/// speed.
pub fn speed_scale() -> f32 {
f32::from_bits(SPEED.load(Ordering::Relaxed))
}
/// Stretch every catalog timeline by `scale`; clamped, and a non-finite
/// `scale` resets to `1.0`.
pub fn set_speed(scale: f32) {
let scale = if scale.is_finite() {
scale.clamp(0.01, 100.0)
} else {
1.0
};
SPEED.store(scale.to_bits(), Ordering::Relaxed);
}
/// Process-wide motion switches.
#[derive(Clone, Copy)]
struct MotionSettings {
reduced_motion: bool,
pause_when_inactive: bool,
}
impl Global for MotionSettings {}
impl MotionSettings {
/// Defaults: the OS flag rules reduce motion; the clock pauses when no
/// window is active.
fn get(cx: &App) -> Self {
cx.try_global::<MotionSettings>().copied().unwrap_or(Self {
reduced_motion: false,
pause_when_inactive: true,
})
}
}
/// App-level motion switches.
pub trait AppExt {
/// The OS reduce-motion flag ORed with the manual override.
fn reduced_motion(&self) -> bool;
/// Set the manual reduce-motion override.
fn set_reduced_motion(&mut self, reduced: bool);
/// Whether the leased clock claims frames while no window is active.
fn pause_when_inactive(&self) -> bool;
fn set_pause_when_inactive(&mut self, pause: bool);
}
impl AppExt for App {
fn reduced_motion(&self) -> bool {
self.reduce_motion() || MotionSettings::get(self).reduced_motion
}
fn set_reduced_motion(&mut self, reduced: bool) {
let mut settings = MotionSettings::get(self);
settings.reduced_motion = reduced;
self.set_global(settings);
}
fn pause_when_inactive(&self) -> bool {
MotionSettings::get(self).pause_when_inactive
}
fn set_pause_when_inactive(&mut self, pause: bool) {
let mut settings = MotionSettings::get(self);
settings.pause_when_inactive = pause;
self.set_global(settings);
}
}
+3 -3
View File
@@ -6,7 +6,7 @@ use gpui::{
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _, StyleRefinement, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _, StyleRefinement,
Styled, Window, div, px, Styled, Window, div, px,
}; };
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use crate::{Selectable, StyledExt, h_flex}; use crate::{Selectable, StyledExt, h_flex};
@@ -98,7 +98,7 @@ impl RenderOnce for NavItem {
.px_2() .px_2()
.py_1() .py_1()
.w_full() .w_full()
.gap_2() .gap(px(theme::SPACE))
.rounded(px(theme::button_radius())) .rounded(px(theme::button_radius()))
.text_color(text_color) .text_color(text_color)
.child(self.icon) .child(self.icon)
@@ -107,7 +107,7 @@ impl RenderOnce for NavItem {
.flex_1() .flex_1()
.min_w_0() .min_w_0()
.truncate() .truncate()
.text_sm() .text_style(TextStyle::Callout)
.child(self.label), .child(self.label),
) )
.when_some(self.suffix, |this, suffix| { .when_some(self.suffix, |this, suffix| {
+11 -12
View File
@@ -5,19 +5,19 @@ use std::time::Duration;
use gpui::prelude::FluentBuilder; use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
Anchor, Animation, AnimationExt, AnyElement, App, AppContext, ClickEvent, Context, Anchor, AnimationExt, AnyElement, App, AppContext, ClickEvent, Context, DismissEvent,
DismissEvent, ElementId, Entity, EventEmitter, InteractiveElement as _, IntoElement, ElementId, Entity, EventEmitter, InteractiveElement as _, IntoElement, ParentElement as _,
ParentElement as _, Render, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Render, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Subscription,
Subscription, Window, div, px, relative, Window, div, px, relative,
}; };
use gpui_base::{ use gpui_base::{
Toast as BaseToast, ToastManager, ToastMotion, ToastOptions, ToastStack, ToastStackState, Toast as BaseToast, ToastManager, ToastMotion, ToastOptions, ToastStack, ToastStackState,
ToastTransitionStatus, ToastTransitionStatus,
}; };
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use crate::animation::cubic_bezier;
use crate::button::{Button, ButtonVariants as _}; use crate::button::{Button, ButtonVariants as _};
use crate::motion::NOTIFICATION_IN;
use crate::{Icon, IconName, Sizable as _, Size, StyledExt, h_flex, v_flex}; use crate::{Icon, IconName, Sizable as _, Size, StyledExt, h_flex, v_flex};
/// How often the notification lifecycle clock is sampled. /// How often the notification lifecycle clock is sampled.
@@ -312,7 +312,7 @@ impl Render for Notification {
this.shadow(theme::surface_shadows()) this.shadow(theme::surface_shadows())
}) })
.p_2() .p_2()
.gap_2() .gap(px(theme::SPACE))
.justify_start() .justify_start()
.items_start() .items_start()
.when(only_message, |this| this.items_center()) .when(only_message, |this| this.items_center())
@@ -326,12 +326,12 @@ impl Render for Notification {
.gap_1() .gap_1()
.overflow_hidden() .overflow_hidden()
.when_some(self.title.clone(), |this, title| { .when_some(self.title.clone(), |this, title| {
this.child(h_flex().h_5().text_sm().font_semibold().child(title)) this.child(h_flex().h_5().text_style(TextStyle::Headline).child(title))
}) })
.when_some(self.message.clone(), |this, message| { .when_some(self.message.clone(), |this, message| {
this.child( this.child(
div() div()
.text_sm() .text_style(TextStyle::Callout)
.when(has_title, |this| this.text_color(cx.theme().text_muted)) .when(has_title, |this| this.text_color(cx.theme().text_muted))
.line_height(relative(1.3)) .line_height(relative(1.3))
.child(message), .child(message),
@@ -339,7 +339,7 @@ impl Render for Notification {
}) })
.when_some(content, |this, content| this.child(content)) .when_some(content, |this, content| this.child(content))
.when_some(action, |this, action| { .when_some(action, |this, action| {
this.gap_2().child( this.gap(px(theme::SPACE)).child(
h_flex() h_flex()
.mt_2() .mt_2()
.w_full() .w_full()
@@ -379,8 +379,7 @@ impl Render for Notification {
})) }))
.with_animation( .with_animation(
ElementId::NamedInteger("slide-down".into(), closing as u64), ElementId::NamedInteger("slide-down".into(), closing as u64),
Animation::new(Duration::from_secs_f64(0.25)) NOTIFICATION_IN.animation(),
.with_easing(cubic_bezier(0.4, 0., 0.2, 1.)),
move |this, delta| { move |this, delta| {
if closing { if closing {
let opacity = 1. - delta; let opacity = 1. - delta;
+3 -3
View File
@@ -11,7 +11,7 @@ use gpui::{
use gpui_base::TextSelectionLayer; use gpui_base::TextSelectionLayer;
use theme::{ use theme::{
ActiveTheme, CLIENT_SIDE_DECORATION_BORDER, CLIENT_SIDE_DECORATION_ROUNDING, ActiveTheme, CLIENT_SIDE_DECORATION_BORDER, CLIENT_SIDE_DECORATION_ROUNDING,
CLIENT_SIDE_DECORATION_SHADOW, surface_shadows, CLIENT_SIDE_DECORATION_SHADOW, base_text_size, surface_shadows,
}; };
use crate::dialog::Dialog; use crate::dialog::Dialog;
@@ -234,8 +234,8 @@ impl Root {
impl Render for Root { impl Render for Root {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let rem_size = cx.theme().font_size; let rem_size = px(base_text_size());
let font_family = cx.theme().font_family.clone(); let font_family = cx.theme().font_sans.clone();
let decorations = window.window_decorations(); let decorations = window.window_decorations();
// Set the base font size // Set the base font size
+5 -5
View File
@@ -1,6 +1,6 @@
use gpui::{App, Div, Pixels, Refineable, StyleRefinement, Styled, div, px}; use gpui::{App, Div, Pixels, Refineable, StyleRefinement, Styled, div, px};
pub use gpui_base::component_traits::{Disableable, Selectable}; pub use gpui_base::component_traits::{Disableable, Selectable};
use theme::{ActiveTheme, surface_radius, surface_shadows}; use theme::{ActiveTheme, TextStyle, Typeset as _, surface_radius, surface_shadows};
/// Returns a `Div` as horizontal flex layout. /// Returns a `Div` as horizontal flex layout.
pub fn h_flex() -> Div { pub fn h_flex() -> Div {
@@ -135,10 +135,10 @@ impl<T: Styled> StyleSized<T> for T {
fn input_h(self, size: Size) -> Self { fn input_h(self, size: Size) -> Self {
match size { match size {
Size::XSmall => self.h_6().text_xs(), Size::XSmall => self.h_6().text_style(TextStyle::Caption),
Size::Small => self.h_8().text_sm(), Size::Small => self.h_8().text_style(TextStyle::Callout),
Size::Medium => self.h_9().text_base(), Size::Medium => self.h_9().text_style(TextStyle::Body),
Size::Large => self.h_12().text_lg(), Size::Large => self.h_12().text_style(TextStyle::Title3),
Size::Size(size) => self.h(px(24.)).text_size(size), Size::Size(size) => self.h(px(24.)).text_size(size),
} }
} }
+8 -3
View File
@@ -7,7 +7,7 @@ use gpui::{
div, px, transparent_black, div, px, transparent_black,
}; };
use gpui_base::{Spring, Switch as BaseSwitch, SwitchThumb, SwitchTrack, spring}; use gpui_base::{Spring, Switch as BaseSwitch, SwitchThumb, SwitchTrack, spring};
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
type OnClick = Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>; type OnClick = Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>;
@@ -113,7 +113,12 @@ impl RenderOnce for Switch {
.items_center() .items_center()
.gap_4() .gap_4()
.when_some(label, |this, label| { .when_some(label, |this, label| {
this.child(div().text_sm().text_color(cx.theme().text).child(label)) this.child(
div()
.text_style(TextStyle::Callout)
.text_color(cx.theme().text)
.child(label),
)
}) })
.child( .child(
SwitchTrack::new((self.id.clone(), "track")) SwitchTrack::new((self.id.clone(), "track"))
@@ -142,7 +147,7 @@ impl RenderOnce for Switch {
this.child( this.child(
div() div()
.pr_3() .pr_3()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(description), .child(description),
) )
+3 -3
View File
@@ -5,7 +5,7 @@ use gpui::{
AnyElement, App, ClickEvent, Div, InteractiveElement, IntoElement, MouseButton, ParentElement, AnyElement, App, ClickEvent, Div, InteractiveElement, IntoElement, MouseButton, ParentElement,
RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window, div, px, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window, div, px,
}; };
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use crate::{Selectable, h_flex}; use crate::{Selectable, h_flex};
@@ -170,7 +170,7 @@ impl RenderOnce for Tab {
.items_center() .items_center()
.text_color(foreground) .text_color(foreground)
.when(segmented, |this| { .when(segmented, |this| {
this.text_xs() this.text_style(TextStyle::Caption)
.flex_1() .flex_1()
.h_6() .h_6()
.rounded(px(theme::button_radius())) .rounded(px(theme::button_radius()))
@@ -188,7 +188,7 @@ impl RenderOnce for Tab {
.h_7() .h_7()
.gap_1() .gap_1()
.px_1p5() .px_1p5()
.text_sm() .text_style(TextStyle::Callout)
.rounded(px(theme::button_radius())) .rounded(px(theme::button_radius()))
.overflow_hidden() .overflow_hidden()
.when(selected && !disabled, |this| { .when(selected && !disabled, |this| {
+4 -5
View File
@@ -1,10 +1,10 @@
use gpui::prelude::FluentBuilder; use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
App, AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString, Styled, App, AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString, Styled,
Window, px, relative, Window, px,
}; };
use gpui_base::Tooltip as BaseTooltip; use gpui_base::Tooltip as BaseTooltip;
use theme::{ActiveTheme, control_radius}; use theme::{ActiveTheme, TextStyle, Typeset as _, control_radius};
pub struct Tooltip { pub struct Tooltip {
text: SharedString, text: SharedString,
@@ -19,7 +19,7 @@ impl Tooltip {
impl Render for Tooltip { impl Render for Tooltip {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
BaseTooltip::new("tooltip") BaseTooltip::new("tooltip")
.font_family(".SystemUIFont") .font_family(cx.theme().font_sans.clone())
.m_3() .m_3()
.p_1p5() .p_1p5()
.border_1() .border_1()
@@ -27,9 +27,8 @@ impl Render for Tooltip {
.bg(cx.theme().surface_overlay) .bg(cx.theme().surface_overlay)
.when(cx.theme().shadow, |this| this.shadow_sm()) .when(cx.theme().shadow, |this| this.shadow_sm())
.rounded(px(control_radius())) .rounded(px(control_radius()))
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text) .text_color(cx.theme().text)
.line_height(relative(1.25))
.child(self.text.clone()) .child(self.text.clone())
} }
} }
+6 -6
View File
@@ -7,7 +7,7 @@ use gpui::{
use instant::Duration; use instant::Duration;
use nostr_connect::prelude::*; use nostr_connect::prelude::*;
use state::{CoopAuthUrlHandler, NostrRegistry, USER_KEYRING}; use state::{CoopAuthUrlHandler, NostrRegistry, USER_KEYRING};
use theme::{ActiveTheme, surface_radius}; use theme::{ActiveTheme, TextStyle, Typeset as _, surface_radius};
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::input::{Input, InputEvent, InputState}; use ui::input::{Input, InputEvent, InputState};
use ui::{Disableable, StyledExt, WindowExtension, v_flex}; use ui::{Disableable, StyledExt, WindowExtension, v_flex};
@@ -223,8 +223,8 @@ impl Render for ImportIdentity {
v_flex() v_flex()
.size_full() .size_full()
.gap_2() .gap(px(theme::SPACE))
.text_sm() .text_style(TextStyle::Callout)
.child( .child(
v_flex() v_flex()
.gap_1() .gap_1()
@@ -250,7 +250,7 @@ impl Render for ImportIdentity {
.p_2() .p_2()
.rounded(px(surface_radius())) .rounded(px(surface_radius()))
.bg(cx.theme().warning_muted) .bg(cx.theme().warning_muted)
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().warning) .text_color(cx.theme().warning)
.line_height(relative(1.2)) .line_height(relative(1.2))
.child(div().child(BUNKER_WARN)), .child(div().child(BUNKER_WARN)),
@@ -262,7 +262,7 @@ impl Render for ImportIdentity {
.p_2() .p_2()
.rounded(px(surface_radius())) .rounded(px(surface_radius()))
.bg(cx.theme().warning_muted) .bg(cx.theme().warning_muted)
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().warning) .text_color(cx.theme().warning)
.line_height(relative(1.2)) .line_height(relative(1.2))
.child(div().child(KEY_WARN)), .child(div().child(KEY_WARN)),
@@ -295,7 +295,7 @@ impl Render for ImportIdentity {
.when_some(self.error.read(cx).as_ref(), |this, error| { .when_some(self.error.read(cx).as_ref(), |this, error| {
this.child( this.child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.text_center() .text_center()
.text_color(cx.theme().danger) .text_color(cx.theme().danger)
.child(error.clone()), .child(error.clone()),
+1 -1
View File
@@ -43,7 +43,7 @@ pub fn open(window: &mut Window, cx: &mut App) {
.child( .child(
v_flex() v_flex()
.gap_3() .gap_3()
.child(Input::new(&input).cleanable(true).text_sm()) .child(Input::new(&input).cleanable(true))
.child( .child(
h_flex() h_flex()
.gap_1() .gap_1()
+6 -6
View File
@@ -13,7 +13,7 @@ use nostr_sdk::prelude::*;
use person::PersonRegistry; use person::PersonRegistry;
use settings::AppSettings; use settings::AppSettings;
use state::NostrRegistry; use state::NostrRegistry;
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::dock::{DockArea, DockPlacement, PanelHandle}; use ui::dock::{DockArea, DockPlacement, PanelHandle};
@@ -332,7 +332,7 @@ impl NewChat {
Nav::new(start + offset) Nav::new(start + offset)
.label(profile.name()) .label(profile.name())
.text_sm() .text_style(TextStyle::Callout)
.when(!AppSettings::get_hide_avatar(cx), |this| { .when(!AppSettings::get_hide_avatar(cx), |this| {
this.prefix( this.prefix(
Avatar::from_source(profile.avatar()) Avatar::from_source(profile.avatar())
@@ -366,7 +366,7 @@ impl NewChat {
.p_0p5() .p_0p5()
.rounded_full() .rounded_full()
.bg(cx.theme().surface_raised) .bg(cx.theme().surface_raised)
.text_size(px(10.)) .text_style(TextStyle::Caption2)
.hover(|this| this.bg(cx.theme().element_hover)) .hover(|this| this.bg(cx.theme().element_hover))
.child( .child(
Avatar::from_source(profile.avatar()) Avatar::from_source(profile.avatar())
@@ -394,7 +394,7 @@ impl Render for NewChat {
h_flex() h_flex()
.gap_1() .gap_1()
.w_full() .w_full()
.child(Input::new(&self.input).flex_1().text_sm().cleanable(true)) .child(Input::new(&self.input).flex_1().cleanable(true))
.child( .child(
Button::new("add") Button::new("add")
.icon(IconName::Plus) .icon(IconName::Plus)
@@ -412,11 +412,11 @@ impl Render for NewChat {
}) })
.map(|this| { .map(|this| {
if let Some(error) = self.error.clone() { if let Some(error) = self.error.clone() {
this.child(div().text_xs().text_color(cx.theme().danger).child(error)) this.child(div().text_style(TextStyle::Caption).text_color(cx.theme().danger).child(error))
} else { } else {
this.child( this.child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.child(notice), .child(notice),
) )
+2 -2
View File
@@ -3,7 +3,7 @@ use std::rc::Rc;
use gpui::{App, AppContext, ClickEvent, Div, Entity, ParentElement, Styled, Window, px}; use gpui::{App, AppContext, ClickEvent, Div, Entity, ParentElement, Styled, Window, px};
use nip29::{GroupId, GroupKey, GroupMetadata, GroupsRegistry}; use nip29::{GroupId, GroupKey, GroupMetadata, GroupsRegistry};
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::input::{Input, InputState}; use ui::input::{Input, InputState};
use ui::notification::Notification; use ui::notification::Notification;
@@ -67,7 +67,7 @@ pub fn open(window: &mut Window, cx: &mut App) {
fn field(label: &'static str, input: &Entity<InputState>, cx: &App) -> Div { fn field(label: &'static str, input: &Entity<InputState>, cx: &App) -> Div {
v_flex() v_flex()
.gap_1() .gap_1()
.text_sm() .text_style(TextStyle::Callout)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(label) .child(label)
.child(Input::new(input)) .child(Input::new(input))
+4 -4
View File
@@ -12,7 +12,7 @@ use nostr_sdk::prelude::*;
use person::{Person, PersonRegistry, shorten_pubkey}; use person::{Person, PersonRegistry, shorten_pubkey};
use settings::AppSettings; use settings::AppSettings;
use state::{NostrRegistry, upload}; use state::{NostrRegistry, upload};
use theme::{ActiveTheme, button_radius}; use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius};
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::dock::DockPlacement; use ui::dock::DockPlacement;
@@ -393,7 +393,7 @@ impl Render for ProfileDialog {
.gap_3() .gap_3()
.rounded(px(button_radius())) .rounded(px(button_radius()))
.bg(cx.theme().surface) .bg(cx.theme().surface)
.text_sm() .text_style(TextStyle::Callout)
.child(shorten_pkey) .child(shorten_pkey)
.child( .child(
Button::new("copy") Button::new("copy")
@@ -513,7 +513,7 @@ impl Render for ProfileDialog {
fn label(label: &'static str, cx: &App) -> Div { fn label(label: &'static str, cx: &App) -> Div {
div() div()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.font_semibold() .font_semibold()
.child(label) .child(label)
@@ -533,5 +533,5 @@ fn field(
v_flex() v_flex()
.gap_1p5() .gap_1p5()
.child(label(text, cx)) .child(label(text, cx))
.child(div().text_sm().text_color(color).child(content)) .child(div().text_style(TextStyle::Callout).text_color(color).child(content))
} }
+12 -13
View File
@@ -11,7 +11,7 @@ use instant::Duration;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
use state::NostrRegistry; use state::NostrRegistry;
use theme::{ActiveTheme, button_radius, panel_radius}; use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius, panel_radius};
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::input::{Input, InputEvent, InputState}; use ui::input::{Input, InputEvent, InputState};
use ui::menu::{DropdownMenu, PopupMenuItem}; use ui::menu::{DropdownMenu, PopupMenuItem};
@@ -108,10 +108,10 @@ impl RelayManager {
let manager = cx.entity().downgrade(); let manager = cx.entity().downgrade();
v_flex() v_flex()
.gap_2() .gap(px(theme::SPACE))
.child( .child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(SharedString::from(GOSSIP_MSG)), .child(SharedString::from(GOSSIP_MSG)),
) )
@@ -132,7 +132,7 @@ impl RelayManager {
.tooltip("Relay metadata") .tooltip("Relay metadata")
.ghost() .ghost()
.h(rems(2.)) .h(rems(2.))
.text_xs() .text_style(TextStyle::Caption)
.dropdown_menu(move |menu, _window, _cx| { .dropdown_menu(move |menu, _window, _cx| {
menu.item(metadata_item(&manager, RelayMetadata::Read)) menu.item(metadata_item(&manager, RelayMetadata::Read))
.item(metadata_item(&manager, RelayMetadata::Write)) .item(metadata_item(&manager, RelayMetadata::Write))
@@ -153,7 +153,7 @@ impl RelayManager {
this.child( this.child(
div() div()
.italic() .italic()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().danger) .text_color(cx.theme().danger)
.child(error.clone()), .child(error.clone()),
) )
@@ -197,10 +197,10 @@ impl RelayManager {
fn render_inbox(&mut self, cx: &mut Context<Self>) -> impl IntoElement { fn render_inbox(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
v_flex() v_flex()
.gap_2() .gap(px(theme::SPACE))
.child( .child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(SharedString::from(INBOX_MSG)), .child(SharedString::from(INBOX_MSG)),
) )
@@ -224,7 +224,7 @@ impl RelayManager {
this.child( this.child(
div() div()
.italic() .italic()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().danger) .text_color(cx.theme().danger)
.child(error.clone()), .child(error.clone()),
) )
@@ -607,7 +607,7 @@ impl Render for RelayManager {
v_flex() v_flex()
.gap_4() .gap_4()
.text_sm() .text_style(TextStyle::Callout)
.child(self.render_tab_bar(cx)) .child(self.render_tab_bar(cx))
.child(content) .child(content)
.child(self.render_actions(cx)) .child(self.render_actions(cx))
@@ -651,15 +651,14 @@ fn relay_row(
.child( .child(
h_flex() h_flex()
.gap_1() .gap_1()
.text_sm() .text_style(TextStyle::Callout)
.child(label) .child(label)
.when_some(badge, |this, badge| { .when_some(badge, |this, badge| {
this.child( this.child(
div() div()
.p_0p5() .p_0p5()
.rounded_xs() .rounded_xs()
.font_semibold() .text_style(TextStyle::Caption2)
.text_size(px(8.))
.child(badge), .child(badge),
) )
}), }),
@@ -683,7 +682,7 @@ fn empty_relays(cx: &App) -> impl IntoElement {
.border_dashed() .border_dashed()
.border_color(cx.theme().border) .border_color(cx.theme().border)
.rounded(px(panel_radius())) .rounded(px(panel_radius()))
.text_sm() .text_style(TextStyle::Callout)
.text_align(TextAlign::Center) .text_align(TextAlign::Center)
.child(SharedString::from("Please add some relays.")) .child(SharedString::from("Please add some relays."))
} }
+4 -4
View File
@@ -7,7 +7,7 @@ use gpui::{
use instant::Duration; use instant::Duration;
use nip4e::DeviceRegistry; use nip4e::DeviceRegistry;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::input::{Input, InputEvent, InputState}; use ui::input::{Input, InputEvent, InputState};
use ui::{IconName, StyledExt, WindowExtension, h_flex, v_flex}; use ui::{IconName, StyledExt, WindowExtension, h_flex, v_flex};
@@ -100,11 +100,11 @@ impl Render for RestoreEncryption {
v_flex() v_flex()
.size_full() .size_full()
.gap_3() .gap_3()
.text_sm() .text_style(TextStyle::Callout)
.child( .child(
v_flex() v_flex()
.gap_1() .gap_1()
.text_sm() .text_style(TextStyle::Callout)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child("Secret Key") .child("Secret Key")
.child(Input::new(&self.key_input)), .child(Input::new(&self.key_input)),
@@ -124,7 +124,7 @@ impl Render for RestoreEncryption {
.when_some(self.error.read(cx).as_ref(), |this, error| { .when_some(self.error.read(cx).as_ref(), |this, error| {
this.child( this.child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.text_center() .text_center()
.text_color(cx.theme().danger) .text_color(cx.theme().danger)
.child(error.clone()), .child(error.clone()),
+13 -13
View File
@@ -12,7 +12,7 @@ use nostr_sdk::prelude::*;
use person::{Person, PersonRegistry, shorten_pubkey}; use person::{Person, PersonRegistry, shorten_pubkey};
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
use state::{BOOTSTRAP_RELAYS, NostrAddress, NostrRegistry, TIMEOUT}; use state::{BOOTSTRAP_RELAYS, NostrAddress, NostrRegistry, TIMEOUT};
use theme::{ActiveTheme, button_radius}; use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius};
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::indicator::Indicator; use ui::indicator::Indicator;
@@ -284,7 +284,7 @@ impl Screening {
.px_2() .px_2()
.gap_1p5() .gap_1p5()
.rounded(px(button_radius())) .rounded(px(button_radius()))
.text_sm() .text_style(TextStyle::Callout)
.hover(|this| this.bg(cx.theme().element_hover)) .hover(|this| this.bg(cx.theme().element_hover))
.child(Avatar::new(profile.avatar()).small()) .child(Avatar::new(profile.avatar()).small())
.child(profile.name()), .child(profile.name()),
@@ -345,7 +345,7 @@ impl Render for Screening {
.justify_center() .justify_center()
.rounded_full() .rounded_full()
.bg(cx.theme().surface_raised) .bg(cx.theme().surface_raised)
.text_sm() .text_style(TextStyle::Callout)
.truncate() .truncate()
.text_ellipsis() .text_ellipsis()
.text_center() .text_center()
@@ -385,11 +385,11 @@ impl Render for Screening {
.child( .child(
h_flex() h_flex()
.items_start() .items_start()
.gap_2() .gap(px(theme::SPACE))
.text_sm() .text_style(TextStyle::Callout)
.child(status_badge(Some(self.followed), cx)) .child(status_badge(Some(self.followed), cx))
.child( .child(
v_flex().text_sm().child("Contact").child( v_flex().text_style(TextStyle::Callout).child("Contact").child(
div() div()
.line_clamp(1) .line_clamp(1)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
@@ -406,12 +406,12 @@ impl Render for Screening {
.child( .child(
h_flex() h_flex()
.items_start() .items_start()
.gap_2() .gap(px(theme::SPACE))
.text_sm() .text_style(TextStyle::Callout)
.child(status_badge(last_active, cx)) .child(status_badge(last_active, cx))
.child( .child(
v_flex() v_flex()
.text_sm() .text_style(TextStyle::Callout)
.child( .child(
h_flex() h_flex()
.gap_0p5() .gap_0p5()
@@ -446,11 +446,11 @@ impl Render for Screening {
.child( .child(
h_flex() h_flex()
.items_start() .items_start()
.gap_2() .gap(px(theme::SPACE))
.child(status_badge(Some(self.verified), cx)) .child(status_badge(Some(self.verified), cx))
.child( .child(
v_flex() v_flex()
.text_sm() .text_style(TextStyle::Callout)
.child({ .child({
if let Some(addr) = self.address(cx) { if let Some(addr) = self.address(cx) {
SharedString::from(format!("{} validation", addr)) SharedString::from(format!("{} validation", addr))
@@ -481,11 +481,11 @@ impl Render for Screening {
.child( .child(
h_flex() h_flex()
.items_start() .items_start()
.gap_2() .gap(px(theme::SPACE))
.child(status_badge(Some(mutuals > 0), cx)) .child(status_badge(Some(mutuals > 0), cx))
.child( .child(
h_flex() h_flex()
.text_sm() .text_style(TextStyle::Callout)
.child( .child(
div() div()
.line_clamp(1) .line_clamp(1)
+10 -6
View File
@@ -1,10 +1,10 @@
use gpui::http_client::Url; use gpui::http_client::Url;
use gpui::{ use gpui::{
App, AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString, Styled, App, AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString, Styled,
Window, div, px, Window, div,
}; };
use settings::AppSettings; use settings::AppSettings;
use theme::{ActiveTheme, AppExt as _, AppearanceMode}; use theme::{ActiveTheme, AppExt as _, AppearanceMode, TextStyle, Typeset as _};
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::group_box::{GroupBox, GroupBoxVariants}; use ui::group_box::{GroupBox, GroupBoxVariants};
use ui::input::{Input, InputState}; use ui::input::{Input, InputState};
@@ -103,10 +103,14 @@ impl Render for Preferences {
.justify_between() .justify_between()
.child( .child(
v_flex() v_flex()
.child(div().text_sm().child(SharedString::from("Mode")))
.child( .child(
div() div()
.text_xs() .text_style(TextStyle::Callout)
.child(SharedString::from("Mode")),
)
.child(
div()
.text_style(TextStyle::Caption)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(SharedString::from(MODE)), .child(SharedString::from(MODE)),
), ),
@@ -154,7 +158,7 @@ impl Render for Preferences {
.child( .child(
h_flex() h_flex()
.gap_1() .gap_1()
.child(Input::new(&self.file_input).text_xs().small()) .child(Input::new(&self.file_input).small())
.child( .child(
Button::new("update-file-server") Button::new("update-file-server")
.icon(IconName::Check) .icon(IconName::Check)
@@ -167,7 +171,7 @@ impl Render for Preferences {
) )
.child( .child(
div() div()
.text_size(px(10.)) .text_style(TextStyle::Caption2)
.italic() .italic()
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.child(SharedString::from("Only support blossom service")), .child(SharedString::from("Only support blossom service")),
+2 -2
View File
@@ -14,7 +14,7 @@ use nostr_sdk::prelude::*;
use serde::Deserialize; use serde::Deserialize;
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
use state::{NostrRegistry, StateEvent}; use state::{NostrRegistry, StateEvent};
use theme::{ActiveTheme, SIDEBAR_WIDTH, Theme}; use theme::{ActiveTheme, TextStyle, Typeset as _, SIDEBAR_WIDTH, Theme};
use ui::button::Button; use ui::button::Button;
use ui::dock::{self, DockArea, DockLayout, DockPlacement, Panel, PanelHandle}; use ui::dock::{self, DockArea, DockLayout, DockPlacement, Panel, PanelHandle};
use ui::notification::{Notification, NotificationKind}; use ui::notification::{Notification, NotificationKind};
@@ -415,7 +415,7 @@ impl Workspace {
.child( .child(
v_flex() v_flex()
.gap_1() .gap_1()
.text_sm() .text_style(TextStyle::Callout)
.child(SharedString::from(ENC_MSG)) .child(SharedString::from(ENC_MSG))
.child( .child(
div() div()
+7 -7
View File
@@ -1,12 +1,12 @@
use anyhow::Error; use anyhow::Error;
use gpui::{ use gpui::{
AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter, FocusHandle, AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter, FocusHandle,
Focusable, IntoElement, ParentElement, Render, SharedString, Styled, Task, Window, div, Focusable, IntoElement, ParentElement, Render, SharedString, Styled, Task, Window, div, px,
}; };
use instant::Duration; use instant::Duration;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use state::USER_KEYRING; use state::USER_KEYRING;
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::dock::{Panel, PanelEvent}; use ui::dock::{Panel, PanelEvent};
use ui::input::{Input, InputState}; use ui::input::{Input, InputState};
@@ -132,24 +132,24 @@ impl Render for BackupPanel {
.w_full() .w_full()
.child( .child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(SharedString::from(MSG)), .child(SharedString::from(MSG)),
) )
.child(divider(cx)) .child(divider(cx))
.child( .child(
v_flex() v_flex()
.gap_2() .gap(px(theme::SPACE))
.flex_1() .flex_1()
.w_full() .w_full()
.text_sm() .text_style(TextStyle::Callout)
.child( .child(
v_flex() v_flex()
.gap_1p5() .gap_1p5()
.w_full() .w_full()
.child( .child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.font_semibold() .font_semibold()
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(SharedString::from("Public Key:")), .child(SharedString::from("Public Key:")),
@@ -162,7 +162,7 @@ impl Render for BackupPanel {
.w_full() .w_full()
.child( .child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.font_semibold() .font_semibold()
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(SharedString::from("Secret Key:")), .child(SharedString::from("Secret Key:")),
+11 -12
View File
@@ -13,7 +13,7 @@ use nip29::{Discovery, GroupCandidate, GroupKey, GroupsEvent, GroupsRegistry, Me
use nostr_sdk::prelude::{RelayUrl, Timestamp}; use nostr_sdk::prelude::{RelayUrl, Timestamp};
use settings::AppSettings; use settings::AppSettings;
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
use theme::{ActiveTheme, button_radius, panel_radius}; use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius, panel_radius};
use ui::avatar::Avatar; 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};
@@ -268,7 +268,7 @@ impl BrowsePanel {
.min_w_0() .min_w_0()
.child( .child(
div() div()
.text_sm() .text_style(TextStyle::Callout)
.font_semibold() .font_semibold()
.line_height(relative(1.2)) .line_height(relative(1.2))
.child("Public Groups"), .child("Public Groups"),
@@ -276,7 +276,7 @@ impl BrowsePanel {
.child( .child(
div() div()
.truncate() .truncate()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.map(|this| match &self.relay { .map(|this| match &self.relay {
Some(relay) => { Some(relay) => {
@@ -300,7 +300,6 @@ impl BrowsePanel {
Input::new(&self.input) Input::new(&self.input)
.appearance(false) .appearance(false)
.small() .small()
.text_sm()
.flex_1(), .flex_1(),
) )
.child( .child(
@@ -389,12 +388,12 @@ impl BrowsePanel {
.flex_1() .flex_1()
.min_h_0() .min_h_0()
.p_2() .p_2()
.gap_2() .gap(px(theme::SPACE))
.child( .child(
h_flex() h_flex()
.w_full() .w_full()
.items_center() .items_center()
.gap_2() .gap(px(theme::SPACE))
.when(!hide_avatar, |this| { .when(!hide_avatar, |this| {
this.child(Avatar::from_source(picture).seed(seed).flex_shrink_0()) this.child(Avatar::from_source(picture).seed(seed).flex_shrink_0())
}) })
@@ -404,7 +403,7 @@ impl BrowsePanel {
.min_w_0() .min_w_0()
.child( .child(
div() div()
.text_sm() .text_style(TextStyle::Callout)
.truncate() .truncate()
.font_semibold() .font_semibold()
.line_height(relative(1.2)) .line_height(relative(1.2))
@@ -413,7 +412,7 @@ impl BrowsePanel {
.child( .child(
div() div()
.truncate() .truncate()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.child(relay), .child(relay),
), ),
@@ -422,7 +421,7 @@ impl BrowsePanel {
.child( .child(
div() div()
.min_w_0() .min_w_0()
.text_sm() .text_style(TextStyle::Callout)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.text_ellipsis() .text_ellipsis()
.line_clamp(2) .line_clamp(2)
@@ -443,7 +442,7 @@ impl BrowsePanel {
.child(Icon::new(IconName::Book)) .child(Icon::new(IconName::Book))
.child( .child(
div() div()
.text_sm() .text_style(TextStyle::Callout)
.child(SharedString::from(format!("{members} members"))), .child(SharedString::from(format!("{members} members"))),
), ),
), ),
@@ -463,7 +462,7 @@ impl Panel for BrowsePanel {
fn title(&self, cx: &App) -> AnyElement { fn title(&self, cx: &App) -> AnyElement {
h_flex() h_flex()
.gap_1() .gap_1()
.text_xs() .text_style(TextStyle::Caption)
.child( .child(
Icon::new(IconName::Compass) Icon::new(IconName::Compass)
.small() .small()
@@ -543,7 +542,7 @@ fn notice(text: &'static str, cx: &App) -> AnyElement {
h_flex() h_flex()
.size_full() .size_full()
.justify_center() .justify_center()
.text_sm() .text_style(TextStyle::Callout)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(text) .child(text)
.into_any_element() .into_any_element()
+8 -8
View File
@@ -12,7 +12,7 @@ use nostr_sdk::prelude::*;
use person::PersonRegistry; use person::PersonRegistry;
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
use state::NostrRegistry; use state::NostrRegistry;
use theme::{ActiveTheme, button_radius, panel_radius}; use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius, panel_radius};
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::dock::{Panel, PanelEvent}; use ui::dock::{Panel, PanelEvent};
@@ -237,8 +237,8 @@ impl ContactListPanel {
.hover(|this| this.bg(cx.theme().element_hover)) .hover(|this| this.bg(cx.theme().element_hover))
.child( .child(
h_flex() h_flex()
.gap_2() .gap(px(theme::SPACE))
.text_sm() .text_style(TextStyle::Callout)
.child( .child(
Avatar::new(profile.avatar()) Avatar::new(profile.avatar())
.seed(profile.avatar_seed()) .seed(profile.avatar_seed())
@@ -274,7 +274,7 @@ impl ContactListPanel {
.border_dashed() .border_dashed()
.border_color(cx.theme().border) .border_color(cx.theme().border)
.rounded(px(panel_radius())) .rounded(px(panel_radius()))
.text_sm() .text_style(TextStyle::Callout)
.text_align(TextAlign::Center) .text_align(TextAlign::Center)
.child(SharedString::from("Please add some relays.")) .child(SharedString::from("Please add some relays."))
} }
@@ -308,13 +308,13 @@ impl Render for ContactListPanel {
.overflow_y_scrollbar() .overflow_y_scrollbar()
.child( .child(
v_flex() v_flex()
.gap_2() .gap(px(theme::SPACE))
.flex_1() .flex_1()
.w_full() .w_full()
.text_sm() .text_style(TextStyle::Callout)
.child( .child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.font_semibold() .font_semibold()
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child("New contact:"), .child("New contact:"),
@@ -342,7 +342,7 @@ impl Render for ContactListPanel {
this.child( this.child(
div() div()
.italic() .italic()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().danger) .text_color(cx.theme().danger)
.child(error.clone()), .child(error.clone()),
) )
+9 -9
View File
@@ -1,9 +1,9 @@
use gpui::{ use gpui::{
AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
IntoElement, ParentElement, Render, SharedString, Styled, Window, div, svg, IntoElement, ParentElement, Render, SharedString, Styled, Window, div, px, svg,
}; };
use state::NostrRegistry; use state::NostrRegistry;
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::dock::{Panel, PanelEvent}; use ui::dock::{Panel, PanelEvent};
use ui::{Icon, IconName, Sizable, StyledExt, h_flex, v_flex}; use ui::{Icon, IconName, Sizable, StyledExt, h_flex, v_flex};
@@ -44,7 +44,7 @@ impl Panel for GreeterPanel {
fn title(&self, cx: &App) -> AnyElement { fn title(&self, cx: &App) -> AnyElement {
h_flex() h_flex()
.gap_1() .gap_1()
.text_xs() .text_style(TextStyle::Caption)
.child( .child(
svg() svg()
.path("brand/coop.svg") .path("brand/coop.svg")
@@ -84,7 +84,7 @@ impl Render for GreeterPanel {
.child( .child(
h_flex() h_flex()
.mb_4() .mb_4()
.gap_2() .gap(px(theme::SPACE))
.w_full() .w_full()
.child( .child(
svg() svg()
@@ -102,7 +102,7 @@ impl Render for GreeterPanel {
) )
.child( .child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(SharedString::from(DESCRIPTION)), .child(SharedString::from(DESCRIPTION)),
), ),
@@ -110,13 +110,13 @@ impl Render for GreeterPanel {
) )
.child( .child(
v_flex() v_flex()
.gap_2() .gap(px(theme::SPACE))
.w_full() .w_full()
.child( .child(
h_flex() h_flex()
.gap_2() .gap(px(theme::SPACE))
.w_full() .w_full()
.text_xs() .text_style(TextStyle::Caption)
.font_semibold() .font_semibold()
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(SharedString::from("Get Started")) .child(SharedString::from("Get Started"))
@@ -124,7 +124,7 @@ impl Render for GreeterPanel {
) )
.child( .child(
v_flex() v_flex()
.gap_2() .gap(px(theme::SPACE))
.w_full() .w_full()
.justify_start() .justify_start()
.items_start() .items_start()
+2 -2
View File
@@ -2,7 +2,7 @@ use gpui::{
AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
IntoElement, ParentElement, Render, SharedString, Styled, Window, IntoElement, ParentElement, Render, SharedString, Styled, Window,
}; };
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use ui::dock::{Panel, PanelEvent}; use ui::dock::{Panel, PanelEvent};
use ui::{Icon, IconName, Sizable, h_flex}; use ui::{Icon, IconName, Sizable, h_flex};
@@ -55,7 +55,7 @@ impl Render for InboxPanel {
h_flex() h_flex()
.size_full() .size_full()
.justify_center() .justify_center()
.text_sm() .text_style(TextStyle::Callout)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(self.name.clone()) .child(self.name.clone())
} }
+7 -7
View File
@@ -11,7 +11,7 @@ use nostr_sdk::prelude::*;
use person::PersonRegistry; use person::PersonRegistry;
use settings::AppSettings; use settings::AppSettings;
use state::NostrRegistry; use state::NostrRegistry;
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::menu::{DropdownMenu, PopupMenuItem}; use ui::menu::{DropdownMenu, PopupMenuItem};
use ui::nav::Nav; use ui::nav::Nav;
@@ -131,7 +131,7 @@ impl Sidebar {
.flex_1() .flex_1()
.min_h_0() .min_h_0()
.w_full() .w_full()
.gap_2() .gap(px(theme::SPACE))
.child( .child(
h_flex() h_flex()
.px_2() .px_2()
@@ -149,7 +149,7 @@ impl Sidebar {
.min_w_0() .min_w_0()
.child( .child(
div() div()
.text_xs() .text_style(TextStyle::Caption)
.truncate() .truncate()
.line_height(relative(1.2)) .line_height(relative(1.2))
.font_semibold() .font_semibold()
@@ -157,7 +157,7 @@ impl Sidebar {
) )
.child( .child(
div() div()
.text_size(px(10.)) .text_style(TextStyle::Caption2)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(total_members), .child(total_members),
), ),
@@ -231,7 +231,7 @@ impl Sidebar {
Nav::new(label) Nav::new(label)
.label(label) .label(label)
.suffix(Icon::new(icon).small().text_color(cx.theme().text_faint)) .suffix(Icon::new(icon).small().text_color(cx.theme().text_faint))
.text_xs() .text_style(TextStyle::Caption)
.font_semibold() .font_semibold()
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.on_click(cx.listener(move |this, _event, _window, cx| { .on_click(cx.listener(move |this, _event, _window, cx| {
@@ -262,7 +262,7 @@ impl Sidebar {
Nav::new(public_key.to_hex()) Nav::new(public_key.to_hex())
.label(person.name()) .label(person.name())
.text_sm() .text_style(TextStyle::Callout)
.font_medium() .font_medium()
.clickable(true) .clickable(true)
.when(!hide_avatar, |this| { .when(!hide_avatar, |this| {
@@ -277,7 +277,7 @@ impl Sidebar {
this.suffix( this.suffix(
div() div()
.truncate() .truncate()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.child(SharedString::from(roles.join(", "))), .child(SharedString::from(roles.join(", "))),
) )
+2 -2
View File
@@ -33,7 +33,7 @@ impl Sidebar {
.h(TABBAR_HEIGHT) .h(TABBAR_HEIGHT)
.flex_shrink_0() .flex_shrink_0()
.items_center() .items_center()
.gap_2() .gap(px(theme::SPACE))
.px_2() .px_2()
.when(cfg!(target_os = "macos"), |this| { .when(cfg!(target_os = "macos"), |this| {
this.pl(px(TRAFFIC_LIGHT_PADDING)) this.pl(px(TRAFFIC_LIGHT_PADDING))
@@ -120,7 +120,7 @@ impl Sidebar {
let staged = updater.read(cx).staged(); let staged = updater.read(cx).staged();
h_flex() h_flex()
.gap_2() .gap(px(theme::SPACE))
.when(!up_to_date, |this| { .when(!up_to_date, |this| {
this.child( this.child(
Button::new("update") Button::new("update")
+2 -2
View File
@@ -6,7 +6,7 @@ use gpui::{
}; };
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use state::NostrRegistry; use state::NostrRegistry;
use theme::ActiveTheme; use theme::{ActiveTheme, TextStyle, Typeset as _};
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::dock::{DockPlacement, PanelHandle}; use ui::dock::{DockPlacement, PanelHandle};
use ui::notification::Notification; use ui::notification::Notification;
@@ -208,7 +208,7 @@ impl Sidebar {
.child( .child(
h_flex() h_flex()
.gap_1() .gap_1()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child("Selected") .child("Selected")
.child( .child(
+4 -4
View File
@@ -8,7 +8,7 @@ use gpui::{
}; };
use nip29::GroupsRegistry; use nip29::GroupsRegistry;
use settings::AppSettings; use settings::AppSettings;
use theme::{ActiveTheme, control_radius}; use theme::{ActiveTheme, TextStyle, Typeset as _, control_radius};
use ui::nav_item::NavItem; use ui::nav_item::NavItem;
use ui::scroll::Scrollbar; use ui::scroll::Scrollbar;
use ui::{Icon, IconName, Sizable, StyledExt, h_flex, v_flex}; use ui::{Icon, IconName, Sizable, StyledExt, h_flex, v_flex};
@@ -61,7 +61,7 @@ impl Sidebar {
.flex_1() .flex_1()
.min_h_0() .min_h_0()
.relative() .relative()
.gap_2() .gap(px(theme::SPACE))
.child( .child(
v_flex() v_flex()
.px_2() .px_2()
@@ -85,7 +85,7 @@ impl Sidebar {
.px_1() .px_1()
.min_w_6() .min_w_6()
.justify_center() .justify_center()
.text_size(px(10.)) .text_style(TextStyle::Caption2)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.text_center() .text_center()
.rounded(px(control_radius())) .rounded(px(control_radius()))
@@ -118,7 +118,7 @@ impl Sidebar {
this.child( this.child(
div() div()
.px_4() .px_4()
.text_xs() .text_style(TextStyle::Caption)
.font_semibold() .font_semibold()
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.child(list_title), .child(list_title),
+5 -5
View File
@@ -7,7 +7,7 @@ use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
InteractiveElement, IntoElement, ObjectFit, ParentElement, Render, SharedString, Styled, InteractiveElement, IntoElement, ObjectFit, ParentElement, Render, SharedString, Styled,
StyledImage, Subscription, Task, UniformListScrollHandle, WeakEntity, Window, div, img, StyledImage, Subscription, Task, UniformListScrollHandle, WeakEntity, Window, div, img, px,
retain_all, retain_all,
}; };
use instant::Duration; use instant::Duration;
@@ -15,7 +15,7 @@ use nip29::{Group, GroupKey, GroupsEvent, GroupsRegistry};
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
use state::{FIND_DELAY, NostrRegistry, StateEvent}; use state::{FIND_DELAY, NostrRegistry, StateEvent};
use theme::{ActiveTheme, TABBAR_HEIGHT}; use theme::{ActiveTheme, TABBAR_HEIGHT, TextStyle, Typeset as _};
use ui::dock::{DockArea, Panel, PanelEvent}; use ui::dock::{DockArea, Panel, PanelEvent};
use ui::indicator::Indicator; use ui::indicator::Indicator;
use ui::input::{InputEvent, InputState}; use ui::input::{InputEvent, InputState};
@@ -276,7 +276,7 @@ impl Render for Sidebar {
.image_cache(retain_all("sidebar")) .image_cache(retain_all("sidebar"))
.size_full() .size_full()
.relative() .relative()
.gap_2() .gap(px(theme::SPACE))
.bg(cx.theme().surface) .bg(cx.theme().surface)
.when_some(current_user.as_ref(), |this, current_user| { .when_some(current_user.as_ref(), |this, current_user| {
this.child(title_bar_drag_handlers( this.child(title_bar_drag_handlers(
@@ -326,14 +326,14 @@ impl Render for Sidebar {
.px_8() .px_8()
.child( .child(
h_flex() h_flex()
.gap_2() .gap(px(theme::SPACE))
.w_full() .w_full()
.h_9() .h_9()
.justify_center() .justify_center()
.bg(cx.theme().bg.opacity(0.85)) .bg(cx.theme().bg.opacity(0.85))
.when(cx.theme().shadow, |this| this.shadow_md()) .when(cx.theme().shadow, |this| this.shadow_md())
.rounded_full() .rounded_full()
.text_xs() .text_style(TextStyle::Caption)
.font_semibold() .font_semibold()
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(Indicator::new().small().color(cx.theme().accent)) .child(Indicator::new().small().color(cx.theme().accent))
+12 -12
View File
@@ -11,7 +11,7 @@ use nip29::Group;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use person::PersonRegistry; use person::PersonRegistry;
use settings::AppSettings; use settings::AppSettings;
use theme::{ActiveTheme, button_radius}; use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius};
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::dialog::DialogButtonProps; use ui::dialog::DialogButtonProps;
use ui::dock::{ClosePanel, DockArea, DockPlacement, PanelHandle}; use ui::dock::{ClosePanel, DockArea, DockPlacement, PanelHandle};
@@ -90,7 +90,7 @@ impl Sidebar {
Nav::new(label) Nav::new(label)
.label(label) .label(label)
.suffix(Icon::new(icon).small().text_color(cx.theme().text_faint)) .suffix(Icon::new(icon).small().text_color(cx.theme().text_faint))
.text_xs() .text_style(TextStyle::Caption)
.font_semibold() .font_semibold()
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.on_click(cx.listener(move |this, _event, _window, cx| { .on_click(cx.listener(move |this, _event, _window, cx| {
@@ -112,7 +112,7 @@ impl Sidebar {
Nav::new(label) Nav::new(label)
.label(label) .label(label)
.text_xs() .text_style(TextStyle::Caption)
.font_semibold() .font_semibold()
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.into_any_element() .into_any_element()
@@ -131,7 +131,7 @@ impl Sidebar {
.border_dashed() .border_dashed()
.border_color(cx.theme().border) .border_color(cx.theme().border)
.rounded(px(button_radius())) .rounded(px(button_radius()))
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.child(*text), .child(*text),
) )
@@ -149,7 +149,7 @@ impl Sidebar {
let nav = Nav::new(index) let nav = Nav::new(index)
.label(name) .label(name)
.text_sm() .text_style(TextStyle::Callout)
.when(!hide_avatar, |this| { .when(!hide_avatar, |this| {
this.prefix( this.prefix(
Avatar::from_source(picture) Avatar::from_source(picture)
@@ -161,7 +161,7 @@ impl Sidebar {
.suffix( .suffix(
div() div()
.font_normal() .font_normal()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.child(created_at), .child(created_at),
) )
@@ -204,7 +204,7 @@ impl Sidebar {
Nav::new(index) Nav::new(index)
.label(person.name()) .label(person.name())
.text_sm() .text_style(TextStyle::Callout)
.when(!hide_avatar, |this| { .when(!hide_avatar, |this| {
this.prefix( this.prefix(
Avatar::from_source(person.avatar()) Avatar::from_source(person.avatar())
@@ -230,7 +230,7 @@ impl Sidebar {
Nav::new(index) Nav::new(index)
.label(person.name()) .label(person.name())
.text_sm() .text_style(TextStyle::Callout)
.when(!hide_avatar, |this| { .when(!hide_avatar, |this| {
this.prefix( this.prefix(
Avatar::from_source(person.avatar()) Avatar::from_source(person.avatar())
@@ -265,7 +265,7 @@ impl Sidebar {
let nav = Nav::new(index) let nav = Nav::new(index)
.label(name) .label(name)
.text_sm() .text_style(TextStyle::Callout)
.when(!hide_avatar, |this| { .when(!hide_avatar, |this| {
this.prefix( this.prefix(
Avatar::from_source(picture) Avatar::from_source(picture)
@@ -278,7 +278,7 @@ impl Sidebar {
this.suffix( this.suffix(
div() div()
.font_normal() .font_normal()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.child(suffix), .child(suffix),
) )
@@ -339,7 +339,7 @@ impl Sidebar {
Nav::new(index) Nav::new(index)
.label(content) .label(content)
.text_sm() .text_style(TextStyle::Callout)
.font_medium() .font_medium()
.when(!hide_avatar, |this| { .when(!hide_avatar, |this| {
this.prefix( this.prefix(
@@ -352,7 +352,7 @@ impl Sidebar {
.suffix( .suffix(
div() div()
.font_normal() .font_normal()
.text_xs() .text_style(TextStyle::Caption)
.text_color(cx.theme().text_faint) .text_color(cx.theme().text_faint)
.child(detail), .child(detail),
) )
+1 -6
View File
@@ -203,12 +203,7 @@ impl Sidebar {
.w_full() .w_full()
.rounded(px(button_radius())) .rounded(px(button_radius()))
.bg(cx.theme().surface_raised) .bg(cx.theme().surface_raised)
.child( .child(Input::new(&self.find_input).appearance(false).small())
Input::new(&self.find_input)
.appearance(false)
.small()
.text_xs(),
)
.child( .child(
Button::new("sidebar-global") Button::new("sidebar-global")
.icon(IconName::Compass) .icon(IconName::Compass)