From e4ebe960779e925855bdd41f0f49601cdbf0234d Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Fri, 23 Oct 2026 11:06:34 +0700 Subject: [PATCH] update --- crates/nip29_ui/src/metadata.rs | 122 ++++++++++------------ crates/ui/src/avatar.rs | 5 + crates/ui/src/form.rs | 28 +++++ crates/ui/src/lib.rs | 2 + crates/workspace/src/dialogs/new_chat.rs | 2 +- crates/workspace/src/dialogs/new_group.rs | 122 +++++++++++----------- crates/workspace/src/dialogs/settings.rs | 78 +++++++------- crates/workspace/src/dock/tab_panel.rs | 2 +- crates/workspace/src/sidebar/header.rs | 18 +--- desktop/src/main.rs | 3 + 10 files changed, 200 insertions(+), 182 deletions(-) create mode 100644 crates/ui/src/form.rs diff --git a/crates/nip29_ui/src/metadata.rs b/crates/nip29_ui/src/metadata.rs index d6f42f47..38b97438 100644 --- a/crates/nip29_ui/src/metadata.rs +++ b/crates/nip29_ui/src/metadata.rs @@ -8,13 +8,13 @@ use gpui::{ }; use gpui_kit::component::button::{Button, ButtonVariants}; use gpui_kit::component::dialog::DialogButtonProps; -use gpui_kit::component::form::field as form_field; use gpui_kit::component::input::InputState; use gpui_kit::component::notification::Notification; use gpui_kit::component::switch::Switch; use gpui_kit::component::{ActiveTheme, Sizable, WindowExt, h_flex, v_flex}; use nip29::{Group, GroupId, GroupMetadata}; use nostr_sdk::prelude::RelayUrl; +use ui::settings_row; use crate::util::field; @@ -126,73 +126,61 @@ pub(crate) fn open(group: WeakEntity, window: &mut Window, cx: &mut App) .child( v_flex() .gap_2() - .child( - form_field() - .label("Private") - .description("Only members can read group messages") - .child( - Switch::new("private") - .checked(current.private) - .on_click(toggle(&flags, |flags| &mut flags.private)), - ), - ) - .child( - form_field() - .label("Restricted") - .description("Only members can post messages") - .child( - Switch::new("restricted") - .checked(current.restricted) - .on_click(toggle(&flags, |flags| { - &mut flags.restricted - })), - ), - ) - .child( - form_field() - .label("Hidden") - .description("Hidden from non-members, not discoverable") - .child( - Switch::new("hidden") - .checked(current.hidden) - .on_click(toggle(&flags, |flags| &mut flags.hidden)), - ), - ) - .child( - form_field() - .label("Closed") - .description("Join requests are ignored (invite-only)") - .child( - Switch::new("closed") - .checked(current.closed) - .on_click(toggle(&flags, |flags| &mut flags.closed)), - ), - ) - .child( - form_field() - .label("LiveKit") - .description(if supported { - "Announce audio and video support." - } else { - "The relay doesn't announce LiveKit support." - }) - .child( - Switch::new("livekit").checked(current.livekit).on_click({ - let flags = flags.clone(); - let livekit = livekit.clone(); + .child(settings_row( + "Private", + "Only members can read group messages", + Switch::new("private") + .checked(current.private) + .on_click(toggle(&flags, |flags| &mut flags.private)), + cx, + )) + .child(settings_row( + "Restricted", + "Only members can post messages", + Switch::new("restricted") + .checked(current.restricted) + .on_click(toggle(&flags, |flags| &mut flags.restricted)), + cx, + )) + .child(settings_row( + "Hidden", + "Hidden from non-members, not discoverable", + Switch::new("hidden") + .checked(current.hidden) + .on_click(toggle(&flags, |flags| &mut flags.hidden)), + cx, + )) + .child(settings_row( + "Closed", + "Join requests are ignored (invite-only)", + Switch::new("closed") + .checked(current.closed) + .on_click(toggle(&flags, |flags| &mut flags.closed)), + cx, + )) + .child(settings_row( + "LiveKit", + if supported { + "Announce audio and video support." + } else { + "The relay doesn't announce LiveKit support." + }, + Switch::new("livekit").checked(current.livekit).on_click({ + let flags = flags.clone(); + let livekit = livekit.clone(); - move |next, _window, cx| { - if *next && *livekit.read(cx) == Some(false) { - return; - } - flags.update(cx, |flags, cx| { - flags.livekit = *next; - cx.notify(); - }); - } - }), - ), - ), + move |next, _window, cx| { + if *next && *livekit.read(cx) == Some(false) { + return; + } + flags.update(cx, |flags, cx| { + flags.livekit = *next; + cx.notify(); + }); + } + }), + cx, + )), ), ) .on_ok({ diff --git a/crates/ui/src/avatar.rs b/crates/ui/src/avatar.rs index 7f33a63a..33d9b4fd 100644 --- a/crates/ui/src/avatar.rs +++ b/crates/ui/src/avatar.rs @@ -167,11 +167,16 @@ impl RenderOnce for PixelAvatar { } } + // Pad the grid so the art stays clear of the circular edge, keeping the + // inset look of signed's rounded-square frame. + let inset = self.side / (GRID_SIZE as f32 * 2.); + div() .grid() .grid_cols(GRID_SIZE as u16) .grid_rows(GRID_SIZE as u16) .size(self.side) + .p(inset) .flex_shrink_0() .rounded_full() .overflow_hidden() diff --git a/crates/ui/src/form.rs b/crates/ui/src/form.rs new file mode 100644 index 00000000..89ac383a --- /dev/null +++ b/crates/ui/src/form.rs @@ -0,0 +1,28 @@ +use gpui::{App, Div, IntoElement, ParentElement, SharedString, Styled, div}; +use gpui_kit::component::{ActiveTheme, h_flex, v_flex}; + +/// A settings row: the label and description on the left, the control on the right. +pub fn settings_row( + label: impl Into, + description: impl Into, + control: impl IntoElement, + cx: &App, +) -> Div { + h_flex() + .gap_3() + .items_center() + .justify_between() + .child( + v_flex() + .flex_1() + .min_w_0() + .child(div().text_sm().child(label.into())) + .child( + div() + .text_xs() + .text_color(cx.theme().muted_foreground) + .child(description.into()), + ), + ) + .child(control) +} diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index 1469086a..acec50ca 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -1,10 +1,12 @@ pub use avatar::Avatar; +pub use form::*; pub use icon::IconName; pub use message::*; pub use text::*; pub use window_drag::title_bar_drag_handlers; pub mod avatar; +pub mod form; pub mod icon; pub mod message; pub mod text; diff --git a/crates/workspace/src/dialogs/new_chat.rs b/crates/workspace/src/dialogs/new_chat.rs index e24a0b70..e46bbf52 100644 --- a/crates/workspace/src/dialogs/new_chat.rs +++ b/crates/workspace/src/dialogs/new_chat.rs @@ -338,7 +338,7 @@ impl NewChat { Nav::new(start + offset) .label(profile.name()) - .text_xs() + .text_sm() .when(!AppSettings::get_hide_avatar(cx), |this| { this.prefix( Avatar::new(profile.avatar()) diff --git a/crates/workspace/src/dialogs/new_group.rs b/crates/workspace/src/dialogs/new_group.rs index ef65175c..06fb8a93 100644 --- a/crates/workspace/src/dialogs/new_group.rs +++ b/crates/workspace/src/dialogs/new_group.rs @@ -3,14 +3,14 @@ use gpui::prelude::FluentBuilder; use gpui::{ App, AppContext, Context, Div, Entity, FocusHandle, InteractiveElement as _, IntoElement, ParentElement, PathPromptOptions, Render, SharedString, StatefulInteractiveElement as _, - Styled, Subscription, Task, WeakEntity, Window, div, px, rems, + StyleRefinement, Styled, Subscription, Task, WeakEntity, Window, div, px, rems, }; use gpui_base::{Popup, Select}; use gpui_kit::assets::IconName; use gpui_kit::base::StyledExt as _; use gpui_kit::component::button::{Button, ButtonVariants}; use gpui_kit::component::dock::{DockArea, DockPlacement, PanelHandle}; -use gpui_kit::component::form::field; +use gpui_kit::component::group_box::{GroupBox, GroupBoxVariants}; use gpui_kit::component::input::{Input, InputEvent, InputState, Textarea, TextareaState}; use gpui_kit::component::separator::Separator; use gpui_kit::component::switch::Switch; @@ -19,14 +19,14 @@ use nip29::{GroupCreateOptions, GroupKey, GroupsEvent, GroupsRegistry}; use nostr_sdk::prelude::*; use settings::AppSettings; use state::upload; -use ui::Avatar; +use ui::{Avatar, settings_row}; const DEFAULT_RELAYS: [&str; 2] = ["wss://relay.ditto.net", "wss://chat.wisp.talk"]; const PRIVATE_DESC: &str = "Only members can read group messages"; const CLOSED_DESC: &str = "Join requests are ignored (invite-only)"; const RESTRICTED_DESC: &str = "Only members can post messages"; const HIDDEN_DESC: &str = "Hidden from non-members, not discoverable"; -const LIST_PRIVATE_DESC: &str = "Kept encrypted in your group list so others can't see you joined"; +const LIST_PRIVATE_DESC: &str = "Kept encrypted in your group list so others can't see"; pub fn open(dock: WeakEntity, window: &mut Window, cx: &mut App) { open_with(dock, None, window, cx); @@ -462,71 +462,72 @@ impl NewGroup { } fn render_access(&self, cx: &mut Context) -> impl IntoElement { - let private = field().label("Private").description(PRIVATE_DESC).child( - Switch::new("private") - .checked(self.private) - .on_click(cx.listener(|this, checked: &bool, _window, cx| { - this.private = *checked; - cx.notify(); - })), - ); - - let closed = field().label("Closed").description(CLOSED_DESC).child( - Switch::new("closed") - .checked(self.closed) - .on_click(cx.listener(|this, checked: &bool, _window, cx| { - this.closed = *checked; - cx.notify(); - })), - ); - - let restricted = field() - .label("Restricted") - .description(RESTRICTED_DESC) - .child( + GroupBox::new() + .fill() + .title("Access settings") + .title_style(group_box_title_style(cx)) + .child(settings_row( + "Private", + PRIVATE_DESC, + Switch::new("private") + .checked(self.private) + .on_click(cx.listener(|this, checked: &bool, _window, cx| { + this.private = *checked; + cx.notify(); + })), + cx, + )) + .child(settings_row( + "Closed", + CLOSED_DESC, + Switch::new("closed") + .checked(self.closed) + .on_click(cx.listener(|this, checked: &bool, _window, cx| { + this.closed = *checked; + cx.notify(); + })), + cx, + )) + .child(settings_row( + "Restricted", + RESTRICTED_DESC, Switch::new("restricted") .checked(self.restricted) .on_click(cx.listener(|this, checked: &bool, _window, cx| { this.restricted = *checked; cx.notify(); })), - ); - - let hidden = field().label("Hidden").description(HIDDEN_DESC).child( - Switch::new("hidden") - .checked(self.hidden) - .on_click(cx.listener(|this, checked: &bool, _window, cx| { - this.hidden = *checked; - cx.notify(); - })), - ); - - v_flex() - .gap_2() - .child(field_label("Access settings", cx)) - .child(switch_row(private, cx)) - .child(switch_row(closed, cx)) - .child(switch_row(restricted, cx)) - .child(switch_row(hidden, cx)) + cx, + )) + .child(settings_row( + "Hidden", + HIDDEN_DESC, + Switch::new("hidden") + .checked(self.hidden) + .on_click(cx.listener(|this, checked: &bool, _window, cx| { + this.hidden = *checked; + cx.notify(); + })), + cx, + )) } fn render_list_setting(&self, cx: &mut Context) -> impl IntoElement { - let list_private = field() - .label("Private on my list") - .description(LIST_PRIVATE_DESC) - .child( + GroupBox::new() + .fill() + .title("List") + .title_style(group_box_title_style(cx)) + .child(settings_row( + "Private on my list", + LIST_PRIVATE_DESC, Switch::new("list-private") .checked(self.list_private) .on_click(cx.listener(|this, checked: &bool, _window, cx| { this.list_private = *checked; cx.notify(); })), - ); - - v_flex() - .gap_2() - .child(field_label("List", cx)) - .child(switch_row(list_private, cx)) + cx, + )) } fn render_error( @@ -662,13 +663,12 @@ fn field_label(text: &'static str, cx: &App) -> Div { .child(text) } -fn switch_row(switch: impl IntoElement, cx: &App) -> Div { - div() - .w_full() - .p_2() - .rounded(cx.theme().radius) - .bg(cx.theme().secondary) - .child(switch) +/// Match [`field_label`]'s design for GroupBox titles. +fn group_box_title_style(cx: &App) -> StyleRefinement { + StyleRefinement::default() + .text_xs() + .text_color(cx.theme().muted_foreground) + .font_semibold() } fn relay_label(relay: &RelayUrl) -> SharedString { diff --git a/crates/workspace/src/dialogs/settings.rs b/crates/workspace/src/dialogs/settings.rs index a3bc831c..c5578263 100644 --- a/crates/workspace/src/dialogs/settings.rs +++ b/crates/workspace/src/dialogs/settings.rs @@ -6,15 +6,14 @@ use gpui::{ use gpui_kit::assets::IconName; use gpui_kit::base::StyledExt as _; use gpui_kit::component::button::{Button, ButtonVariants}; -use gpui_kit::component::form::field; use gpui_kit::component::group_box::{GroupBox, GroupBoxVariants}; use gpui_kit::component::input::{Input, InputState}; use gpui_kit::component::menu::{DropdownMenu, PopupMenuItem}; use gpui_kit::component::notification::Notification; use gpui_kit::component::switch::Switch; use gpui_kit::component::{ActiveTheme, Disableable, Sizable, WindowExt, h_flex, v_flex}; -use settings::AppSettings; -use settings::AppearanceMode; +use settings::{AppSettings, AppearanceMode}; +use ui::settings_row; pub fn init(window: &mut Window, cx: &mut App) -> Entity { cx.new(|cx| Preferences::new(window, cx)) @@ -114,28 +113,35 @@ impl Render for Preferences { .id("general") .title("General") .fill() - .child(field().label("Screening").description(SCREENING).child( + .child(settings_row( + "Screening", + SCREENING, Switch::new("screening").checked(screening).on_click( move |_, _window, cx| { AppSettings::update_screening(!screening, cx); }, ), + cx, )) - .child( - field().label("Web of Trust").description(WOT).child( - Switch::new("wot-auto-trust") - .checked(wot_auto_trust) - .on_click(move |_, _window, cx| { - AppSettings::update_wot_auto_trust(!wot_auto_trust, cx); - }), - ), - ) - .child(field().label("Hide user avatar").description(AVATAR).child( + .child(settings_row( + "Web of Trust", + WOT, + Switch::new("wot-auto-trust") + .checked(wot_auto_trust) + .on_click(move |_, _window, cx| { + AppSettings::update_wot_auto_trust(!wot_auto_trust, cx); + }), + cx, + )) + .child(settings_row( + "Hide user avatar", + AVATAR, Switch::new("avatar").checked(hide_avatar).on_click( move |_, _window, cx| { AppSettings::update_hide_avatar(!hide_avatar, cx); }, ), + cx, )), ) .child( @@ -179,19 +185,17 @@ impl Render for Preferences { .id("background") .title("Background") .fill() - .child( - field() - .label("Panel background") - .description(BACKGROUND) - .child( - Switch::new("panel-background") - .checked(panel_background) - .on_click(move |_, _window, cx| { - AppSettings::update_panel_background(!panel_background, cx); - cx.refresh_windows(); - }), - ), - ) + .child(settings_row( + "Panel background", + BACKGROUND, + Switch::new("panel-background") + .checked(panel_background) + .on_click(move |_, _window, cx| { + AppSettings::update_panel_background(!panel_background, cx); + cx.refresh_windows(); + }), + cx, + )) .child( h_flex() .gap_3() @@ -245,16 +249,16 @@ impl Render for Preferences { .id("experiments") .title("Experiments") .fill() - .child( - field() - .label("Decoupling Encryption Key") - .description(NIP4E) - .child(Switch::new("nip4e").checked(nip4e).on_click( - move |_, _window, cx| { - AppSettings::update_nip4e(!nip4e, cx); - }, - )), - ), + .child(settings_row( + "Decoupling Encryption Key", + NIP4E, + Switch::new("nip4e") + .checked(nip4e) + .on_click(move |_, _window, cx| { + AppSettings::update_nip4e(!nip4e, cx); + }), + cx, + )), ) .child( GroupBox::new() diff --git a/crates/workspace/src/dock/tab_panel.rs b/crates/workspace/src/dock/tab_panel.rs index a086c4dd..678b8d63 100644 --- a/crates/workspace/src/dock/tab_panel.rs +++ b/crates/workspace/src/dock/tab_panel.rs @@ -358,7 +358,7 @@ impl CoopTabGroupSkin { this } else { this.text_color(cx.theme().tab_foreground) - .bg(cx.theme().tab) + .bg(cx.theme().list_hover) } }) .selected(active) diff --git a/crates/workspace/src/sidebar/header.rs b/crates/workspace/src/sidebar/header.rs index c6be5ca7..fc0ca359 100644 --- a/crates/workspace/src/sidebar/header.rs +++ b/crates/workspace/src/sidebar/header.rs @@ -87,22 +87,10 @@ impl Sidebar { .small() .dropdown_menu(|menu, _window, _cx| { menu.label("Public Group") - .menu_with_icon( - "New Group", - IconName::Group, - Box::new(Command::NewGroup), - ) - .menu_with_icon( - "Join Group", - IconName::Door, - Box::new(Command::JoinGroup), - ) + .menu("New Group", Box::new(Command::NewGroup)) + .menu("Join Group", Box::new(Command::JoinGroup)) .separator() - .menu_with_icon( - "New Chat", - IconName::Message, - Box::new(Command::NewChat), - ) + .menu("New Chat", Box::new(Command::NewChat)) }), ) .when(self.group_open, |this| { diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 51dd851f..7c37c5b8 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -193,6 +193,9 @@ fn apply_appearance(appearance: AppearanceMode, cx: &mut App) { AppearanceMode::Light => Theme::change(ThemeMode::Light, None, cx), AppearanceMode::Dark => Theme::change(ThemeMode::Dark, None, cx), } + + // Like signed, inputs show only their tinted border when focused. + Theme::global_mut(cx).focus_ring = false; } /// Holds the watcher subscription so it lives as long as the app.