chore: Upgrade to GPUI3 (#6)

* wip: gpui3

* wip: gpui3

* chore: fix clippy
This commit is contained in:
reya
2025-01-28 08:25:49 +07:00
committed by GitHub
parent 3c15e74e56
commit 72a6d79bc5
62 changed files with 2572 additions and 2511 deletions

View File

@@ -4,8 +4,8 @@ use common::constants::{
NEW_MESSAGE_SUB_ID,
};
use gpui::{
actions, point, px, size, App, AppContext, Bounds, SharedString, TitlebarOptions,
VisualContext, WindowBounds, WindowKind, WindowOptions,
actions, point, px, size, App, AppContext, Application, Bounds, SharedString, TitlebarOptions,
WindowBounds, WindowKind, WindowOptions,
};
#[cfg(target_os = "linux")]
use gpui::{WindowBackgroundAppearance, WindowDecorations};
@@ -67,7 +67,7 @@ async fn main() {
// Merge all requests into single subscription
tokio::spawn(async move { handle_metadata(client, mta_rx).await });
App::new()
Application::new()
.with_assets(Assets)
.with_http_client(Arc::new(reqwest_client::ReqwestClient::new()))
.run(move |cx| {
@@ -177,12 +177,11 @@ async fn main() {
..Default::default()
};
cx.open_window(opts, |cx| {
cx.set_window_title(APP_NAME);
cx.set_app_id(APP_ID);
cx.open_window(opts, |window, cx| {
window.set_window_title(APP_NAME);
window.set_app_id(APP_ID);
cx.activate(true);
cx.new_view(|cx| Root::new(cx.new_view(AppView::new).into(), cx))
cx.new(|cx| Root::new(cx.new(|cx| AppView::new(window, cx)).into(), window, cx))
})
.expect("System error");
});
@@ -301,6 +300,6 @@ async fn handle_metadata(client: &'static Client, mut mta_rx: mpsc::Receiver<Pub
});
}
fn quit(_: &Quit, cx: &mut AppContext) {
fn quit(_: &Quit, cx: &mut App) {
cx.quit();
}

View File

@@ -1,8 +1,8 @@
use super::{chat::ChatPanel, onboarding::Onboarding, sidebar::Sidebar, welcome::WelcomePanel};
use gpui::{
actions, div, img, impl_internal_actions, px, svg, Axis, BorrowAppContext, Edges,
InteractiveElement, IntoElement, ObjectFit, ParentElement, Render, Styled, StyledImage, View,
ViewContext, VisualContext, WeakView, WindowContext,
actions, div, img, impl_internal_actions, px, svg, App, AppContext, Axis, BorrowAppContext,
Context, Edges, Entity, InteractiveElement, IntoElement, ObjectFit, ParentElement, Render,
Styled, StyledImage, WeakEntity, Window,
};
use registry::{app::AppRegistry, chat::ChatRegistry, contact::Contact};
use serde::Deserialize;
@@ -45,22 +45,22 @@ pub const DOCK_AREA: DockAreaTab = DockAreaTab {
};
pub struct AppView {
onboarding: View<Onboarding>,
dock: View<DockArea>,
onboarding: Entity<Onboarding>,
dock: Entity<DockArea>,
}
impl AppView {
pub fn new(cx: &mut ViewContext<'_, Self>) -> AppView {
let onboarding = cx.new_view(Onboarding::new);
let dock = cx.new_view(|cx| DockArea::new(DOCK_AREA.id, Some(DOCK_AREA.version), cx));
pub fn new(window: &mut Window, cx: &mut Context<'_, Self>) -> AppView {
let onboarding = cx.new(|cx| Onboarding::new(window, cx));
let dock = cx.new(|cx| DockArea::new(DOCK_AREA.id, Some(DOCK_AREA.version), window, cx));
// Get current user from app state
let weak_user = cx.global::<AppRegistry>().user();
if let Some(user) = weak_user.upgrade() {
cx.observe(&user, move |view, this, cx| {
cx.observe_in(&user, window, |view, this, window, cx| {
if this.read(cx).is_some() {
Self::render_dock(view.dock.downgrade(), cx);
Self::render_dock(view.dock.downgrade(), window, cx);
}
})
.detach();
@@ -69,30 +69,33 @@ impl AppView {
AppView { onboarding, dock }
}
fn render_dock(dock_area: WeakView<DockArea>, cx: &mut WindowContext) {
let left = DockItem::panel(Arc::new(Sidebar::new(cx)));
fn render_dock(dock_area: WeakEntity<DockArea>, window: &mut Window, cx: &mut App) {
let left = DockItem::panel(Arc::new(Sidebar::new(window, cx)));
let center = DockItem::split_with_sizes(
Axis::Vertical,
vec![DockItem::tabs(
vec![Arc::new(WelcomePanel::new(cx))],
vec![Arc::new(WelcomePanel::new(window, cx))],
None,
&dock_area,
window,
cx,
)],
vec![None],
&dock_area,
window,
cx,
);
_ = dock_area.update(cx, |view, cx| {
view.set_version(DOCK_AREA.version, cx);
view.set_left_dock(left, Some(px(240.)), true, cx);
view.set_center(center, cx);
view.set_version(DOCK_AREA.version, window, cx);
view.set_left_dock(left, Some(px(240.)), true, window, cx);
view.set_center(center, window, cx);
view.set_dock_collapsible(
Edges {
left: false,
..Default::default()
},
window,
cx,
);
// TODO: support right dock?
@@ -112,7 +115,7 @@ impl AppView {
.rounded_full()
.object_fit(ObjectFit::Cover),
)
.popup_menu(move |this, _cx| {
.popup_menu(move |this, _, _cx| {
this.menu("Profile", Box::new(OpenProfile))
.menu("Contacts", Box::new(OpenContacts))
.menu("Settings", Box::new(OpenSettings))
@@ -121,40 +124,58 @@ impl AppView {
})
}
fn on_panel_action(&mut self, action: &AddPanel, cx: &mut ViewContext<Self>) {
fn on_panel_action(&mut self, action: &AddPanel, window: &mut Window, cx: &mut Context<Self>) {
match &action.panel {
PanelKind::Room(id) => {
if let Some(weak_room) = cx.global::<ChatRegistry>().room(id, cx) {
if let Some(room) = weak_room.upgrade() {
let panel = Arc::new(ChatPanel::new(room, cx));
let panel = Arc::new(ChatPanel::new(room, window, cx));
self.dock.update(cx, |dock_area, cx| {
dock_area.add_panel(panel, action.position, cx);
dock_area.add_panel(panel, action.position, window, cx);
});
} else {
cx.push_notification((
NotificationType::Error,
"System error. Cannot open this chat room.",
));
window.push_notification(
(
NotificationType::Error,
"System error. Cannot open this chat room.",
),
cx,
);
}
}
}
};
}
fn on_profile_action(&mut self, _action: &OpenProfile, cx: &mut ViewContext<Self>) {
fn on_profile_action(
&mut self,
_action: &OpenProfile,
window: &mut Window,
cx: &mut Context<Self>,
) {
// TODO
}
fn on_contacts_action(&mut self, _action: &OpenContacts, cx: &mut ViewContext<Self>) {
fn on_contacts_action(
&mut self,
_action: &OpenContacts,
window: &mut Window,
cx: &mut Context<Self>,
) {
// TODO
}
fn on_settings_action(&mut self, _action: &OpenSettings, cx: &mut ViewContext<Self>) {
fn on_settings_action(
&mut self,
_action: &OpenSettings,
window: &mut Window,
cx: &mut Context<Self>,
) {
// TODO
}
fn on_logout_action(&mut self, _action: &Logout, cx: &mut ViewContext<Self>) {
fn on_logout_action(&mut self, _action: &Logout, window: &mut Window, cx: &mut Context<Self>) {
cx.update_global::<AppRegistry, _>(|this, cx| {
this.logout(cx);
// Reset nostr client
@@ -166,9 +187,9 @@ impl AppView {
}
impl Render for AppView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let modal_layer = Root::render_modal_layer(cx);
let notification_layer = Root::render_notification_layer(cx);
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let modal_layer = Root::render_modal_layer(window, cx);
let notification_layer = Root::render_notification_layer(window, cx);
let state = cx.global::<AppRegistry>();
div()
@@ -189,7 +210,7 @@ impl Render for AppView {
.text_color(cx.theme().base.step(cx, ColorScaleStep::THREE)),
),
)
} else if let Some(contact) = state.current_user(cx) {
} else if let Some(contact) = state.current_user(window, cx) {
this.child(
TitleBar::new()
// Left side

View File

@@ -1,6 +1,6 @@
use gpui::{
div, img, px, InteractiveElement, IntoElement, ParentElement, RenderOnce, SharedString, Styled,
WindowContext,
div, img, px, App, InteractiveElement, IntoElement, ParentElement, RenderOnce, SharedString,
Styled, Window,
};
use registry::contact::Contact;
use ui::{
@@ -36,7 +36,7 @@ impl Message {
}
impl RenderOnce for Message {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
div()
.group(&self.ago)
.relative()

View File

@@ -4,10 +4,10 @@ use common::{
utils::{compare, message_time, nip96_upload},
};
use gpui::{
div, img, list, px, white, AnyElement, AppContext, Context, EventEmitter, Flatten, FocusHandle,
FocusableView, InteractiveElement, IntoElement, ListAlignment, ListState, Model, ObjectFit,
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, View, ViewContext, VisualContext, WeakModel, WeakView, WindowContext,
Styled, StyledImage, WeakEntity, Window,
};
use itertools::Itertools;
use message::Message;
@@ -18,10 +18,7 @@ use state::get_client;
use tokio::sync::oneshot;
use ui::{
button::{Button, ButtonRounded, ButtonVariants},
dock_area::{
panel::{Panel, PanelEvent},
state::PanelState,
},
dock_area::panel::{Panel, PanelEvent},
input::{InputEvent, TextInput},
popup_menu::PopupMenu,
prelude::FluentBuilder,
@@ -45,48 +42,51 @@ pub struct ChatPanel {
// Chat Room
id: SharedString,
name: SharedString,
room: Model<Room>,
state: Model<State>,
room: Entity<Room>,
state: Entity<State>,
list: ListState,
// New Message
input: View<TextInput>,
input: Entity<TextInput>,
// Media
attaches: Model<Option<Vec<Url>>>,
attaches: Entity<Option<Vec<Url>>>,
is_uploading: bool,
}
impl ChatPanel {
pub fn new(model: Model<Room>, cx: &mut WindowContext) -> View<Self> {
pub fn new(model: Entity<Room>, window: &mut Window, cx: &mut App) -> Entity<Self> {
let room = model.read(cx);
let id = room.id.to_string().into();
let name = room.title.clone().unwrap_or("Untitled".into());
cx.observe_new_views::<Self>(|this, cx| {
this.load_messages(cx);
})
.detach();
cx.new(|cx| {
cx.observe_new::<Self>(|this, window, cx| {
if let Some(window) = window {
this.load_messages(window, cx);
}
})
.detach();
cx.new_view(|cx| {
// Form
let input = cx.new_view(|cx| {
TextInput::new(cx)
let input = cx.new(|cx| {
TextInput::new(window, cx)
.appearance(false)
.text_size(ui::Size::Small)
.placeholder("Message...")
});
// List
let state = cx.new_model(|_| State {
let state = cx.new(|_| State {
count: 0,
items: vec![],
});
// Send message when user presses enter
cx.subscribe(
cx.subscribe_in(
&input,
move |this: &mut ChatPanel, view, input_event, cx| {
window,
move |this: &mut ChatPanel, view, input_event, window, cx| {
if let InputEvent::PressEnter = input_event {
this.send_message(view.downgrade(), cx);
this.send_message(view.downgrade(), window, cx);
}
},
)
@@ -100,7 +100,7 @@ impl ChatPanel {
items.len(),
ListAlignment::Bottom,
Pixels(256.),
move |idx, _cx| {
move |idx, _window, _cx| {
let item = items.get(idx).unwrap().clone();
div().child(item).into_any_element()
},
@@ -110,19 +110,19 @@ impl ChatPanel {
})
.detach();
cx.observe(&model, |this, model, cx| {
this.load_new_messages(model.downgrade(), cx);
cx.observe_in(&model, window, |this, model, window, cx| {
this.load_new_messages(model.downgrade(), window, cx);
})
.detach();
let attaches = cx.new_model(|_| None);
let attaches = cx.new(|_| None);
Self {
closeable: true,
zoomable: true,
focus_handle: cx.focus_handle(),
room: model,
list: ListState::new(0, ListAlignment::Bottom, Pixels(256.), move |_, _| {
list: ListState::new(0, ListAlignment::Bottom, Pixels(256.), move |_, _, _| {
div().into_any_element()
}),
is_uploading: false,
@@ -135,7 +135,7 @@ impl ChatPanel {
})
}
fn load_messages(&self, cx: &mut ViewContext<Self>) {
fn load_messages(&self, window: &mut Window, cx: &mut Context<Self>) {
let room = self.room.read(cx);
let members = room.members.clone();
let owner = room.owner.clone();
@@ -206,7 +206,7 @@ impl ChatPanel {
let total = items.len();
_ = async_cx.update_model(&async_state, |a, b| {
_ = async_cx.update_entity(&async_state, |a, b| {
a.items = items;
a.count = total;
b.notify();
@@ -216,7 +216,12 @@ impl ChatPanel {
.detach();
}
fn load_new_messages(&self, model: WeakModel<Room>, cx: &mut ViewContext<Self>) {
fn load_new_messages(
&self,
model: WeakEntity<Room>,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(model) = model.upgrade() {
let room = model.read(cx);
let items: Vec<Message> = room
@@ -233,7 +238,7 @@ impl ChatPanel {
})
.collect();
cx.update_model(&self.state, |model, cx| {
cx.update_entity(&self.state, |model, cx| {
let messages: Vec<Message> = items
.into_iter()
.filter_map(|new| {
@@ -252,7 +257,12 @@ impl ChatPanel {
}
}
fn send_message(&mut self, view: WeakView<TextInput>, cx: &mut ViewContext<Self>) {
fn send_message(
&mut self,
view: WeakEntity<TextInput>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let room = self.room.read(cx);
let owner = room.owner.clone();
let mut members = room.members.to_vec();
@@ -262,7 +272,7 @@ impl ChatPanel {
let mut content = self.input.read(cx).text().to_string();
if content.is_empty() {
cx.push_notification("Message cannot be empty");
window.push_notification("Message cannot be empty", cx);
return;
}
@@ -279,12 +289,13 @@ impl ChatPanel {
// Update input state
if let Some(input) = view.upgrade() {
cx.update_view(&input, |input, cx| {
input.set_loading(true, cx);
input.set_disabled(true, cx);
cx.update_entity(&input, |input, cx| {
input.set_loading(true, window, cx);
input.set_disabled(true, window, cx);
});
}
/*
cx.spawn(|this, mut async_cx| async move {
// Send message to all members
async_cx
@@ -315,8 +326,8 @@ impl ChatPanel {
.detach();
if let Some(view) = this.upgrade() {
_ = async_cx.update_view(&view, |this, cx| {
cx.update_model(&this.state, |model, cx| {
_ = async_cx.update_entity(&view, |this, cx| {
cx.update_entity(&this.state, |model, cx| {
let message = Message::new(
owner,
content.to_string().into(),
@@ -331,17 +342,18 @@ impl ChatPanel {
}
if let Some(input) = view.upgrade() {
_ = async_cx.update_view(&input, |input, cx| {
input.set_loading(false, cx);
input.set_disabled(false, cx);
input.set_text("", cx);
_ = async_cx.update_entity(&input, |input, cx| {
input.set_loading(false, window, cx);
input.set_disabled(false, window, cx);
input.set_text("", window, cx);
});
}
})
.detach();
*/
}
fn upload(&mut self, cx: &mut ViewContext<Self>) {
fn upload(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let attaches = self.attaches.clone();
let paths = cx.prompt_for_paths(PathPromptOptions {
files: true,
@@ -373,14 +385,14 @@ impl ChatPanel {
if let Ok(url) = rx.await {
// Stop loading spinner
if let Some(view) = this.upgrade() {
_ = async_cx.update_view(&view, |this, cx| {
_ = async_cx.update_entity(&view, |this, cx| {
this.is_uploading = false;
cx.notify();
});
}
// Update attaches model
_ = async_cx.update_model(&attaches, |model, cx| {
_ = async_cx.update_entity(&attaches, |model, cx| {
if let Some(model) = model.as_mut() {
model.push(url);
} else {
@@ -398,7 +410,7 @@ impl ChatPanel {
.detach();
}
fn remove(&mut self, url: &Url, cx: &mut ViewContext<Self>) {
fn remove(&mut self, url: &Url, window: &mut Window, cx: &mut Context<Self>) {
self.attaches.update(cx, |model, cx| {
if let Some(urls) = model.as_mut() {
let ix = urls.iter().position(|x| x == url).unwrap();
@@ -414,7 +426,7 @@ impl Panel for ChatPanel {
self.id.clone()
}
fn panel_facepile(&self, cx: &WindowContext) -> Option<Vec<String>> {
fn panel_facepile(&self, cx: &App) -> Option<Vec<String>> {
Some(
self.room
.read(cx)
@@ -425,41 +437,37 @@ impl Panel for ChatPanel {
)
}
fn title(&self, _cx: &WindowContext) -> AnyElement {
fn title(&self, _cx: &App) -> AnyElement {
self.name.clone().into_any_element()
}
fn closeable(&self, _cx: &WindowContext) -> bool {
fn closeable(&self, _cx: &App) -> bool {
self.closeable
}
fn zoomable(&self, _cx: &WindowContext) -> bool {
fn zoomable(&self, _cx: &App) -> bool {
self.zoomable
}
fn popup_menu(&self, menu: PopupMenu, _cx: &WindowContext) -> PopupMenu {
fn popup_menu(&self, menu: PopupMenu, _cx: &App) -> PopupMenu {
menu.track_focus(&self.focus_handle)
}
fn toolbar_buttons(&self, _cx: &WindowContext) -> Vec<Button> {
fn toolbar_buttons(&self, _window: &Window, _cx: &App) -> Vec<Button> {
vec![]
}
fn dump(&self, _cx: &AppContext) -> PanelState {
PanelState::new(self)
}
}
impl EventEmitter<PanelEvent> for ChatPanel {}
impl FocusableView for ChatPanel {
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
impl Focusable for ChatPanel {
fn focus_handle(&self, _: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for ChatPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.size_full()
.child(list(self.list.clone()).flex_1())
@@ -504,8 +512,8 @@ impl Render for ChatPanel {
.text_color(white()),
),
)
.on_click(cx.listener(move |this, _, cx| {
this.remove(&url, cx);
.on_click(cx.listener(move |this, _, window, cx| {
this.remove(&url, window, cx);
}))
}))
})
@@ -520,8 +528,8 @@ impl Render for ChatPanel {
Button::new("upload")
.icon(Icon::new(IconName::Upload))
.ghost()
.on_click(cx.listener(move |this, _, cx| {
this.upload(cx);
.on_click(cx.listener(move |this, _, window, cx| {
this.upload(window, cx);
}))
.loading(self.is_uploading),
)
@@ -542,8 +550,12 @@ impl Render for ChatPanel {
.bold()
.rounded(ButtonRounded::Medium)
.label("SEND")
.on_click(cx.listener(|this, _, cx| {
this.send_message(this.input.downgrade(), cx)
.on_click(cx.listener(|this, _, window, cx| {
this.send_message(
this.input.downgrade(),
window,
cx,
)
})),
),
),

View File

@@ -1,34 +1,44 @@
use common::constants::KEYRING_SERVICE;
use gpui::{div, IntoElement, ParentElement, Render, Styled, View, ViewContext, VisualContext};
use gpui::{
div, App, AppContext, Context, Entity, IntoElement, ParentElement, Render, Styled, Window,
};
use nostr_sdk::prelude::*;
use registry::{app::AppRegistry, contact::Contact};
use state::get_client;
use ui::input::{InputEvent, TextInput};
pub struct Onboarding {
input: View<TextInput>,
input: Entity<TextInput>,
}
impl Onboarding {
pub fn new(cx: &mut ViewContext<'_, Self>) -> Self {
let input = cx.new_view(|cx| {
let mut input = TextInput::new(cx);
input.set_size(ui::Size::Medium, cx);
pub fn new(window: &mut Window, cx: &mut Context<'_, Self>) -> Self {
let input = cx.new(|cx| {
let mut input = TextInput::new(window, cx);
input.set_size(ui::Size::Medium, window, cx);
input
});
cx.subscribe(&input, move |_, text_input, input_event, cx| {
if let InputEvent::PressEnter = input_event {
let content = text_input.read(cx).text().to_string();
_ = Self::save_keys(&content, cx);
}
})
cx.subscribe_in(
&input,
window,
move |_, text_input, input_event, window, cx| {
if let InputEvent::PressEnter = input_event {
let content = text_input.read(cx).text().to_string();
_ = Self::save_keys(&content, window, cx);
}
},
)
.detach();
Self { input }
}
fn save_keys(content: &str, cx: &mut ViewContext<Self>) -> anyhow::Result<(), anyhow::Error> {
fn save_keys(
content: &str,
window: &mut Window,
cx: &mut Context<Self>,
) -> anyhow::Result<(), anyhow::Error> {
let keys = Keys::parse(content)?;
let public_key = keys.public_key();
let bech32 = public_key.to_bech32()?;
@@ -75,7 +85,7 @@ impl Onboarding {
}
impl Render for Onboarding {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.size_full()
.flex()

View File

@@ -1,8 +1,8 @@
use common::utils::{random_name, room_hash};
use gpui::{
div, img, impl_internal_actions, px, uniform_list, Context, FocusHandle, InteractiveElement,
IntoElement, Model, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled,
View, ViewContext, VisualContext, WindowContext,
div, img, impl_internal_actions, px, uniform_list, App, AppContext, Context, Entity,
FocusHandle, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
StatefulInteractiveElement, Styled, Window,
};
use nostr_sdk::prelude::*;
use registry::{app::AppRegistry, contact::Contact, room::Room};
@@ -24,48 +24,54 @@ struct SelectContact(PublicKey);
impl_internal_actions!(contacts, [SelectContact]);
pub struct Compose {
title_input: View<TextInput>,
message_input: View<TextInput>,
user_input: View<TextInput>,
contacts: Model<Option<Vec<Contact>>>,
selected: Model<HashSet<PublicKey>>,
title_input: Entity<TextInput>,
message_input: Entity<TextInput>,
user_input: Entity<TextInput>,
contacts: Entity<Option<Vec<Contact>>>,
selected: Entity<HashSet<PublicKey>>,
focus_handle: FocusHandle,
is_loading: bool,
}
impl Compose {
pub fn new(cx: &mut ViewContext<'_, Self>) -> Self {
let contacts = cx.new_model(|_| None);
let selected = cx.new_model(|_| HashSet::new());
pub fn new(window: &mut Window, cx: &mut Context<'_, Self>) -> Self {
let contacts = cx.new(|_| None);
let selected = cx.new(|_| HashSet::new());
let user_input = cx.new_view(|cx| {
TextInput::new(cx)
let user_input = cx.new(|cx| {
TextInput::new(window, cx)
.text_size(ui::Size::Small)
.small()
.placeholder("npub1...")
});
let title_input = cx.new_view(|cx| {
let title_input = cx.new(|cx| {
let name = random_name(2);
let mut input = TextInput::new(cx).appearance(false).text_size(Size::XSmall);
let mut input = TextInput::new(window, cx)
.appearance(false)
.text_size(Size::XSmall);
input.set_placeholder("Family... . (Optional)");
input.set_text(name, cx);
input.set_text(name, window, cx);
input
});
let message_input = cx.new_view(|cx| {
TextInput::new(cx)
let message_input = cx.new(|cx| {
TextInput::new(window, cx)
.appearance(false)
.text_size(Size::XSmall)
.placeholder("Hello... (Optional)")
});
cx.subscribe(&user_input, move |this, _, input_event, cx| {
if let InputEvent::PressEnter = input_event {
this.add(cx);
}
})
cx.subscribe_in(
&user_input,
window,
move |this, _, input_event, window, cx| {
if let InputEvent::PressEnter = input_event {
this.add(window, cx);
}
},
)
.detach();
cx.spawn(|this, mut async_cx| {
@@ -89,7 +95,7 @@ impl Compose {
if let Ok(contacts) = query {
if let Some(view) = this.upgrade() {
_ = async_cx.update_view(&view, |this, cx| {
_ = async_cx.update_entity(&view, |this, cx| {
this.contacts.update(cx, |this, cx| {
*this = Some(contacts);
cx.notify();
@@ -114,8 +120,8 @@ impl Compose {
}
}
pub fn room(&self, cx: &WindowContext) -> Option<Room> {
let current_user = cx.global::<AppRegistry>().current_user(cx);
pub fn room(&self, window: &Window, cx: &App) -> Option<Room> {
let current_user = cx.global::<AppRegistry>().current_user(window, cx);
if let Some(current_user) = current_user {
// Convert selected pubkeys into nostr tags
@@ -151,7 +157,7 @@ impl Compose {
}
}
pub fn label(&self, cx: &WindowContext) -> SharedString {
pub fn label(&self, window: &Window, cx: &App) -> SharedString {
if self.selected.read(cx).len() > 1 {
"Create Group DM".into()
} else {
@@ -159,7 +165,7 @@ impl Compose {
}
}
fn add(&mut self, cx: &mut ViewContext<Self>) {
fn add(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let content = self.user_input.read(cx).text().to_string();
let input = self.user_input.downgrade();
@@ -183,7 +189,7 @@ impl Compose {
if let Ok(metadata) = query {
if let Some(view) = this.upgrade() {
_ = async_cx.update_view(&view, |this, cx| {
_ = async_cx.update_entity(&view, |this, cx| {
this.contacts.update(cx, |this, cx| {
if let Some(members) = this {
members.insert(0, Contact::new(public_key, metadata));
@@ -202,8 +208,8 @@ impl Compose {
}
if let Some(input) = input.upgrade() {
_ = async_cx.update_view(&input, |input, cx| {
input.set_text("", cx);
_ = async_cx.update_entity(&input, |input, cx| {
// input.set_text("", window, cx);
});
}
}
@@ -214,7 +220,12 @@ impl Compose {
}
}
fn on_action_select(&mut self, action: &SelectContact, cx: &mut ViewContext<Self>) {
fn on_action_select(
&mut self,
action: &SelectContact,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.selected.update(cx, |this, cx| {
if this.contains(&action.0) {
this.remove(&action.0);
@@ -227,7 +238,7 @@ impl Compose {
}
impl Render for Compose {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let msg =
"Start a conversation with someone using their npub or NIP-05 (like foo@bar.com).";
@@ -291,7 +302,9 @@ impl Render for Compose {
.small()
.rounded(ButtonRounded::Size(px(9999.)))
.loading(self.is_loading)
.on_click(cx.listener(|this, _, cx| this.add(cx))),
.on_click(
cx.listener(|this, _, window, cx| this.add(window, cx)),
),
)
.child(self.user_input.clone()),
)
@@ -299,10 +312,10 @@ impl Render for Compose {
if let Some(contacts) = self.contacts.read(cx).clone() {
this.child(
uniform_list(
cx.view().clone(),
cx.model().clone(),
"contacts",
contacts.len(),
move |this, range, cx| {
move |this, range, window, cx| {
let selected = this.selected.read(cx);
let mut items = Vec::new();
@@ -348,9 +361,9 @@ impl Render for Compose {
.base
.step(cx, ColorScaleStep::THREE))
})
.on_click(move |_, cx| {
cx.dispatch_action(Box::new(
SelectContact(item.public_key()),
.on_click(move |_, window, cx| {
cx.dispatch_action(&SelectContact(
item.public_key(),
));
}),
);

View File

@@ -1,8 +1,8 @@
use crate::views::app::{AddPanel, PanelKind};
use common::utils::message_ago;
use gpui::{
div, img, percentage, prelude::FluentBuilder, px, InteractiveElement, IntoElement,
ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, ViewContext,
div, img, percentage, prelude::FluentBuilder, px, Context, InteractiveElement, IntoElement,
ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Window,
};
use registry::chat::ChatRegistry;
use ui::{
@@ -18,7 +18,7 @@ pub struct Inbox {
}
impl Inbox {
pub fn new(_cx: &mut ViewContext<'_, Self>) -> Self {
pub fn new(_window: &mut Window, _cx: &mut Context<'_, Self>) -> Self {
Self {
label: "Inbox".into(),
is_collapsed: false,
@@ -38,7 +38,7 @@ impl Inbox {
})
}
fn render_item(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render_item(&self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let weak_model = cx.global::<ChatRegistry>().inbox();
if let Some(model) = weak_model.upgrade() {
@@ -91,8 +91,8 @@ impl Inbox {
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
.child(ago),
)
.on_click(cx.listener(move |this, _, cx| {
this.action(id, cx);
.on_click(cx.listener(move |this, _, window, cx| {
this.action(id, window, cx);
}))
}))
}
@@ -102,11 +102,13 @@ impl Inbox {
}
}
fn action(&self, id: u64, cx: &mut ViewContext<Self>) {
cx.dispatch_action(Box::new(AddPanel {
fn action(&self, id: u64, _window: &mut Window, cx: &mut Context<Self>) {
let action = AddPanel {
panel: PanelKind::Room(id),
position: DockPlacement::Center,
}))
};
cx.dispatch_action(&action)
}
}
@@ -122,7 +124,7 @@ impl Collapsible for Inbox {
}
impl Render for Inbox {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.px_2()
.gap_1()
@@ -145,11 +147,13 @@ impl Render for Inbox {
)
.child(self.label.clone())
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE)))
.on_click(cx.listener(move |view, _event, cx| {
.on_click(cx.listener(move |view, _event, _window, cx| {
view.is_collapsed = !view.is_collapsed;
cx.notify();
})),
)
.when(!self.is_collapsed, |this| this.child(self.render_item(cx)))
.when(!self.is_collapsed, |this| {
this.child(self.render_item(window, cx))
})
}
}

View File

@@ -1,17 +1,14 @@
use crate::views::sidebar::inbox::Inbox;
use compose::Compose;
use gpui::{
div, px, AnyElement, AppContext, BorrowAppContext, Entity, EntityId, EventEmitter, FocusHandle,
FocusableView, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
StatefulInteractiveElement, Styled, View, ViewContext, VisualContext, WindowContext,
div, px, AnyElement, App, AppContext, BorrowAppContext, Context, Entity, EntityId,
EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render,
SharedString, StatefulInteractiveElement, Styled, Window,
};
use registry::chat::ChatRegistry;
use ui::{
button::{Button, ButtonRounded, ButtonVariants},
dock_area::{
panel::{Panel, PanelEvent},
state::PanelState,
},
dock_area::panel::{Panel, PanelEvent},
popup_menu::PopupMenu,
scroll::ScrollbarAxis,
theme::{scale::ColorScaleStep, ActiveTheme},
@@ -28,33 +25,33 @@ pub struct Sidebar {
zoomable: bool,
focus_handle: FocusHandle,
// Dock
inbox: View<Inbox>,
inbox: Entity<Inbox>,
view_id: EntityId,
}
impl Sidebar {
pub fn new(cx: &mut WindowContext) -> View<Self> {
cx.new_view(Self::view)
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
cx.new(|cx| Self::view(window, cx))
}
fn view(cx: &mut ViewContext<Self>) -> Self {
let inbox = cx.new_view(Inbox::new);
fn view(window: &mut Window, cx: &mut Context<Self>) -> Self {
let inbox = cx.new(|cx| Inbox::new(window, cx));
Self {
name: "Sidebar".into(),
closeable: true,
zoomable: true,
focus_handle: cx.focus_handle(),
view_id: cx.view().entity_id(),
view_id: cx.model().entity_id(),
inbox,
}
}
fn show_compose(&mut self, cx: &mut ViewContext<Self>) {
let compose = cx.new_view(Compose::new);
fn show_compose(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let compose = cx.new(|cx| Compose::new(window, cx));
cx.open_modal(move |modal, cx| {
let label = compose.read(cx).label(cx);
window.open_modal(cx, move |modal, window, cx| {
let label = compose.read(cx).label(window, cx);
modal
.title("Direct Messages")
@@ -72,13 +69,13 @@ impl Sidebar {
.bold()
.rounded(ButtonRounded::Large)
.w_full()
.on_click(cx.listener_for(&compose, |this, _, cx| {
if let Some(room) = this.room(cx) {
.on_click(window.listener_for(&compose, |this, _, window, cx| {
if let Some(room) = this.room(window, cx) {
cx.update_global::<ChatRegistry, _>(|this, cx| {
this.new_room(room, cx);
});
cx.close_modal();
window.close_modal(cx);
}
})),
),
@@ -92,41 +89,37 @@ impl Panel for Sidebar {
"Sidebar".into()
}
fn title(&self, _cx: &WindowContext) -> AnyElement {
fn title(&self, _cx: &App) -> AnyElement {
self.name.clone().into_any_element()
}
fn closeable(&self, _cx: &WindowContext) -> bool {
fn closeable(&self, _cx: &App) -> bool {
self.closeable
}
fn zoomable(&self, _cx: &WindowContext) -> bool {
fn zoomable(&self, _cx: &App) -> bool {
self.zoomable
}
fn popup_menu(&self, menu: PopupMenu, _cx: &WindowContext) -> PopupMenu {
fn popup_menu(&self, menu: PopupMenu, _cx: &App) -> PopupMenu {
menu.track_focus(&self.focus_handle)
}
fn toolbar_buttons(&self, _cx: &WindowContext) -> Vec<Button> {
fn toolbar_buttons(&self, _window: &Window, _cx: &App) -> Vec<Button> {
vec![]
}
fn dump(&self, _cx: &AppContext) -> PanelState {
PanelState::new(self)
}
}
impl EventEmitter<PanelEvent> for Sidebar {}
impl FocusableView for Sidebar {
fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
impl Focusable for Sidebar {
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl Render for Sidebar {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.scrollable(self.view_id, ScrollbarAxis::Vertical)
.py_3()
@@ -159,7 +152,7 @@ impl Render for Sidebar {
)
.child("New Message")
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE)))
.on_click(cx.listener(|this, _, cx| this.show_compose(cx))),
.on_click(cx.listener(|this, _, window, cx| this.show_compose(window, cx))),
),
)
.child(self.inbox.clone())

View File

@@ -1,13 +1,10 @@
use gpui::{
div, svg, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView, IntoElement,
ParentElement, Render, SharedString, Styled, View, ViewContext, VisualContext, WindowContext,
div, svg, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
IntoElement, ParentElement, Render, SharedString, Styled, Window,
};
use ui::{
button::Button,
dock_area::{
panel::{Panel, PanelEvent},
state::PanelState,
},
dock_area::panel::{Panel, PanelEvent},
popup_menu::PopupMenu,
theme::{scale::ColorScaleStep, ActiveTheme},
StyledExt,
@@ -21,11 +18,11 @@ pub struct WelcomePanel {
}
impl WelcomePanel {
pub fn new(cx: &mut WindowContext) -> View<Self> {
cx.new_view(Self::view)
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
cx.new(|cx| Self::view(window, cx))
}
fn view(cx: &mut ViewContext<Self>) -> Self {
fn view(_window: &mut Window, cx: &mut Context<Self>) -> Self {
Self {
name: "Welcome".into(),
closeable: true,
@@ -40,41 +37,37 @@ impl Panel for WelcomePanel {
"WelcomePanel".into()
}
fn title(&self, _cx: &WindowContext) -> AnyElement {
fn title(&self, _cx: &App) -> AnyElement {
self.name.clone().into_any_element()
}
fn closeable(&self, _cx: &WindowContext) -> bool {
fn closeable(&self, _cx: &App) -> bool {
self.closeable
}
fn zoomable(&self, _cx: &WindowContext) -> bool {
fn zoomable(&self, _cx: &App) -> bool {
self.zoomable
}
fn popup_menu(&self, menu: PopupMenu, _cx: &WindowContext) -> PopupMenu {
fn popup_menu(&self, menu: PopupMenu, _cx: &App) -> PopupMenu {
menu.track_focus(&self.focus_handle)
}
fn toolbar_buttons(&self, _cx: &WindowContext) -> Vec<Button> {
fn toolbar_buttons(&self, _window: &Window, _cx: &App) -> Vec<Button> {
vec![]
}
fn dump(&self, _cx: &AppContext) -> PanelState {
PanelState::new(self)
}
}
impl EventEmitter<PanelEvent> for WelcomePanel {}
impl FocusableView for WelcomePanel {
fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
impl Focusable for WelcomePanel {
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl Render for WelcomePanel {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.size_full()
.flex()