diff --git a/crates/app/src/views/app.rs b/crates/app/src/views/app.rs index e4af6bc..db1ef89 100644 --- a/crates/app/src/views/app.rs +++ b/crates/app/src/views/app.rs @@ -16,9 +16,7 @@ use ui::{ Icon, IconName, Root, Sizable, TitleBar, }; -use super::{ - chat, contacts, onboarding, profile, settings, sidebar::Sidebar, welcome::WelcomePanel, -}; +use super::{chat, contacts, onboarding, profile, settings, sidebar, welcome}; #[derive(Clone, PartialEq, Eq, Deserialize)] pub enum PanelKind { @@ -65,11 +63,11 @@ impl AppView { pub fn new(account: NostrProfile, window: &mut Window, cx: &mut Context<'_, Self>) -> AppView { let dock = cx.new(|cx| DockArea::new(DOCK_AREA.id, Some(DOCK_AREA.version), window, cx)); let weak_dock = dock.downgrade(); - let left_panel = DockItem::panel(Arc::new(Sidebar::new(window, cx))); + let left_panel = DockItem::panel(Arc::new(sidebar::init(window, cx))); let center_panel = DockItem::split_with_sizes( Axis::Vertical, vec![DockItem::tabs( - vec![Arc::new(WelcomePanel::new(window, cx))], + vec![Arc::new(welcome::init(window, cx))], None, &weak_dock, window, diff --git a/crates/app/src/views/chat/mod.rs b/crates/app/src/views/chat/mod.rs index f7645a6..88547aa 100644 --- a/crates/app/src/views/chat/mod.rs +++ b/crates/app/src/views/chat/mod.rs @@ -5,10 +5,10 @@ use common::{ utils::{compare, message_time, nip96_upload}, }; use gpui::{ - div, img, list, px, white, AnyElement, App, AppContext, Context, Entity, EventEmitter, Flatten, - FocusHandle, Focusable, InteractiveElement, IntoElement, ListAlignment, ListState, ObjectFit, - ParentElement, PathPromptOptions, Pixels, Render, SharedString, StatefulInteractiveElement, - Styled, StyledImage, WeakEntity, Window, + div, img, list, prelude::FluentBuilder, px, white, AnyElement, App, AppContext, Context, + Entity, EventEmitter, Flatten, FocusHandle, Focusable, InteractiveElement, IntoElement, + ListAlignment, ListState, ObjectFit, ParentElement, PathPromptOptions, Pixels, Render, + SharedString, StatefulInteractiveElement, Styled, StyledImage, WeakEntity, Window, }; use itertools::Itertools; use message::Message; @@ -21,7 +21,6 @@ use ui::{ dock_area::panel::{Panel, PanelEvent}, input::{InputEvent, TextInput}, popup_menu::PopupMenu, - prelude::FluentBuilder, theme::{scale::ColorScaleStep, ActiveTheme}, v_flex, ContextModal, Icon, IconName, Sizable, }; diff --git a/crates/app/src/views/contacts/mod.rs b/crates/app/src/views/contacts.rs similarity index 95% rename from crates/app/src/views/contacts/mod.rs rename to crates/app/src/views/contacts.rs index 05bb18b..39e4a66 100644 --- a/crates/app/src/views/contacts/mod.rs +++ b/crates/app/src/views/contacts.rs @@ -1,8 +1,8 @@ use common::profile::NostrProfile; use gpui::{ - div, img, px, uniform_list, AnyElement, App, AppContext, Context, Entity, EventEmitter, - FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render, SharedString, - Styled, Window, + div, img, prelude::FluentBuilder, px, uniform_list, AnyElement, App, AppContext, Context, + Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, + Render, SharedString, Styled, Window, }; use nostr_sdk::prelude::*; use state::get_client; @@ -12,7 +12,6 @@ use ui::{ dock_area::panel::{Panel, PanelEvent}, indicator::Indicator, popup_menu::PopupMenu, - prelude::FluentBuilder, theme::{scale::ColorScaleStep, ActiveTheme}, Sizable, }; diff --git a/crates/app/src/views/onboarding/mod.rs b/crates/app/src/views/onboarding.rs similarity index 98% rename from crates/app/src/views/onboarding/mod.rs rename to crates/app/src/views/onboarding.rs index 667cc5d..c786bf4 100644 --- a/crates/app/src/views/onboarding/mod.rs +++ b/crates/app/src/views/onboarding.rs @@ -1,8 +1,8 @@ use app_state::registry::AppRegistry; use common::profile::NostrProfile; use gpui::{ - div, relative, svg, App, AppContext, BorrowAppContext, Context, Entity, IntoElement, - ParentElement, Render, Styled, Window, + div, prelude::FluentBuilder, relative, svg, App, AppContext, BorrowAppContext, Context, Entity, + IntoElement, ParentElement, Render, Styled, Window, }; use nostr_connect::prelude::*; use state::get_client; @@ -12,7 +12,6 @@ use ui::{ button::{Button, ButtonVariants}, input::{InputEvent, TextInput}, notification::NotificationType, - prelude::FluentBuilder, theme::{scale::ColorScaleStep, ActiveTheme}, ContextModal, Root, Size, StyledExt, }; diff --git a/crates/app/src/views/profile/mod.rs b/crates/app/src/views/profile.rs similarity index 100% rename from crates/app/src/views/profile/mod.rs rename to crates/app/src/views/profile.rs diff --git a/crates/app/src/views/settings/mod.rs b/crates/app/src/views/settings.rs similarity index 100% rename from crates/app/src/views/settings/mod.rs rename to crates/app/src/views/settings.rs diff --git a/crates/app/src/views/sidebar/compose.rs b/crates/app/src/views/sidebar/compose.rs index dd929e4..ec1611f 100644 --- a/crates/app/src/views/sidebar/compose.rs +++ b/crates/app/src/views/sidebar/compose.rs @@ -5,9 +5,9 @@ use common::{ utils::{random_name, room_hash}, }; use gpui::{ - div, img, impl_internal_actions, px, uniform_list, App, AppContext, Context, Entity, - FocusHandle, InteractiveElement, IntoElement, ParentElement, Render, SharedString, - StatefulInteractiveElement, Styled, Window, + div, img, impl_internal_actions, prelude::FluentBuilder, px, uniform_list, App, AppContext, + Context, Entity, FocusHandle, InteractiveElement, IntoElement, ParentElement, Render, + SharedString, StatefulInteractiveElement, Styled, Window, }; use nostr_sdk::prelude::*; use serde::Deserialize; @@ -18,7 +18,6 @@ use ui::{ button::{Button, ButtonRounded}, indicator::Indicator, input::{InputEvent, TextInput}, - prelude::FluentBuilder, theme::{scale::ColorScaleStep, ActiveTheme}, Icon, IconName, Sizable, Size, StyledExt, }; diff --git a/crates/app/src/views/sidebar/mod.rs b/crates/app/src/views/sidebar/mod.rs index 8faa628..f7ed93f 100644 --- a/crates/app/src/views/sidebar/mod.rs +++ b/crates/app/src/views/sidebar/mod.rs @@ -18,6 +18,10 @@ use ui::{ mod compose; mod inbox; +pub fn init(window: &mut Window, cx: &mut App) -> Entity { + Sidebar::new(window, cx) +} + pub struct Sidebar { // Panel name: SharedString, diff --git a/crates/app/src/views/welcome.rs b/crates/app/src/views/welcome.rs index 0b0b448..2588cb8 100644 --- a/crates/app/src/views/welcome.rs +++ b/crates/app/src/views/welcome.rs @@ -10,14 +10,18 @@ use ui::{ StyledExt, }; -pub struct WelcomePanel { +pub fn init(window: &mut Window, cx: &mut App) -> Entity { + Welcome::new(window, cx) +} + +pub struct Welcome { name: SharedString, closable: bool, zoomable: bool, focus_handle: FocusHandle, } -impl WelcomePanel { +impl Welcome { pub fn new(window: &mut Window, cx: &mut App) -> Entity { cx.new(|cx| Self::view(window, cx)) } @@ -32,7 +36,7 @@ impl WelcomePanel { } } -impl Panel for WelcomePanel { +impl Panel for Welcome { fn panel_id(&self) -> SharedString { "WelcomePanel".into() } @@ -58,15 +62,15 @@ impl Panel for WelcomePanel { } } -impl EventEmitter for WelcomePanel {} +impl EventEmitter for Welcome {} -impl Focusable for WelcomePanel { +impl Focusable for Welcome { fn focus_handle(&self, _: &App) -> gpui::FocusHandle { self.focus_handle.clone() } } -impl Render for WelcomePanel { +impl Render for Welcome { fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context) -> impl IntoElement { div() .size_full() diff --git a/crates/ui/src/dropdown.rs b/crates/ui/src/dropdown.rs index 7164b96..a12de09 100644 --- a/crates/ui/src/dropdown.rs +++ b/crates/ui/src/dropdown.rs @@ -2,7 +2,8 @@ use gpui::{ actions, anchored, canvas, deferred, div, prelude::FluentBuilder, px, rems, AnyElement, App, AppContext, Bounds, ClickEvent, Context, DismissEvent, ElementId, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, Length, ParentElement, - Pixels, Render, SharedString, StatefulInteractiveElement, Styled, Task, WeakEntity, Window, + Pixels, Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, + WeakEntity, Window, }; use crate::{ @@ -245,6 +246,8 @@ pub struct Dropdown { /// Store the bounds of the input bounds: Bounds, disabled: bool, + #[allow(dead_code)] + subscriptions: Vec, } pub struct SearchableVec { @@ -255,6 +258,7 @@ pub struct SearchableVec { impl SearchableVec { pub fn new(items: impl Into>) -> Self { let items = items.into(); + Self { items: items.clone(), matched_items: items, @@ -345,10 +349,10 @@ where list }); - cx.on_blur(&list.focus_handle(cx), window, Self::on_blur) - .detach(); - - cx.on_blur(&focus_handle, window, Self::on_blur).detach(); + let subscriptions = vec![ + cx.on_blur(&list.focus_handle(cx), window, Self::on_blur), + cx.on_blur(&focus_handle, window, Self::on_blur), + ]; let mut this = Self { id: id.into(), @@ -365,6 +369,7 @@ where menu_width: Length::Auto, bounds: Bounds::default(), disabled: false, + subscriptions, }; this.set_selected_index(selected_index, window, cx); this diff --git a/crates/ui/src/input/input.rs b/crates/ui/src/input/input.rs index ac824ff..1a55304 100644 --- a/crates/ui/src/input/input.rs +++ b/crates/ui/src/input/input.rs @@ -3,7 +3,7 @@ use gpui::{ ClipboardItem, Context, Entity, EntityInputHandler, EventEmitter, FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Rems, Render, ScrollHandle, - ScrollWheelEvent, SharedString, Styled as _, UTF16Selection, Window, WrappedLine, + ScrollWheelEvent, SharedString, Styled as _, Subscription, UTF16Selection, Window, WrappedLine, }; use smallvec::SmallVec; use std::{cell::Cell, ops::Range, rc::Rc}; @@ -213,6 +213,8 @@ pub struct TextInput { pub(crate) scroll_size: gpui::Size, /// To remember the horizontal column (x-coordinate) of the cursor position. preferred_x_offset: Option, + #[allow(dead_code)] + subscriptions: Vec, } impl EventEmitter for TextInput {} @@ -222,7 +224,25 @@ impl TextInput { let focus_handle = cx.focus_handle(); let blink_cursor = cx.new(|_| BlinkCursor::new()); let history = History::new().group_interval(std::time::Duration::from_secs(1)); - let input = Self { + let subscriptions = vec![ + // Observe the blink cursor to repaint the view when it changes. + cx.observe(&blink_cursor, |_, _, cx| cx.notify()), + // Blink the cursor when the window is active, pause when it's not. + cx.observe_window_activation(window, |input, window, cx| { + if window.is_window_active() { + let focus_handle = input.focus_handle.clone(); + if focus_handle.is_focused(window) { + input.blink_cursor.update(cx, |blink_cursor, cx| { + blink_cursor.start(cx); + }); + } + } + }), + cx.on_focus(&focus_handle, window, Self::on_focus), + cx.on_blur(&focus_handle, window, Self::on_blur), + ]; + + Self { focus_handle: focus_handle.clone(), text: "".into(), multi_line: false, @@ -256,29 +276,8 @@ impl TextInput { scrollbar_state: Rc::new(Cell::new(ScrollbarState::default())), scroll_size: gpui::size(px(0.), px(0.)), preferred_x_offset: None, - }; - - // Observe the blink cursor to repaint the view when it changes. - cx.observe(&input.blink_cursor, |_, _, cx| cx.notify()) - .detach(); - - // Blink the cursor when the window is active, pause when it's not. - cx.observe_window_activation(window, |input, window, cx| { - if window.is_window_active() { - let focus_handle = input.focus_handle.clone(); - if focus_handle.is_focused(window) { - input.blink_cursor.update(cx, |blink_cursor, cx| { - blink_cursor.start(cx); - }); - } - } - }) - .detach(); - - cx.on_focus(&focus_handle, window, Self::on_focus).detach(); - cx.on_blur(&focus_handle, window, Self::on_blur).detach(); - - input + subscriptions, + } } /// Use the text input field as a multi-line Textarea. diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index 9769dab..b928617 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -15,7 +15,6 @@ pub mod modal; pub mod notification; pub mod popover; pub mod popup_menu; -pub mod prelude; pub mod progress; pub mod radio; pub mod resizable; diff --git a/crates/ui/src/list/list.rs b/crates/ui/src/list/list.rs index 5e3987c..593c6c4 100644 --- a/crates/ui/src/list/list.rs +++ b/crates/ui/src/list/list.rs @@ -8,7 +8,7 @@ use gpui::{ actions, div, prelude::FluentBuilder, px, uniform_list, AnyElement, App, AppContext, Context, Entity, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, Length, ListSizingBehavior, MouseButton, ParentElement, Render, ScrollStrategy, SharedString, Styled, - Task, UniformListScrollHandle, Window, + Subscription, Task, UniformListScrollHandle, Window, }; use smol::Timer; use std::{cell::Cell, rc::Rc, time::Duration}; @@ -111,6 +111,7 @@ pub struct List { selected_index: Option, right_clicked_index: Option, _search_task: Task<()>, + query_input_subscription: Subscription, } impl List @@ -129,8 +130,8 @@ where .cleanable() }); - cx.subscribe_in(&query_input, window, Self::on_query_input_event) - .detach(); + let query_input_subscription = + cx.subscribe_in(&query_input, window, Self::on_query_input_event); Self { focus_handle: cx.focus_handle(), @@ -146,6 +147,7 @@ where loading: false, size: Size::default(), _search_task: Task::ready(()), + query_input_subscription, } } @@ -180,8 +182,8 @@ where window: &mut Window, cx: &mut Context, ) { - cx.subscribe_in(&query_input, window, Self::on_query_input_event) - .detach(); + self.query_input_subscription = + cx.subscribe_in(&query_input, window, Self::on_query_input_event); self.query_input = Some(query_input); } diff --git a/crates/ui/src/notification.rs b/crates/ui/src/notification.rs index 6039e96..488f5b7 100644 --- a/crates/ui/src/notification.rs +++ b/crates/ui/src/notification.rs @@ -12,10 +12,16 @@ use crate::{ use gpui::{ div, prelude::FluentBuilder, px, Animation, AnimationExt, App, AppContext, ClickEvent, Context, DismissEvent, ElementId, Entity, EventEmitter, InteractiveElement as _, IntoElement, - ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, Window, + ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, Subscription, + Window, }; use smol::Timer; -use std::{any::TypeId, collections::VecDeque, sync::Arc, time::Duration}; +use std::{ + any::TypeId, + collections::{HashMap, VecDeque}, + sync::Arc, + time::Duration, +}; pub enum NotificationType { Info, @@ -24,7 +30,7 @@ pub enum NotificationType { Error, } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Hash, Eq)] pub(crate) enum NotificationId { Id(TypeId), IdAndElementId(TypeId, ElementId), @@ -294,6 +300,7 @@ pub struct NotificationList { /// Notifications that will be auto hidden. pub(crate) notifications: VecDeque>, expanded: bool, + subscriptions: HashMap, } impl NotificationList { @@ -301,6 +308,7 @@ impl NotificationList { Self { notifications: VecDeque::new(), expanded: false, + subscriptions: HashMap::new(), } } @@ -319,10 +327,13 @@ impl NotificationList { let notification = cx.new(|_| notification); - cx.subscribe(¬ification, move |view, _, _: &DismissEvent, cx| { - view.notifications.retain(|note| id != note.read(cx).id); - }) - .detach(); + self.subscriptions.insert( + id.clone(), + cx.subscribe(¬ification, move |view, _, _: &DismissEvent, cx| { + view.notifications.retain(|note| id != note.read(cx).id); + view.subscriptions.remove(&id); + }), + ); self.notifications.push_back(notification.clone()); if autohide { diff --git a/crates/ui/src/popup_menu.rs b/crates/ui/src/popup_menu.rs index 1094b53..048ea0b 100644 --- a/crates/ui/src/popup_menu.rs +++ b/crates/ui/src/popup_menu.rs @@ -11,7 +11,8 @@ use gpui::{ actions, anchored, canvas, div, prelude::FluentBuilder, px, rems, Action, AnyElement, App, AppContext, Bounds, Context, Corner, DismissEvent, Edges, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, Keystroke, ParentElement, Pixels, - Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled, WeakEntity, Window, + Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled, Subscription, + WeakEntity, Window, }; use std::{cell::Cell, ops::Deref, rc::Rc}; @@ -114,7 +115,8 @@ pub struct PopupMenu { scroll_state: Rc>, action_focus_handle: Option, - _subscriptions: [gpui::Subscription; 1], + #[allow(dead_code)] + subscriptions: Vec, } impl PopupMenu { @@ -125,10 +127,12 @@ impl PopupMenu { ) -> Entity { cx.new(|cx| { let focus_handle = cx.focus_handle(); - let _on_blur_subscription = - cx.on_blur(&focus_handle, window, |this: &mut PopupMenu, window, cx| { - this.dismiss(&Dismiss, window, cx) - }); + let subscriptions = + vec![ + cx.on_blur(&focus_handle, window, |this: &mut PopupMenu, window, cx| { + this.dismiss(&Dismiss, window, cx) + }), + ]; let menu = Self { focus_handle, @@ -144,7 +148,7 @@ impl PopupMenu { scrollable: false, scroll_handle: ScrollHandle::default(), scroll_state: Rc::new(Cell::new(ScrollbarState::default())), - _subscriptions: [_on_blur_subscription], + subscriptions, }; window.refresh(); f(menu, window, cx) diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs deleted file mode 100644 index 42c687e..0000000 --- a/crates/ui/src/prelude.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub use gpui::prelude::*; -pub use gpui::{ - div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId, - InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled, Window, -};